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