1 /* 2 * Copyright (C) 2020 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.ArrayList; 20 import java.util.HashMap; 21 import java.util.HashSet; 22 import java.util.List; 23 import java.util.Map; 24 import java.util.Set; 25 import java.util.TreeMap; 26 import java.util.TreeSet; 27 import java.util.regex.Pattern; 28 29 public class FlattenConfig { 30 private static final Pattern RE_SPACE = Pattern.compile("\\p{Space}+"); 31 private static final String PRODUCTS_PREFIX = "PRODUCTS"; 32 33 private final Errors mErrors; 34 private final GenericConfig mGenericConfig; 35 private final Map<String, GenericConfig.ConfigFile> mGenericConfigs; 36 private final FlatConfig mResult = new FlatConfig(); 37 private final Map<String, Value> mVariables; 38 /** 39 * Files that have been visited, to prevent infinite recursion. There are no 40 * conditionals at this point in the processing, so we don't need a stack, just 41 * a single set. 42 */ 43 private final Set<Str> mStack = new HashSet(); 44 45 FlattenConfig(Errors errors, GenericConfig genericConfig)46 private FlattenConfig(Errors errors, GenericConfig genericConfig) { 47 mErrors = errors; 48 mGenericConfig = genericConfig; 49 mGenericConfigs = genericConfig.getFiles(); 50 mVariables = mResult.getValues(); 51 52 // Base class fields 53 mResult.copyFrom(genericConfig); 54 } 55 56 /** 57 * Flatten a GenericConfig to a FlatConfig. 58 * 59 * Makes three passes through the genericConfig, one to flatten the single variables, 60 * one to flatten the list variables, and one to flatten the unknown variables. Each 61 * has a slightly different algorithm. 62 */ flatten(Errors errors, GenericConfig genericConfig)63 public static FlatConfig flatten(Errors errors, GenericConfig genericConfig) { 64 final FlattenConfig flattener = new FlattenConfig(errors, genericConfig); 65 return flattener.flattenImpl(); 66 } 67 flattenImpl()68 private FlatConfig flattenImpl() { 69 final List<String> rootNodes = mGenericConfig.getRootNodes(); 70 if (rootNodes.size() == 0) { 71 mErrors.ERROR_DUMPCONFIG.add("No root nodes in PRODUCTS phase."); 72 return null; 73 } else if (rootNodes.size() != 1) { 74 final StringBuilder msg = new StringBuilder( 75 "Ignoring extra root nodes in PRODUCTS phase. All nodes are:"); 76 for (final String rn: rootNodes) { 77 msg.append(' '); 78 msg.append(rn); 79 } 80 mErrors.WARNING_DUMPCONFIG.add(msg.toString()); 81 } 82 final String root = rootNodes.get(0); 83 84 // TODO: Do we need to worry about the initial state of variables? Anything 85 // that from the product config 86 87 flattenListVars(root); 88 flattenSingleVars(root); 89 flattenUnknownVars(root); 90 flattenInheritsFrom(root); 91 92 setDefaultKnownVars(); 93 94 // TODO: This only supports the single product mode of import-nodes, which is all the 95 // real build does. m product-graph and friends will have to be rewritten. 96 mVariables.put("PRODUCTS", new Value(VarType.UNKNOWN, new Str(root))); 97 98 return mResult; 99 } 100 101 interface AssignCallback { onAssignStatement(GenericConfig.Assign assign)102 void onAssignStatement(GenericConfig.Assign assign); 103 } 104 105 interface InheritCallback { onInheritStatement(GenericConfig.Inherit assign)106 void onInheritStatement(GenericConfig.Inherit assign); 107 } 108 109 /** 110 * Do a bunch of validity checks, and then iterate through each of the statements 111 * in the given file. For Assignments, the callback is only called for variables 112 * matching varType. 113 * 114 * Adds makefiles which have been traversed to the 'seen' set, and will not traverse 115 * into an inherit statement if its makefile has already been seen. 116 */ forEachStatement(Str filename, VarType varType, Set<String> seen, AssignCallback assigner, InheritCallback inheriter)117 private void forEachStatement(Str filename, VarType varType, Set<String> seen, 118 AssignCallback assigner, InheritCallback inheriter) { 119 if (mStack.contains(filename)) { 120 mErrors.ERROR_INFINITE_RECURSION.add(filename.getPosition(), 121 "File is already in the inherit-product stack: " + filename); 122 return; 123 } 124 125 mStack.add(filename); 126 try { 127 final GenericConfig.ConfigFile genericFile = mGenericConfigs.get(filename.toString()); 128 129 if (genericFile == null) { 130 mErrors.ERROR_MISSING_CONFIG_FILE.add(filename.getPosition(), 131 "Unable to find config file: " + filename); 132 return; 133 } 134 135 for (final GenericConfig.Statement statement: genericFile.getStatements()) { 136 if (statement instanceof GenericConfig.Assign) { 137 if (assigner != null) { 138 final GenericConfig.Assign assign = (GenericConfig.Assign)statement; 139 final String varName = assign.getName(); 140 141 // Assert that we're not stomping on another variable, which 142 // really should be impossible at this point. 143 assertVarType(filename, varName); 144 145 if (mGenericConfig.getVarType(varName) == varType) { 146 assigner.onAssignStatement(assign); 147 } 148 } 149 } else if (statement instanceof GenericConfig.Inherit) { 150 if (inheriter != null) { 151 final GenericConfig.Inherit inherit = (GenericConfig.Inherit)statement; 152 if (seen != null) { 153 if (seen.contains(inherit.getFilename().toString())) { 154 continue; 155 } 156 seen.add(inherit.getFilename().toString()); 157 } 158 inheriter.onInheritStatement(inherit); 159 } 160 } 161 } 162 } finally { 163 // Also executes after return statements, so we always remove this. 164 mStack.remove(filename); 165 } 166 } 167 168 /** 169 * Call 'inheriter' for each child of 'filename' in alphabetical order. 170 */ forEachInheritAlpha(final Str filename, VarType varType, Set<String> seen, InheritCallback inheriter)171 private void forEachInheritAlpha(final Str filename, VarType varType, Set<String> seen, 172 InheritCallback inheriter) { 173 final TreeMap<Str, GenericConfig.Inherit> alpha = new TreeMap(); 174 forEachStatement(filename, varType, null, null, 175 (inherit) -> { 176 alpha.put(inherit.getFilename(), inherit); 177 }); 178 for (final GenericConfig.Inherit inherit: alpha.values()) { 179 // Handle 'seen' here where we actaully call back, not before, so that 180 // the proper traversal order is preserved. 181 if (seen != null) { 182 if (seen.contains(inherit.getFilename().toString())) { 183 continue; 184 } 185 seen.add(inherit.getFilename().toString()); 186 } 187 inheriter.onInheritStatement(inherit); 188 } 189 } 190 191 /** 192 * Traverse the inheritance hierarchy, setting list-value product config variables. 193 */ flattenListVars(final String filename)194 private void flattenListVars(final String filename) { 195 Map<String, Value> vars = flattenListVars(new Str(filename), new HashSet()); 196 // Add the result of the recursion to mVariables. We know there will be 197 // no collisions because this function only handles list variables. 198 for (Map.Entry<String, Value> entry: vars.entrySet()) { 199 mVariables.put(entry.getKey(), entry.getValue()); 200 } 201 } 202 203 /** 204 * Return the variables defined, recursively, by 'filename.' The 'seen' set 205 * accumulates which nodes have been visited, as each is only done once. 206 * 207 * This convoluted algorithm isn't ideal, but it matches what is in node_fns.mk. 208 */ flattenListVars(final Str filename, Set<String> seen)209 private Map<String, Value> flattenListVars(final Str filename, Set<String> seen) { 210 Map<String, Value> result = new HashMap(); 211 212 // Recurse into our children first in alphabetical order, building a map of 213 // that filename to its flattened values. The order matters here because 214 // we will only look at each child once, and when a file appears multiple 215 // times, its variables must have the right set, based on whether it's been 216 // seen before. This preserves the order from node_fns.mk. 217 218 // Child filename --> { varname --> value } 219 final Map<Str, Map<String, Value>> children = new HashMap(); 220 forEachInheritAlpha(filename, VarType.LIST, seen, 221 (inherit) -> { 222 final Str child = inherit.getFilename(); 223 children.put(child, flattenListVars(child, seen)); 224 }); 225 226 // Now, traverse the values again in the original source order to concatenate the values. 227 // Note that the contcatenation order is *different* from the inherit order above. 228 forEachStatement(filename, VarType.LIST, null, 229 (assign) -> { 230 assignToListVar(result, assign.getName(), assign.getValue()); 231 }, 232 (inherit) -> { 233 final Map<String, Value> child = children.get(inherit.getFilename()); 234 // child == null happens if this node has been visited before. 235 if (child != null) { 236 for (Map.Entry<String, Value> entry: child.entrySet()) { 237 final String varName = entry.getKey(); 238 final Value varVal = entry.getValue(); 239 appendToListVar(result, varName, varVal.getList()); 240 } 241 } 242 }); 243 244 return result; 245 } 246 247 /** 248 * Traverse the inheritance hierarchy, setting single-value product config variables. 249 */ flattenSingleVars(final String filename)250 private void flattenSingleVars(final String filename) { 251 flattenSingleVars(new Str(filename), new HashSet(), new HashSet()); 252 } 253 flattenSingleVars(final Str filename, Set<String> seen1, Set<String> seen2)254 private void flattenSingleVars(final Str filename, Set<String> seen1, Set<String> seen2) { 255 // flattenSingleVars has two loops. The first sets all variables that are 256 // defined for *this* file. The second traverses through the inheritance, 257 // to fill in values that weren't defined in this file. The first appearance of 258 // the variable is the one that wins. 259 260 forEachStatement(filename, VarType.SINGLE, seen1, 261 (assign) -> { 262 final String varName = assign.getName(); 263 Value v = mVariables.get(varName); 264 // Only take the first value that we see for single variables. 265 Value value = mVariables.get(varName); 266 if (!mVariables.containsKey(varName)) { 267 final List<Str> valueList = assign.getValue(); 268 // There should never be more than one item in this list, because 269 // SINGLE values should never be appended to. 270 if (valueList.size() != 1) { 271 final StringBuilder positions = new StringBuilder("["); 272 for (Str s: valueList) { 273 positions.append(s.getPosition()); 274 } 275 positions.append(" ]"); 276 throw new RuntimeException("Value list found for SINGLE variable " 277 + varName + " size=" + valueList.size() 278 + "positions=" + positions.toString()); 279 } 280 mVariables.put(varName, 281 new Value(VarType.SINGLE, 282 valueList.get(0))); 283 } 284 }, null); 285 286 forEachInheritAlpha(filename, VarType.SINGLE, seen2, 287 (inherit) -> { 288 flattenSingleVars(inherit.getFilename(), seen1, seen2); 289 }); 290 } 291 292 /** 293 * Traverse the inheritance hierarchy and flatten the values 294 */ flattenUnknownVars(String filename)295 private void flattenUnknownVars(String filename) { 296 flattenUnknownVars(new Str(filename), new HashSet()); 297 } 298 flattenUnknownVars(final Str filename, Set<String> seen)299 private void flattenUnknownVars(final Str filename, Set<String> seen) { 300 // flattenUnknownVars has two loops: First to attempt to set the variable from 301 // this file, and then a second loop to handle the inheritance. This is odd 302 // but it matches the order the files are included in node_fns.mk. The last appearance 303 // of the value is the one that wins. 304 305 forEachStatement(filename, VarType.UNKNOWN, null, 306 (assign) -> { 307 // Overwrite the current value with whatever is now in the file. 308 mVariables.put(assign.getName(), 309 new Value(VarType.UNKNOWN, 310 flattenAssignList(assign, new Str("")))); 311 }, null); 312 313 forEachInheritAlpha(filename, VarType.UNKNOWN, seen, 314 (inherit) -> { 315 flattenUnknownVars(inherit.getFilename(), seen); 316 }); 317 } 318 319 String prefix = ""; 320 321 /** 322 * Sets the PRODUCTS.<filename>.INHERITS_FROM variables. 323 */ flattenInheritsFrom(final String filename)324 private void flattenInheritsFrom(final String filename) { 325 flattenInheritsFrom(new Str(filename)); 326 } 327 328 /** 329 * This flatten function, unlike the others visits all of the nodes regardless 330 * of whether they have been seen before, because that's what the make code does. 331 */ flattenInheritsFrom(final Str filename)332 private void flattenInheritsFrom(final Str filename) { 333 // Recurse, and gather the list our chlidren 334 final TreeSet<Str> children = new TreeSet(); 335 forEachStatement(filename, VarType.LIST, null, null, 336 (inherit) -> { 337 children.add(inherit.getFilename()); 338 flattenInheritsFrom(inherit.getFilename()); 339 }); 340 341 final String varName = "PRODUCTS." + filename + ".INHERITS_FROM"; 342 if (children.size() > 0) { 343 // Build the space separated list. 344 boolean first = true; 345 final StringBuilder val = new StringBuilder(); 346 for (Str child: children) { 347 if (first) { 348 first = false; 349 } else { 350 val.append(' '); 351 } 352 val.append(child); 353 } 354 mVariables.put(varName, new Value(VarType.UNKNOWN, new Str(val.toString()))); 355 } else { 356 // Clear whatever flattenUnknownVars happened to have put in. 357 mVariables.remove(varName); 358 } 359 } 360 361 /** 362 * Throw an exception if there's an existing variable with a different type. 363 */ assertVarType(Str filename, String varName)364 private void assertVarType(Str filename, String varName) { 365 if (mGenericConfig.getVarType(varName) == VarType.UNKNOWN) { 366 final Value prevValue = mVariables.get(varName); 367 if (prevValue != null 368 && prevValue.getVarType() != VarType.UNKNOWN) { 369 throw new RuntimeException("Mismatched var types:" 370 + " filename=" + filename 371 + " varType=" + mGenericConfig.getVarType(varName) 372 + " varName=" + varName 373 + " prevValue=" + Value.debugString(prevValue)); 374 } 375 } 376 } 377 378 /** 379 * Depending on whether the assignment is prepending, appending, setting, etc., 380 * update the value. We can infer which of those operations it is by the length 381 * and contents of the values. Each value in the list was originally separated 382 * by the previous value. 383 */ assignToListVar(Map<String, Value> vars, String varName, List<Str> items)384 private void assignToListVar(Map<String, Value> vars, String varName, List<Str> items) { 385 final Value value = vars.get(varName); 386 final List<Str> orig = value == null ? new ArrayList() : value.getList(); 387 final List<Str> result = new ArrayList(); 388 if (items.size() > 0) { 389 for (int i = 0; i < items.size(); i++) { 390 if (i != 0) { 391 result.addAll(orig); 392 } 393 final Str item = items.get(i); 394 addWords(result, item); 395 } 396 } 397 vars.put(varName, new Value(result)); 398 } 399 400 /** 401 * Appends all of the words in in 'items' to an entry in vars keyed by 'varName', 402 * creating one if necessary. 403 */ appendToListVar(Map<String, Value> vars, String varName, List<Str> items)404 private static void appendToListVar(Map<String, Value> vars, String varName, List<Str> items) { 405 Value value = vars.get(varName); 406 if (value == null) { 407 value = new Value(new ArrayList()); 408 vars.put(varName, value); 409 } 410 final List<Str> out = value.getList(); 411 for (Str item: items) { 412 addWords(out, item); 413 } 414 } 415 416 /** 417 * Split 'item' on spaces, and add each of them as a word to 'out'. 418 */ addWords(List<Str> out, Str item)419 private static void addWords(List<Str> out, Str item) { 420 for (String word: RE_SPACE.split(item.toString().trim())) { 421 if (word.length() > 0) { 422 out.add(new Str(item.getPosition(), word)); 423 } 424 } 425 } 426 427 /** 428 * Flatten the list of strings in an Assign statement, using the previous value 429 * as a separator. 430 */ flattenAssignList(GenericConfig.Assign assign, Str previous)431 private Str flattenAssignList(GenericConfig.Assign assign, Str previous) { 432 final StringBuilder result = new StringBuilder(); 433 Position position = previous.getPosition(); 434 final List<Str> list = assign.getValue(); 435 final int size = list.size(); 436 for (int i = 0; i < size; i++) { 437 final Str item = list.get(i); 438 result.append(item.toString()); 439 if (i != size - 1) { 440 result.append(previous); 441 } 442 final Position pos = item.getPosition(); 443 if (pos != null && pos.getFile() != null) { 444 position = pos; 445 } 446 } 447 return new Str(position, result.toString()); 448 } 449 450 /** 451 * Make sure that each of the product config variables has a default value. 452 */ setDefaultKnownVars()453 private void setDefaultKnownVars() { 454 for (Map.Entry<String, VarType> entry: mGenericConfig.getProductVars().entrySet()) { 455 final String varName = entry.getKey(); 456 final VarType varType = entry.getValue(); 457 458 final Value val = mVariables.get(varName); 459 if (val == null) { 460 mVariables.put(varName, new Value(varType)); 461 } 462 } 463 464 465 // TODO: These two for now as well, until we can rewrite the enforce packages exist 466 // handling. 467 if (!mVariables.containsKey("PRODUCT_ENFORCE_PACKAGES_EXIST")) { 468 mVariables.put("PRODUCT_ENFORCE_PACKAGES_EXIST", new Value(VarType.UNKNOWN)); 469 } 470 if (!mVariables.containsKey("PRODUCT_ENFORCE_PACKAGES_EXIST_ALLOW_LIST")) { 471 mVariables.put("PRODUCT_ENFORCE_PACKAGES_EXIST_ALLOW_LIST", new Value(VarType.UNKNOWN)); 472 } 473 } 474 } 475