Merge changes I9c1995f8,Id6763781,I52e5c07f,I4706e32f,I7d74b226
* changes: Keep the first and last snapshot of variables. Generate GenericConfig objects from MakeConfig objects. Emit and parse the product config variables from kati/make Add class to fork and exec kati, based on the commandline option given. Add a CSV parser to parse the output from kati.
This commit is contained in:
commit
2fd88e0600
|
@ -0,0 +1,129 @@
|
|||
# Read and dump the product configuration.
|
||||
|
||||
# Called from the product-config tool, not from the main build system.
|
||||
|
||||
#
|
||||
# Ensure we are being called correctly
|
||||
#
|
||||
ifndef KATI
|
||||
$(warning Kati must be used to call dumpconfig.mk, not make.)
|
||||
$(error stopping)
|
||||
endif
|
||||
|
||||
ifdef DEFAULT_GOAL
|
||||
$(warning Calling dumpconfig.mk from inside the make build system is not)
|
||||
$(warning supported. It is only meant to be called via kati by product-confing.)
|
||||
$(error stopping)
|
||||
endif
|
||||
|
||||
ifndef TARGET_PRODUCT
|
||||
$(warning dumpconfig.mk requires TARGET_PRODUCT to be set)
|
||||
$(error stopping)
|
||||
endif
|
||||
|
||||
ifndef TARGET_BUILD_VARIANT
|
||||
$(warning dumpconfig.mk requires TARGET_BUILD_VARIANT to be set)
|
||||
$(error stopping)
|
||||
endif
|
||||
|
||||
ifneq (build/make/core/config.mk,$(wildcard build/make/core/config.mk))
|
||||
$(warning dumpconfig must be called from the root of the source tree)
|
||||
$(error stopping)
|
||||
endif
|
||||
|
||||
ifeq (,$(DUMPCONFIG_FILE))
|
||||
$(warning dumpconfig requires DUMPCONFIG_FILE to be set)
|
||||
$(error stopping)
|
||||
endif
|
||||
|
||||
# Before we do anything else output the format version.
|
||||
$(file > $(DUMPCONFIG_FILE),dumpconfig_version,1)
|
||||
$(file >> $(DUMPCONFIG_FILE),dumpconfig_file,$(DUMPCONFIG_FILE))
|
||||
|
||||
# Default goal for dumpconfig
|
||||
dumpconfig:
|
||||
$(file >> $(DUMPCONFIG_FILE),***DONE***)
|
||||
@echo ***DONE***
|
||||
|
||||
# TODO(Remove): These need to be set externally
|
||||
OUT_DIR := out
|
||||
TMPDIR = /tmp/build-temp
|
||||
BUILD_DATETIME_FILE := $(OUT_DIR)/build_date.txt
|
||||
|
||||
# Escape quotation marks for CSV, and wraps in quotation marks.
|
||||
define escape-for-csv
|
||||
"$(subst ","",$1)"
|
||||
endef
|
||||
|
||||
# Args:
|
||||
# $(1): include stack
|
||||
define dump-import-start
|
||||
$(eval $(file >> $(DUMPCONFIG_FILE),import,$(strip $(1))))
|
||||
endef
|
||||
|
||||
# Args:
|
||||
# $(1): include stack
|
||||
define dump-import-done
|
||||
$(eval $(file >> $(DUMPCONFIG_FILE),imported,$(strip $(1))))
|
||||
endef
|
||||
|
||||
# Args:
|
||||
# $(1): Current file
|
||||
# $(2): Inherited file
|
||||
define dump-inherit
|
||||
$(eval $(file >> $(DUMPCONFIG_FILE),inherit,$(strip $(1)),$(strip $(2))))
|
||||
endef
|
||||
|
||||
# Args:
|
||||
# $(1): Config phase (PRODUCT or DEVICE)
|
||||
# $(2): Root nodes to import
|
||||
# $(3): All variable names
|
||||
# $(4): Single-value variables
|
||||
# $(5): Makefile being processed
|
||||
define dump-phase-start
|
||||
$(eval $(file >> $(DUMPCONFIG_FILE),phase,$(strip $(1)),$(strip $(2)))) \
|
||||
$(foreach var,$(3), \
|
||||
$(eval $(file >> $(DUMPCONFIG_FILE),var,$(if $(filter $(4),$(var)),single,list),$(var))) \
|
||||
) \
|
||||
$(call dump-config-vals,$(strip $(5)),initial)
|
||||
endef
|
||||
|
||||
# Args:
|
||||
# $(1): Makefile being processed
|
||||
define dump-phase-end
|
||||
$(call dump-config-vals,$(strip $(1)),final)
|
||||
endef
|
||||
|
||||
define dump-debug
|
||||
$(eval $(file >> $(DUMPCONFIG_FILE),debug,$(1)))
|
||||
endef
|
||||
|
||||
# Skip these when dumping. They're not used and they cause a lot of noise in the dump.
|
||||
DUMPCONFIG_SKIP_VARS := \
|
||||
.VARIABLES \
|
||||
.KATI_SYMBOLS \
|
||||
1 \
|
||||
2 \
|
||||
LOCAL_PATH \
|
||||
MAKEFILE_LIST \
|
||||
PARENT_PRODUCT_FILES \
|
||||
current_mk \
|
||||
inherit_var \
|
||||
np \
|
||||
_node_import_context \
|
||||
_included \
|
||||
_include_stack \
|
||||
_in \
|
||||
_nic.%
|
||||
|
||||
# Args:
|
||||
# $(1): Makefile that was included
|
||||
# $(2): block (before,import,after,initial,final)
|
||||
define dump-config-vals
|
||||
$(foreach var,$(filter-out $(DUMPCONFIG_SKIP_VARS),$(.KATI_SYMBOLS)),\
|
||||
$(eval $(file >> $(DUMPCONFIG_FILE),val,$(call escape-for-csv,$(1)),$(2),$(call escape-for-csv,$(var)),$(call escape-for-csv,$($(var))),$(call escape-for-csv,$(KATI_variable_location $(var))))) \
|
||||
)
|
||||
endef
|
||||
|
||||
include build/make/core/config.mk
|
||||
|
|
@ -195,7 +195,11 @@ define _import-node
|
|||
$(call clear-var-list, $(3))
|
||||
$(eval LOCAL_PATH := $(patsubst %/,%,$(dir $(2))))
|
||||
$(eval MAKEFILE_LIST :=)
|
||||
$(call dump-import-start,$(_include_stack))
|
||||
$(call dump-config-vals,$(2),before)
|
||||
$(eval include $(2))
|
||||
$(call dump-import-done,$(_include_stack))
|
||||
$(call dump-config-vals,$(2),after)
|
||||
$(eval _included := $(filter-out $(2),$(MAKEFILE_LIST)))
|
||||
$(eval MAKEFILE_LIST :=)
|
||||
$(eval LOCAL_PATH :=)
|
||||
|
@ -250,6 +254,7 @@ endef
|
|||
# of the default list semantics
|
||||
#
|
||||
define import-nodes
|
||||
$(call dump-phase-start,$(1),$(2),$(3),$(4),build/make/core/node_fns.mk) \
|
||||
$(if \
|
||||
$(foreach _in,$(2), \
|
||||
$(eval _node_import_context := _nic.$(1).[[$(_in)]]) \
|
||||
|
@ -263,5 +268,6 @@ $(if \
|
|||
$(if $(_include_stack),$(eval $(error ASSERTION FAILED: _include_stack \
|
||||
should be empty here: $(_include_stack))),) \
|
||||
) \
|
||||
,)
|
||||
,) \
|
||||
$(call dump-phase-end,build/make/core/node_fns.mk)
|
||||
endef
|
||||
|
|
|
@ -460,7 +460,9 @@ define inherit-product
|
|||
$(eval current_mk := $(strip $(word 1,$(_include_stack)))) \
|
||||
$(eval inherit_var := PRODUCTS.$(current_mk).INHERITS_FROM) \
|
||||
$(eval $(inherit_var) := $(sort $($(inherit_var)) $(np))) \
|
||||
$(eval PARENT_PRODUCT_FILES := $(sort $(PARENT_PRODUCT_FILES) $(current_mk)))
|
||||
$(eval PARENT_PRODUCT_FILES := $(sort $(PARENT_PRODUCT_FILES) $(current_mk))) \
|
||||
$(call dump-inherit,$(strip $(word 1,$(_include_stack))),$(1)) \
|
||||
$(call dump-config-vals,$(current_mk),inherit)
|
||||
endef
|
||||
|
||||
# Specifies a number of path prefixes, relative to PRODUCT_OUT, where the
|
||||
|
|
|
@ -0,0 +1,121 @@
|
|||
/*
|
||||
* Copyright (C) 2020 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.build.config;
|
||||
|
||||
import java.io.PrintStream;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.TreeMap;
|
||||
|
||||
/**
|
||||
* Common parts between MakeConfig and the to-be-added GenericConfig, BazelConfig and SoongConfig.
|
||||
*/
|
||||
public class ConfigBase {
|
||||
protected String mPhase;
|
||||
protected List<String> mRootNodes;
|
||||
|
||||
/**
|
||||
* State of the make varaible environment from before the first config file.
|
||||
*/
|
||||
protected Map<String, Str> mInitialVariables = new HashMap();
|
||||
|
||||
/**
|
||||
* State of the make varaible environment from after the first config file.
|
||||
*/
|
||||
protected Map<String, Str> mFinalVariables = new HashMap();
|
||||
|
||||
|
||||
/**
|
||||
* The variables that are handled specially.
|
||||
*/
|
||||
protected final TreeMap<String, VarType> mProductVars = new TreeMap();
|
||||
|
||||
/**
|
||||
* Whether a product config variable is a list or single-value variable.
|
||||
*/
|
||||
public enum VarType {
|
||||
LIST,
|
||||
SINGLE,
|
||||
UNKNOWN // For non-product vars
|
||||
}
|
||||
|
||||
public void setPhase(String phase) {
|
||||
mPhase = phase;
|
||||
}
|
||||
|
||||
public String getPhase() {
|
||||
return mPhase;
|
||||
}
|
||||
|
||||
public void setRootNodes(List<String> filenames) {
|
||||
mRootNodes = new ArrayList(filenames);
|
||||
}
|
||||
|
||||
public List<String> getRootNodes() {
|
||||
return mRootNodes;
|
||||
}
|
||||
|
||||
public void addProductVar(String name, VarType type) {
|
||||
mProductVars.put(name, type);
|
||||
}
|
||||
|
||||
public TreeMap<String, VarType> getProductVars() {
|
||||
return mProductVars;
|
||||
}
|
||||
|
||||
public VarType getVarType(String name) {
|
||||
final VarType t = mProductVars.get(name);
|
||||
if (t != null) {
|
||||
return t;
|
||||
} else {
|
||||
return VarType.UNKNOWN;
|
||||
}
|
||||
}
|
||||
|
||||
public boolean isProductVar(String name) {
|
||||
return mProductVars.get(name) != null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the state the make variable environment from before the first config file.
|
||||
*/
|
||||
public Map<String, Str> getInitialVariables() {
|
||||
return mInitialVariables;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the state the make variable environment from before the first config file.
|
||||
*/
|
||||
public Map<String, Str> getFinalVariables() {
|
||||
return mFinalVariables;
|
||||
}
|
||||
|
||||
/**
|
||||
* Copy common base class fields from that to this.
|
||||
*/
|
||||
public void copyFrom(ConfigBase that) {
|
||||
setPhase(that.getPhase());
|
||||
setRootNodes(that.getRootNodes());
|
||||
for (Map.Entry<String, ConfigBase.VarType> entry: that.getProductVars().entrySet()) {
|
||||
addProductVar(entry.getKey(), entry.getValue());
|
||||
}
|
||||
mInitialVariables = new HashMap(that.getInitialVariables());
|
||||
mFinalVariables = new HashMap(that.getFinalVariables());
|
||||
}
|
||||
}
|
|
@ -0,0 +1,206 @@
|
|||
/*
|
||||
* Copyright (C) 2021 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.build.config;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* Converts a MakeConfig into a Generic config by applying heuristics about
|
||||
* the types of variable assignments that we do.
|
||||
*/
|
||||
public class ConvertMakeToGenericConfig {
|
||||
private final Errors mErrors;
|
||||
|
||||
public ConvertMakeToGenericConfig(Errors errors) {
|
||||
mErrors = errors;
|
||||
}
|
||||
|
||||
public GenericConfig convert(MakeConfig make) {
|
||||
final GenericConfig result = new GenericConfig();
|
||||
|
||||
// Base class fields
|
||||
result.copyFrom(make);
|
||||
|
||||
// Each file
|
||||
for (MakeConfig.ConfigFile f: make.getConfigFiles()) {
|
||||
final GenericConfig.ConfigFile genericFile
|
||||
= new GenericConfig.ConfigFile(f.getFilename());
|
||||
result.addConfigFile(genericFile);
|
||||
|
||||
final List<MakeConfig.Block> blocks = f.getBlocks();
|
||||
|
||||
// Some assertions:
|
||||
// TODO: Include better context for these errors.
|
||||
// There should always be at least a BEGIN and an AFTER, so assert this.
|
||||
if (blocks.size() < 2) {
|
||||
throw new RuntimeException("expected at least blocks.size() >= 2. Actcual size: "
|
||||
+ blocks.size());
|
||||
}
|
||||
if (blocks.get(0).getBlockType() != MakeConfig.BlockType.BEFORE) {
|
||||
throw new RuntimeException("expected first block to be BEFORE");
|
||||
}
|
||||
if (blocks.get(blocks.size() - 1).getBlockType() != MakeConfig.BlockType.AFTER) {
|
||||
throw new RuntimeException("expected first block to be AFTER");
|
||||
}
|
||||
// Everything in between should be an INHERIT block.
|
||||
for (int index = 1; index < blocks.size() - 1; index++) {
|
||||
if (blocks.get(index).getBlockType() != MakeConfig.BlockType.INHERIT) {
|
||||
throw new RuntimeException("expected INHERIT at block " + index);
|
||||
}
|
||||
}
|
||||
|
||||
// Each block represents a snapshot of the interpreter variable state (minus a few big
|
||||
// sets of variables which we don't export because they're used in the internals
|
||||
// of node_fns.mk, so we know they're not necessary here). The first (BEFORE) one
|
||||
// is everything that is set before the file is included, so it forms the base
|
||||
// for everything else.
|
||||
MakeConfig.Block prevBlock = blocks.get(0);
|
||||
|
||||
for (int index = 1; index < blocks.size(); index++) {
|
||||
final MakeConfig.Block block = blocks.get(index);
|
||||
for (final Map.Entry<String, Str> entry: block.getVars().entrySet()) {
|
||||
final String varName = entry.getKey();
|
||||
final GenericConfig.Assign assign = convertAssignment(block.getBlockType(),
|
||||
block.getInheritedFile(), make.getVarType(varName), varName,
|
||||
entry.getValue(), prevBlock.getVar(varName));
|
||||
if (assign != null) {
|
||||
genericFile.addStatement(assign);
|
||||
}
|
||||
}
|
||||
// Handle variables that are in prevBlock but not block -- they were
|
||||
// deleted. Is this even possible, or do they show up as ""? We will
|
||||
// treat them as positive assigments to empty string
|
||||
for (String prevName: prevBlock.getVars().keySet()) {
|
||||
if (!block.getVars().containsKey(prevName)) {
|
||||
genericFile.addStatement(
|
||||
new GenericConfig.Assign(prevName, new Str("")));
|
||||
}
|
||||
}
|
||||
if (block.getBlockType() == MakeConfig.BlockType.INHERIT) {
|
||||
genericFile.addStatement(
|
||||
new GenericConfig.Inherit(block.getInheritedFile()));
|
||||
}
|
||||
// For next iteration
|
||||
prevBlock = block;
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts one variable from a MakeConfig Block into a GenericConfig Assignment.
|
||||
*/
|
||||
GenericConfig.Assign convertAssignment(MakeConfig.BlockType blockType, Str inheritedFile,
|
||||
ConfigBase.VarType varType, String varName, Str varVal, Str prevVal) {
|
||||
if (prevVal == null) {
|
||||
// New variable.
|
||||
return new GenericConfig.Assign(varName, varVal);
|
||||
} else if (!varVal.equals(prevVal)) {
|
||||
// The value changed from the last block.
|
||||
if (varVal.equals("")) {
|
||||
// It was set to empty
|
||||
return new GenericConfig.Assign(varName, varVal);
|
||||
} else {
|
||||
// Product vars have the @inherit processing. Other vars we
|
||||
// will just ignore and put in one section at the end, based
|
||||
// on the difference between the BEFORE and AFTER blocks.
|
||||
if (varType == ConfigBase.VarType.UNKNOWN) {
|
||||
if (blockType == MakeConfig.BlockType.AFTER) {
|
||||
// For UNKNOWN variables, we don't worry about the
|
||||
// intermediate steps, just take the final value.
|
||||
return new GenericConfig.Assign(varName, varVal);
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
} else {
|
||||
return convertInheritedVar(blockType, inheritedFile,
|
||||
varName, varVal, prevVal);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// Variable not touched
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle the special inherited values, where the inherit-product puts in the
|
||||
* @inherit:... markers, adding Statements to the ConfigFile.
|
||||
*/
|
||||
GenericConfig.Assign convertInheritedVar(MakeConfig.BlockType blockType, Str inheritedFile,
|
||||
String varName, Str varVal, Str prevVal) {
|
||||
String varText = varVal.toString();
|
||||
String prevText = prevVal.toString().trim();
|
||||
if (blockType == MakeConfig.BlockType.INHERIT) {
|
||||
// inherit-product appends @inherit:... so drop that.
|
||||
final String marker = "@inherit:" + inheritedFile;
|
||||
if (varText.endsWith(marker)) {
|
||||
varText = varText.substring(0, varText.length() - marker.length()).trim();
|
||||
} else {
|
||||
mErrors.ERROR_IMPROPER_PRODUCT_VAR_MARKER.add(varVal.getPosition(),
|
||||
"Variable didn't end with marker \"" + marker + "\": " + varText);
|
||||
}
|
||||
}
|
||||
|
||||
if (!varText.equals(prevText)) {
|
||||
// If the variable value was actually changed.
|
||||
final ArrayList<String> words = split(varText, prevText);
|
||||
if (words.size() == 0) {
|
||||
// Pure Assignment, none of the previous value is present.
|
||||
return new GenericConfig.Assign(varName, new Str(varVal.getPosition(), varText));
|
||||
} else {
|
||||
// Self referential value (prepend, append, both).
|
||||
if (words.size() > 2) {
|
||||
// This is indicative of a construction that might not be quite
|
||||
// what we want. The above code will do something that works if it was
|
||||
// of the form "VAR := a $(VAR) b $(VAR) c", but if the original code
|
||||
// something else this won't work. This doesn't happen in AOSP, but
|
||||
// it's a theoretically possibility, so someone might do it.
|
||||
mErrors.WARNING_VARIABLE_RECURSION.add(varVal.getPosition(),
|
||||
"Possible unsupported variable recursion: "
|
||||
+ varName + " = " + varVal + " (prev=" + prevVal + ")");
|
||||
}
|
||||
return new GenericConfig.Assign(varName, Str.toList(varVal.getPosition(), words));
|
||||
}
|
||||
} else {
|
||||
// Variable not touched
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Split 'haystack' on occurrences of 'needle'. Trims each string of whitespace
|
||||
* to preserve make list semantics.
|
||||
*/
|
||||
private static ArrayList<String> split(String haystack, String needle) {
|
||||
final ArrayList<String> result = new ArrayList();
|
||||
final int needleLen = needle.length();
|
||||
if (needleLen == 0) {
|
||||
return result;
|
||||
}
|
||||
int start = 0;
|
||||
int end;
|
||||
while ((end = haystack.indexOf(needle, start)) >= 0) {
|
||||
result.add(haystack.substring(start, end).trim());
|
||||
start = end + needleLen;
|
||||
}
|
||||
result.add(haystack.substring(start).trim());
|
||||
return result;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,242 @@
|
|||
|
||||
/*
|
||||
* Copyright (C) 2020 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.build.config;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.Reader;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* A CSV parser.
|
||||
*/
|
||||
public class CsvParser {
|
||||
/**
|
||||
* Internal string buffer grows by this amount.
|
||||
*/
|
||||
private static final int CHUNK_SIZE = 64 * 1024;
|
||||
|
||||
/**
|
||||
* Error parsing.
|
||||
*/
|
||||
public static class ParseException extends Exception {
|
||||
private int mLine;
|
||||
private int mColumn;
|
||||
|
||||
public ParseException(int line, int column, String message) {
|
||||
super(message);
|
||||
mLine = line;
|
||||
mColumn = column;
|
||||
}
|
||||
|
||||
/**
|
||||
* Line number in source file.
|
||||
*/
|
||||
public int getLine() {
|
||||
return mLine;
|
||||
}
|
||||
|
||||
/**
|
||||
* Column in source file.
|
||||
*/
|
||||
public int getColumn() {
|
||||
return mColumn;
|
||||
}
|
||||
}
|
||||
|
||||
public static class Line {
|
||||
private final int mLineNumber;
|
||||
private final List<String> mFields;
|
||||
|
||||
Line(int lineno, List<String> fields) {
|
||||
mLineNumber = lineno;
|
||||
mFields = fields;
|
||||
}
|
||||
|
||||
public int getLine() {
|
||||
return mLineNumber;
|
||||
}
|
||||
|
||||
public List<String> getFields() {
|
||||
return mFields;
|
||||
}
|
||||
}
|
||||
|
||||
// Parser States
|
||||
private static final int STATE_START_LINE = 0;
|
||||
private static final int STATE_START_FIELD = 1;
|
||||
private static final int STATE_INSIDE_QUOTED_FIELD = 2;
|
||||
private static final int STATE_FIRST_QUOTATION_MARK = 3;
|
||||
private static final int STATE_INSIDE_UNQUOTED_FIELD = 4;
|
||||
private static final int STATE_DONE = 5;
|
||||
|
||||
// Parser Actions
|
||||
private static final int ACTION_APPEND_CHAR = 1;
|
||||
private static final int ACTION_FIELD_COMPLETE = 2;
|
||||
private static final int ACTION_LINE_COMPLETE = 4;
|
||||
|
||||
/**
|
||||
* Constructor.
|
||||
*/
|
||||
private CsvParser() {
|
||||
}
|
||||
|
||||
/**
|
||||
* Reads CSV and returns a list of Line objects.
|
||||
*
|
||||
* Handles newlines inside fields quoted with double quotes (").
|
||||
*
|
||||
* Doesn't report blank lines, but does include empty fields.
|
||||
*/
|
||||
public static List<Line> parse(Reader reader)
|
||||
throws ParseException, IOException {
|
||||
ArrayList<Line> result = new ArrayList();
|
||||
int line = 1;
|
||||
int column = 1;
|
||||
int pos = 0;
|
||||
char[] buf = new char[CHUNK_SIZE];
|
||||
HashMap<String,String> stringPool = new HashMap();
|
||||
ArrayList<String> fields = new ArrayList();
|
||||
|
||||
int state = STATE_START_LINE;
|
||||
while (state != STATE_DONE) {
|
||||
int c = reader.read();
|
||||
int action = 0;
|
||||
|
||||
if (state == STATE_START_LINE) {
|
||||
if (c <= 0) {
|
||||
// No data, skip ACTION_LINE_COMPLETE.
|
||||
state = STATE_DONE;
|
||||
} else if (c == '"') {
|
||||
state = STATE_INSIDE_QUOTED_FIELD;
|
||||
} else if (c == ',') {
|
||||
action = ACTION_FIELD_COMPLETE;
|
||||
state = STATE_START_FIELD;
|
||||
} else if (c == '\n') {
|
||||
// Consume the newline, state stays STATE_START_LINE.
|
||||
} else {
|
||||
action = ACTION_APPEND_CHAR;
|
||||
state = STATE_INSIDE_UNQUOTED_FIELD;
|
||||
}
|
||||
} else if (state == STATE_START_FIELD) {
|
||||
if (c <= 0) {
|
||||
// Field will be empty
|
||||
action = ACTION_FIELD_COMPLETE | ACTION_LINE_COMPLETE;
|
||||
state = STATE_DONE;
|
||||
} else if (c == '"') {
|
||||
state = STATE_INSIDE_QUOTED_FIELD;
|
||||
} else if (c == ',') {
|
||||
action = ACTION_FIELD_COMPLETE;
|
||||
state = STATE_START_FIELD;
|
||||
} else if (c == '\n') {
|
||||
action = ACTION_FIELD_COMPLETE | ACTION_LINE_COMPLETE;
|
||||
state = STATE_START_LINE;
|
||||
} else {
|
||||
action = ACTION_APPEND_CHAR;
|
||||
state = STATE_INSIDE_UNQUOTED_FIELD;
|
||||
}
|
||||
} else if (state == STATE_INSIDE_QUOTED_FIELD) {
|
||||
if (c <= 0) {
|
||||
throw new ParseException(line, column,
|
||||
"Bad input: End of input inside quoted field.");
|
||||
} else if (c == '"') {
|
||||
state = STATE_FIRST_QUOTATION_MARK;
|
||||
} else {
|
||||
action = ACTION_APPEND_CHAR;
|
||||
}
|
||||
} else if (state == STATE_FIRST_QUOTATION_MARK) {
|
||||
if (c <= 0) {
|
||||
action = ACTION_FIELD_COMPLETE | ACTION_LINE_COMPLETE;
|
||||
state = STATE_DONE;
|
||||
} else if (c == '"') {
|
||||
action = ACTION_APPEND_CHAR;
|
||||
state = STATE_INSIDE_QUOTED_FIELD;
|
||||
} else if (c == ',') {
|
||||
action = ACTION_FIELD_COMPLETE;
|
||||
state = STATE_START_FIELD;
|
||||
} else if (c == '\n') {
|
||||
action = ACTION_FIELD_COMPLETE | ACTION_LINE_COMPLETE;
|
||||
state = STATE_START_LINE;
|
||||
} else {
|
||||
throw new ParseException(line, column,
|
||||
"Bad input: Character after field ended or unquoted '\"'.");
|
||||
}
|
||||
} else if (state == STATE_INSIDE_UNQUOTED_FIELD) {
|
||||
if (c <= 0) {
|
||||
action = ACTION_FIELD_COMPLETE | ACTION_LINE_COMPLETE;
|
||||
state = STATE_DONE;
|
||||
} else if (c == ',') {
|
||||
action = ACTION_FIELD_COMPLETE;
|
||||
state = STATE_START_FIELD;
|
||||
} else if (c == '\n') {
|
||||
action = ACTION_FIELD_COMPLETE | ACTION_LINE_COMPLETE;
|
||||
state = STATE_START_LINE;
|
||||
} else {
|
||||
action = ACTION_APPEND_CHAR;
|
||||
}
|
||||
}
|
||||
|
||||
if ((action & ACTION_APPEND_CHAR) != 0) {
|
||||
// Reallocate buffer if necessary. Hopefully not often because CHUNK_SIZE is big.
|
||||
if (pos >= buf.length) {
|
||||
char[] old = buf;
|
||||
buf = new char[old.length + CHUNK_SIZE];
|
||||
System.arraycopy(old, 0, buf, 0, old.length);
|
||||
}
|
||||
// Store the character
|
||||
buf[pos] = (char)c;
|
||||
pos++;
|
||||
}
|
||||
if ((action & ACTION_FIELD_COMPLETE) != 0) {
|
||||
// A lot of the strings are duplicated, so pool them to reduce peak memory
|
||||
// usage. This could be made slightly better by having a custom key class
|
||||
// that does the lookup without making a new String that gets immediately
|
||||
// thrown away.
|
||||
String field = new String(buf, 0, pos);
|
||||
final String cached = stringPool.get(field);
|
||||
if (cached == null) {
|
||||
stringPool.put(field, field);
|
||||
} else {
|
||||
field = cached;
|
||||
}
|
||||
fields.add(field);
|
||||
pos = 0;
|
||||
}
|
||||
if ((action & ACTION_LINE_COMPLETE) != 0) {
|
||||
// Only report lines with any contents
|
||||
if (fields.size() > 0) {
|
||||
result.add(new Line(line, fields));
|
||||
fields = new ArrayList();
|
||||
}
|
||||
}
|
||||
|
||||
if (c == '\n') {
|
||||
line++;
|
||||
column = 1;
|
||||
} else {
|
||||
column++;
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,314 @@
|
|||
/*
|
||||
* Copyright (C) 2020 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.build.config;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.Reader;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Map;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
/**
|
||||
* Parses the output of ckati building build/make/core/dumpconfig.mk.
|
||||
*
|
||||
* The format is as follows:
|
||||
* - All processed lines are colon (':') separated fields.
|
||||
* - Lines before the dumpconfig_version line are dropped for forward compatibility
|
||||
* - Lines where the first field is config_var describe variables declared in makefiles
|
||||
* (implemented by the dump-config-vals macro)
|
||||
* Field Description
|
||||
* 0 "config_var" row type
|
||||
* 1 Product makefile being processed
|
||||
* 2 The variable name
|
||||
* 3 The value of the variable
|
||||
* 4 The location of the variable, as best tracked by kati
|
||||
*/
|
||||
public class DumpConfigParser {
|
||||
private static final boolean DEBUG = true;
|
||||
|
||||
private final Errors mErrors;
|
||||
private final String mFilename;
|
||||
private final Reader mReader;
|
||||
|
||||
private final ArrayList<MakeConfig> mResults = new ArrayList();
|
||||
|
||||
private static final Pattern LIST_SEPARATOR = Pattern.compile("\\s+");
|
||||
|
||||
/**
|
||||
* Constructor.
|
||||
*/
|
||||
private DumpConfigParser(Errors errors, String filename, Reader reader) {
|
||||
mErrors = errors;
|
||||
mFilename = filename;
|
||||
mReader = reader;
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse the text into a list of MakeConfig objects.
|
||||
*/
|
||||
public static ArrayList<MakeConfig> parse(Errors errors, String filename, Reader reader)
|
||||
throws CsvParser.ParseException, IOException {
|
||||
DumpConfigParser parser = new DumpConfigParser(errors, filename, reader);
|
||||
parser.parseImpl();
|
||||
return parser.mResults;
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse the input.
|
||||
*/
|
||||
private void parseImpl() throws CsvParser.ParseException, IOException {
|
||||
final List<CsvParser.Line> lines = CsvParser.parse(mReader);
|
||||
final int lineCount = lines.size();
|
||||
int index = 0;
|
||||
|
||||
int dumpconfigVersion = 0;
|
||||
|
||||
// Ignore lines until until we get a dumpconfig_version line for forward compatibility.
|
||||
// In a previous life, this loop parsed from all of kati's stdout, not just the file
|
||||
// that dumpconfig.mk writes, but it's harmless to leave this loop in. It gives us a
|
||||
// little bit of flexibility which we probably won't need anyway, this tool probably
|
||||
// won't diverge from dumpconfig.mk anyway.
|
||||
for (; index < lineCount; index++) {
|
||||
final CsvParser.Line line = lines.get(index);
|
||||
final List<String> fields = line.getFields();
|
||||
|
||||
if (matchLineType(line, "dumpconfig_version", 1)) {
|
||||
try {
|
||||
dumpconfigVersion = Integer.parseInt(fields.get(1));
|
||||
} catch (NumberFormatException ex) {
|
||||
mErrors.WARNING_DUMPCONFIG.add(
|
||||
new Position(mFilename, line.getLine()),
|
||||
"Couldn't parse dumpconfig_version: " + fields.get(1));
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// If we never saw dumpconfig_version, there's a problem with the command, so stop.
|
||||
if (dumpconfigVersion == 0) {
|
||||
mErrors.ERROR_DUMPCONFIG.fatal(
|
||||
new Position(mFilename),
|
||||
"Never saw a valid dumpconfig_version line.");
|
||||
}
|
||||
|
||||
// Any lines before the start signal will be dropped. We create garbage objects
|
||||
// here to avoid having to check for null everywhere.
|
||||
MakeConfig makeConfig = new MakeConfig();
|
||||
MakeConfig.ConfigFile configFile = new MakeConfig.ConfigFile("<ignored>");
|
||||
MakeConfig.Block block = new MakeConfig.Block(MakeConfig.BlockType.UNSET);
|
||||
Map<String, Str> initialVariables = new HashMap();
|
||||
Map<String, Str> finalVariables = new HashMap();
|
||||
|
||||
// Number of "phases" we've seen so far.
|
||||
for (; index < lineCount; index++) {
|
||||
final CsvParser.Line line = lines.get(index);
|
||||
final List<String> fields = line.getFields();
|
||||
final String lineType = fields.get(0);
|
||||
|
||||
if (matchLineType(line, "phase", 2)) {
|
||||
// Start the new one
|
||||
makeConfig = new MakeConfig();
|
||||
makeConfig.setPhase(fields.get(1));
|
||||
makeConfig.setRootNodes(splitList(fields.get(2)));
|
||||
mResults.add(makeConfig);
|
||||
initialVariables = makeConfig.getInitialVariables();
|
||||
finalVariables = makeConfig.getFinalVariables();
|
||||
|
||||
if (DEBUG) {
|
||||
System.out.println("PHASE:");
|
||||
System.out.println(" " + makeConfig.getPhase());
|
||||
System.out.println(" " + makeConfig.getRootNodes());
|
||||
}
|
||||
} else if (matchLineType(line, "var", 2)) {
|
||||
final MakeConfig.VarType type = "list".equals(fields.get(1))
|
||||
? MakeConfig.VarType.LIST : MakeConfig.VarType.SINGLE;
|
||||
makeConfig.addProductVar(fields.get(2), type);
|
||||
|
||||
if (DEBUG) {
|
||||
System.out.println(" VAR: " + type + " " + fields.get(2));
|
||||
}
|
||||
} else if (matchLineType(line, "import", 1)) {
|
||||
final List<String> importStack = splitList(fields.get(1));
|
||||
if (importStack.size() == 0) {
|
||||
mErrors.WARNING_DUMPCONFIG.add(
|
||||
new Position(mFilename, line.getLine()),
|
||||
"'import' line with empty include stack.");
|
||||
continue;
|
||||
}
|
||||
|
||||
// The beginning of importing a new file.
|
||||
configFile = new MakeConfig.ConfigFile(importStack.get(0));
|
||||
if (makeConfig.addConfigFile(configFile) != null) {
|
||||
mErrors.WARNING_DUMPCONFIG.add(
|
||||
new Position(mFilename, line.getLine()),
|
||||
"Duplicate file imported in section: " + configFile.getFilename());
|
||||
}
|
||||
// We expect a Variable block next.
|
||||
block = new MakeConfig.Block(MakeConfig.BlockType.BEFORE);
|
||||
configFile.addBlock(block);
|
||||
|
||||
if (DEBUG) {
|
||||
System.out.println(" IMPORT: " + configFile.getFilename());
|
||||
}
|
||||
} else if (matchLineType(line, "inherit", 2)) {
|
||||
final String currentFile = fields.get(1);
|
||||
final String inheritedFile = fields.get(2);
|
||||
if (!configFile.getFilename().equals(currentFile)) {
|
||||
mErrors.WARNING_DUMPCONFIG.add(
|
||||
new Position(mFilename, line.getLine()),
|
||||
"Unexpected current file in 'inherit' line '" + currentFile
|
||||
+ "' while processing '" + configFile.getFilename() + "'");
|
||||
continue;
|
||||
}
|
||||
|
||||
// There is already a file in progress, so add another var block to that.
|
||||
block = new MakeConfig.Block(MakeConfig.BlockType.INHERIT);
|
||||
// TODO: Make dumpconfig.mk also output a Position for inherit-product
|
||||
block.setInheritedFile(new Str(inheritedFile));
|
||||
configFile.addBlock(block);
|
||||
|
||||
if (DEBUG) {
|
||||
System.out.println(" INHERIT: " + inheritedFile);
|
||||
}
|
||||
} else if (matchLineType(line, "imported", 1)) {
|
||||
final List<String> importStack = splitList(fields.get(1));
|
||||
if (importStack.size() == 0) {
|
||||
mErrors.WARNING_DUMPCONFIG.add(
|
||||
new Position(mFilename, line.getLine()),
|
||||
"'imported' line with empty include stack.");
|
||||
continue;
|
||||
}
|
||||
final String currentFile = importStack.get(0);
|
||||
if (!configFile.getFilename().equals(currentFile)) {
|
||||
mErrors.WARNING_DUMPCONFIG.add(
|
||||
new Position(mFilename, line.getLine()),
|
||||
"Unexpected current file in 'imported' line '" + currentFile
|
||||
+ "' while processing '" + configFile.getFilename() + "'");
|
||||
continue;
|
||||
}
|
||||
|
||||
// There is already a file in progress, so add another var block to that.
|
||||
// This will be the last one, but will check that after parsing.
|
||||
block = new MakeConfig.Block(MakeConfig.BlockType.AFTER);
|
||||
configFile.addBlock(block);
|
||||
|
||||
if (DEBUG) {
|
||||
System.out.println(" AFTER: " + currentFile);
|
||||
}
|
||||
} else if (matchLineType(line, "val", 5)) {
|
||||
final String productMakefile = fields.get(1);
|
||||
final String blockTypeString = fields.get(2);
|
||||
final String varName = fields.get(3);
|
||||
final String varValue = fields.get(4);
|
||||
final Position pos = Position.parse(fields.get(5));
|
||||
final Str str = new Str(pos, varValue);
|
||||
|
||||
if (blockTypeString.equals("initial")) {
|
||||
initialVariables.put(varName, str);
|
||||
} else if (blockTypeString.equals("final")) {
|
||||
finalVariables.put(varName, str);
|
||||
} else {
|
||||
if (!productMakefile.equals(configFile.getFilename())) {
|
||||
mErrors.WARNING_DUMPCONFIG.add(
|
||||
new Position(mFilename, line.getLine()),
|
||||
"Mismatched 'val' product makefile."
|
||||
+ " Expected: " + configFile.getFilename()
|
||||
+ " Saw: " + productMakefile);
|
||||
continue;
|
||||
}
|
||||
|
||||
final MakeConfig.BlockType blockType = parseBlockType(line, blockTypeString);
|
||||
if (blockType == null) {
|
||||
continue;
|
||||
}
|
||||
if (blockType != block.getBlockType()) {
|
||||
mErrors.WARNING_DUMPCONFIG.add(
|
||||
new Position(mFilename, line.getLine()),
|
||||
"Mismatched 'val' block type."
|
||||
+ " Expected: " + block.getBlockType()
|
||||
+ " Saw: " + blockType);
|
||||
}
|
||||
|
||||
// Add the variable to the block in progress
|
||||
block.addVar(varName, str);
|
||||
}
|
||||
} else {
|
||||
if (DEBUG) {
|
||||
System.out.print("# ");
|
||||
for (int d = 0; d < fields.size(); d++) {
|
||||
System.out.print(fields.get(d));
|
||||
if (d != fields.size() - 1) {
|
||||
System.out.print(",");
|
||||
}
|
||||
}
|
||||
System.out.println();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Return true if the line type matches 'lineType' and there are at least 'fieldCount'
|
||||
* fields (not including the first field which is the line type).
|
||||
*/
|
||||
private boolean matchLineType(CsvParser.Line line, String lineType, int fieldCount) {
|
||||
final List<String> fields = line.getFields();
|
||||
if (!lineType.equals(fields.get(0))) {
|
||||
return false;
|
||||
}
|
||||
if (fields.size() < (fieldCount + 1)) {
|
||||
mErrors.WARNING_DUMPCONFIG.add(new Position(mFilename, line.getLine()),
|
||||
fields.get(0) + " line has " + fields.size() + " fields. Expected at least "
|
||||
+ (fieldCount + 1) + " fields.");
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Split a string with space separated items (i.e. the make list format) into a List<String>.
|
||||
*/
|
||||
private static List<String> splitList(String text) {
|
||||
// Arrays.asList returns a fixed-length List, so we copy it into an ArrayList to not
|
||||
// propagate that surprise detail downstream.
|
||||
return new ArrayList(Arrays.asList(LIST_SEPARATOR.split(text.trim())));
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse a BockType or issue a warning if it can't be parsed.
|
||||
*/
|
||||
private MakeConfig.BlockType parseBlockType(CsvParser.Line line, String text) {
|
||||
if ("before".equals(text)) {
|
||||
return MakeConfig.BlockType.BEFORE;
|
||||
} else if ("inherit".equals(text)) {
|
||||
return MakeConfig.BlockType.INHERIT;
|
||||
} else if ("after".equals(text)) {
|
||||
return MakeConfig.BlockType.AFTER;
|
||||
} else {
|
||||
mErrors.WARNING_DUMPCONFIG.add(
|
||||
new Position(mFilename, line.getLine()),
|
||||
"Invalid block type: " + text);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -30,7 +30,7 @@ import java.util.Map;
|
|||
* <b>Naming Convention:</b>
|
||||
* <ul>
|
||||
* <li>ERROR_ for Categories with isLevelSettable false and Level.ERROR
|
||||
* <li>WARNING_ for Categories with isLevelSettable false and default WARNING or HIDDEN
|
||||
* <li>WARNING_ for Categories with isLevelSettable true and default WARNING or HIDDEN
|
||||
* <li>Don't have isLevelSettable true and not ERROR. (The constructor asserts this).
|
||||
* </ul>
|
||||
*/
|
||||
|
@ -42,4 +42,21 @@ public class Errors extends ErrorReporter {
|
|||
public final Category WARNING_UNKNOWN_COMMAND_LINE_ERROR = new Category(2, true, Level.HIDDEN,
|
||||
"Passing unknown errors on the command line. Hidden by default for\n"
|
||||
+ "forward compatibility.");
|
||||
|
||||
public final Category ERROR_KATI = new Category(3, false, Level.ERROR,
|
||||
"Error executing or reading from Kati.");
|
||||
|
||||
public final Category WARNING_DUMPCONFIG = new Category(4, true, Level.WARNING,
|
||||
"Anomaly parsing the output of kati and dumpconfig.mk.");
|
||||
|
||||
public final Category ERROR_DUMPCONFIG = new Category(5, false, Level.ERROR,
|
||||
"Error parsing the output of kati and dumpconfig.mk.");
|
||||
|
||||
public final Category WARNING_VARIABLE_RECURSION = new Category(6, true, Level.WARNING,
|
||||
"Possible unsupported variable recursion.");
|
||||
|
||||
// This could be a warning, but it's very likely that the data is corrupted somehow
|
||||
// if we're seeing this.
|
||||
public final Category ERROR_IMPROPER_PRODUCT_VAR_MARKER = new Category(7, true, Level.ERROR,
|
||||
"Bad input from dumpvars causing corrupted product variables.");
|
||||
}
|
||||
|
|
|
@ -0,0 +1,131 @@
|
|||
/*
|
||||
* Copyright (C) 2020 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.build.config;
|
||||
|
||||
import java.io.PrintStream;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.TreeMap;
|
||||
|
||||
/**
|
||||
* Language-agnostic representation of a configuration statement.
|
||||
*/
|
||||
public class GenericConfig extends ConfigBase {
|
||||
/**
|
||||
* The config files that were imported in this config pass.
|
||||
*/
|
||||
protected final TreeMap<String, ConfigFile> mConfigFiles = new TreeMap();
|
||||
|
||||
/**
|
||||
* A configuration file.
|
||||
*/
|
||||
public static class ConfigFile {
|
||||
/**
|
||||
* The name of the file, relative to the tree root.
|
||||
*/
|
||||
private final String mFilename;
|
||||
|
||||
/**
|
||||
* Sections of variable definitions and import statements. Product config
|
||||
* files will always have at least one block.
|
||||
*/
|
||||
private final ArrayList<Statement> mStatements = new ArrayList();
|
||||
|
||||
public ConfigFile(String filename) {
|
||||
mFilename = filename;
|
||||
}
|
||||
|
||||
public String getFilename() {
|
||||
return mFilename;
|
||||
}
|
||||
|
||||
public void addStatement(Statement statement) {
|
||||
mStatements.add(statement);
|
||||
}
|
||||
|
||||
public ArrayList<Statement> getStatements() {
|
||||
return mStatements;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Base class for statements that appear in config files.
|
||||
*/
|
||||
public static class Statement {
|
||||
}
|
||||
|
||||
/**
|
||||
* A variable assignment.
|
||||
*/
|
||||
public static class Assign extends Statement {
|
||||
private final String mVarName;
|
||||
private final List<Str> mValue;
|
||||
|
||||
/**
|
||||
* Assignment of a single value
|
||||
*/
|
||||
public Assign(String varName, Str value) {
|
||||
mVarName = varName;
|
||||
mValue = new ArrayList();
|
||||
mValue.add(value);
|
||||
}
|
||||
|
||||
/**
|
||||
* Assignment referencing a previous value.
|
||||
* VAR := $(1) $(VAR) $(2) $(VAR) $(3)
|
||||
*/
|
||||
public Assign(String varName, List<Str> value) {
|
||||
mVarName = varName;
|
||||
mValue = value;
|
||||
}
|
||||
|
||||
public String getName() {
|
||||
return mVarName;
|
||||
}
|
||||
|
||||
public List<Str> getValue() {
|
||||
return mValue;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* An $(inherit-product FILENAME) statement
|
||||
*/
|
||||
public static class Inherit extends Statement {
|
||||
private final Str mFilename;
|
||||
|
||||
public Inherit(Str filename) {
|
||||
mFilename = filename;
|
||||
}
|
||||
|
||||
public Str getFilename() {
|
||||
return mFilename;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds the given config file. Returns any one previously added, or null.
|
||||
*/
|
||||
public ConfigFile addConfigFile(ConfigFile file) {
|
||||
return mConfigFiles.put(file.getFilename(), file);
|
||||
}
|
||||
|
||||
public TreeMap<String, ConfigFile> getFiles() {
|
||||
return mConfigFiles;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,26 @@
|
|||
/*
|
||||
* Copyright (C) 2020 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.build.config;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Wrapper for invoking kati.
|
||||
*/
|
||||
public interface Kati {
|
||||
public MakeConfig loadProductConfig();
|
||||
}
|
|
@ -0,0 +1,43 @@
|
|||
/*
|
||||
* Copyright (C) 2020 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.build.config;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
|
||||
public interface KatiCommand {
|
||||
public static class KatiException extends Exception {
|
||||
private String mStderr;
|
||||
|
||||
public KatiException(List<String> cmd, String stderr) {
|
||||
super("Error running kati: " + Arrays.toString(cmd.toArray()));
|
||||
mStderr = stderr;
|
||||
}
|
||||
|
||||
public String getStderr() {
|
||||
return mStderr;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Run kati directly. Returns stdout data.
|
||||
*
|
||||
* @throws KatiException if there is an error. KatiException will contain
|
||||
* the stderr from the kati invocation.
|
||||
*/
|
||||
public String run(String[] args) throws KatiException;
|
||||
}
|
|
@ -0,0 +1,139 @@
|
|||
/*
|
||||
* Copyright (C) 2020 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.build.config;
|
||||
|
||||
import java.io.InputStream;
|
||||
import java.io.InputStreamReader;
|
||||
import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
|
||||
public class KatiCommandImpl implements KatiCommand {
|
||||
final Errors mErrors;
|
||||
final Options mOptions;
|
||||
|
||||
/**
|
||||
* Runnable that consumes all of an InputStream until EOF, writes the contents
|
||||
* into a StringBuilder, and then closes the stream.
|
||||
*/
|
||||
class OutputReader implements Runnable {
|
||||
private final InputStream mStream;
|
||||
private final StringBuilder mOutput;
|
||||
|
||||
OutputReader(InputStream stream, StringBuilder output) {
|
||||
mStream = stream;
|
||||
mOutput = output;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
final char[] buf = new char[16*1024];
|
||||
final InputStreamReader reader = new InputStreamReader(mStream, StandardCharsets.UTF_8);
|
||||
try {
|
||||
int amt;
|
||||
while ((amt = reader.read(buf, 0, buf.length)) >= 0) {
|
||||
mOutput.append(buf, 0, amt);
|
||||
}
|
||||
} catch (IOException ex) {
|
||||
mErrors.ERROR_KATI.add("Error reading from kati: " + ex.getMessage());
|
||||
} finally {
|
||||
try {
|
||||
reader.close();
|
||||
} catch (IOException ex) {
|
||||
// Close doesn't throw
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public KatiCommandImpl(Errors errors, Options options) {
|
||||
mErrors = errors;
|
||||
mOptions = options;
|
||||
}
|
||||
|
||||
/**
|
||||
* Run kati directly. Returns stdout data.
|
||||
*
|
||||
* @throws KatiException if there is an error. KatiException will contain
|
||||
* the stderr from the kati invocation.
|
||||
*/
|
||||
public String run(String[] args) throws KatiException {
|
||||
final ArrayList<String> cmd = new ArrayList();
|
||||
cmd.add(mOptions.getCKatiBin());
|
||||
for (String arg: args) {
|
||||
cmd.add(arg);
|
||||
}
|
||||
|
||||
final ProcessBuilder builder = new ProcessBuilder(cmd);
|
||||
builder.redirectOutput(ProcessBuilder.Redirect.PIPE);
|
||||
builder.redirectError(ProcessBuilder.Redirect.PIPE);
|
||||
|
||||
Process process = null;
|
||||
|
||||
try {
|
||||
process = builder.start();
|
||||
} catch (IOException ex) {
|
||||
throw new KatiException(cmd, "IOException running process: " + ex.getMessage());
|
||||
}
|
||||
|
||||
final StringBuilder stdout = new StringBuilder();
|
||||
final Thread stdoutThread = new Thread(new OutputReader(process.getInputStream(), stdout),
|
||||
"kati_stdout_reader");
|
||||
stdoutThread.start();
|
||||
|
||||
final StringBuilder stderr = new StringBuilder();
|
||||
final Thread stderrThread = new Thread(new OutputReader(process.getErrorStream(), stderr),
|
||||
"kati_stderr_reader");
|
||||
stderrThread.start();
|
||||
|
||||
int returnCode = waitForProcess(process);
|
||||
joinThread(stdoutThread);
|
||||
joinThread(stderrThread);
|
||||
|
||||
if (returnCode != 0) {
|
||||
throw new KatiException(cmd, stderr.toString());
|
||||
}
|
||||
|
||||
return stdout.toString();
|
||||
}
|
||||
|
||||
/**
|
||||
* Wrap Process.waitFor() because it throws InterruptedException.
|
||||
*/
|
||||
private static int waitForProcess(Process proc) {
|
||||
while (true) {
|
||||
try {
|
||||
return proc.waitFor();
|
||||
} catch (InterruptedException ex) {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Wrap Thread.join() because it throws InterruptedException.
|
||||
*/
|
||||
private static void joinThread(Thread thread) {
|
||||
while (true) {
|
||||
try {
|
||||
thread.join();
|
||||
return;
|
||||
} catch (InterruptedException ex) {
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,113 @@
|
|||
/*
|
||||
* Copyright (C) 2020 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.build.config;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileReader;
|
||||
import java.io.IOException;
|
||||
import java.nio.file.Paths;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
public class KatiImpl implements Kati {
|
||||
// Subdirectory inside out for config stuff.
|
||||
private static final String CONFIG_SUBDIR = "config";
|
||||
|
||||
private final Errors mErrors;
|
||||
private final Options mOptions;
|
||||
private final KatiCommand mCommand;
|
||||
|
||||
// TODO: Do we need to consider the whole or a greater subset of the
|
||||
// environment (or a hash of it?). In theory product-variant is enough, but we know
|
||||
// people use stuff from the environment, even though we're trying to get rid of that.
|
||||
private String getWorkDirPath() {
|
||||
return Paths.get(mOptions.getOutDir(), CONFIG_SUBDIR,
|
||||
mOptions.getProduct() + '-' + mOptions.getVariant()).toString();
|
||||
}
|
||||
|
||||
private String getDumpConfigCsvPath() {
|
||||
return Paths.get(getWorkDirPath(), "dumpconfig.csv").toString();
|
||||
}
|
||||
|
||||
public KatiImpl(Errors errors, Options options) {
|
||||
this(errors, options, new KatiCommandImpl(errors, options));
|
||||
}
|
||||
|
||||
// VisibleForTesting
|
||||
public KatiImpl(Errors errors, Options options, KatiCommand command) {
|
||||
mErrors = errors;
|
||||
mOptions = options;
|
||||
mCommand = command;
|
||||
}
|
||||
|
||||
@Override
|
||||
public MakeConfig loadProductConfig() {
|
||||
final String csvPath = getDumpConfigCsvPath();
|
||||
try {
|
||||
File workDir = new File(getWorkDirPath());
|
||||
|
||||
if (!workDir.mkdirs()) {
|
||||
mErrors.ERROR_KATI.add("Unable to create directory: " + workDir);
|
||||
return null; // TODO: throw exception?
|
||||
}
|
||||
|
||||
System.out.println("running kati");
|
||||
String out = mCommand.run(new String[] {
|
||||
"-f", "build/make/core/dumpconfig.mk",
|
||||
"DUMPCONFIG_FILE=" + csvPath
|
||||
});
|
||||
|
||||
if (!out.contains("***DONE***")) {
|
||||
mErrors.ERROR_KATI.add(
|
||||
"Unknown error with kati, but it didn't print ***DONE*** message");
|
||||
return null; // TODO: throw exception?
|
||||
}
|
||||
// TODO: Check that output was good.
|
||||
} catch (KatiCommand.KatiException ex) {
|
||||
mErrors.ERROR_KATI.add("Error running kati:\n" + ex.getStderr());
|
||||
return null;
|
||||
}
|
||||
|
||||
if (!(new File(csvPath)).canRead()) {
|
||||
mErrors.ERROR_KATI.add("Kati ran but did not create " + csvPath);
|
||||
return null;
|
||||
}
|
||||
|
||||
try (FileReader reader = new FileReader(csvPath)) {
|
||||
System.out.println("csvPath=" + csvPath);
|
||||
List<MakeConfig> makeConfigs = DumpConfigParser.parse(mErrors, csvPath, reader);
|
||||
|
||||
if (makeConfigs.size() == 0) {
|
||||
// TODO: Issue error?
|
||||
return null;
|
||||
}
|
||||
|
||||
// TODO: There are multiple passes. That should be cleaned up in the make
|
||||
// build system, but for now, the first one is the one we want.
|
||||
return makeConfigs.get(0);
|
||||
} catch (CsvParser.ParseException ex) {
|
||||
mErrors.ERROR_KATI.add(new Position(csvPath, ex.getLine()),
|
||||
"Unable to parse output of dumpconfig.mk: " + ex.getMessage());
|
||||
return null; // TODO: throw exception?
|
||||
} catch (IOException ex) {
|
||||
System.out.println(ex);
|
||||
mErrors.ERROR_KATI.add("Unable to read " + csvPath + ": " + ex.getMessage());
|
||||
return null; // TODO: throw exception?
|
||||
}
|
||||
}
|
||||
}
|
|
@ -16,6 +16,10 @@
|
|||
|
||||
package com.android.build.config;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.TreeSet;
|
||||
|
||||
public class Main {
|
||||
private final Errors mErrors;
|
||||
private final Options mOptions;
|
||||
|
@ -31,6 +35,25 @@ public class Main {
|
|||
// TODO: Check the build environment to make sure we're running in a real
|
||||
// build environment, e.g. actually inside a source tree, with TARGET_PRODUCT
|
||||
// and TARGET_BUILD_VARIANT defined, etc.
|
||||
Kati kati = new KatiImpl(mErrors, mOptions);
|
||||
MakeConfig makeConfig = kati.loadProductConfig();
|
||||
if (makeConfig == null || mErrors.hadError()) {
|
||||
return;
|
||||
}
|
||||
|
||||
System.out.println();
|
||||
System.out.println("====================");
|
||||
System.out.println("PRODUCT CONFIG FILES");
|
||||
System.out.println("====================");
|
||||
makeConfig.printToStream(System.out);
|
||||
|
||||
ConvertMakeToGenericConfig m2g = new ConvertMakeToGenericConfig(mErrors);
|
||||
GenericConfig generic = m2g.convert(makeConfig);
|
||||
|
||||
System.out.println("======================");
|
||||
System.out.println("REGENERATED MAKE FILES");
|
||||
System.out.println("======================");
|
||||
MakeWriter.write(System.out, generic, 0);
|
||||
|
||||
// TODO: Run kati and extract the variables and convert all that into starlark files.
|
||||
|
||||
|
@ -38,8 +61,6 @@ public class Main {
|
|||
|
||||
// TODO: Get the variables that were defined in starlark and use that to write
|
||||
// out the make, soong and bazel input files.
|
||||
mErrors.ERROR_COMMAND_LINE.add("asdf");
|
||||
throw new RuntimeException("poop");
|
||||
}
|
||||
|
||||
public static void main(String[] args) {
|
||||
|
@ -47,7 +68,7 @@ public class Main {
|
|||
int exitCode = 0;
|
||||
|
||||
try {
|
||||
Options options = Options.parse(errors, args);
|
||||
Options options = Options.parse(errors, args, System.getenv());
|
||||
if (errors.hadError()) {
|
||||
Options.printHelp(System.err);
|
||||
System.err.println();
|
||||
|
@ -62,7 +83,7 @@ public class Main {
|
|||
Options.printHelp(System.out);
|
||||
return;
|
||||
}
|
||||
} catch (CommandException ex) {
|
||||
} catch (CommandException | Errors.FatalException ex) {
|
||||
// These are user errors, so don't show a stack trace
|
||||
exitCode = 1;
|
||||
} catch (Throwable ex) {
|
||||
|
|
|
@ -0,0 +1,170 @@
|
|||
/*
|
||||
* Copyright (C) 2020 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.build.config;
|
||||
|
||||
import java.io.PrintStream;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.TreeMap;
|
||||
|
||||
public class MakeConfig extends ConfigBase {
|
||||
/**
|
||||
* The config files that were imported in this config pass.
|
||||
*/
|
||||
protected final ArrayList<ConfigFile> mConfigFiles = new ArrayList();
|
||||
|
||||
public enum BlockType {
|
||||
UNSET,
|
||||
BEFORE,
|
||||
INHERIT,
|
||||
AFTER
|
||||
}
|
||||
|
||||
public static class ConfigFile {
|
||||
/**
|
||||
* The name of the file, relative to the tree root.
|
||||
*/
|
||||
private final String mFilename;
|
||||
|
||||
/**
|
||||
* Sections of variable definitions and import statements. Product config
|
||||
* files will always have at least one block.
|
||||
*/
|
||||
private final ArrayList<Block> mBlocks = new ArrayList();
|
||||
|
||||
public ConfigFile(String filename) {
|
||||
mFilename = filename;
|
||||
}
|
||||
|
||||
public String getFilename() {
|
||||
return mFilename;
|
||||
}
|
||||
|
||||
public void addBlock(Block block) {
|
||||
mBlocks.add(block);
|
||||
}
|
||||
|
||||
public ArrayList<Block> getBlocks() {
|
||||
return mBlocks;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A set of variables that were defined.
|
||||
*/
|
||||
public static class Block {
|
||||
private final BlockType mBlockType;
|
||||
private final TreeMap<String, Str> mValues = new TreeMap();
|
||||
private Str mInheritedFile;
|
||||
|
||||
public Block(BlockType blockType) {
|
||||
mBlockType = blockType;
|
||||
}
|
||||
|
||||
public BlockType getBlockType() {
|
||||
return mBlockType;
|
||||
}
|
||||
|
||||
public void addVar(String varName, Str varValue) {
|
||||
mValues.put(varName, varValue);
|
||||
}
|
||||
|
||||
public Str getVar(String varName) {
|
||||
return mValues.get(varName);
|
||||
}
|
||||
|
||||
public TreeMap<String, Str> getVars() {
|
||||
return mValues;
|
||||
}
|
||||
|
||||
public void setInheritedFile(Str filename) {
|
||||
mInheritedFile = filename;
|
||||
}
|
||||
|
||||
public Str getInheritedFile() {
|
||||
return mInheritedFile;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds the given config file. Returns any one previously added, or null.
|
||||
*/
|
||||
public ConfigFile addConfigFile(ConfigFile file) {
|
||||
ConfigFile prev = null;
|
||||
for (ConfigFile f: mConfigFiles) {
|
||||
if (f.getFilename().equals(file.getFilename())) {
|
||||
prev = f;
|
||||
break;
|
||||
}
|
||||
}
|
||||
mConfigFiles.add(file);
|
||||
return prev;
|
||||
}
|
||||
|
||||
public List<ConfigFile> getConfigFiles() {
|
||||
return mConfigFiles;
|
||||
}
|
||||
|
||||
public void printToStream(PrintStream out) {
|
||||
out.println("MakeConfig {");
|
||||
out.println(" phase: " + mPhase);
|
||||
out.println(" rootNodes: " + mRootNodes);
|
||||
out.print(" singleVars: [ ");
|
||||
for (Map.Entry<String,VarType> entry: mProductVars.entrySet()) {
|
||||
if (entry.getValue() == VarType.SINGLE) {
|
||||
out.print(entry.getKey());
|
||||
out.print(" ");
|
||||
}
|
||||
}
|
||||
out.println("]");
|
||||
out.print(" listVars: [ ");
|
||||
for (Map.Entry<String,VarType> entry: mProductVars.entrySet()) {
|
||||
if (entry.getValue() == VarType.LIST) {
|
||||
out.print(entry.getKey());
|
||||
out.print(" ");
|
||||
}
|
||||
}
|
||||
out.println("]");
|
||||
out.println(" configFiles: [");
|
||||
for (final ConfigFile configFile: mConfigFiles) {
|
||||
out.println(" ConfigFile {");
|
||||
out.println(" filename: " + configFile.getFilename());
|
||||
out.println(" blocks: [");
|
||||
for (Block block: configFile.getBlocks()) {
|
||||
out.println(" Block {");
|
||||
out.println(" type: " + block.getBlockType());
|
||||
if (block.getBlockType() == BlockType.INHERIT) {
|
||||
out.println(" inherited: " + block.getInheritedFile());
|
||||
}
|
||||
out.println(" values: {");
|
||||
for (Map.Entry<String,Str> var: block.getVars().entrySet()) {
|
||||
if (!var.getKey().equals("PRODUCT_PACKAGES")) {
|
||||
continue;
|
||||
}
|
||||
out.println(" " + var.getKey() + ": " + var.getValue());
|
||||
}
|
||||
out.println(" }");
|
||||
out.println(" }");
|
||||
}
|
||||
out.println(" ]");
|
||||
out.println(" }");
|
||||
}
|
||||
out.println(" ] // configFiles");
|
||||
out.println("} // MakeConfig");
|
||||
}
|
||||
}
|
|
@ -0,0 +1,155 @@
|
|||
/*
|
||||
* Copyright (C) 2021 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.build.config;
|
||||
|
||||
import java.io.PrintStream;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.TreeMap;
|
||||
|
||||
public class MakeWriter {
|
||||
public static final int FLAG_WRITE_HEADER = 1;
|
||||
public static final int FLAG_WRITE_ANNOTATIONS = 1 << 1;
|
||||
|
||||
private final boolean mWriteHeader;
|
||||
private final boolean mWriteAnnotations;
|
||||
|
||||
public static void write(PrintStream out, GenericConfig config, int flags) {
|
||||
(new MakeWriter(flags)).write(out, config);
|
||||
}
|
||||
|
||||
private MakeWriter(int flags) {
|
||||
mWriteHeader = (flags & FLAG_WRITE_HEADER) != 0;
|
||||
mWriteAnnotations = (flags & FLAG_WRITE_ANNOTATIONS) != 0;
|
||||
}
|
||||
|
||||
private void write(PrintStream out, GenericConfig config) {
|
||||
for (GenericConfig.ConfigFile file: config.getFiles().values()) {
|
||||
out.println("---------------------------------------------------------");
|
||||
out.println("FILE: " + file.getFilename());
|
||||
out.println("---------------------------------------------------------");
|
||||
writeFile(out, config, file);
|
||||
out.println();
|
||||
}
|
||||
out.println("---------------------------------------------------------");
|
||||
out.println("VARIABLES TOUCHED BY MAKE BASED CONFIG:");
|
||||
out.println("---------------------------------------------------------");
|
||||
writeStrVars(out, getModifiedVars(config.getInitialVariables(),
|
||||
config.getFinalVariables()), config);
|
||||
}
|
||||
|
||||
private void writeFile(PrintStream out, GenericConfig config, GenericConfig.ConfigFile file) {
|
||||
if (mWriteHeader) {
|
||||
out.println("# This file is generated by the product_config tool");
|
||||
}
|
||||
for (GenericConfig.Statement statement: file.getStatements()) {
|
||||
if (statement instanceof GenericConfig.Assign) {
|
||||
writeAssign(out, config, (GenericConfig.Assign)statement);
|
||||
} else if (statement instanceof GenericConfig.Inherit) {
|
||||
writeInherit(out, (GenericConfig.Inherit)statement);
|
||||
} else {
|
||||
throw new RuntimeException("Unexpected Statement: " + statement);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void writeAssign(PrintStream out, GenericConfig config,
|
||||
GenericConfig.Assign statement) {
|
||||
final List<Str> values = statement.getValue();
|
||||
final int size = values.size();
|
||||
final String varName = statement.getName();
|
||||
Position pos = null;
|
||||
if (size == 0) {
|
||||
return;
|
||||
} else if (size == 1) {
|
||||
// Plain :=
|
||||
final Str value = values.get(0);
|
||||
out.print(varName + " := " + value);
|
||||
pos = value.getPosition();
|
||||
} else if (size == 2 && values.get(0).toString().length() == 0) {
|
||||
// Plain +=
|
||||
final Str value = values.get(1);
|
||||
out.print(varName + " += " + value);
|
||||
pos = value.getPosition();
|
||||
} else {
|
||||
// Write it out the long way
|
||||
out.print(varName + " := " + values.get(0));
|
||||
for (int i = 1; i < size; i++) {
|
||||
out.print("$(" + varName + ") " + values.get(i));
|
||||
pos = values.get(i).getPosition();
|
||||
}
|
||||
}
|
||||
if (mWriteAnnotations) {
|
||||
out.print(" # " + config.getVarType(varName) + " " + pos);
|
||||
}
|
||||
out.println();
|
||||
}
|
||||
|
||||
private void writeInherit(PrintStream out, GenericConfig.Inherit statement) {
|
||||
final Str filename = statement.getFilename();
|
||||
out.print("$(call inherit-product " + filename + ")");
|
||||
if (mWriteAnnotations) {
|
||||
out.print(" # " + filename.getPosition());
|
||||
}
|
||||
out.println();
|
||||
}
|
||||
|
||||
private static Map<String, Str> getModifiedVars(Map<String, Str> before,
|
||||
Map<String, Str> after) {
|
||||
final HashMap<String, Str> result = new HashMap();
|
||||
// Entries that were added or changed.
|
||||
for (Map.Entry<String, Str> afterEntry: after.entrySet()) {
|
||||
final String varName = afterEntry.getKey();
|
||||
final Str afterValue = afterEntry.getValue();
|
||||
final Str beforeValue = before.get(varName);
|
||||
if (beforeValue == null || !beforeValue.equals(afterValue)) {
|
||||
result.put(varName, afterValue);
|
||||
}
|
||||
}
|
||||
// removed Entries that were removed, we just treat them as
|
||||
for (Map.Entry<String, Str> beforeEntry: before.entrySet()) {
|
||||
final String varName = beforeEntry.getKey();
|
||||
if (!after.containsKey(varName)) {
|
||||
result.put(varName, new Str(""));
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
private static class Var {
|
||||
Var(String name, Str val) {
|
||||
this.name = name;
|
||||
this.val = val;
|
||||
}
|
||||
final String name;
|
||||
final Str val;
|
||||
}
|
||||
|
||||
private static void writeStrVars(PrintStream out, Map<String, Str> vars, ConfigBase config) {
|
||||
// Sort by file name and var name
|
||||
TreeMap<String, Var> sorted = new TreeMap();
|
||||
for (Map.Entry<String, Str> entry: vars.entrySet()) {
|
||||
sorted.put(entry.getValue().getPosition().toString() + " " + entry.getKey(),
|
||||
new Var(entry.getKey(), entry.getValue()));
|
||||
}
|
||||
// Print it
|
||||
for (Var var: sorted.values()) {
|
||||
out.println(var.val.getPosition() + var.name + " := " + var.val);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -17,6 +17,7 @@
|
|||
package com.android.build.config;
|
||||
|
||||
import java.io.PrintStream;
|
||||
import java.util.Map;
|
||||
import java.util.TreeMap;
|
||||
|
||||
public class Options {
|
||||
|
@ -27,19 +28,50 @@ public class Options {
|
|||
|
||||
private Action mAction = Action.DEFAULT;
|
||||
|
||||
private String mProduct;
|
||||
private String mVariant;
|
||||
private String mOutDir;
|
||||
private String mCKatiBin;
|
||||
|
||||
public Action getAction() {
|
||||
return mAction;
|
||||
}
|
||||
|
||||
public String getProduct() {
|
||||
return mProduct;
|
||||
}
|
||||
|
||||
public String getVariant() {
|
||||
return mVariant;
|
||||
}
|
||||
|
||||
public String getOutDir() {
|
||||
return mOutDir != null ? mOutDir : "out";
|
||||
}
|
||||
|
||||
public String getCKatiBin() {
|
||||
return mCKatiBin;
|
||||
}
|
||||
|
||||
public static void printHelp(PrintStream out) {
|
||||
out.println("usage: product_config");
|
||||
out.println();
|
||||
out.println("OPTIONS");
|
||||
out.println("REQUIRED FLAGS");
|
||||
out.println(" --ckati_bin CKATI Kati binary to use.");
|
||||
out.println();
|
||||
out.println("OPTIONAL FLAGS");
|
||||
out.println(" --hide ERROR_ID Suppress this error.");
|
||||
out.println(" --error ERROR_ID Make this ERROR_ID a fatal error.");
|
||||
out.println(" --help -h This message.");
|
||||
out.println(" --warning ERROR_ID Make this ERROR_ID a warning.");
|
||||
out.println();
|
||||
out.println("REQUIRED ENVIRONMENT");
|
||||
out.println(" TARGET_PRODUCT Product to build from lunch command.");
|
||||
out.println(" TARGET_BUILD_VARIANT Build variant from lunch command.");
|
||||
out.println();
|
||||
out.println("OPTIONAL ENVIRONMENT");
|
||||
out.println(" OUT_DIR Build output directory. Defaults to \"out\".");
|
||||
out.println();
|
||||
out.println("ERRORS");
|
||||
out.println(" The following are the errors that can be controlled on the");
|
||||
out.println(" commandline with the --hide --warning --error flags.");
|
||||
|
@ -63,20 +95,26 @@ public class Options {
|
|||
|
||||
private Errors mErrors;
|
||||
private String[] mArgs;
|
||||
private Map<String,String> mEnv;
|
||||
private Options mResult = new Options();
|
||||
private int mIndex;
|
||||
private boolean mSkipRequiredArgValidation;
|
||||
|
||||
public Parser(Errors errors, String[] args) {
|
||||
public Parser(Errors errors, String[] args, Map<String,String> env) {
|
||||
mErrors = errors;
|
||||
mArgs = args;
|
||||
mEnv = env;
|
||||
}
|
||||
|
||||
public Options parse() {
|
||||
// Args
|
||||
try {
|
||||
while (mIndex < mArgs.length) {
|
||||
final String arg = mArgs[mIndex];
|
||||
|
||||
if ("--hide".equals(arg)) {
|
||||
if ("--ckati_bin".equals(arg)) {
|
||||
mResult.mCKatiBin = requireNextStringArg(arg);
|
||||
} else if ("--hide".equals(arg)) {
|
||||
handleErrorCode(arg, Errors.Level.HIDDEN);
|
||||
} else if ("--error".equals(arg)) {
|
||||
handleErrorCode(arg, Errors.Level.ERROR);
|
||||
|
@ -99,11 +137,45 @@ public class Options {
|
|||
mErrors.ERROR_COMMAND_LINE.add(ex.getMessage());
|
||||
}
|
||||
|
||||
// Environment
|
||||
mResult.mProduct = mEnv.get("TARGET_PRODUCT");
|
||||
mResult.mVariant = mEnv.get("TARGET_BUILD_VARIANT");
|
||||
mResult.mOutDir = mEnv.get("OUT_DIR");
|
||||
|
||||
validateArgs();
|
||||
|
||||
return mResult;
|
||||
}
|
||||
|
||||
private void addWarning(Errors.Category category, String message) {
|
||||
category.add(message);
|
||||
/**
|
||||
* For testing; don't generate errors about missing arguments
|
||||
*/
|
||||
public void setSkipRequiredArgValidation() {
|
||||
mSkipRequiredArgValidation = true;
|
||||
}
|
||||
|
||||
private void validateArgs() {
|
||||
if (!mSkipRequiredArgValidation) {
|
||||
if (mResult.mCKatiBin == null || "".equals(mResult.mCKatiBin)) {
|
||||
addMissingArgError("--ckati_bin");
|
||||
}
|
||||
if (mResult.mProduct == null) {
|
||||
addMissingEnvError("TARGET_PRODUCT");
|
||||
}
|
||||
if (mResult.mVariant == null) {
|
||||
addMissingEnvError("TARGET_BUILD_VARIANT");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void addMissingArgError(String argName) {
|
||||
mErrors.ERROR_COMMAND_LINE.add("Required command line argument missing: "
|
||||
+ argName);
|
||||
}
|
||||
|
||||
private void addMissingEnvError(String envName) {
|
||||
mErrors.ERROR_COMMAND_LINE.add("Required environment variable missing: "
|
||||
+ envName);
|
||||
}
|
||||
|
||||
private String getNextNonFlagArg() {
|
||||
|
@ -117,6 +189,14 @@ public class Options {
|
|||
return mArgs[mIndex];
|
||||
}
|
||||
|
||||
private String requireNextStringArg(String arg) throws ParseException {
|
||||
final String val = getNextNonFlagArg();
|
||||
if (val == null) {
|
||||
throw new ParseException(arg + " requires a string argument.");
|
||||
}
|
||||
return val;
|
||||
}
|
||||
|
||||
private int requireNextNumberArg(String arg) throws ParseException {
|
||||
final String val = getNextNonFlagArg();
|
||||
if (val == null) {
|
||||
|
@ -151,7 +231,7 @@ public class Options {
|
|||
* <p>
|
||||
* Adds errors encountered to Errors object.
|
||||
*/
|
||||
public static Options parse(Errors errors, String[] args) {
|
||||
return (new Parser(errors, args)).parse();
|
||||
public static Options parse(Errors errors, String[] args, Map<String, String> env) {
|
||||
return (new Parser(errors, args, env)).parse();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -16,6 +16,9 @@
|
|||
|
||||
package com.android.build.config;
|
||||
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
/**
|
||||
* Position in a source file.
|
||||
*/
|
||||
|
@ -25,6 +28,9 @@ public class Position implements Comparable<Position> {
|
|||
*/
|
||||
public static final int NO_LINE = -1;
|
||||
|
||||
private static final Pattern REGEX = Pattern.compile("([^:]*)(?::(\\d)*)?:?\\s*");
|
||||
public static final String UNKNOWN = "<unknown>";
|
||||
|
||||
private final String mFile;
|
||||
private final int mLine;
|
||||
|
||||
|
@ -63,12 +69,39 @@ public class Position implements Comparable<Position> {
|
|||
return mLine;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return a Position object from a string containing <filename>:<line>, or the default
|
||||
* Position(null, NO_LINE) if the string can't be parsed.
|
||||
*/
|
||||
public static Position parse(String str) {
|
||||
final Matcher m = REGEX.matcher(str);
|
||||
if (!m.matches()) {
|
||||
return new Position();
|
||||
}
|
||||
String filename = m.group(1);
|
||||
if (filename.length() == 0 || UNKNOWN.equals(filename)) {
|
||||
filename = null;
|
||||
}
|
||||
String lineString = m.group(2);
|
||||
int line;
|
||||
if (lineString == null || lineString.length() == 0) {
|
||||
line = NO_LINE;
|
||||
} else {
|
||||
try {
|
||||
line = Integer.parseInt(lineString);
|
||||
} catch (NumberFormatException ex) {
|
||||
line = NO_LINE;
|
||||
}
|
||||
}
|
||||
return new Position(filename, line);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
if (mFile == null && mLine == NO_LINE) {
|
||||
return "";
|
||||
} else if (mFile == null && mLine != NO_LINE) {
|
||||
return "<unknown>:" + mLine + ": ";
|
||||
return UNKNOWN + ":" + mLine + ": ";
|
||||
} else if (mFile != null && mLine == NO_LINE) {
|
||||
return mFile + ": ";
|
||||
} else { // if (mFile != null && mLine != NO_LINE)
|
||||
|
|
|
@ -0,0 +1,78 @@
|
|||
/*
|
||||
* Copyright (C) 2020 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.build.config;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* A String and a Position, where it came from in source code.
|
||||
*/
|
||||
public class Str {
|
||||
private String mValue;
|
||||
private Position mPosition;
|
||||
|
||||
public Str(String s) {
|
||||
mValue = s;
|
||||
mPosition = new Position();
|
||||
}
|
||||
|
||||
public Str(Position pos, String s) {
|
||||
mValue = s;
|
||||
mPosition = pos;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return mValue;
|
||||
}
|
||||
|
||||
public Position getPosition() {
|
||||
return mPosition;
|
||||
}
|
||||
|
||||
/**
|
||||
* Str is equal if the string value is equal, regardless of whether the position
|
||||
* is the same.
|
||||
*/
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (o == null) {
|
||||
return false;
|
||||
} else if (o instanceof String) {
|
||||
return mValue.equals(o);
|
||||
} else if (o instanceof Str) {
|
||||
final Str that = (Str)o;
|
||||
return mValue.equals(that.mValue);
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return mValue.hashCode();
|
||||
}
|
||||
|
||||
public static ArrayList<Str> toList(Position pos, List<String> list) {
|
||||
final ArrayList<Str> result = new ArrayList(list.size());
|
||||
for (String s: list) {
|
||||
result.add(new Str(pos, s));
|
||||
}
|
||||
return result;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,148 @@
|
|||
|
||||
/*
|
||||
* Copyright (C) 2020 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.build.config;
|
||||
|
||||
import org.junit.Assert;
|
||||
import org.junit.Test;
|
||||
|
||||
import java.io.StringReader;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Test for CSV parser class.
|
||||
*/
|
||||
public class CsvParserTest {
|
||||
public String listsToStrings(String[] expected, List<String> actual) {
|
||||
return "expected=" + Arrays.toString(expected)
|
||||
+ " actual=" + Arrays.toString(actual.toArray());
|
||||
}
|
||||
|
||||
public void assertLineEquals(CsvParser.Line actual, int lineno, String... fields) {
|
||||
if (actual.getLine() != lineno) {
|
||||
throw new RuntimeException("lineno mismatch: expected=" + lineno
|
||||
+ " actual=" + actual.getLine());
|
||||
}
|
||||
if (fields.length != actual.getFields().size()) {
|
||||
throw new RuntimeException("getFields().size() mismatch: expected=" + fields.length
|
||||
+ " actual=" + actual.getFields().size()
|
||||
+ " values: " + listsToStrings(fields, actual.getFields()));
|
||||
}
|
||||
for (int i = 0; i < fields.length; i++) {
|
||||
if (!fields[i].equals(actual.getFields().get(i))) {
|
||||
throw new RuntimeException("getFields().get(" + i + ") mismatch: expected="
|
||||
+ fields[i] + " actual=" + actual.getFields().get(i)
|
||||
+ " values: " + listsToStrings(fields, actual.getFields()));
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testEmptyString() throws Exception {
|
||||
List<CsvParser.Line> lines = CsvParser.parse(new StringReader(
|
||||
""));
|
||||
|
||||
Assert.assertEquals(0, lines.size());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testLexerOneCharacter() throws Exception {
|
||||
List<CsvParser.Line> lines = CsvParser.parse(new StringReader(
|
||||
"a"));
|
||||
|
||||
Assert.assertEquals(1, lines.size());
|
||||
assertLineEquals(lines.get(0), 1, "a");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testLexerTwoFieldsNoNewline() throws Exception {
|
||||
List<CsvParser.Line> lines = CsvParser.parse(new StringReader(
|
||||
"a,b"));
|
||||
|
||||
Assert.assertEquals(1, lines.size());
|
||||
assertLineEquals(lines.get(0), 1, "a", "b");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testLexerTwoFieldsNewline() throws Exception {
|
||||
List<CsvParser.Line> lines = CsvParser.parse(new StringReader(
|
||||
"a,b\n"));
|
||||
|
||||
Assert.assertEquals(1, lines.size());
|
||||
assertLineEquals(lines.get(0), 1, "a", "b");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testEndsWithTwoNewlines() throws Exception {
|
||||
List<CsvParser.Line> lines = CsvParser.parse(new StringReader(
|
||||
"a,b\n\n"));
|
||||
|
||||
Assert.assertEquals(1, lines.size());
|
||||
assertLineEquals(lines.get(0), 1, "a", "b");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testOnlyNewlines() throws Exception {
|
||||
List<CsvParser.Line> lines = CsvParser.parse(new StringReader(
|
||||
"\n\n\n\n"));
|
||||
|
||||
Assert.assertEquals(0, lines.size());
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
public void testLexerComplex() throws Exception {
|
||||
List<CsvParser.Line> lines = CsvParser.parse(new StringReader(
|
||||
",\"ab\"\"\nc\",,de\n"
|
||||
+ "fg,\n"
|
||||
+ "\n"
|
||||
+ ",\n"
|
||||
+ "hijk"));
|
||||
|
||||
Assert.assertEquals(4, lines.size());
|
||||
assertLineEquals(lines.get(0), 2, "", "ab\"\nc", "", "de");
|
||||
assertLineEquals(lines.get(1), 3, "fg", "");
|
||||
assertLineEquals(lines.get(2), 5, "", "");
|
||||
assertLineEquals(lines.get(3), 6, "hijk");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testEndInsideQuoted() throws Exception {
|
||||
try {
|
||||
List<CsvParser.Line> lines = CsvParser.parse(new StringReader(
|
||||
"\"asd"));
|
||||
throw new RuntimeException("Didn't throw ParseException");
|
||||
} catch (CsvParser.ParseException ex) {
|
||||
System.out.println("Caught: " + ex);
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCharacterAfterQuotedField() throws Exception {
|
||||
try {
|
||||
List<CsvParser.Line> lines = CsvParser.parse(new StringReader(
|
||||
"\"\"a"));
|
||||
throw new RuntimeException("Didn't throw ParseException");
|
||||
} catch (CsvParser.ParseException ex) {
|
||||
System.out.println("Caught: " + ex);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -19,12 +19,24 @@ package com.android.build.config;
|
|||
import org.junit.Assert;
|
||||
import org.junit.Test;
|
||||
|
||||
import java.util.HashMap;
|
||||
|
||||
public class OptionsTest {
|
||||
|
||||
private Options parse(Errors errors, String[] args) {
|
||||
final HashMap<String, String> env = new HashMap();
|
||||
env.put("TARGET_PRODUCT", "test_product");
|
||||
env.put("TARGET_BUILD_VARIANT", "user");
|
||||
final Options.Parser parser = new Options.Parser(errors, args, env);
|
||||
parser.setSkipRequiredArgValidation();
|
||||
return parser.parse();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testErrorMissingLast() {
|
||||
final Errors errors = new Errors();
|
||||
|
||||
final Options options = Options.parse(errors, new String[] {
|
||||
final Options options = parse(errors, new String[] {
|
||||
"--error"
|
||||
});
|
||||
|
||||
|
@ -37,7 +49,7 @@ public class OptionsTest {
|
|||
public void testErrorMissingNotLast() {
|
||||
final Errors errors = new Errors();
|
||||
|
||||
final Options options = Options.parse(errors, new String[] {
|
||||
final Options options = parse(errors, new String[] {
|
||||
"--error", "--warning", "2"
|
||||
});
|
||||
|
||||
|
@ -50,7 +62,7 @@ public class OptionsTest {
|
|||
public void testErrorNotNumeric() {
|
||||
final Errors errors = new Errors();
|
||||
|
||||
final Options options = Options.parse(errors, new String[] {
|
||||
final Options options = parse(errors, new String[] {
|
||||
"--error", "notgood"
|
||||
});
|
||||
|
||||
|
@ -63,7 +75,7 @@ public class OptionsTest {
|
|||
public void testErrorInvalidError() {
|
||||
final Errors errors = new Errors();
|
||||
|
||||
final Options options = Options.parse(errors, new String[] {
|
||||
final Options options = parse(errors, new String[] {
|
||||
"--error", "50000"
|
||||
});
|
||||
|
||||
|
@ -76,7 +88,7 @@ public class OptionsTest {
|
|||
public void testErrorOne() {
|
||||
final Errors errors = new Errors();
|
||||
|
||||
final Options options = Options.parse(errors, new String[] {
|
||||
final Options options = parse(errors, new String[] {
|
||||
"--error", "2"
|
||||
});
|
||||
|
||||
|
@ -89,7 +101,7 @@ public class OptionsTest {
|
|||
public void testWarningOne() {
|
||||
final Errors errors = new Errors();
|
||||
|
||||
final Options options = Options.parse(errors, new String[] {
|
||||
final Options options = parse(errors, new String[] {
|
||||
"--warning", "2"
|
||||
});
|
||||
|
||||
|
@ -102,7 +114,7 @@ public class OptionsTest {
|
|||
public void testHideOne() {
|
||||
final Errors errors = new Errors();
|
||||
|
||||
final Options options = Options.parse(errors, new String[] {
|
||||
final Options options = parse(errors, new String[] {
|
||||
"--hide", "2"
|
||||
});
|
||||
|
||||
|
@ -110,5 +122,16 @@ public class OptionsTest {
|
|||
Assert.assertEquals(Options.Action.DEFAULT, options.getAction());
|
||||
Assert.assertFalse(errors.hadWarningOrError());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testEnv() {
|
||||
final Errors errors = new Errors();
|
||||
|
||||
final Options options = parse(errors, new String[0]);
|
||||
|
||||
Assert.assertEquals("test_product", options.getProduct());
|
||||
Assert.assertEquals("user", options.getVariant());
|
||||
Assert.assertFalse(errors.hadWarningOrError());
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -0,0 +1,68 @@
|
|||
/*
|
||||
* Copyright (C) 2020 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.build.config;
|
||||
|
||||
import org.junit.Assert;
|
||||
import org.junit.Test;
|
||||
|
||||
import java.util.HashMap;
|
||||
|
||||
public class PositionTest {
|
||||
|
||||
@Test
|
||||
public void testParseEmpty() {
|
||||
final Position pos = Position.parse("");
|
||||
|
||||
Assert.assertEquals(null, pos.getFile());
|
||||
Assert.assertEquals(Position.NO_LINE, pos.getLine());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testParseOnlyFile() {
|
||||
final Position pos = Position.parse("asdf");
|
||||
|
||||
Assert.assertEquals("asdf", pos.getFile());
|
||||
Assert.assertEquals(Position.NO_LINE, pos.getLine());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testParseBoth() {
|
||||
final Position pos = Position.parse("asdf:1");
|
||||
|
||||
Assert.assertEquals("asdf", pos.getFile());
|
||||
Assert.assertEquals(1, pos.getLine());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testParseEndsWithColon() {
|
||||
final Position pos = Position.parse("asdf:");
|
||||
|
||||
Assert.assertEquals("asdf", pos.getFile());
|
||||
Assert.assertEquals(Position.NO_LINE, pos.getLine());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testParseEndsWithSpace() {
|
||||
final Position pos = Position.parse("asdf: ");
|
||||
|
||||
Assert.assertEquals("asdf", pos.getFile());
|
||||
Assert.assertEquals(Position.NO_LINE, pos.getLine());
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
|
@ -39,8 +39,10 @@ public class TestRunner {
|
|||
System.out.println(failure.getTrace());
|
||||
}
|
||||
});
|
||||
Result result = junit.run(ErrorReporterTest.class,
|
||||
OptionsTest.class);
|
||||
Result result = junit.run(CsvParserTest.class,
|
||||
ErrorReporterTest.class,
|
||||
OptionsTest.class,
|
||||
PositionTest.class);
|
||||
if (!result.wasSuccessful()) {
|
||||
System.out.println("\n*** FAILED ***");
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue