Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 8523601c authored by Joe Onorato's avatar Joe Onorato
Browse files

Generate GenericConfig objects from MakeConfig objects.

Applies heuristics to the sequence of Blocks to do so.

Test: rm -rf out/config/ && m product-config-test product-config && java -jar out/host/linux-x86/testcases/product-config-test/product-config-test.jar && time ( out/host/linux-x86/bin/product-config --ckati_bin /source/kati/ckati > ~/Desktop/out.txt )
Change-Id: Id6763781bc876e2b2e0be320a7259c1ed41c2334
parent f20c93af
Loading
Loading
Loading
Loading
+15 −0
Original line number Diff line number Diff line
@@ -63,6 +63,10 @@ public class ConfigBase {
        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) {
@@ -75,4 +79,15 @@ public class ConfigBase {
    public boolean isProductVar(String name) {
        return mProductVars.get(name) != null;
    }

    /**
     * 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());
        }
    }
}
+206 −0
Original line number Diff line number Diff line
/*
 * 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;
    }
}
+4 −4
Original line number Diff line number Diff line

/*
 * Copyright (C) 2020 The Android Open Source Project
 *
@@ -183,7 +182,8 @@ public class DumpConfigParser {

                // There is already a file in progress, so add another var block to that.
                block = new MakeConfig.Block(MakeConfig.BlockType.INHERIT);
                block.setInheritedFile(inheritedFile);
                // TODO: Make dumpconfig.mk also output a Position for inherit-product
                block.setInheritedFile(new Str(inheritedFile));
                configFile.addBlock(block);

                if (DEBUG) {
@@ -240,8 +240,8 @@ public class DumpConfigParser {
                                + " Saw: " + blockType);
                }
                
                // Add the value to the block in progress
                block.addValue(varName, new Str(pos, varValue));
                // Add the variable to the block in progress
                block.addVar(varName, new Str(pos, varValue));
            } else {
                if (DEBUG) {
                    System.out.print("# ");
+7 −0
Original line number Diff line number Diff line
@@ -52,4 +52,11 @@ public class Errors extends ErrorReporter {
    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.");
}
+131 −0
Original line number Diff line number Diff line
/*
 * 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;
    }
}
Loading