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