aboutsummaryrefslogtreecommitdiff
path: root/tools/product_config/src/com/android/build/config/ConvertMakeToGenericConfig.java
diff options
context:
space:
mode:
Diffstat (limited to 'tools/product_config/src/com/android/build/config/ConvertMakeToGenericConfig.java')
-rw-r--r--tools/product_config/src/com/android/build/config/ConvertMakeToGenericConfig.java235
1 files changed, 235 insertions, 0 deletions
diff --git a/tools/product_config/src/com/android/build/config/ConvertMakeToGenericConfig.java b/tools/product_config/src/com/android/build/config/ConvertMakeToGenericConfig.java
new file mode 100644
index 0000000000..39bd5dfe7a
--- /dev/null
+++ b/tools/product_config/src/com/android/build/config/ConvertMakeToGenericConfig.java
@@ -0,0 +1,235 @@
+/*
+ * 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(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(products);
+
+ // Each file
+ for (MakeConfig.ConfigFile f: products.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(), products.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;
+ }
+ }
+
+ // 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;
+ }
+
+ /**
+ * Converts one variable from a MakeConfig Block into a GenericConfig Assignment.
+ */
+ GenericConfig.Assign convertAssignment(MakeConfig.BlockType blockType, Str inheritedFile,
+ 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.length() == 0) {
+ // 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 == 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;
+ }
+}