blob: a48c704b17eec9165a5669ea1514631665a5d287 [file] [log] [blame]
/*
* Copyright (C) 2012 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.idegen;
import com.google.common.base.Objects;
import com.google.common.base.Preconditions;
import com.google.common.base.Splitter;
import com.google.common.base.Strings;
import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import com.google.common.collect.Sets;
import com.google.common.io.Files;
import com.google.common.io.LineProcessor;
import java.io.File;
import java.io.IOException;
import java.nio.charset.Charset;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.logging.Logger;
/**
* Parses the make files and finds the appropriate section a given module.
*/
public class MakeFileParser {
private static final Logger logger = Logger.getLogger(MakeFileParser.class.getName());
public static final String VALUE_DELIMITER = "|";
private File makeFile;
private HashMap<String, String> values;
/**
* Create a parser for a given make file and module name.
* <p>
* A make file may contain multiple modules.
*
* @param makeFile The make file to parse.
*/
public MakeFileParser(File makeFile) {
this.makeFile = Preconditions.checkNotNull(makeFile);
}
public Iterable<String> getValues(String key) {
String str = values.get(key);
if (str == null) {
return null;
}
return Splitter.on(VALUE_DELIMITER).trimResults().omitEmptyStrings().split(str);
}
/**
* Extracts the relevant portion of the make file and converts into key value pairs. <p> Since
* each make file may contain multiple build targets (modules), this method will determine which
* part is the correct portion for the given module name.
*/
public void parse() throws IOException {
values = Maps.newHashMap();
logger.info("Parsing " + makeFile.getCanonicalPath());
Files.readLines(makeFile, Charset.forName("UTF-8"), new MakeFileLineProcessor());
}
@Override
public String toString() {
return Objects.toStringHelper(this).add("values", values).toString();
}
private class MakeFileLineProcessor implements LineProcessor<Object> {
private StringBuilder lineBuffer;
// Keep a list of LOCAL_ variables to clear when CLEAR_VARS is encountered.
private HashSet<String> localVars = Sets.newHashSet();
@Override
public boolean processLine(String line) throws IOException {
String trimmed = line.trim();
// Skip comments.
if (!trimmed.isEmpty() && trimmed.charAt(0) == '#') {
return true;
}
appendPartialLine(trimmed);
if (!trimmed.isEmpty() && trimmed.charAt(trimmed.length() - 1) == '\\') {
// This is a partial line. Do not process yet.
return true;
}
String completeLine = lineBuffer.toString().trim();
// Reset the line buffer.
lineBuffer = null;
if (Strings.isNullOrEmpty(completeLine)) {
return true;
}
processKeyValuePairs(completeLine);
return true;
}
private void processKeyValuePairs(String line) {
if (line.contains("=")) {
String[] arr;
if (line.contains(":")) {
arr = line.split(":=");
} else {
arr = line.split("\\+=");
}
if (arr.length > 2) {
logger.info("Malformed line " + line);
} else {
// Store the key in case the line continues
String key = arr[0].trim();
if (arr.length == 2) {
// There may be multiple values on one line.
List<String> valuesArr = tokenizeValue(arr[1]);
for (String value : valuesArr) {
appendValue(key, value);
}
}
}
} else {
//logger.info("Skipping line " + line);
}
}
private void appendPartialLine(String line) {
if (lineBuffer == null) {
lineBuffer = new StringBuilder();
} else {
lineBuffer.append(" ");
}
if (line.endsWith("\\")) {
lineBuffer.append(line.substring(0, line.length() - 1).trim());
} else {
lineBuffer.append(line);
}
}
private List<String> tokenizeValue(String rawValue) {
String value = rawValue.trim();
ArrayList<String> result = Lists.newArrayList();
if (value.isEmpty()) {
return result;
}
// Value may contain function calls such as "$(call all-java-files-under)" or refer
// to variables such as "$(my_var)"
value = findVariables(value);
String[] tokens = value.split(" ");
Collections.addAll(result, tokens);
return result;
}
private String findVariables(String value) {
int variableStart = value.indexOf("$(");
// Keep going until we substituted all variables.
while (variableStart > -1) {
StringBuilder sb = new StringBuilder();
sb.append(value.substring(0, variableStart));
// variable found
int variableEnd = findClosingParen(value, variableStart);
if (variableEnd > variableStart) {
String result = substituteVariables(value.substring(variableStart + 2, variableEnd));
sb.append(result);
} else {
throw new IllegalArgumentException(
"Malformed variable reference in make file: " + value);
}
if (variableEnd + 1 < value.length()) {
sb.append(value.substring(variableEnd + 1));
}
value = sb.toString();
variableStart = value.indexOf("$(");
}
return value;
}
private int findClosingParen(String value, int startIndex) {
int openParenCount = 0;
for (int i = startIndex; i < value.length(); i++) {
char ch = value.charAt(i);
if (ch == ')') {
openParenCount--;
if (openParenCount == 0) {
return i;
}
} else if (ch == '(') {
openParenCount++;
}
}
return -1;
}
/**
* Look for and handle $(...) variables.
*/
private String substituteVariables(String rawValue) {
if (rawValue.isEmpty()) {
return rawValue;
}
String value = rawValue;
if (value.startsWith("call all-java-files-under")) {
// Ignore the call and function, keep the args.
value = value.substring(25).trim();
} else if (value.startsWith("call")) {
value = value.substring(4).trim();
}
// Check for single variable
if (value.indexOf(' ') == -1) {
// Substitute.
value = values.get(value);
if (value == null) {
value = "";
}
return value;
} else {
return findVariables(value);
}
}
@Override
public Object getResult() {
return null;
}
/**
* Add a value to the hash map. If the key already exists, will append instead of
* over-writing the existing value.
*
* @param key The hashmap key
* @param newValue The value to append.
*/
private void appendValue(String key, String newValue) {
String value = values.get(key);
if (value == null) {
values.put(key, newValue);
} else {
values.put(key, value + VALUE_DELIMITER + newValue);
}
}
}
public static void main(String[] args) {
MakeFileParser parser = new MakeFileParser(new File(args[0]));
try {
parser.parse();
} catch (IOException e) {
e.printStackTrace();
}
System.out.println(parser.toString());
}
}