blob: d982dba7edb27a1a3d2b236ca386d04269792492 [file] [log] [blame]
/*
* 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",
// This can be set temporarily by product_config.mk
".KATI_ALLOW_RULES"
);
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;
}
}