Generate FlatConfig objects from GenericConfig objects.
Doesn't include tests. More of those will come later. Test: build/make/tools/product_config/test.sh Change-Id: Icd2b455ac5f7b4773ba332fc40e994dc6f024f1b
This commit is contained in:
parent
d6a7f16416
commit
38a57bf1df
20 changed files with 1269 additions and 77 deletions
|
@ -36,6 +36,10 @@ ifeq (,$(DUMPCONFIG_FILE))
|
|||
$(error stopping)
|
||||
endif
|
||||
|
||||
# Skip the second inclusion of all of the product config files, because
|
||||
# we will do these checks in the product_config tool.
|
||||
SKIP_ARTIFACT_PATH_REQUIREMENT_PRODUCTS_CHECK := true
|
||||
|
||||
# Before we do anything else output the format version.
|
||||
$(file > $(DUMPCONFIG_FILE),dumpconfig_version,1)
|
||||
$(file >> $(DUMPCONFIG_FILE),dumpconfig_file,$(DUMPCONFIG_FILE))
|
||||
|
@ -75,7 +79,7 @@ $(eval $(file >> $(DUMPCONFIG_FILE),inherit,$(strip $(1)),$(strip $(2))))
|
|||
endef
|
||||
|
||||
# Args:
|
||||
# $(1): Config phase (PRODUCT or DEVICE)
|
||||
# $(1): Config phase (PRODUCT, EXPAND, or DEVICE)
|
||||
# $(2): Root nodes to import
|
||||
# $(3): All variable names
|
||||
# $(4): Single-value variables
|
||||
|
@ -104,10 +108,21 @@ DUMPCONFIG_SKIP_VARS := \
|
|||
.KATI_SYMBOLS \
|
||||
1 \
|
||||
2 \
|
||||
3 \
|
||||
4 \
|
||||
5 \
|
||||
6 \
|
||||
7 \
|
||||
8 \
|
||||
9 \
|
||||
LOCAL_PATH \
|
||||
MAKEFILE_LIST \
|
||||
PARENT_PRODUCT_FILES \
|
||||
current_mk \
|
||||
_eiv_ev \
|
||||
_eiv_i \
|
||||
_eiv_sv \
|
||||
_eiv_tv \
|
||||
inherit_var \
|
||||
np \
|
||||
_node_import_context \
|
||||
|
|
|
@ -606,6 +606,8 @@ get-product-var = $(PRODUCTS.$(strip $(1)).$(2))
|
|||
# to a shorthand that is more convenient to read from elsewhere.
|
||||
#
|
||||
define strip-product-vars
|
||||
$(call dump-phase-start,PRODUCT-EXPAND,,$(_product_var_list),$(_product_single_value_vars), \
|
||||
build/make/core/product.mk) \
|
||||
$(foreach v,\
|
||||
$(_product_var_list) \
|
||||
PRODUCT_ENFORCE_PACKAGES_EXIST \
|
||||
|
@ -613,7 +615,8 @@ $(foreach v,\
|
|||
$(eval $(v) := $(strip $(PRODUCTS.$(INTERNAL_PRODUCT).$(v)))) \
|
||||
$(eval get-product-var = $$(if $$(filter $$(1),$$(INTERNAL_PRODUCT)),$$($$(2)),$$(PRODUCTS.$$(strip $$(1)).$$(2)))) \
|
||||
$(KATI_obsolete_var PRODUCTS.$(INTERNAL_PRODUCT).$(v),Use $(v) instead) \
|
||||
)
|
||||
) \
|
||||
$(call dump-phase-end,build/make/core/product.mk)
|
||||
endef
|
||||
|
||||
define add-to-product-copy-files-if-exists
|
||||
|
|
|
@ -163,12 +163,14 @@ endif # Import all or just the current product makefile
|
|||
# Quick check
|
||||
$(check-all-products)
|
||||
|
||||
ifeq ($(SKIP_ARTIFACT_PATH_REQUIREMENT_PRODUCTS_CHECK),)
|
||||
# Import all the products that have made artifact path requirements, so that we can verify
|
||||
# the artifacts they produce.
|
||||
# These are imported after check-all-products because some of them might not be real products.
|
||||
$(foreach makefile,$(ARTIFACT_PATH_REQUIREMENT_PRODUCTS),\
|
||||
$(if $(filter-out $(makefile),$(PRODUCTS)),$(eval $(call import-products,$(makefile))))\
|
||||
)
|
||||
endif
|
||||
|
||||
ifneq ($(filter dump-products, $(MAKECMDGOALS)),)
|
||||
$(dump-products)
|
||||
|
@ -181,14 +183,16 @@ INTERNAL_PRODUCT := $(call resolve-short-product-name, $(TARGET_PRODUCT))
|
|||
ifneq ($(current_product_makefile),$(INTERNAL_PRODUCT))
|
||||
$(error PRODUCT_NAME inconsistent in $(current_product_makefile) and $(INTERNAL_PRODUCT))
|
||||
endif
|
||||
current_product_makefile :=
|
||||
all_product_makefiles :=
|
||||
all_product_configs :=
|
||||
|
||||
|
||||
############################################################################
|
||||
# Strip and assign the PRODUCT_ variables.
|
||||
$(call strip-product-vars)
|
||||
|
||||
current_product_makefile :=
|
||||
all_product_makefiles :=
|
||||
all_product_configs :=
|
||||
|
||||
#############################################################################
|
||||
# Quick check and assign default values
|
||||
|
||||
|
|
49
tools/product_config/inherit_tree.py
Executable file
49
tools/product_config/inherit_tree.py
Executable file
|
@ -0,0 +1,49 @@
|
|||
#!/usr/bin/env python3
|
||||
|
||||
#
|
||||
# Run from the root of the tree, after product-config has been run to see
|
||||
# the product inheritance hierarchy for the current lunch target.
|
||||
#
|
||||
|
||||
import csv
|
||||
import sys
|
||||
|
||||
def PrintNodes(graph, node, prefix):
|
||||
sys.stdout.write("%s%s" % (prefix, node))
|
||||
children = graph.get(node, [])
|
||||
if children:
|
||||
sys.stdout.write(" {\n")
|
||||
for child in sorted(graph.get(node, [])):
|
||||
PrintNodes(graph, child, prefix + " ")
|
||||
sys.stdout.write("%s}\n" % prefix);
|
||||
else:
|
||||
sys.stdout.write("\n")
|
||||
|
||||
def main(argv):
|
||||
if len(argv) != 2:
|
||||
print("usage: inherit_tree.py out/$TARGET_PRODUCT-$TARGET_BUILD_VARIANT/dumpconfig.csv")
|
||||
sys.exit(1)
|
||||
|
||||
root = None
|
||||
graph = {}
|
||||
with open(argv[1], newline='') as csvfile:
|
||||
for line in csv.reader(csvfile):
|
||||
if not root:
|
||||
# Look for PRODUCTS
|
||||
if len(line) < 3 or line[0] != "phase" or line[1] != "PRODUCTS":
|
||||
continue
|
||||
root = line[2]
|
||||
else:
|
||||
# Everything else
|
||||
if len(line) < 3 or line[0] != "inherit":
|
||||
continue
|
||||
graph.setdefault(line[1], list()).append(line[2])
|
||||
|
||||
PrintNodes(graph, root, "")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main(sys.argv)
|
||||
|
||||
# vim: set expandtab ts=2 sw=2 sts=2:
|
||||
|
|
@ -31,14 +31,20 @@ public class ConvertMakeToGenericConfig {
|
|||
mErrors = errors;
|
||||
}
|
||||
|
||||
public GenericConfig convert(MakeConfig make) {
|
||||
public GenericConfig convert(Map<String, MakeConfig> make) {
|
||||
final GenericConfig result = new GenericConfig();
|
||||
|
||||
final MakeConfig products = make.get("PRODUCTS");
|
||||
if (products == null) {
|
||||
mErrors.ERROR_DUMPCONFIG.add("Could not find PRODUCTS phase in dumpconfig output.");
|
||||
return null;
|
||||
}
|
||||
|
||||
// Base class fields
|
||||
result.copyFrom(make);
|
||||
result.copyFrom(products);
|
||||
|
||||
// Each file
|
||||
for (MakeConfig.ConfigFile f: make.getConfigFiles()) {
|
||||
for (MakeConfig.ConfigFile f: products.getConfigFiles()) {
|
||||
final GenericConfig.ConfigFile genericFile
|
||||
= new GenericConfig.ConfigFile(f.getFilename());
|
||||
result.addConfigFile(genericFile);
|
||||
|
@ -77,7 +83,7 @@ public class ConvertMakeToGenericConfig {
|
|||
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,
|
||||
block.getInheritedFile(), products.getVarType(varName), varName,
|
||||
entry.getValue(), prevBlock.getVar(varName));
|
||||
if (assign != null) {
|
||||
genericFile.addStatement(assign);
|
||||
|
@ -100,6 +106,29 @@ public class ConvertMakeToGenericConfig {
|
|||
prevBlock = block;
|
||||
}
|
||||
}
|
||||
|
||||
// Overwrite the final variables with the ones that come from the PRODUCTS-EXPAND phase.
|
||||
// Drop the ones that were newly defined between the two phases, but leave values
|
||||
// that were modified between. We do need to reproduce that logic in this tool.
|
||||
final MakeConfig expand = make.get("PRODUCT-EXPAND");
|
||||
if (expand == null) {
|
||||
mErrors.ERROR_DUMPCONFIG.add("Could not find PRODUCT-EXPAND phase in dumpconfig"
|
||||
+ " output.");
|
||||
return null;
|
||||
}
|
||||
final Map<String, Str> productsFinal = products.getFinalVariables();
|
||||
final Map<String, Str> expandInitial = expand.getInitialVariables();
|
||||
final Map<String, Str> expandFinal = expand.getFinalVariables();
|
||||
final Map<String, Str> finalFinal = result.getFinalVariables();
|
||||
finalFinal.clear();
|
||||
for (Map.Entry<String, Str> var: expandFinal.entrySet()) {
|
||||
final String varName = var.getKey();
|
||||
if (expandInitial.containsKey(varName) && !productsFinal.containsKey(varName)) {
|
||||
continue;
|
||||
}
|
||||
finalFinal.put(varName, var.getValue());
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
|
@ -113,7 +142,7 @@ public class ConvertMakeToGenericConfig {
|
|||
return new GenericConfig.Assign(varName, varVal);
|
||||
} else if (!varVal.equals(prevVal)) {
|
||||
// The value changed from the last block.
|
||||
if (varVal.equals("")) {
|
||||
if (varVal.length() == 0) {
|
||||
// It was set to empty
|
||||
return new GenericConfig.Assign(varName, varVal);
|
||||
} else {
|
||||
|
|
|
@ -44,13 +44,13 @@ import java.util.regex.Pattern;
|
|||
* 4 The location of the variable, as best tracked by kati
|
||||
*/
|
||||
public class DumpConfigParser {
|
||||
private static final boolean DEBUG = true;
|
||||
private static final boolean DEBUG = false;
|
||||
|
||||
private final Errors mErrors;
|
||||
private final String mFilename;
|
||||
private final Reader mReader;
|
||||
|
||||
private final ArrayList<MakeConfig> mResults = new ArrayList();
|
||||
private final Map<String,MakeConfig> mResults = new HashMap();
|
||||
|
||||
private static final Pattern LIST_SEPARATOR = Pattern.compile("\\s+");
|
||||
|
||||
|
@ -64,9 +64,9 @@ public class DumpConfigParser {
|
|||
}
|
||||
|
||||
/**
|
||||
* Parse the text into a list of MakeConfig objects.
|
||||
* Parse the text into a map of the phase names to MakeConfig objects.
|
||||
*/
|
||||
public static ArrayList<MakeConfig> parse(Errors errors, String filename, Reader reader)
|
||||
public static Map<String,MakeConfig> parse(Errors errors, String filename, Reader reader)
|
||||
throws CsvParser.ParseException, IOException {
|
||||
DumpConfigParser parser = new DumpConfigParser(errors, filename, reader);
|
||||
parser.parseImpl();
|
||||
|
@ -130,7 +130,16 @@ public class DumpConfigParser {
|
|||
makeConfig = new MakeConfig();
|
||||
makeConfig.setPhase(fields.get(1));
|
||||
makeConfig.setRootNodes(splitList(fields.get(2)));
|
||||
mResults.add(makeConfig);
|
||||
// If there is a duplicate phase of the same name, continue parsing, but
|
||||
// don't add it. Emit a warning.
|
||||
if (!mResults.containsKey(makeConfig.getPhase())) {
|
||||
mResults.put(makeConfig.getPhase(), makeConfig);
|
||||
} else {
|
||||
mErrors.WARNING_DUMPCONFIG.add(
|
||||
new Position(mFilename, line.getLine()),
|
||||
"Duplicate phase: " + makeConfig.getPhase()
|
||||
+ ". This one will be dropped.");
|
||||
}
|
||||
initialVariables = makeConfig.getInitialVariables();
|
||||
finalVariables = makeConfig.getFinalVariables();
|
||||
|
||||
|
|
|
@ -171,7 +171,7 @@ public class ErrorReporter {
|
|||
/**
|
||||
* An instance of an error happening.
|
||||
*/
|
||||
public class Entry {
|
||||
public static class Entry {
|
||||
private final Category mCategory;
|
||||
private final Position mPosition;
|
||||
private final String mMessage;
|
||||
|
|
|
@ -59,4 +59,16 @@ public class Errors extends ErrorReporter {
|
|||
// 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.");
|
||||
|
||||
public final Category ERROR_MISSING_CONFIG_FILE = new Category(8, true, Level.ERROR,
|
||||
"Unable to find config file.");
|
||||
|
||||
public final Category ERROR_INFINITE_RECURSION = new Category(9, true, Level.ERROR,
|
||||
"A file tries to inherit-product from itself or its own inherited products.");
|
||||
|
||||
// TODO: This will become obsolete when it is possible to have starlark-based product
|
||||
// config files.
|
||||
public final Category WARNING_DIFFERENT_FROM_KATI = new Category(1000, true, Level.WARNING,
|
||||
"The cross-check with the original kati implementation failed.");
|
||||
|
||||
}
|
||||
|
|
|
@ -0,0 +1,36 @@
|
|||
/*
|
||||
* 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;
|
||||
|
||||
/**
|
||||
* Flattened configuration -- set of variables after all assignments and inherits have
|
||||
* been executed.
|
||||
*/
|
||||
public class FlatConfig extends ConfigBase {
|
||||
|
||||
private final TreeMap<String, Value> mValues = new TreeMap();
|
||||
|
||||
public TreeMap<String, Value> getValues() {
|
||||
return mValues;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,474 @@
|
|||
/*
|
||||
* 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.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.TreeMap;
|
||||
import java.util.TreeSet;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
public class FlattenConfig {
|
||||
private static final Pattern RE_SPACE = Pattern.compile("\\p{Space}+");
|
||||
private static final String PRODUCTS_PREFIX = "PRODUCTS";
|
||||
|
||||
private final Errors mErrors;
|
||||
private final GenericConfig mGenericConfig;
|
||||
private final Map<String, GenericConfig.ConfigFile> mGenericConfigs;
|
||||
private final FlatConfig mResult = new FlatConfig();
|
||||
private final Map<String, Value> mVariables;
|
||||
/**
|
||||
* Files that have been visited, to prevent infinite recursion. There are no
|
||||
* conditionals at this point in the processing, so we don't need a stack, just
|
||||
* a single set.
|
||||
*/
|
||||
private final Set<Str> mStack = new HashSet();
|
||||
|
||||
|
||||
private FlattenConfig(Errors errors, GenericConfig genericConfig) {
|
||||
mErrors = errors;
|
||||
mGenericConfig = genericConfig;
|
||||
mGenericConfigs = genericConfig.getFiles();
|
||||
mVariables = mResult.getValues();
|
||||
|
||||
// Base class fields
|
||||
mResult.copyFrom(genericConfig);
|
||||
}
|
||||
|
||||
/**
|
||||
* Flatten a GenericConfig to a FlatConfig.
|
||||
*
|
||||
* Makes three passes through the genericConfig, one to flatten the single variables,
|
||||
* one to flatten the list variables, and one to flatten the unknown variables. Each
|
||||
* has a slightly different algorithm.
|
||||
*/
|
||||
public static FlatConfig flatten(Errors errors, GenericConfig genericConfig) {
|
||||
final FlattenConfig flattener = new FlattenConfig(errors, genericConfig);
|
||||
return flattener.flattenImpl();
|
||||
}
|
||||
|
||||
private FlatConfig flattenImpl() {
|
||||
final List<String> rootNodes = mGenericConfig.getRootNodes();
|
||||
if (rootNodes.size() == 0) {
|
||||
mErrors.ERROR_DUMPCONFIG.add("No root nodes in PRODUCTS phase.");
|
||||
return null;
|
||||
} else if (rootNodes.size() != 1) {
|
||||
final StringBuilder msg = new StringBuilder(
|
||||
"Ignoring extra root nodes in PRODUCTS phase. All nodes are:");
|
||||
for (final String rn: rootNodes) {
|
||||
msg.append(' ');
|
||||
msg.append(rn);
|
||||
}
|
||||
mErrors.WARNING_DUMPCONFIG.add(msg.toString());
|
||||
}
|
||||
final String root = rootNodes.get(0);
|
||||
|
||||
// TODO: Do we need to worry about the initial state of variables? Anything
|
||||
// that from the product config
|
||||
|
||||
flattenListVars(root);
|
||||
flattenSingleVars(root);
|
||||
flattenUnknownVars(root);
|
||||
flattenInheritsFrom(root);
|
||||
|
||||
setDefaultKnownVars();
|
||||
|
||||
// TODO: This only supports the single product mode of import-nodes, which is all the
|
||||
// real build does. m product-graph and friends will have to be rewritten.
|
||||
mVariables.put("PRODUCTS", new Value(VarType.UNKNOWN, new Str(root)));
|
||||
|
||||
return mResult;
|
||||
}
|
||||
|
||||
interface AssignCallback {
|
||||
void onAssignStatement(GenericConfig.Assign assign);
|
||||
}
|
||||
|
||||
interface InheritCallback {
|
||||
void onInheritStatement(GenericConfig.Inherit assign);
|
||||
}
|
||||
|
||||
/**
|
||||
* Do a bunch of validity checks, and then iterate through each of the statements
|
||||
* in the given file. For Assignments, the callback is only called for variables
|
||||
* matching varType.
|
||||
*
|
||||
* Adds makefiles which have been traversed to the 'seen' set, and will not traverse
|
||||
* into an inherit statement if its makefile has already been seen.
|
||||
*/
|
||||
private void forEachStatement(Str filename, VarType varType, Set<String> seen,
|
||||
AssignCallback assigner, InheritCallback inheriter) {
|
||||
if (mStack.contains(filename)) {
|
||||
mErrors.ERROR_INFINITE_RECURSION.add(filename.getPosition(),
|
||||
"File is already in the inherit-product stack: " + filename);
|
||||
return;
|
||||
}
|
||||
|
||||
mStack.add(filename);
|
||||
try {
|
||||
final GenericConfig.ConfigFile genericFile = mGenericConfigs.get(filename.toString());
|
||||
|
||||
if (genericFile == null) {
|
||||
mErrors.ERROR_MISSING_CONFIG_FILE.add(filename.getPosition(),
|
||||
"Unable to find config file: " + filename);
|
||||
return;
|
||||
}
|
||||
|
||||
for (final GenericConfig.Statement statement: genericFile.getStatements()) {
|
||||
if (statement instanceof GenericConfig.Assign) {
|
||||
if (assigner != null) {
|
||||
final GenericConfig.Assign assign = (GenericConfig.Assign)statement;
|
||||
final String varName = assign.getName();
|
||||
|
||||
// Assert that we're not stomping on another variable, which
|
||||
// really should be impossible at this point.
|
||||
assertVarType(filename, varName);
|
||||
|
||||
if (mGenericConfig.getVarType(varName) == varType) {
|
||||
assigner.onAssignStatement(assign);
|
||||
}
|
||||
}
|
||||
} else if (statement instanceof GenericConfig.Inherit) {
|
||||
if (inheriter != null) {
|
||||
final GenericConfig.Inherit inherit = (GenericConfig.Inherit)statement;
|
||||
if (seen != null) {
|
||||
if (seen.contains(inherit.getFilename().toString())) {
|
||||
continue;
|
||||
}
|
||||
seen.add(inherit.getFilename().toString());
|
||||
}
|
||||
inheriter.onInheritStatement(inherit);
|
||||
}
|
||||
}
|
||||
}
|
||||
} finally {
|
||||
// Also executes after return statements, so we always remove this.
|
||||
mStack.remove(filename);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Call 'inheriter' for each child of 'filename' in alphabetical order.
|
||||
*/
|
||||
private void forEachInheritAlpha(final Str filename, VarType varType, Set<String> seen,
|
||||
InheritCallback inheriter) {
|
||||
final TreeMap<Str, GenericConfig.Inherit> alpha = new TreeMap();
|
||||
forEachStatement(filename, varType, null, null,
|
||||
(inherit) -> {
|
||||
alpha.put(inherit.getFilename(), inherit);
|
||||
});
|
||||
for (final GenericConfig.Inherit inherit: alpha.values()) {
|
||||
// Handle 'seen' here where we actaully call back, not before, so that
|
||||
// the proper traversal order is preserved.
|
||||
if (seen != null) {
|
||||
if (seen.contains(inherit.getFilename().toString())) {
|
||||
continue;
|
||||
}
|
||||
seen.add(inherit.getFilename().toString());
|
||||
}
|
||||
inheriter.onInheritStatement(inherit);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Traverse the inheritance hierarchy, setting list-value product config variables.
|
||||
*/
|
||||
private void flattenListVars(final String filename) {
|
||||
Map<String, Value> vars = flattenListVars(new Str(filename), new HashSet());
|
||||
// Add the result of the recursion to mVariables. We know there will be
|
||||
// no collisions because this function only handles list variables.
|
||||
for (Map.Entry<String, Value> entry: vars.entrySet()) {
|
||||
mVariables.put(entry.getKey(), entry.getValue());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the variables defined, recursively, by 'filename.' The 'seen' set
|
||||
* accumulates which nodes have been visited, as each is only done once.
|
||||
*
|
||||
* This convoluted algorithm isn't ideal, but it matches what is in node_fns.mk.
|
||||
*/
|
||||
private Map<String, Value> flattenListVars(final Str filename, Set<String> seen) {
|
||||
Map<String, Value> result = new HashMap();
|
||||
|
||||
// Recurse into our children first in alphabetical order, building a map of
|
||||
// that filename to its flattened values. The order matters here because
|
||||
// we will only look at each child once, and when a file appears multiple
|
||||
// times, its variables must have the right set, based on whether it's been
|
||||
// seen before. This preserves the order from node_fns.mk.
|
||||
|
||||
// Child filename --> { varname --> value }
|
||||
final Map<Str, Map<String, Value>> children = new HashMap();
|
||||
forEachInheritAlpha(filename, VarType.LIST, seen,
|
||||
(inherit) -> {
|
||||
final Str child = inherit.getFilename();
|
||||
children.put(child, flattenListVars(child, seen));
|
||||
});
|
||||
|
||||
// Now, traverse the values again in the original source order to concatenate the values.
|
||||
// Note that the contcatenation order is *different* from the inherit order above.
|
||||
forEachStatement(filename, VarType.LIST, null,
|
||||
(assign) -> {
|
||||
assignToListVar(result, assign.getName(), assign.getValue());
|
||||
},
|
||||
(inherit) -> {
|
||||
final Map<String, Value> child = children.get(inherit.getFilename());
|
||||
// child == null happens if this node has been visited before.
|
||||
if (child != null) {
|
||||
for (Map.Entry<String, Value> entry: child.entrySet()) {
|
||||
final String varName = entry.getKey();
|
||||
final Value varVal = entry.getValue();
|
||||
appendToListVar(result, varName, varVal.getList());
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Traverse the inheritance hierarchy, setting single-value product config variables.
|
||||
*/
|
||||
private void flattenSingleVars(final String filename) {
|
||||
flattenSingleVars(new Str(filename), new HashSet(), new HashSet());
|
||||
}
|
||||
|
||||
private void flattenSingleVars(final Str filename, Set<String> seen1, Set<String> seen2) {
|
||||
// flattenSingleVars has two loops. The first sets all variables that are
|
||||
// defined for *this* file. The second traverses through the inheritance,
|
||||
// to fill in values that weren't defined in this file. The first appearance of
|
||||
// the variable is the one that wins.
|
||||
|
||||
forEachStatement(filename, VarType.SINGLE, seen1,
|
||||
(assign) -> {
|
||||
final String varName = assign.getName();
|
||||
Value v = mVariables.get(varName);
|
||||
// Only take the first value that we see for single variables.
|
||||
Value value = mVariables.get(varName);
|
||||
if (!mVariables.containsKey(varName)) {
|
||||
final List<Str> valueList = assign.getValue();
|
||||
// There should never be more than one item in this list, because
|
||||
// SINGLE values should never be appended to.
|
||||
if (valueList.size() != 1) {
|
||||
final StringBuilder positions = new StringBuilder("[");
|
||||
for (Str s: valueList) {
|
||||
positions.append(s.getPosition());
|
||||
}
|
||||
positions.append(" ]");
|
||||
throw new RuntimeException("Value list found for SINGLE variable "
|
||||
+ varName + " size=" + valueList.size()
|
||||
+ "positions=" + positions.toString());
|
||||
}
|
||||
mVariables.put(varName,
|
||||
new Value(VarType.SINGLE,
|
||||
valueList.get(0)));
|
||||
}
|
||||
}, null);
|
||||
|
||||
forEachInheritAlpha(filename, VarType.SINGLE, seen2,
|
||||
(inherit) -> {
|
||||
flattenSingleVars(inherit.getFilename(), seen1, seen2);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Traverse the inheritance hierarchy and flatten the values
|
||||
*/
|
||||
private void flattenUnknownVars(String filename) {
|
||||
flattenUnknownVars(new Str(filename), new HashSet());
|
||||
}
|
||||
|
||||
private void flattenUnknownVars(final Str filename, Set<String> seen) {
|
||||
// flattenUnknownVars has two loops: First to attempt to set the variable from
|
||||
// this file, and then a second loop to handle the inheritance. This is odd
|
||||
// but it matches the order the files are included in node_fns.mk. The last appearance
|
||||
// of the value is the one that wins.
|
||||
|
||||
forEachStatement(filename, VarType.UNKNOWN, null,
|
||||
(assign) -> {
|
||||
// Overwrite the current value with whatever is now in the file.
|
||||
mVariables.put(assign.getName(),
|
||||
new Value(VarType.UNKNOWN,
|
||||
flattenAssignList(assign, new Str(""))));
|
||||
}, null);
|
||||
|
||||
forEachInheritAlpha(filename, VarType.UNKNOWN, seen,
|
||||
(inherit) -> {
|
||||
flattenUnknownVars(inherit.getFilename(), seen);
|
||||
});
|
||||
}
|
||||
|
||||
String prefix = "";
|
||||
|
||||
/**
|
||||
* Sets the PRODUCTS.<filename>.INHERITS_FROM variables.
|
||||
*/
|
||||
private void flattenInheritsFrom(final String filename) {
|
||||
flattenInheritsFrom(new Str(filename));
|
||||
}
|
||||
|
||||
/**
|
||||
* This flatten function, unlike the others visits all of the nodes regardless
|
||||
* of whether they have been seen before, because that's what the make code does.
|
||||
*/
|
||||
private void flattenInheritsFrom(final Str filename) {
|
||||
// Recurse, and gather the list our chlidren
|
||||
final TreeSet<Str> children = new TreeSet();
|
||||
forEachStatement(filename, VarType.LIST, null, null,
|
||||
(inherit) -> {
|
||||
children.add(inherit.getFilename());
|
||||
flattenInheritsFrom(inherit.getFilename());
|
||||
});
|
||||
|
||||
final String varName = "PRODUCTS." + filename + ".INHERITS_FROM";
|
||||
if (children.size() > 0) {
|
||||
// Build the space separated list.
|
||||
boolean first = true;
|
||||
final StringBuilder val = new StringBuilder();
|
||||
for (Str child: children) {
|
||||
if (first) {
|
||||
first = false;
|
||||
} else {
|
||||
val.append(' ');
|
||||
}
|
||||
val.append(child);
|
||||
}
|
||||
mVariables.put(varName, new Value(VarType.UNKNOWN, new Str(val.toString())));
|
||||
} else {
|
||||
// Clear whatever flattenUnknownVars happened to have put in.
|
||||
mVariables.remove(varName);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Throw an exception if there's an existing variable with a different type.
|
||||
*/
|
||||
private void assertVarType(Str filename, String varName) {
|
||||
if (mGenericConfig.getVarType(varName) == VarType.UNKNOWN) {
|
||||
final Value prevValue = mVariables.get(varName);
|
||||
if (prevValue != null
|
||||
&& prevValue.getVarType() != VarType.UNKNOWN) {
|
||||
throw new RuntimeException("Mismatched var types:"
|
||||
+ " filename=" + filename
|
||||
+ " varType=" + mGenericConfig.getVarType(varName)
|
||||
+ " varName=" + varName
|
||||
+ " prevValue=" + Value.debugString(prevValue));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Depending on whether the assignment is prepending, appending, setting, etc.,
|
||||
* update the value. We can infer which of those operations it is by the length
|
||||
* and contents of the values. Each value in the list was originally separated
|
||||
* by the previous value.
|
||||
*/
|
||||
private void assignToListVar(Map<String, Value> vars, String varName, List<Str> items) {
|
||||
final Value value = vars.get(varName);
|
||||
final List<Str> orig = value == null ? new ArrayList() : value.getList();
|
||||
final List<Str> result = new ArrayList();
|
||||
if (items.size() > 0) {
|
||||
for (int i = 0; i < items.size(); i++) {
|
||||
if (i != 0) {
|
||||
result.addAll(orig);
|
||||
}
|
||||
final Str item = items.get(i);
|
||||
addWords(result, item);
|
||||
}
|
||||
}
|
||||
vars.put(varName, new Value(result));
|
||||
}
|
||||
|
||||
/**
|
||||
* Appends all of the words in in 'items' to an entry in vars keyed by 'varName',
|
||||
* creating one if necessary.
|
||||
*/
|
||||
private static void appendToListVar(Map<String, Value> vars, String varName, List<Str> items) {
|
||||
Value value = vars.get(varName);
|
||||
if (value == null) {
|
||||
value = new Value(new ArrayList());
|
||||
vars.put(varName, value);
|
||||
}
|
||||
final List<Str> out = value.getList();
|
||||
for (Str item: items) {
|
||||
addWords(out, item);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Split 'item' on spaces, and add each of them as a word to 'out'.
|
||||
*/
|
||||
private static void addWords(List<Str> out, Str item) {
|
||||
for (String word: RE_SPACE.split(item.toString().trim())) {
|
||||
if (word.length() > 0) {
|
||||
out.add(new Str(item.getPosition(), word));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Flatten the list of strings in an Assign statement, using the previous value
|
||||
* as a separator.
|
||||
*/
|
||||
private Str flattenAssignList(GenericConfig.Assign assign, Str previous) {
|
||||
final StringBuilder result = new StringBuilder();
|
||||
Position position = previous.getPosition();
|
||||
final List<Str> list = assign.getValue();
|
||||
final int size = list.size();
|
||||
for (int i = 0; i < size; i++) {
|
||||
final Str item = list.get(i);
|
||||
result.append(item.toString());
|
||||
if (i != size - 1) {
|
||||
result.append(previous);
|
||||
}
|
||||
final Position pos = item.getPosition();
|
||||
if (pos != null && pos.getFile() != null) {
|
||||
position = pos;
|
||||
}
|
||||
}
|
||||
return new Str(position, result.toString());
|
||||
}
|
||||
|
||||
/**
|
||||
* Make sure that each of the product config variables has a default value.
|
||||
*/
|
||||
private void setDefaultKnownVars() {
|
||||
for (Map.Entry<String, VarType> entry: mGenericConfig.getProductVars().entrySet()) {
|
||||
final String varName = entry.getKey();
|
||||
final VarType varType = entry.getValue();
|
||||
|
||||
final Value val = mVariables.get(varName);
|
||||
if (val == null) {
|
||||
mVariables.put(varName, new Value(varType));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// TODO: These two for now as well, until we can rewrite the enforce packages exist
|
||||
// handling.
|
||||
if (!mVariables.containsKey("PRODUCT_ENFORCE_PACKAGES_EXIST")) {
|
||||
mVariables.put("PRODUCT_ENFORCE_PACKAGES_EXIST", new Value(VarType.UNKNOWN));
|
||||
}
|
||||
if (!mVariables.containsKey("PRODUCT_ENFORCE_PACKAGES_EXIST_ALLOW_LIST")) {
|
||||
mVariables.put("PRODUCT_ENFORCE_PACKAGES_EXIST_ALLOW_LIST", new Value(VarType.UNKNOWN));
|
||||
}
|
||||
}
|
||||
}
|
|
@ -16,11 +16,11 @@
|
|||
|
||||
package com.android.build.config;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* Wrapper for invoking kati.
|
||||
*/
|
||||
public interface Kati {
|
||||
public MakeConfig loadProductConfig();
|
||||
public Map<String, MakeConfig> loadProductConfig();
|
||||
}
|
||||
|
|
|
@ -56,17 +56,16 @@ public class KatiImpl implements Kati {
|
|||
}
|
||||
|
||||
@Override
|
||||
public MakeConfig loadProductConfig() {
|
||||
public Map<String, MakeConfig> loadProductConfig() {
|
||||
final String csvPath = getDumpConfigCsvPath();
|
||||
try {
|
||||
File workDir = new File(getWorkDirPath());
|
||||
|
||||
if (!workDir.mkdirs()) {
|
||||
if ((workDir.exists() && !workDir.isDirectory()) || !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
|
||||
|
@ -89,17 +88,14 @@ public class KatiImpl implements Kati {
|
|||
}
|
||||
|
||||
try (FileReader reader = new FileReader(csvPath)) {
|
||||
System.out.println("csvPath=" + csvPath);
|
||||
List<MakeConfig> makeConfigs = DumpConfigParser.parse(mErrors, csvPath, reader);
|
||||
Map<String, 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);
|
||||
return makeConfigs;
|
||||
} catch (CsvParser.ParseException ex) {
|
||||
mErrors.ERROR_KATI.add(new Position(csvPath, ex.getLine()),
|
||||
"Unable to parse output of dumpconfig.mk: " + ex.getMessage());
|
||||
|
|
|
@ -18,6 +18,7 @@ package com.android.build.config;
|
|||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.TreeMap;
|
||||
import java.util.TreeSet;
|
||||
|
||||
public class Main {
|
||||
|
@ -30,30 +31,44 @@ public class Main {
|
|||
}
|
||||
|
||||
void run() {
|
||||
System.out.println("Hello World");
|
||||
|
||||
// TODO: Check the build environment to make sure we're running in a real
|
||||
// build environment, e.g. actually inside a source tree, with TARGET_PRODUCT
|
||||
// and TARGET_BUILD_VARIANT defined, etc.
|
||||
Kati kati = new KatiImpl(mErrors, mOptions);
|
||||
MakeConfig makeConfig = kati.loadProductConfig();
|
||||
if (makeConfig == null || mErrors.hadError()) {
|
||||
Map<String, MakeConfig> makeConfigs = kati.loadProductConfig();
|
||||
if (makeConfigs == null || mErrors.hadError()) {
|
||||
return;
|
||||
}
|
||||
|
||||
System.out.println();
|
||||
System.out.println("====================");
|
||||
System.out.println("PRODUCT CONFIG FILES");
|
||||
System.out.println("====================");
|
||||
makeConfig.printToStream(System.out);
|
||||
if (false) {
|
||||
for (MakeConfig makeConfig: (new TreeMap<String, MakeConfig>(makeConfigs)).values()) {
|
||||
System.out.println();
|
||||
System.out.println("=======================================");
|
||||
System.out.println("PRODUCT CONFIG FILES : " + makeConfig.getPhase());
|
||||
System.out.println("=======================================");
|
||||
makeConfig.printToStream(System.out);
|
||||
}
|
||||
}
|
||||
|
||||
ConvertMakeToGenericConfig m2g = new ConvertMakeToGenericConfig(mErrors);
|
||||
GenericConfig generic = m2g.convert(makeConfig);
|
||||
GenericConfig generic = m2g.convert(makeConfigs);
|
||||
if (false) {
|
||||
System.out.println("======================");
|
||||
System.out.println("REGENERATED MAKE FILES");
|
||||
System.out.println("======================");
|
||||
MakeWriter.write(System.out, generic, 0);
|
||||
}
|
||||
|
||||
System.out.println("======================");
|
||||
System.out.println("REGENERATED MAKE FILES");
|
||||
System.out.println("======================");
|
||||
MakeWriter.write(System.out, generic, 0);
|
||||
// TODO: Lookup shortened name as used in PRODUCT_NAME / TARGET_PRODUCT
|
||||
FlatConfig flat = FlattenConfig.flatten(mErrors, generic);
|
||||
if (false) {
|
||||
System.out.println("=======================");
|
||||
System.out.println("FLATTENED VARIABLE LIST");
|
||||
System.out.println("=======================");
|
||||
MakeWriter.write(System.out, flat, 0);
|
||||
}
|
||||
|
||||
OutputChecker checker = new OutputChecker(flat);
|
||||
checker.reportErrors(mErrors);
|
||||
|
||||
// TODO: Run kati and extract the variables and convert all that into starlark files.
|
||||
|
||||
|
@ -97,7 +112,10 @@ public class Main {
|
|||
} finally {
|
||||
// Print errors and warnings
|
||||
errors.printErrors(System.err);
|
||||
if (errors.hadError()) {
|
||||
exitCode = 1;
|
||||
}
|
||||
System.exit(exitCode);
|
||||
}
|
||||
System.exit(exitCode);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -30,15 +30,20 @@ public class MakeWriter {
|
|||
private final boolean mWriteAnnotations;
|
||||
|
||||
public static void write(PrintStream out, GenericConfig config, int flags) {
|
||||
(new MakeWriter(flags)).write(out, config);
|
||||
(new MakeWriter(flags)).writeGeneric(out, config);
|
||||
}
|
||||
|
||||
public static void write(PrintStream out, FlatConfig config, int flags) {
|
||||
(new MakeWriter(flags)).writeFlat(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) {
|
||||
private void writeGeneric(PrintStream out, GenericConfig config) {
|
||||
for (GenericConfig.ConfigFile file: config.getFiles().values()) {
|
||||
out.println("---------------------------------------------------------");
|
||||
out.println("FILE: " + file.getFilename());
|
||||
|
@ -49,7 +54,7 @@ public class MakeWriter {
|
|||
out.println("---------------------------------------------------------");
|
||||
out.println("VARIABLES TOUCHED BY MAKE BASED CONFIG:");
|
||||
out.println("---------------------------------------------------------");
|
||||
writeStrVars(out, getModifiedVars(config.getInitialVariables(),
|
||||
writeStrVars(out, OutputChecker.getModifiedVars(config.getInitialVariables(),
|
||||
config.getFinalVariables()), config);
|
||||
}
|
||||
|
||||
|
@ -109,28 +114,6 @@ public class MakeWriter {
|
|||
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;
|
||||
|
@ -152,4 +135,27 @@ public class MakeWriter {
|
|||
out.println(var.val.getPosition() + var.name + " := " + var.val);
|
||||
}
|
||||
}
|
||||
|
||||
private void writeFlat(PrintStream out, FlatConfig config) {
|
||||
// TODO: Print positions.
|
||||
for (Map.Entry<String, Value> entry: config.getValues().entrySet()) {
|
||||
out.print(entry.getKey());
|
||||
out.print(" := ");
|
||||
|
||||
final Value value = entry.getValue();
|
||||
if (value.getVarType() == VarType.LIST) {
|
||||
final List<Str> list = value.getList();
|
||||
final int size = list.size();
|
||||
for (int i = 0; i < size; i++) {
|
||||
out.print(list.get(i).toString());
|
||||
if (i != size - 1) {
|
||||
out.print(" \\\n ");
|
||||
}
|
||||
}
|
||||
} else {
|
||||
out.print(value.getStr().toString());
|
||||
}
|
||||
out.println();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -87,7 +87,7 @@ public class Options {
|
|||
}
|
||||
|
||||
static class Parser {
|
||||
private class ParseException extends Exception {
|
||||
private static class ParseException extends Exception {
|
||||
public ParseException(String message) {
|
||||
super(message);
|
||||
}
|
||||
|
|
|
@ -0,0 +1,186 @@
|
|||
/*
|
||||
* 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.Arrays;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.TreeMap;
|
||||
|
||||
/**
|
||||
* Compares the make-based configuration as reported by dumpconfig.mk
|
||||
* with what was computed from the new tool.
|
||||
*/
|
||||
public class OutputChecker {
|
||||
// Differences that we know about, either know issues to be fixed or intentional.
|
||||
private static final RegexSet IGNORED_VARIABLES = new RegexSet(
|
||||
// TODO: Rewrite the enforce packages exist logic into this tool.
|
||||
"PRODUCT_ENFORCE_PACKAGES_EXIST",
|
||||
"PRODUCT_ENFORCE_PACKAGES_EXIST_ALLOW_LIST",
|
||||
"PRODUCTS\\..*\\.PRODUCT_ENFORCE_PACKAGES_EXIST",
|
||||
"PRODUCTS\\..*\\.PRODUCT_ENFORCE_PACKAGES_EXIST_ALLOW_LIST",
|
||||
|
||||
// This is generated by this tool, but comes later in the make build system.
|
||||
"INTERNAL_PRODUCT");
|
||||
|
||||
private final FlatConfig mConfig;
|
||||
private final TreeMap<String, Variable> mVariables;
|
||||
|
||||
/**
|
||||
* Represents the before and after state of a variable.
|
||||
*/
|
||||
public static class Variable {
|
||||
public final String name;
|
||||
public final VarType type;
|
||||
public final Str original;
|
||||
public final Value updated;
|
||||
|
||||
public Variable(String name, VarType type, Str original) {
|
||||
this(name, type, original, null);
|
||||
}
|
||||
|
||||
public Variable(String name, VarType type, Str original, Value updated) {
|
||||
this.name = name;
|
||||
this.type = type;
|
||||
this.original = original;
|
||||
this.updated = updated;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return copy of this Variable with the updated field also set.
|
||||
*/
|
||||
public Variable addUpdated(Value updated) {
|
||||
return new Variable(name, type, original, updated);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return whether normalizedOriginal and normalizedUpdate are equal.
|
||||
*/
|
||||
public boolean isSame() {
|
||||
final Str normalizedOriginal = Value.normalize(original);
|
||||
final Str normalizedUpdated = Value.normalize(updated);
|
||||
if (normalizedOriginal == null && normalizedUpdated == null) {
|
||||
return true;
|
||||
} else if (normalizedOriginal != null) {
|
||||
return normalizedOriginal.equals(normalizedUpdated);
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Construct OutputChecker with the config it will check.
|
||||
*/
|
||||
public OutputChecker(FlatConfig config) {
|
||||
mConfig = config;
|
||||
mVariables = getVariables(config);
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a WARNING_DIFFERENT_FROM_KATI for each of the variables which have changed.
|
||||
*/
|
||||
public void reportErrors(Errors errors) {
|
||||
for (Variable var: getDifferences()) {
|
||||
if (IGNORED_VARIABLES.matches(var.name)) {
|
||||
continue;
|
||||
}
|
||||
errors.WARNING_DIFFERENT_FROM_KATI.add("product_config processing differs from"
|
||||
+ " kati processing for " + var.type + " variable " + var.name + ".\n"
|
||||
+ "original: "
|
||||
+ Value.oneLinePerWord(var.original, "<null>") + "\n"
|
||||
+ "updated: "
|
||||
+ Value.oneLinePerWord(var.updated, "<null>"));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the Variables that are different between the normalized form of the original
|
||||
* and updated. If one is null and the other is not, even if one is an empty string,
|
||||
* the values are considered different.
|
||||
*/
|
||||
public List<Variable> getDifferences() {
|
||||
final ArrayList<Variable> result = new ArrayList();
|
||||
for (Variable var: mVariables.values()) {
|
||||
if (!var.isSame()) {
|
||||
result.add(var);
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get all of the variables for this config.
|
||||
*
|
||||
* VisibleForTesting
|
||||
*/
|
||||
static TreeMap<String, Variable> getVariables(FlatConfig config) {
|
||||
final TreeMap<String, Variable> result = new TreeMap();
|
||||
|
||||
// Add the original values to mAll
|
||||
for (Map.Entry<String, Str> entry: getModifiedVars(config.getInitialVariables(),
|
||||
config.getFinalVariables()).entrySet()) {
|
||||
final String name = entry.getKey();
|
||||
result.put(name, new Variable(name, config.getVarType(name), entry.getValue()));
|
||||
}
|
||||
|
||||
// Add the updated values to mAll
|
||||
for (Map.Entry<String, Value> entry: config.getValues().entrySet()) {
|
||||
final String name = entry.getKey();
|
||||
final Value value = entry.getValue();
|
||||
Variable var = result.get(name);
|
||||
if (var == null) {
|
||||
result.put(name, new Variable(name, config.getVarType(name), null, value));
|
||||
} else {
|
||||
result.put(name, var.addUpdated(value));
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the entries that are different in the two maps.
|
||||
*/
|
||||
public 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 empty string
|
||||
for (Map.Entry<String, Str> beforeEntry: before.entrySet()) {
|
||||
final String varName = beforeEntry.getKey();
|
||||
if (!after.containsKey(varName)) {
|
||||
result.put(varName, new Str(""));
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,43 @@
|
|||
/*
|
||||
* 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.regex.Pattern;
|
||||
|
||||
/**
|
||||
* Returns whether a string matches one of a set of presupplied regexes.
|
||||
*/
|
||||
public class RegexSet {
|
||||
private final Pattern[] mPatterns;
|
||||
|
||||
public RegexSet(String... patterns) {
|
||||
mPatterns = new Pattern[patterns.length];
|
||||
for (int i = 0; i < patterns.length; i++) {
|
||||
mPatterns[i] = Pattern.compile(patterns[i]);
|
||||
}
|
||||
}
|
||||
|
||||
public boolean matches(String s) {
|
||||
for (Pattern p: mPatterns) {
|
||||
if (p.matcher(s).matches()) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
|
@ -22,7 +22,7 @@ import java.util.List;
|
|||
/**
|
||||
* A String and a Position, where it came from in source code.
|
||||
*/
|
||||
public class Str {
|
||||
public class Str implements Comparable<Str> {
|
||||
private String mValue;
|
||||
private Position mPosition;
|
||||
|
||||
|
@ -36,6 +36,10 @@ public class Str {
|
|||
mPosition = pos;
|
||||
}
|
||||
|
||||
public int length() {
|
||||
return mValue.length();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return mValue;
|
||||
|
@ -51,16 +55,11 @@ public class Str {
|
|||
*/
|
||||
@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 {
|
||||
if (!(o instanceof Str)) {
|
||||
return false;
|
||||
}
|
||||
final Str that = (Str)o;
|
||||
return mValue.equals(that.mValue);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -68,6 +67,11 @@ public class Str {
|
|||
return mValue.hashCode();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int compareTo(Str that) {
|
||||
return this.mValue.compareTo(that.mValue);
|
||||
}
|
||||
|
||||
public static ArrayList<Str> toList(Position pos, List<String> list) {
|
||||
final ArrayList<Str> result = new ArrayList(list.size());
|
||||
for (String s: list) {
|
||||
|
|
218
tools/product_config/src/com/android/build/config/Value.java
Normal file
218
tools/product_config/src/com/android/build/config/Value.java
Normal file
|
@ -0,0 +1,218 @@
|
|||
/*
|
||||
* 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;
|
||||
import java.util.regex.Pattern;
|
||||
import java.util.regex.Matcher;
|
||||
|
||||
/**
|
||||
* Class to hold the two types of variables we support, strings and lists of strings.
|
||||
*/
|
||||
public class Value {
|
||||
private static final Pattern SPACES = Pattern.compile("\\s+");
|
||||
|
||||
private final VarType mVarType;
|
||||
private final Str mStr;
|
||||
private final ArrayList<Str> mList;
|
||||
|
||||
/**
|
||||
* Construct an appropriately typed empty value.
|
||||
*/
|
||||
public Value(VarType varType) {
|
||||
mVarType = varType;
|
||||
if (varType == VarType.LIST) {
|
||||
mStr = null;
|
||||
mList = new ArrayList();
|
||||
mList.add(new Str(""));
|
||||
} else {
|
||||
mStr = new Str("");
|
||||
mList = null;
|
||||
}
|
||||
}
|
||||
|
||||
public Value(VarType varType, Str str) {
|
||||
mVarType = varType;
|
||||
mStr = str;
|
||||
mList = null;
|
||||
}
|
||||
|
||||
public Value(List<Str> list) {
|
||||
mVarType = VarType.LIST;
|
||||
mStr = null;
|
||||
mList = new ArrayList(list);
|
||||
}
|
||||
|
||||
public VarType getVarType() {
|
||||
return mVarType;
|
||||
}
|
||||
|
||||
public Str getStr() {
|
||||
return mStr;
|
||||
}
|
||||
|
||||
public List<Str> getList() {
|
||||
return mList;
|
||||
}
|
||||
|
||||
/**
|
||||
* Normalize a string that is behaving as a list.
|
||||
*/
|
||||
public static String normalize(String str) {
|
||||
if (str == null) {
|
||||
return null;
|
||||
}
|
||||
return SPACES.matcher(str.trim()).replaceAll(" ").trim();
|
||||
}
|
||||
|
||||
/**
|
||||
* Normalize a string that is behaving as a list.
|
||||
*/
|
||||
public static Str normalize(Str str) {
|
||||
if (str == null) {
|
||||
return null;
|
||||
}
|
||||
return new Str(str.getPosition(), normalize(str.toString()));
|
||||
}
|
||||
|
||||
/**
|
||||
* Normalize a this Value into the same format as normalize(Str).
|
||||
*/
|
||||
public static Str normalize(Value val) {
|
||||
if (val == null) {
|
||||
return null;
|
||||
}
|
||||
if (val.mStr != null) {
|
||||
return normalize(val.mStr);
|
||||
}
|
||||
|
||||
if (val.mList.size() == 0) {
|
||||
return new Str("");
|
||||
}
|
||||
|
||||
StringBuilder result = new StringBuilder();
|
||||
final int size = val.mList.size();
|
||||
boolean first = true;
|
||||
for (int i = 0; i < size; i++) {
|
||||
String s = val.mList.get(i).toString().trim();
|
||||
if (s.length() > 0) {
|
||||
if (!first) {
|
||||
result.append(" ");
|
||||
} else {
|
||||
first = false;
|
||||
}
|
||||
result.append(s);
|
||||
}
|
||||
}
|
||||
|
||||
// Just use the first item's position.
|
||||
return new Str(val.mList.get(0).getPosition(), result.toString());
|
||||
}
|
||||
|
||||
/**
|
||||
* Put each word in 'str' on its own line in make format. If 'val' is null,
|
||||
* 'nullValue' is returned.
|
||||
*/
|
||||
public static String oneLinePerWord(Value val, String nullValue) {
|
||||
if (val == null) {
|
||||
return nullValue;
|
||||
}
|
||||
final String s = normalize(val).toString();
|
||||
final Matcher m = SPACES.matcher(s);
|
||||
final StringBuilder result = new StringBuilder();
|
||||
if (s.length() > 0 && (val.mVarType == VarType.LIST || m.find())) {
|
||||
result.append("\\\n ");
|
||||
}
|
||||
result.append(m.replaceAll(" \\\\\n "));
|
||||
return result.toString();
|
||||
}
|
||||
|
||||
/**
|
||||
* Put each word in 'str' on its own line in make format. If 'str' is null,
|
||||
* nullValue is returned.
|
||||
*/
|
||||
public static String oneLinePerWord(Str str, String nullValue) {
|
||||
if (str == null) {
|
||||
return nullValue;
|
||||
}
|
||||
final Matcher m = SPACES.matcher(normalize(str.toString()));
|
||||
final StringBuilder result = new StringBuilder();
|
||||
if (m.find()) {
|
||||
result.append("\\\n ");
|
||||
}
|
||||
result.append(m.replaceAll(" \\\\\n "));
|
||||
return result.toString();
|
||||
}
|
||||
|
||||
/**
|
||||
* Return a string representing this value with detailed debugging information.
|
||||
*/
|
||||
public static String debugString(Value val) {
|
||||
if (val == null) {
|
||||
return "null";
|
||||
}
|
||||
|
||||
final StringBuilder str = new StringBuilder("Value(");
|
||||
if (val.mStr != null) {
|
||||
str.append("mStr=");
|
||||
str.append("\"");
|
||||
str.append(val.mStr.toString());
|
||||
str.append("\"");
|
||||
if (false) {
|
||||
str.append(" (");
|
||||
str.append(val.mStr.getPosition().toString());
|
||||
str.append(")");
|
||||
}
|
||||
}
|
||||
if (val.mList != null) {
|
||||
str.append("mList=");
|
||||
str.append("[");
|
||||
for (Str s: val.mList) {
|
||||
str.append(" \"");
|
||||
str.append(s.toString());
|
||||
if (false) {
|
||||
str.append("\" (");
|
||||
str.append(s.getPosition().toString());
|
||||
str.append(")");
|
||||
} else {
|
||||
str.append("\"");
|
||||
}
|
||||
}
|
||||
str.append(" ]");
|
||||
}
|
||||
str.append(")");
|
||||
return str.toString();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the Positions of all of the parts of this Value.
|
||||
*/
|
||||
public List<Position> getPositions() {
|
||||
List<Position> result = new ArrayList();
|
||||
if (mStr != null) {
|
||||
result.add(mStr.getPosition());
|
||||
}
|
||||
if (mList != null) {
|
||||
for (Str str: mList) {
|
||||
result.add(str.getPosition());
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
90
tools/product_config/test.sh
Executable file
90
tools/product_config/test.sh
Executable file
|
@ -0,0 +1,90 @@
|
|||
#!/bin/bash
|
||||
|
||||
#
|
||||
# This script runs the full set of tests for product config:
|
||||
# 1. Build the product-config tool.
|
||||
# 2. Run the unit tests.
|
||||
# 3. Run the product config for every product available in the current
|
||||
# source tree, for each of user, userdebug and eng.
|
||||
# - To restrict which products or variants are run, set the
|
||||
# PRODUCTS or VARIANTS environment variables.
|
||||
# - Products for which the make based product config fails are
|
||||
# skipped.
|
||||
#
|
||||
|
||||
# The PRODUCTS variable is used by the build, and setting it in the environment
|
||||
# interferes with that, so unset it. (That should probably be fixed)
|
||||
products=$PRODUCTS
|
||||
variants=$VARIANTS
|
||||
unset PRODUCTS
|
||||
unset VARIANTS
|
||||
|
||||
# Don't use lunch from the user's shell
|
||||
unset TARGET_PRODUCT
|
||||
unset TARGET_BUILD_VARIANT
|
||||
|
||||
function die() {
|
||||
format=$1
|
||||
shift
|
||||
printf "$format\nStopping...\n" $@ >&2
|
||||
exit 1;
|
||||
}
|
||||
|
||||
[[ -f build/make/envsetup.sh ]] || die "Run this script from the root of the tree."
|
||||
: ${products:=$(build/soong/soong_ui.bash --dumpvar-mode all_named_products | sed -e "s/ /\n/g" | sort -u )}
|
||||
: ${variants:="user userdebug eng"}
|
||||
: ${CKATI_BIN:=prebuilts/build-tools/$(build/soong/soong_ui.bash --dumpvar-mode HOST_PREBUILT_TAG)/bin/ckati}
|
||||
|
||||
function if_signal_exit() {
|
||||
[[ $1 -lt 128 ]] || exit $1
|
||||
}
|
||||
|
||||
build/soong/soong_ui.bash --build-mode --all-modules --dir="$(pwd)" product-config-test product-config \
|
||||
|| die "Build failed."
|
||||
|
||||
echo
|
||||
echo Running unit tests
|
||||
java -jar out/host/linux-x86/testcases/product-config-test/product-config-test.jar
|
||||
unit_tests=$?
|
||||
if_signal_exit $unit_tests
|
||||
|
||||
failed_baseline_checks=
|
||||
for product in $products ; do
|
||||
for variant in $variants ; do
|
||||
echo
|
||||
echo Checking to see if $product-$variant works with make
|
||||
TARGET_PRODUCT=$product TARGET_BUILD_VARIANT=$variant build/soong/soong_ui.bash --dumpvar-mode TARGET_PRODUCT &> /dev/null
|
||||
exit_status=$?
|
||||
if_signal_exit $exit_status
|
||||
if [ $exit_status -ne 0 ] ; then
|
||||
echo Combo fails with make, skipping product-config test run for $product-$variant
|
||||
else
|
||||
echo Running product-config for $product-$variant
|
||||
rm -rf out/config/$product-$variant
|
||||
TARGET_PRODUCT=$product TARGET_BUILD_VARIANT=$variant product-config \
|
||||
--ckati_bin $CKATI_BIN \
|
||||
--error 1000
|
||||
exit_status=$?
|
||||
if_signal_exit $exit_status
|
||||
if [ $exit_status -ne 0 ] ; then
|
||||
failed_baseline_checks="$failed_baseline_checks $product-$variant"
|
||||
fi
|
||||
fi
|
||||
done
|
||||
done
|
||||
|
||||
echo
|
||||
echo
|
||||
echo "------------------------------"
|
||||
echo SUMMARY
|
||||
echo "------------------------------"
|
||||
|
||||
echo -n "Unit tests "
|
||||
if [ $unit_tests -eq 0 ] ; then echo PASSED ; else echo FAILED ; fi
|
||||
|
||||
echo -n "Baseline checks "
|
||||
if [ "$failed_baseline_checks" = "" ] ; then echo PASSED ; else echo FAILED ; fi
|
||||
for combo in $failed_baseline_checks ; do
|
||||
echo " ... $combo"
|
||||
done
|
||||
|
Loading…
Reference in a new issue