1// Copyright 2021 Google Inc. All rights reserved. 2// 3// Licensed under the Apache License, Version 2.0 (the "License"); 4// you may not use this file except in compliance with the License. 5// You may obtain a copy of the License at 6// 7// http://www.apache.org/licenses/LICENSE-2.0 8// 9// Unless required by applicable law or agreed to in writing, software 10// distributed under the License is distributed on an "AS IS" BASIS, 11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12// See the License for the specific language governing permissions and 13// limitations under the License. 14 15package android 16 17import ( 18 "fmt" 19 "reflect" 20 "regexp" 21 "sort" 22 "strings" 23 24 "android/soong/bazel" 25 "android/soong/starlark_fmt" 26 27 "github.com/google/blueprint" 28) 29 30// BazelVarExporter is a collection of configuration variables that can be exported for use in Bazel rules 31type BazelVarExporter interface { 32 // asBazel expands strings of configuration variables into their concrete values 33 asBazel(Config, ExportedStringVariables, ExportedStringListVariables, ExportedConfigDependingVariables) []bazelConstant 34} 35 36// ExportedVariables is a collection of interdependent configuration variables 37type ExportedVariables struct { 38 // Maps containing toolchain variables that are independent of the 39 // environment variables of the build. 40 exportedStringVars ExportedStringVariables 41 exportedStringListVars ExportedStringListVariables 42 exportedStringListDictVars ExportedStringListDictVariables 43 44 exportedVariableReferenceDictVars ExportedVariableReferenceDictVariables 45 46 /// Maps containing variables that are dependent on the build config. 47 exportedConfigDependingVars ExportedConfigDependingVariables 48 49 pctx PackageContext 50} 51 52// NewExportedVariables creats an empty ExportedVariables struct with non-nil maps 53func NewExportedVariables(pctx PackageContext) ExportedVariables { 54 return ExportedVariables{ 55 exportedStringVars: ExportedStringVariables{}, 56 exportedStringListVars: ExportedStringListVariables{}, 57 exportedStringListDictVars: ExportedStringListDictVariables{}, 58 exportedVariableReferenceDictVars: ExportedVariableReferenceDictVariables{}, 59 exportedConfigDependingVars: ExportedConfigDependingVariables{}, 60 pctx: pctx, 61 } 62} 63 64func (ev ExportedVariables) asBazel(config Config, 65 stringVars ExportedStringVariables, stringListVars ExportedStringListVariables, cfgDepVars ExportedConfigDependingVariables) []bazelConstant { 66 ret := []bazelConstant{} 67 ret = append(ret, ev.exportedStringVars.asBazel(config, stringVars, stringListVars, cfgDepVars)...) 68 ret = append(ret, ev.exportedStringListVars.asBazel(config, stringVars, stringListVars, cfgDepVars)...) 69 ret = append(ret, ev.exportedStringListDictVars.asBazel(config, stringVars, stringListVars, cfgDepVars)...) 70 // Note: ExportedVariableReferenceDictVars collections can only contain references to other variables and must be printed last 71 ret = append(ret, ev.exportedVariableReferenceDictVars.asBazel(config, stringVars, stringListVars, cfgDepVars)...) 72 return ret 73} 74 75// ExportStringStaticVariable declares a static string variable and exports it to 76// Bazel's toolchain. 77func (ev ExportedVariables) ExportStringStaticVariable(name string, value string) { 78 ev.pctx.StaticVariable(name, value) 79 ev.exportedStringVars.set(name, value) 80} 81 82// ExportStringListStaticVariable declares a static variable and exports it to 83// Bazel's toolchain. 84func (ev ExportedVariables) ExportStringListStaticVariable(name string, value []string) { 85 ev.pctx.StaticVariable(name, strings.Join(value, " ")) 86 ev.exportedStringListVars.set(name, value) 87} 88 89// ExportVariableConfigMethod declares a variable whose value is evaluated at 90// runtime via a function with access to the Config and exports it to Bazel's 91// toolchain. 92func (ev ExportedVariables) ExportVariableConfigMethod(name string, method interface{}) blueprint.Variable { 93 ev.exportedConfigDependingVars.set(name, method) 94 return ev.pctx.VariableConfigMethod(name, method) 95} 96 97// ExportSourcePathVariable declares a static "source path" variable and exports 98// it to Bazel's toolchain. 99func (ev ExportedVariables) ExportSourcePathVariable(name string, value string) { 100 ev.pctx.SourcePathVariable(name, value) 101 ev.exportedStringVars.set(name, value) 102} 103 104// ExportVariableFuncVariable declares a variable whose value is evaluated at 105// runtime via a function and exports it to Bazel's toolchain. 106func (ev ExportedVariables) ExportVariableFuncVariable(name string, f func() string) { 107 ev.exportedConfigDependingVars.set(name, func(config Config) string { 108 return f() 109 }) 110 ev.pctx.VariableFunc(name, func(PackageVarContext) string { 111 return f() 112 }) 113} 114 115// ExportString only exports a variable to Bazel, but does not declare it in Soong 116func (ev ExportedVariables) ExportString(name string, value string) { 117 ev.exportedStringVars.set(name, value) 118} 119 120// ExportStringList only exports a variable to Bazel, but does not declare it in Soong 121func (ev ExportedVariables) ExportStringList(name string, value []string) { 122 ev.exportedStringListVars.set(name, value) 123} 124 125// ExportStringListDict only exports a variable to Bazel, but does not declare it in Soong 126func (ev ExportedVariables) ExportStringListDict(name string, value map[string][]string) { 127 ev.exportedStringListDictVars.set(name, value) 128} 129 130// ExportVariableReferenceDict only exports a variable to Bazel, but does not declare it in Soong 131func (ev ExportedVariables) ExportVariableReferenceDict(name string, value map[string]string) { 132 ev.exportedVariableReferenceDictVars.set(name, value) 133} 134 135// ExportedConfigDependingVariables is a mapping of variable names to functions 136// of type func(config Config) string which return the runtime-evaluated string 137// value of a particular variable 138type ExportedConfigDependingVariables map[string]interface{} 139 140func (m ExportedConfigDependingVariables) set(k string, v interface{}) { 141 m[k] = v 142} 143 144// Ensure that string s has no invalid characters to be generated into the bzl file. 145func validateCharacters(s string) string { 146 for _, c := range []string{`\n`, `"`, `\`} { 147 if strings.Contains(s, c) { 148 panic(fmt.Errorf("%s contains illegal character %s", s, c)) 149 } 150 } 151 return s 152} 153 154type bazelConstant struct { 155 variableName string 156 internalDefinition string 157 sortLast bool 158} 159 160// ExportedStringVariables is a mapping of variable names to string values 161type ExportedStringVariables map[string]string 162 163func (m ExportedStringVariables) set(k string, v string) { 164 m[k] = v 165} 166 167func (m ExportedStringVariables) asBazel(config Config, 168 stringVars ExportedStringVariables, stringListVars ExportedStringListVariables, cfgDepVars ExportedConfigDependingVariables) []bazelConstant { 169 ret := make([]bazelConstant, 0, len(m)) 170 for k, variableValue := range m { 171 expandedVar, err := expandVar(config, variableValue, stringVars, stringListVars, cfgDepVars) 172 if err != nil { 173 panic(fmt.Errorf("error expanding config variable %s: %s", k, err)) 174 } 175 if len(expandedVar) > 1 { 176 panic(fmt.Errorf("%s expands to more than one string value: %s", variableValue, expandedVar)) 177 } 178 ret = append(ret, bazelConstant{ 179 variableName: k, 180 internalDefinition: fmt.Sprintf(`"%s"`, validateCharacters(expandedVar[0])), 181 }) 182 } 183 return ret 184} 185 186// ExportedStringListVariables is a mapping of variable names to a list of strings 187type ExportedStringListVariables map[string][]string 188 189func (m ExportedStringListVariables) set(k string, v []string) { 190 m[k] = v 191} 192 193func (m ExportedStringListVariables) asBazel(config Config, 194 stringScope ExportedStringVariables, stringListScope ExportedStringListVariables, 195 exportedVars ExportedConfigDependingVariables) []bazelConstant { 196 ret := make([]bazelConstant, 0, len(m)) 197 // For each exported variable, recursively expand elements in the variableValue 198 // list to ensure that interpolated variables are expanded according to their values 199 // in the variable scope. 200 for k, variableValue := range m { 201 var expandedVars []string 202 for _, v := range variableValue { 203 expandedVar, err := expandVar(config, v, stringScope, stringListScope, exportedVars) 204 if err != nil { 205 panic(fmt.Errorf("Error expanding config variable %s=%s: %s", k, v, err)) 206 } 207 expandedVars = append(expandedVars, expandedVar...) 208 } 209 // Assign the list as a bzl-private variable; this variable will be exported 210 // out through a constants struct later. 211 ret = append(ret, bazelConstant{ 212 variableName: k, 213 internalDefinition: starlark_fmt.PrintStringList(expandedVars, 0), 214 }) 215 } 216 return ret 217} 218 219// ExportedStringListDictVariables is a mapping from variable names to a 220// dictionary which maps keys to lists of strings 221type ExportedStringListDictVariables map[string]map[string][]string 222 223func (m ExportedStringListDictVariables) set(k string, v map[string][]string) { 224 m[k] = v 225} 226 227// Since dictionaries are not supported in Ninja, we do not expand variables for dictionaries 228func (m ExportedStringListDictVariables) asBazel(_ Config, _ ExportedStringVariables, 229 _ ExportedStringListVariables, _ ExportedConfigDependingVariables) []bazelConstant { 230 ret := make([]bazelConstant, 0, len(m)) 231 for k, dict := range m { 232 ret = append(ret, bazelConstant{ 233 variableName: k, 234 internalDefinition: starlark_fmt.PrintStringListDict(dict, 0), 235 }) 236 } 237 return ret 238} 239 240// ExportedVariableReferenceDictVariables is a mapping from variable names to a 241// dictionary which references previously defined variables. This is used to 242// create a Starlark output such as: 243// string_var1 = "string1 244// var_ref_dict_var1 = { 245// "key1": string_var1 246// } 247// This type of variable collection must be expanded last so that it recognizes 248// previously defined variables. 249type ExportedVariableReferenceDictVariables map[string]map[string]string 250 251func (m ExportedVariableReferenceDictVariables) set(k string, v map[string]string) { 252 m[k] = v 253} 254 255func (m ExportedVariableReferenceDictVariables) asBazel(_ Config, _ ExportedStringVariables, 256 _ ExportedStringListVariables, _ ExportedConfigDependingVariables) []bazelConstant { 257 ret := make([]bazelConstant, 0, len(m)) 258 for n, dict := range m { 259 for k, v := range dict { 260 matches, err := variableReference(v) 261 if err != nil { 262 panic(err) 263 } else if !matches.matches { 264 panic(fmt.Errorf("Expected a variable reference, got %q", v)) 265 } else if len(matches.fullVariableReference) != len(v) { 266 panic(fmt.Errorf("Expected only a variable reference, got %q", v)) 267 } 268 dict[k] = "_" + matches.variable 269 } 270 ret = append(ret, bazelConstant{ 271 variableName: n, 272 internalDefinition: starlark_fmt.PrintDict(dict, 0), 273 sortLast: true, 274 }) 275 } 276 return ret 277} 278 279// BazelToolchainVars expands an ExportedVariables collection and returns a string 280// of formatted Starlark variable definitions 281func BazelToolchainVars(config Config, exportedVars ExportedVariables) string { 282 results := exportedVars.asBazel( 283 config, 284 exportedVars.exportedStringVars, 285 exportedVars.exportedStringListVars, 286 exportedVars.exportedConfigDependingVars, 287 ) 288 289 sort.Slice(results, func(i, j int) bool { 290 if results[i].sortLast != results[j].sortLast { 291 return !results[i].sortLast 292 } 293 return results[i].variableName < results[j].variableName 294 }) 295 296 definitions := make([]string, 0, len(results)) 297 constants := make([]string, 0, len(results)) 298 for _, b := range results { 299 definitions = append(definitions, 300 fmt.Sprintf("_%s = %s", b.variableName, b.internalDefinition)) 301 constants = append(constants, 302 fmt.Sprintf("%[1]s%[2]s = _%[2]s,", starlark_fmt.Indention(1), b.variableName)) 303 } 304 305 // Build the exported constants struct. 306 ret := bazel.GeneratedBazelFileWarning 307 ret += "\n\n" 308 ret += strings.Join(definitions, "\n\n") 309 ret += "\n\n" 310 ret += "constants = struct(\n" 311 ret += strings.Join(constants, "\n") 312 ret += "\n)" 313 314 return ret 315} 316 317type match struct { 318 matches bool 319 fullVariableReference string 320 variable string 321} 322 323func variableReference(input string) (match, error) { 324 // e.g. "${ExternalCflags}" 325 r := regexp.MustCompile(`\${(?:config\.)?([a-zA-Z0-9_]+)}`) 326 327 matches := r.FindStringSubmatch(input) 328 if len(matches) == 0 { 329 return match{}, nil 330 } 331 if len(matches) != 2 { 332 return match{}, fmt.Errorf("Expected to only match 1 subexpression in %s, got %d", input, len(matches)-1) 333 } 334 return match{ 335 matches: true, 336 fullVariableReference: matches[0], 337 // Index 1 of FindStringSubmatch contains the subexpression match 338 // (variable name) of the capture group. 339 variable: matches[1], 340 }, nil 341} 342 343// expandVar recursively expand interpolated variables in the exportedVars scope. 344// 345// We're using a string slice to track the seen variables to avoid 346// stackoverflow errors with infinite recursion. it's simpler to use a 347// string slice than to handle a pass-by-referenced map, which would make it 348// quite complex to track depth-first interpolations. It's also unlikely the 349// interpolation stacks are deep (n > 1). 350func expandVar(config Config, toExpand string, stringScope ExportedStringVariables, 351 stringListScope ExportedStringListVariables, exportedVars ExportedConfigDependingVariables) ([]string, error) { 352 353 // Internal recursive function. 354 var expandVarInternal func(string, map[string]bool) (string, error) 355 expandVarInternal = func(toExpand string, seenVars map[string]bool) (string, error) { 356 var ret string 357 remainingString := toExpand 358 for len(remainingString) > 0 { 359 matches, err := variableReference(remainingString) 360 if err != nil { 361 panic(err) 362 } 363 if !matches.matches { 364 return ret + remainingString, nil 365 } 366 matchIndex := strings.Index(remainingString, matches.fullVariableReference) 367 ret += remainingString[:matchIndex] 368 remainingString = remainingString[matchIndex+len(matches.fullVariableReference):] 369 370 variable := matches.variable 371 // toExpand contains a variable. 372 if _, ok := seenVars[variable]; ok { 373 return ret, fmt.Errorf( 374 "Unbounded recursive interpolation of variable: %s", variable) 375 } 376 // A map is passed-by-reference. Create a new map for 377 // this scope to prevent variables seen in one depth-first expansion 378 // to be also treated as "seen" in other depth-first traversals. 379 newSeenVars := map[string]bool{} 380 for k := range seenVars { 381 newSeenVars[k] = true 382 } 383 newSeenVars[variable] = true 384 if unexpandedVars, ok := stringListScope[variable]; ok { 385 expandedVars := []string{} 386 for _, unexpandedVar := range unexpandedVars { 387 expandedVar, err := expandVarInternal(unexpandedVar, newSeenVars) 388 if err != nil { 389 return ret, err 390 } 391 expandedVars = append(expandedVars, expandedVar) 392 } 393 ret += strings.Join(expandedVars, " ") 394 } else if unexpandedVar, ok := stringScope[variable]; ok { 395 expandedVar, err := expandVarInternal(unexpandedVar, newSeenVars) 396 if err != nil { 397 return ret, err 398 } 399 ret += expandedVar 400 } else if unevaluatedVar, ok := exportedVars[variable]; ok { 401 evalFunc := reflect.ValueOf(unevaluatedVar) 402 validateVariableMethod(variable, evalFunc) 403 evaluatedResult := evalFunc.Call([]reflect.Value{reflect.ValueOf(config)}) 404 evaluatedValue := evaluatedResult[0].Interface().(string) 405 expandedVar, err := expandVarInternal(evaluatedValue, newSeenVars) 406 if err != nil { 407 return ret, err 408 } 409 ret += expandedVar 410 } else { 411 return "", fmt.Errorf("Unbound config variable %s", variable) 412 } 413 } 414 return ret, nil 415 } 416 var ret []string 417 stringFields := splitStringKeepingQuotedSubstring(toExpand, ' ') 418 for _, v := range stringFields { 419 val, err := expandVarInternal(v, map[string]bool{}) 420 if err != nil { 421 return ret, err 422 } 423 ret = append(ret, val) 424 } 425 426 return ret, nil 427} 428 429// splitStringKeepingQuotedSubstring splits a string on a provided separator, 430// but it will not split substrings inside unescaped double quotes. If the double 431// quotes are escaped, then the returned string will only include the quote, and 432// not the escape. 433func splitStringKeepingQuotedSubstring(s string, delimiter byte) []string { 434 var ret []string 435 quote := byte('"') 436 437 var substring []byte 438 quoted := false 439 escaped := false 440 441 for i := range s { 442 if !quoted && s[i] == delimiter { 443 ret = append(ret, string(substring)) 444 substring = []byte{} 445 continue 446 } 447 448 characterIsEscape := i < len(s)-1 && s[i] == '\\' && s[i+1] == quote 449 if characterIsEscape { 450 escaped = true 451 continue 452 } 453 454 if s[i] == quote { 455 if !escaped { 456 quoted = !quoted 457 } 458 escaped = false 459 } 460 461 substring = append(substring, s[i]) 462 } 463 464 ret = append(ret, string(substring)) 465 466 return ret 467} 468 469func validateVariableMethod(name string, methodValue reflect.Value) { 470 methodType := methodValue.Type() 471 if methodType.Kind() != reflect.Func { 472 panic(fmt.Errorf("method given for variable %s is not a function", 473 name)) 474 } 475 if n := methodType.NumIn(); n != 1 { 476 panic(fmt.Errorf("method for variable %s has %d inputs (should be 1)", 477 name, n)) 478 } 479 if n := methodType.NumOut(); n != 1 { 480 panic(fmt.Errorf("method for variable %s has %d outputs (should be 1)", 481 name, n)) 482 } 483 if kind := methodType.Out(0).Kind(); kind != reflect.String { 484 panic(fmt.Errorf("method for variable %s does not return a string", 485 name)) 486 } 487} 488