1 /* 2 * Copyright (C) 2012 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package com.android.idegen; 18 19 import com.google.common.base.MoreObjects; 20 import com.google.common.base.Preconditions; 21 import com.google.common.base.Splitter; 22 import com.google.common.base.Strings; 23 import com.google.common.collect.Lists; 24 import com.google.common.collect.Maps; 25 import com.google.common.collect.Sets; 26 import com.google.common.io.Files; 27 import com.google.common.io.LineProcessor; 28 29 import java.io.File; 30 import java.io.IOException; 31 import java.nio.charset.Charset; 32 import java.util.ArrayList; 33 import java.util.Collections; 34 import java.util.HashMap; 35 import java.util.HashSet; 36 import java.util.List; 37 import java.util.logging.Logger; 38 39 /** 40 * Parses the make files and finds the appropriate section a given module. 41 */ 42 public class MakeFileParser { 43 MakeFileParser.class.getNamenull44 private static final Logger logger = Logger.getLogger(MakeFileParser.class.getName()); 45 public static final String VALUE_DELIMITER = "|"; 46 47 private File makeFile; 48 private HashMap<String, String> values; 49 50 /** 51 * Create a parser for a given make file and module name. 52 * <p> 53 * A make file may contain multiple modules. 54 * 55 * @param makeFile The make file to parse. 56 */ 57 public MakeFileParser(File makeFile) { 58 this.makeFile = Preconditions.checkNotNull(makeFile); 59 } 60 61 public Iterable<String> getValues(String key) { 62 String str = values.get(key); 63 if (str == null) { 64 return null; 65 } .trimResultsnull66 return Splitter.on(VALUE_DELIMITER).trimResults().omitEmptyStrings().split(str); 67 } 68 69 /** 70 * Extracts the relevant portion of the make file and converts into key value pairs. <p> Since 71 * each make file may contain multiple build targets (modules), this method will determine which 72 * part is the correct portion for the given module name. 73 */ parsenull74 public void parse() throws IOException { 75 values = Maps.newHashMap(); 76 logger.info("Parsing " + makeFile.getCanonicalPath()); 77 78 Files.readLines(makeFile, Charset.forName("UTF-8"), new MakeFileLineProcessor()); 79 } 80 81 @Override toStringnull82 public String toString() { 83 return MoreObjects.toStringHelper(this).add("values", values).toString(); 84 } 85 86 private class MakeFileLineProcessor implements LineProcessor<Object> { 87 88 private StringBuilder lineBuffer; 89 90 // Keep a list of LOCAL_ variables to clear when CLEAR_VARS is encountered. Sets.newHashSetnull91 private HashSet<String> localVars = Sets.newHashSet(); 92 93 @Override 94 public boolean processLine(String line) throws IOException { line.trimnull95 String trimmed = line.trim(); 96 // Skip comments. \x21trimmed.isEmptynull97 if (!trimmed.isEmpty() && trimmed.charAt(0) == '#') { 98 return true; 99 } 100 appendPartialLine(trimmed); 101 trimmed.lengthnull102 if (!trimmed.isEmpty() && trimmed.charAt(trimmed.length() - 1) == '\\') { 103 // This is a partial line. Do not process yet. 104 return true; 105 } 106 lineBuffer.toStringnull107 String completeLine = lineBuffer.toString().trim(); 108 // Reset the line buffer. 109 lineBuffer = null; 110 111 if (Strings.isNullOrEmpty(completeLine)) { 112 return true; 113 } 114 115 processKeyValuePairs(completeLine); 116 return true; 117 } 118 119 private void processKeyValuePairs(String line) { 120 if (line.contains("=")) { 121 String[] arr; 122 if (line.contains(":")) { 123 arr = line.split(":="); 124 } else { 125 arr = line.split("\\+="); 126 } 127 if (arr.length > 2) { 128 logger.info("Malformed line " + line); 129 } else { 130 // Store the key in case the line continues arr[0].trimnull131 String key = arr[0].trim(); 132 if (arr.length == 2) { 133 // There may be multiple values on one line. 134 List<String> valuesArr = tokenizeValue(arr[1]); 135 for (String value : valuesArr) { 136 appendValue(key, value); 137 } 138 139 } 140 } 141 } else { 142 //logger.info("Skipping line " + line); 143 } 144 } 145 146 private void appendPartialLine(String line) { 147 if (lineBuffer == null) { StringBuildernull148 lineBuffer = new StringBuilder(); 149 } else { 150 lineBuffer.append(" "); 151 } 152 if (line.endsWith("\\")) { line.lengthnull153 lineBuffer.append(line.substring(0, line.length() - 1).trim()); 154 } else { 155 lineBuffer.append(line); 156 } 157 } 158 159 private List<String> tokenizeValue(String rawValue) { rawValue.trimnull160 String value = rawValue.trim(); Lists.newArrayListnull161 ArrayList<String> result = Lists.newArrayList(); value.isEmptynull162 if (value.isEmpty()) { 163 return result; 164 } 165 callsnull166 // Value may contain function calls such as "$(call all-java-files-under)" or refer 167 // to variables such as "$(my_var)" 168 value = findVariables(value); 169 170 String[] tokens = value.split(" "); 171 Collections.addAll(result, tokens); 172 return result; 173 } 174 175 private String findVariables(String value) { 176 177 int variableStart = value.indexOf("$("); 178 // Keep going until we substituted all variables. 179 while (variableStart > -1) { StringBuildernull180 StringBuilder sb = new StringBuilder(); 181 sb.append(value.substring(0, variableStart)); 182 183 // variable found 184 int variableEnd = findClosingParen(value, variableStart); 185 if (variableEnd > variableStart) { 186 String result = substituteVariables(value.substring(variableStart + 2, variableEnd)); 187 sb.append(result); 188 } else { 189 throw new IllegalArgumentException( 190 "Malformed variable reference in make file: " + value); 191 } value.lengthnull192 if (variableEnd + 1 < value.length()) { 193 sb.append(value.substring(variableEnd + 1)); 194 } sb.toStringnull195 value = sb.toString(); 196 variableStart = value.indexOf("$("); 197 } 198 return value; 199 } 200 201 private int findClosingParen(String value, int startIndex) { 202 int openParenCount = 0; value.lengthnull203 for (int i = startIndex; i < value.length(); i++) { 204 char ch = value.charAt(i); 205 if (ch == ')') { 206 openParenCount--; 207 if (openParenCount == 0) { 208 return i; 209 } 210 } else if (ch == '(') { 211 openParenCount++; 212 } 213 } 214 return -1; 215 } 216 217 /** 218 * Look for and handle $(...) variables. 219 */ 220 private String substituteVariables(String rawValue) { rawValue.isEmptynull221 if (rawValue.isEmpty()) { 222 return rawValue; 223 } 224 String value = rawValue; 225 if (value.startsWith("call all-java-files-under")) { ,null226 // Ignore the call and function, keep the args. 227 value = value.substring(25).trim(); 228 } else if (value.startsWith("call")) { .trimnull229 value = value.substring(4).trim(); 230 } 231 232 // Check for single variable 233 if (value.indexOf(' ') == -1) { 234 // Substitute. 235 value = values.get(value); 236 if (value == null) { 237 value = ""; 238 } 239 return value; 240 } else { 241 return findVariables(value); 242 } 243 } 244 245 @Override getResultnull246 public Object getResult() { 247 return null; 248 } 249 250 /** 251 * Add a value to the hash map. If the key already exists, will append instead of 252 * over-writing the existing value. 253 * 254 * @param key The hashmap key 255 * @param newValue The value to append. 256 */ 257 private void appendValue(String key, String newValue) { 258 String value = values.get(key); 259 if (value == null) { 260 values.put(key, newValue); 261 } else { 262 values.put(key, value + VALUE_DELIMITER + newValue); 263 } 264 } 265 } 266 267 public static void main(String[] args) { 268 MakeFileParser parser = new MakeFileParser(new File(args[0])); 269 try { parser.parsenull270 parser.parse(); 271 } catch (IOException e) { e.printStackTracenull272 e.printStackTrace(); 273 } parser.toStringnull274 System.out.println(parser.toString()); 275 } 276 } 277