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:
129
core/dumpconfig.mk
Normal file
129
core/dumpconfig.mk
Normal file
@@ -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))
|
$(call clear-var-list, $(3))
|
||||||
$(eval LOCAL_PATH := $(patsubst %/,%,$(dir $(2))))
|
$(eval LOCAL_PATH := $(patsubst %/,%,$(dir $(2))))
|
||||||
$(eval MAKEFILE_LIST :=)
|
$(eval MAKEFILE_LIST :=)
|
||||||
|
$(call dump-import-start,$(_include_stack))
|
||||||
|
$(call dump-config-vals,$(2),before)
|
||||||
$(eval include $(2))
|
$(eval include $(2))
|
||||||
|
$(call dump-import-done,$(_include_stack))
|
||||||
|
$(call dump-config-vals,$(2),after)
|
||||||
$(eval _included := $(filter-out $(2),$(MAKEFILE_LIST)))
|
$(eval _included := $(filter-out $(2),$(MAKEFILE_LIST)))
|
||||||
$(eval MAKEFILE_LIST :=)
|
$(eval MAKEFILE_LIST :=)
|
||||||
$(eval LOCAL_PATH :=)
|
$(eval LOCAL_PATH :=)
|
||||||
@@ -250,6 +254,7 @@ endef
|
|||||||
# of the default list semantics
|
# of the default list semantics
|
||||||
#
|
#
|
||||||
define import-nodes
|
define import-nodes
|
||||||
|
$(call dump-phase-start,$(1),$(2),$(3),$(4),build/make/core/node_fns.mk) \
|
||||||
$(if \
|
$(if \
|
||||||
$(foreach _in,$(2), \
|
$(foreach _in,$(2), \
|
||||||
$(eval _node_import_context := _nic.$(1).[[$(_in)]]) \
|
$(eval _node_import_context := _nic.$(1).[[$(_in)]]) \
|
||||||
@@ -263,5 +268,6 @@ $(if \
|
|||||||
$(if $(_include_stack),$(eval $(error ASSERTION FAILED: _include_stack \
|
$(if $(_include_stack),$(eval $(error ASSERTION FAILED: _include_stack \
|
||||||
should be empty here: $(_include_stack))),) \
|
should be empty here: $(_include_stack))),) \
|
||||||
) \
|
) \
|
||||||
,)
|
,) \
|
||||||
|
$(call dump-phase-end,build/make/core/node_fns.mk)
|
||||||
endef
|
endef
|
||||||
|
@@ -460,7 +460,9 @@ define inherit-product
|
|||||||
$(eval current_mk := $(strip $(word 1,$(_include_stack)))) \
|
$(eval current_mk := $(strip $(word 1,$(_include_stack)))) \
|
||||||
$(eval inherit_var := PRODUCTS.$(current_mk).INHERITS_FROM) \
|
$(eval inherit_var := PRODUCTS.$(current_mk).INHERITS_FROM) \
|
||||||
$(eval $(inherit_var) := $(sort $($(inherit_var)) $(np))) \
|
$(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
|
endef
|
||||||
|
|
||||||
# Specifies a number of path prefixes, relative to PRODUCT_OUT, where the
|
# 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;
|
||||||
|
}
|
||||||
|
}
|
242
tools/product_config/src/com/android/build/config/CsvParser.java
Normal file
242
tools/product_config/src/com/android/build/config/CsvParser.java
Normal file
@@ -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>
|
* <b>Naming Convention:</b>
|
||||||
* <ul>
|
* <ul>
|
||||||
* <li>ERROR_ for Categories with isLevelSettable false and Level.ERROR
|
* <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).
|
* <li>Don't have isLevelSettable true and not ERROR. (The constructor asserts this).
|
||||||
* </ul>
|
* </ul>
|
||||||
*/
|
*/
|
||||||
@@ -42,4 +42,21 @@ public class Errors extends ErrorReporter {
|
|||||||
public final Category WARNING_UNKNOWN_COMMAND_LINE_ERROR = new Category(2, true, Level.HIDDEN,
|
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"
|
"Passing unknown errors on the command line. Hidden by default for\n"
|
||||||
+ "forward compatibility.");
|
+ "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;
|
||||||
|
}
|
||||||
|
}
|
26
tools/product_config/src/com/android/build/config/Kati.java
Normal file
26
tools/product_config/src/com/android/build/config/Kati.java
Normal file
@@ -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) {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
113
tools/product_config/src/com/android/build/config/KatiImpl.java
Normal file
113
tools/product_config/src/com/android/build/config/KatiImpl.java
Normal file
@@ -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;
|
package com.android.build.config;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.TreeSet;
|
||||||
|
|
||||||
public class Main {
|
public class Main {
|
||||||
private final Errors mErrors;
|
private final Errors mErrors;
|
||||||
private final Options mOptions;
|
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
|
// 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
|
// build environment, e.g. actually inside a source tree, with TARGET_PRODUCT
|
||||||
// and TARGET_BUILD_VARIANT defined, etc.
|
// 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.
|
// 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
|
// TODO: Get the variables that were defined in starlark and use that to write
|
||||||
// out the make, soong and bazel input files.
|
// out the make, soong and bazel input files.
|
||||||
mErrors.ERROR_COMMAND_LINE.add("asdf");
|
|
||||||
throw new RuntimeException("poop");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void main(String[] args) {
|
public static void main(String[] args) {
|
||||||
@@ -47,7 +68,7 @@ public class Main {
|
|||||||
int exitCode = 0;
|
int exitCode = 0;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
Options options = Options.parse(errors, args);
|
Options options = Options.parse(errors, args, System.getenv());
|
||||||
if (errors.hadError()) {
|
if (errors.hadError()) {
|
||||||
Options.printHelp(System.err);
|
Options.printHelp(System.err);
|
||||||
System.err.println();
|
System.err.println();
|
||||||
@@ -62,7 +83,7 @@ public class Main {
|
|||||||
Options.printHelp(System.out);
|
Options.printHelp(System.out);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
} catch (CommandException ex) {
|
} catch (CommandException | Errors.FatalException ex) {
|
||||||
// These are user errors, so don't show a stack trace
|
// These are user errors, so don't show a stack trace
|
||||||
exitCode = 1;
|
exitCode = 1;
|
||||||
} catch (Throwable ex) {
|
} 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;
|
package com.android.build.config;
|
||||||
|
|
||||||
import java.io.PrintStream;
|
import java.io.PrintStream;
|
||||||
|
import java.util.Map;
|
||||||
import java.util.TreeMap;
|
import java.util.TreeMap;
|
||||||
|
|
||||||
public class Options {
|
public class Options {
|
||||||
@@ -27,19 +28,50 @@ public class Options {
|
|||||||
|
|
||||||
private Action mAction = Action.DEFAULT;
|
private Action mAction = Action.DEFAULT;
|
||||||
|
|
||||||
|
private String mProduct;
|
||||||
|
private String mVariant;
|
||||||
|
private String mOutDir;
|
||||||
|
private String mCKatiBin;
|
||||||
|
|
||||||
public Action getAction() {
|
public Action getAction() {
|
||||||
return mAction;
|
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) {
|
public static void printHelp(PrintStream out) {
|
||||||
out.println("usage: product_config");
|
out.println("usage: product_config");
|
||||||
out.println();
|
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(" --hide ERROR_ID Suppress this error.");
|
||||||
out.println(" --error ERROR_ID Make this ERROR_ID a fatal error.");
|
out.println(" --error ERROR_ID Make this ERROR_ID a fatal error.");
|
||||||
out.println(" --help -h This message.");
|
out.println(" --help -h This message.");
|
||||||
out.println(" --warning ERROR_ID Make this ERROR_ID a warning.");
|
out.println(" --warning ERROR_ID Make this ERROR_ID a warning.");
|
||||||
out.println();
|
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("ERRORS");
|
||||||
out.println(" The following are the errors that can be controlled on the");
|
out.println(" The following are the errors that can be controlled on the");
|
||||||
out.println(" commandline with the --hide --warning --error flags.");
|
out.println(" commandline with the --hide --warning --error flags.");
|
||||||
@@ -63,20 +95,26 @@ public class Options {
|
|||||||
|
|
||||||
private Errors mErrors;
|
private Errors mErrors;
|
||||||
private String[] mArgs;
|
private String[] mArgs;
|
||||||
|
private Map<String,String> mEnv;
|
||||||
private Options mResult = new Options();
|
private Options mResult = new Options();
|
||||||
private int mIndex;
|
private int mIndex;
|
||||||
|
private boolean mSkipRequiredArgValidation;
|
||||||
|
|
||||||
public Parser(Errors errors, String[] args) {
|
public Parser(Errors errors, String[] args, Map<String,String> env) {
|
||||||
mErrors = errors;
|
mErrors = errors;
|
||||||
mArgs = args;
|
mArgs = args;
|
||||||
|
mEnv = env;
|
||||||
}
|
}
|
||||||
|
|
||||||
public Options parse() {
|
public Options parse() {
|
||||||
|
// Args
|
||||||
try {
|
try {
|
||||||
while (mIndex < mArgs.length) {
|
while (mIndex < mArgs.length) {
|
||||||
final String arg = mArgs[mIndex];
|
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);
|
handleErrorCode(arg, Errors.Level.HIDDEN);
|
||||||
} else if ("--error".equals(arg)) {
|
} else if ("--error".equals(arg)) {
|
||||||
handleErrorCode(arg, Errors.Level.ERROR);
|
handleErrorCode(arg, Errors.Level.ERROR);
|
||||||
@@ -99,11 +137,45 @@ public class Options {
|
|||||||
mErrors.ERROR_COMMAND_LINE.add(ex.getMessage());
|
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;
|
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() {
|
private String getNextNonFlagArg() {
|
||||||
@@ -117,6 +189,14 @@ public class Options {
|
|||||||
return mArgs[mIndex];
|
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 {
|
private int requireNextNumberArg(String arg) throws ParseException {
|
||||||
final String val = getNextNonFlagArg();
|
final String val = getNextNonFlagArg();
|
||||||
if (val == null) {
|
if (val == null) {
|
||||||
@@ -151,7 +231,7 @@ public class Options {
|
|||||||
* <p>
|
* <p>
|
||||||
* Adds errors encountered to Errors object.
|
* Adds errors encountered to Errors object.
|
||||||
*/
|
*/
|
||||||
public static Options parse(Errors errors, String[] args) {
|
public static Options parse(Errors errors, String[] args, Map<String, String> env) {
|
||||||
return (new Parser(errors, args)).parse();
|
return (new Parser(errors, args, env)).parse();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -16,6 +16,9 @@
|
|||||||
|
|
||||||
package com.android.build.config;
|
package com.android.build.config;
|
||||||
|
|
||||||
|
import java.util.regex.Matcher;
|
||||||
|
import java.util.regex.Pattern;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Position in a source file.
|
* Position in a source file.
|
||||||
*/
|
*/
|
||||||
@@ -25,6 +28,9 @@ public class Position implements Comparable<Position> {
|
|||||||
*/
|
*/
|
||||||
public static final int NO_LINE = -1;
|
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 String mFile;
|
||||||
private final int mLine;
|
private final int mLine;
|
||||||
|
|
||||||
@@ -63,12 +69,39 @@ public class Position implements Comparable<Position> {
|
|||||||
return mLine;
|
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
|
@Override
|
||||||
public String toString() {
|
public String toString() {
|
||||||
if (mFile == null && mLine == NO_LINE) {
|
if (mFile == null && mLine == NO_LINE) {
|
||||||
return "";
|
return "";
|
||||||
} else if (mFile == null && mLine != NO_LINE) {
|
} else if (mFile == null && mLine != NO_LINE) {
|
||||||
return "<unknown>:" + mLine + ": ";
|
return UNKNOWN + ":" + mLine + ": ";
|
||||||
} else if (mFile != null && mLine == NO_LINE) {
|
} else if (mFile != null && mLine == NO_LINE) {
|
||||||
return mFile + ": ";
|
return mFile + ": ";
|
||||||
} else { // if (mFile != null && mLine != NO_LINE)
|
} else { // if (mFile != null && mLine != NO_LINE)
|
||||||
|
78
tools/product_config/src/com/android/build/config/Str.java
Normal file
78
tools/product_config/src/com/android/build/config/Str.java
Normal file
@@ -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.Assert;
|
||||||
import org.junit.Test;
|
import org.junit.Test;
|
||||||
|
|
||||||
|
import java.util.HashMap;
|
||||||
|
|
||||||
public class OptionsTest {
|
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
|
@Test
|
||||||
public void testErrorMissingLast() {
|
public void testErrorMissingLast() {
|
||||||
final Errors errors = new Errors();
|
final Errors errors = new Errors();
|
||||||
|
|
||||||
final Options options = Options.parse(errors, new String[] {
|
final Options options = parse(errors, new String[] {
|
||||||
"--error"
|
"--error"
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -37,7 +49,7 @@ public class OptionsTest {
|
|||||||
public void testErrorMissingNotLast() {
|
public void testErrorMissingNotLast() {
|
||||||
final Errors errors = new Errors();
|
final Errors errors = new Errors();
|
||||||
|
|
||||||
final Options options = Options.parse(errors, new String[] {
|
final Options options = parse(errors, new String[] {
|
||||||
"--error", "--warning", "2"
|
"--error", "--warning", "2"
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -50,7 +62,7 @@ public class OptionsTest {
|
|||||||
public void testErrorNotNumeric() {
|
public void testErrorNotNumeric() {
|
||||||
final Errors errors = new Errors();
|
final Errors errors = new Errors();
|
||||||
|
|
||||||
final Options options = Options.parse(errors, new String[] {
|
final Options options = parse(errors, new String[] {
|
||||||
"--error", "notgood"
|
"--error", "notgood"
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -63,7 +75,7 @@ public class OptionsTest {
|
|||||||
public void testErrorInvalidError() {
|
public void testErrorInvalidError() {
|
||||||
final Errors errors = new Errors();
|
final Errors errors = new Errors();
|
||||||
|
|
||||||
final Options options = Options.parse(errors, new String[] {
|
final Options options = parse(errors, new String[] {
|
||||||
"--error", "50000"
|
"--error", "50000"
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -76,7 +88,7 @@ public class OptionsTest {
|
|||||||
public void testErrorOne() {
|
public void testErrorOne() {
|
||||||
final Errors errors = new Errors();
|
final Errors errors = new Errors();
|
||||||
|
|
||||||
final Options options = Options.parse(errors, new String[] {
|
final Options options = parse(errors, new String[] {
|
||||||
"--error", "2"
|
"--error", "2"
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -89,7 +101,7 @@ public class OptionsTest {
|
|||||||
public void testWarningOne() {
|
public void testWarningOne() {
|
||||||
final Errors errors = new Errors();
|
final Errors errors = new Errors();
|
||||||
|
|
||||||
final Options options = Options.parse(errors, new String[] {
|
final Options options = parse(errors, new String[] {
|
||||||
"--warning", "2"
|
"--warning", "2"
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -102,7 +114,7 @@ public class OptionsTest {
|
|||||||
public void testHideOne() {
|
public void testHideOne() {
|
||||||
final Errors errors = new Errors();
|
final Errors errors = new Errors();
|
||||||
|
|
||||||
final Options options = Options.parse(errors, new String[] {
|
final Options options = parse(errors, new String[] {
|
||||||
"--hide", "2"
|
"--hide", "2"
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -110,5 +122,16 @@ public class OptionsTest {
|
|||||||
Assert.assertEquals(Options.Action.DEFAULT, options.getAction());
|
Assert.assertEquals(Options.Action.DEFAULT, options.getAction());
|
||||||
Assert.assertFalse(errors.hadWarningOrError());
|
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());
|
System.out.println(failure.getTrace());
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
Result result = junit.run(ErrorReporterTest.class,
|
Result result = junit.run(CsvParserTest.class,
|
||||||
OptionsTest.class);
|
ErrorReporterTest.class,
|
||||||
|
OptionsTest.class,
|
||||||
|
PositionTest.class);
|
||||||
if (!result.wasSuccessful()) {
|
if (!result.wasSuccessful()) {
|
||||||
System.out.println("\n*** FAILED ***");
|
System.out.println("\n*** FAILED ***");
|
||||||
}
|
}
|
||||||
|
Reference in New Issue
Block a user