1 /* 2 * Copyright (C) 2021 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package com.android.build.config; 18 19 import java.util.Arrays; 20 import java.util.ArrayList; 21 import java.util.HashMap; 22 import java.util.HashSet; 23 import java.util.List; 24 import java.util.Map; 25 import java.util.TreeMap; 26 27 /** 28 * Compares the make-based configuration as reported by dumpconfig.mk 29 * with what was computed from the new tool. 30 */ 31 public class OutputChecker { 32 // Differences that we know about, either know issues to be fixed or intentional. 33 private static final RegexSet IGNORED_VARIABLES = new RegexSet( 34 // TODO: Rewrite the enforce packages exist logic into this tool. 35 "PRODUCT_ENFORCE_PACKAGES_EXIST", 36 "PRODUCT_ENFORCE_PACKAGES_EXIST_ALLOW_LIST", 37 "PRODUCTS\\..*\\.PRODUCT_ENFORCE_PACKAGES_EXIST", 38 "PRODUCTS\\..*\\.PRODUCT_ENFORCE_PACKAGES_EXIST_ALLOW_LIST", 39 40 // This is generated by this tool, but comes later in the make build system. 41 "INTERNAL_PRODUCT", 42 43 // This can be set temporarily by product_config.mk 44 ".KATI_ALLOW_RULES" 45 ); 46 47 private final FlatConfig mConfig; 48 private final TreeMap<String, Variable> mVariables; 49 50 /** 51 * Represents the before and after state of a variable. 52 */ 53 public static class Variable { 54 public final String name; 55 public final VarType type; 56 public final Str original; 57 public final Value updated; 58 Variable(String name, VarType type, Str original)59 public Variable(String name, VarType type, Str original) { 60 this(name, type, original, null); 61 } 62 Variable(String name, VarType type, Str original, Value updated)63 public Variable(String name, VarType type, Str original, Value updated) { 64 this.name = name; 65 this.type = type; 66 this.original = original; 67 this.updated = updated; 68 } 69 70 /** 71 * Return copy of this Variable with the updated field also set. 72 */ addUpdated(Value updated)73 public Variable addUpdated(Value updated) { 74 return new Variable(name, type, original, updated); 75 } 76 77 /** 78 * Return whether normalizedOriginal and normalizedUpdate are equal. 79 */ isSame()80 public boolean isSame() { 81 final Str normalizedOriginal = Value.normalize(original); 82 final Str normalizedUpdated = Value.normalize(updated); 83 if (normalizedOriginal == null && normalizedUpdated == null) { 84 return true; 85 } else if (normalizedOriginal != null) { 86 return normalizedOriginal.equals(normalizedUpdated); 87 } else { 88 return false; 89 } 90 } 91 } 92 93 /** 94 * Construct OutputChecker with the config it will check. 95 */ OutputChecker(FlatConfig config)96 public OutputChecker(FlatConfig config) { 97 mConfig = config; 98 mVariables = getVariables(config); 99 } 100 101 /** 102 * Add a WARNING_DIFFERENT_FROM_KATI for each of the variables which have changed. 103 */ reportErrors(Errors errors)104 public void reportErrors(Errors errors) { 105 for (Variable var: getDifferences()) { 106 if (IGNORED_VARIABLES.matches(var.name)) { 107 continue; 108 } 109 errors.WARNING_DIFFERENT_FROM_KATI.add("product_config processing differs from" 110 + " kati processing for " + var.type + " variable " + var.name + ".\n" 111 + "original: " 112 + Value.oneLinePerWord(var.original, "<null>") + "\n" 113 + "updated: " 114 + Value.oneLinePerWord(var.updated, "<null>")); 115 } 116 } 117 118 /** 119 * Get the Variables that are different between the normalized form of the original 120 * and updated. If one is null and the other is not, even if one is an empty string, 121 * the values are considered different. 122 */ getDifferences()123 public List<Variable> getDifferences() { 124 final ArrayList<Variable> result = new ArrayList(); 125 for (Variable var: mVariables.values()) { 126 if (!var.isSame()) { 127 result.add(var); 128 } 129 } 130 return result; 131 } 132 133 /** 134 * Get all of the variables for this config. 135 * 136 * VisibleForTesting 137 */ getVariables(FlatConfig config)138 static TreeMap<String, Variable> getVariables(FlatConfig config) { 139 final TreeMap<String, Variable> result = new TreeMap(); 140 141 // Add the original values to mAll 142 for (Map.Entry<String, Str> entry: getModifiedVars(config.getInitialVariables(), 143 config.getFinalVariables()).entrySet()) { 144 final String name = entry.getKey(); 145 result.put(name, new Variable(name, config.getVarType(name), entry.getValue())); 146 } 147 148 // Add the updated values to mAll 149 for (Map.Entry<String, Value> entry: config.getValues().entrySet()) { 150 final String name = entry.getKey(); 151 final Value value = entry.getValue(); 152 Variable var = result.get(name); 153 if (var == null) { 154 result.put(name, new Variable(name, config.getVarType(name), null, value)); 155 } else { 156 result.put(name, var.addUpdated(value)); 157 } 158 } 159 160 return result; 161 } 162 163 /** 164 * Get the entries that are different in the two maps. 165 */ getModifiedVars(Map<String, Str> before, Map<String, Str> after)166 public static Map<String, Str> getModifiedVars(Map<String, Str> before, 167 Map<String, Str> after) { 168 final HashMap<String, Str> result = new HashMap(); 169 170 // Entries that were added or changed. 171 for (Map.Entry<String, Str> afterEntry: after.entrySet()) { 172 final String varName = afterEntry.getKey(); 173 final Str afterValue = afterEntry.getValue(); 174 final Str beforeValue = before.get(varName); 175 if (beforeValue == null || !beforeValue.equals(afterValue)) { 176 result.put(varName, afterValue); 177 } 178 } 179 180 // removed Entries that were removed, we just treat them as empty string 181 for (Map.Entry<String, Str> beforeEntry: before.entrySet()) { 182 final String varName = beforeEntry.getKey(); 183 if (!after.containsKey(varName)) { 184 result.put(varName, new Str("")); 185 } 186 } 187 188 return result; 189 } 190 } 191