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 ret = append(ret, ev.exportedConfigDependingVars.asBazel(config, stringVars, stringListVars, cfgDepVars)...) 73 return ret 74} 75 76// ExportStringStaticVariable declares a static string variable and exports it to 77// Bazel's toolchain. 78func (ev ExportedVariables) ExportStringStaticVariable(name string, value string) { 79 ev.pctx.StaticVariable(name, value) 80 ev.exportedStringVars.set(name, value) 81} 82 83// ExportStringListStaticVariable declares a static variable and exports it to 84// Bazel's toolchain. 85func (ev ExportedVariables) ExportStringListStaticVariable(name string, value []string) { 86 ev.pctx.StaticVariable(name, strings.Join(value, " ")) 87 ev.exportedStringListVars.set(name, value) 88} 89 90// ExportVariableConfigMethod declares a variable whose value is evaluated at 91// runtime via a function with access to the Config and exports it to Bazel's 92// toolchain. 93func (ev ExportedVariables) ExportVariableConfigMethod(name string, method interface{}) blueprint.Variable { 94 ev.exportedConfigDependingVars.set(name, method) 95 return ev.pctx.VariableConfigMethod(name, method) 96} 97 98func (ev ExportedVariables) ExportStringStaticVariableWithEnvOverride(name, envVar, defaultVal string) { 99 ev.ExportVariableConfigMethod(name, func(config Config) string { 100 if override := config.Getenv(envVar); override != "" { 101 return override 102 } 103 return defaultVal 104 }) 105} 106 107// ExportSourcePathVariable declares a static "source path" variable and exports 108// it to Bazel's toolchain. 109func (ev ExportedVariables) ExportSourcePathVariable(name string, value string) { 110 ev.pctx.SourcePathVariable(name, value) 111 ev.exportedStringVars.set(name, value) 112} 113 114// ExportVariableFuncVariable declares a variable whose value is evaluated at 115// runtime via a function and exports it to Bazel's toolchain. 116func (ev ExportedVariables) ExportVariableFuncVariable(name string, f func() string) { 117 ev.exportedConfigDependingVars.set(name, func(config Config) string { 118 return f() 119 }) 120 ev.pctx.VariableFunc(name, func(PackageVarContext) string { 121 return f() 122 }) 123} 124 125// ExportString only exports a variable to Bazel, but does not declare it in Soong 126func (ev ExportedVariables) ExportString(name string, value string) { 127 ev.exportedStringVars.set(name, value) 128} 129 130// ExportStringList only exports a variable to Bazel, but does not declare it in Soong 131func (ev ExportedVariables) ExportStringList(name string, value []string) { 132 ev.exportedStringListVars.set(name, value) 133} 134 135// ExportStringListDict only exports a variable to Bazel, but does not declare it in Soong 136func (ev ExportedVariables) ExportStringListDict(name string, value map[string][]string) { 137 ev.exportedStringListDictVars.set(name, value) 138} 139 140// ExportVariableReferenceDict only exports a variable to Bazel, but does not declare it in Soong 141func (ev ExportedVariables) ExportVariableReferenceDict(name string, value map[string]string) { 142 ev.exportedVariableReferenceDictVars.set(name, value) 143} 144 145// ExportedConfigDependingVariables is a mapping of variable names to functions 146// of type func(config Config) string which return the runtime-evaluated string 147// value of a particular variable 148type ExportedConfigDependingVariables map[string]interface{} 149 150func (m ExportedConfigDependingVariables) set(k string, v interface{}) { 151 m[k] = v 152} 153 154func (m ExportedConfigDependingVariables) asBazel(config Config, 155 stringVars ExportedStringVariables, stringListVars ExportedStringListVariables, cfgDepVars ExportedConfigDependingVariables) []bazelConstant { 156 ret := make([]bazelConstant, 0, len(m)) 157 for variable, unevaluatedVar := range m { 158 evalFunc := reflect.ValueOf(unevaluatedVar) 159 validateVariableMethod(variable, evalFunc) 160 evaluatedResult := evalFunc.Call([]reflect.Value{reflect.ValueOf(config)}) 161 evaluatedValue := evaluatedResult[0].Interface().(string) 162 expandedVars, err := expandVar(config, evaluatedValue, stringVars, stringListVars, cfgDepVars) 163 if err != nil { 164 panic(fmt.Errorf("error expanding config variable %s: %s", variable, err)) 165 } 166 if len(expandedVars) > 1 { 167 ret = append(ret, bazelConstant{ 168 variableName: variable, 169 internalDefinition: starlark_fmt.PrintStringList(expandedVars, 0), 170 }) 171 } else { 172 ret = append(ret, bazelConstant{ 173 variableName: variable, 174 internalDefinition: fmt.Sprintf(`"%s"`, validateCharacters(expandedVars[0])), 175 }) 176 } 177 } 178 return ret 179} 180 181// Ensure that string s has no invalid characters to be generated into the bzl file. 182func validateCharacters(s string) string { 183 for _, c := range []string{`\n`, `"`, `\`} { 184 if strings.Contains(s, c) { 185 panic(fmt.Errorf("%s contains illegal character %s", s, c)) 186 } 187 } 188 return s 189} 190 191type bazelConstant struct { 192 variableName string 193 internalDefinition string 194 sortLast bool 195} 196 197// ExportedStringVariables is a mapping of variable names to string values 198type ExportedStringVariables map[string]string 199 200func (m ExportedStringVariables) set(k string, v string) { 201 m[k] = v 202} 203 204func (m ExportedStringVariables) asBazel(config Config, 205 stringVars ExportedStringVariables, stringListVars ExportedStringListVariables, cfgDepVars ExportedConfigDependingVariables) []bazelConstant { 206 ret := make([]bazelConstant, 0, len(m)) 207 for k, variableValue := range m { 208 expandedVar, err := expandVar(config, variableValue, stringVars, stringListVars, cfgDepVars) 209 if err != nil { 210 panic(fmt.Errorf("error expanding config variable %s: %s", k, err)) 211 } 212 if len(expandedVar) > 1 { 213 panic(fmt.Errorf("%s expands to more than one string value: %s", variableValue, expandedVar)) 214 } 215 ret = append(ret, bazelConstant{ 216 variableName: k, 217 internalDefinition: fmt.Sprintf(`"%s"`, validateCharacters(expandedVar[0])), 218 }) 219 } 220 return ret 221} 222 223// ExportedStringListVariables is a mapping of variable names to a list of strings 224type ExportedStringListVariables map[string][]string 225 226func (m ExportedStringListVariables) set(k string, v []string) { 227 m[k] = v 228} 229 230func (m ExportedStringListVariables) asBazel(config Config, 231 stringScope ExportedStringVariables, stringListScope ExportedStringListVariables, 232 exportedVars ExportedConfigDependingVariables) []bazelConstant { 233 ret := make([]bazelConstant, 0, len(m)) 234 // For each exported variable, recursively expand elements in the variableValue 235 // list to ensure that interpolated variables are expanded according to their values 236 // in the variable scope. 237 for k, variableValue := range m { 238 var expandedVars []string 239 for _, v := range variableValue { 240 expandedVar, err := expandVar(config, v, stringScope, stringListScope, exportedVars) 241 if err != nil { 242 panic(fmt.Errorf("Error expanding config variable %s=%s: %s", k, v, err)) 243 } 244 expandedVars = append(expandedVars, expandedVar...) 245 } 246 // Assign the list as a bzl-private variable; this variable will be exported 247 // out through a constants struct later. 248 ret = append(ret, bazelConstant{ 249 variableName: k, 250 internalDefinition: starlark_fmt.PrintStringList(expandedVars, 0), 251 }) 252 } 253 return ret 254} 255 256// ExportedStringListDictVariables is a mapping from variable names to a 257// dictionary which maps keys to lists of strings 258type ExportedStringListDictVariables map[string]map[string][]string 259 260func (m ExportedStringListDictVariables) set(k string, v map[string][]string) { 261 m[k] = v 262} 263 264// Since dictionaries are not supported in Ninja, we do not expand variables for dictionaries 265func (m ExportedStringListDictVariables) asBazel(_ Config, _ ExportedStringVariables, 266 _ ExportedStringListVariables, _ ExportedConfigDependingVariables) []bazelConstant { 267 ret := make([]bazelConstant, 0, len(m)) 268 for k, dict := range m { 269 ret = append(ret, bazelConstant{ 270 variableName: k, 271 internalDefinition: starlark_fmt.PrintStringListDict(dict, 0), 272 }) 273 } 274 return ret 275} 276 277// ExportedVariableReferenceDictVariables is a mapping from variable names to a 278// dictionary which references previously defined variables. This is used to 279// create a Starlark output such as: 280// 281// string_var1 = "string1 282// var_ref_dict_var1 = { 283// "key1": string_var1 284// } 285// 286// This type of variable collection must be expanded last so that it recognizes 287// previously defined variables. 288type ExportedVariableReferenceDictVariables map[string]map[string]string 289 290func (m ExportedVariableReferenceDictVariables) set(k string, v map[string]string) { 291 m[k] = v 292} 293 294func (m ExportedVariableReferenceDictVariables) asBazel(_ Config, _ ExportedStringVariables, 295 _ ExportedStringListVariables, _ ExportedConfigDependingVariables) []bazelConstant { 296 ret := make([]bazelConstant, 0, len(m)) 297 for n, dict := range m { 298 for k, v := range dict { 299 matches, err := variableReference(v) 300 if err != nil { 301 panic(err) 302 } else if !matches.matches { 303 panic(fmt.Errorf("Expected a variable reference, got %q", v)) 304 } else if len(matches.fullVariableReference) != len(v) { 305 panic(fmt.Errorf("Expected only a variable reference, got %q", v)) 306 } 307 dict[k] = "_" + matches.variable 308 } 309 ret = append(ret, bazelConstant{ 310 variableName: n, 311 internalDefinition: starlark_fmt.PrintDict(dict, 0), 312 sortLast: true, 313 }) 314 } 315 return ret 316} 317 318// BazelToolchainVars expands an ExportedVariables collection and returns a string 319// of formatted Starlark variable definitions 320func BazelToolchainVars(config Config, exportedVars ExportedVariables) string { 321 results := exportedVars.asBazel( 322 config, 323 exportedVars.exportedStringVars, 324 exportedVars.exportedStringListVars, 325 exportedVars.exportedConfigDependingVars, 326 ) 327 328 sort.Slice(results, func(i, j int) bool { 329 if results[i].sortLast != results[j].sortLast { 330 return !results[i].sortLast 331 } 332 return results[i].variableName < results[j].variableName 333 }) 334 335 definitions := make([]string, 0, len(results)) 336 constants := make([]string, 0, len(results)) 337 for _, b := range results { 338 definitions = append(definitions, 339 fmt.Sprintf("_%s = %s", b.variableName, b.internalDefinition)) 340 constants = append(constants, 341 fmt.Sprintf("%[1]s%[2]s = _%[2]s,", starlark_fmt.Indention(1), b.variableName)) 342 } 343 344 // Build the exported constants struct. 345 ret := bazel.GeneratedBazelFileWarning 346 ret += "\n\n" 347 ret += strings.Join(definitions, "\n\n") 348 ret += "\n\n" 349 ret += "constants = struct(\n" 350 ret += strings.Join(constants, "\n") 351 ret += "\n)" 352 353 return ret 354} 355 356type match struct { 357 matches bool 358 fullVariableReference string 359 variable string 360} 361 362func variableReference(input string) (match, error) { 363 // e.g. "${ExternalCflags}" 364 r := regexp.MustCompile(`\${(?:config\.)?([a-zA-Z0-9_]+)}`) 365 366 matches := r.FindStringSubmatch(input) 367 if len(matches) == 0 { 368 return match{}, nil 369 } 370 if len(matches) != 2 { 371 return match{}, fmt.Errorf("Expected to only match 1 subexpression in %s, got %d", input, len(matches)-1) 372 } 373 return match{ 374 matches: true, 375 fullVariableReference: matches[0], 376 // Index 1 of FindStringSubmatch contains the subexpression match 377 // (variable name) of the capture group. 378 variable: matches[1], 379 }, nil 380} 381 382// expandVar recursively expand interpolated variables in the exportedVars scope. 383// 384// We're using a string slice to track the seen variables to avoid 385// stackoverflow errors with infinite recursion. it's simpler to use a 386// string slice than to handle a pass-by-referenced map, which would make it 387// quite complex to track depth-first interpolations. It's also unlikely the 388// interpolation stacks are deep (n > 1). 389func expandVar(config Config, toExpand string, stringScope ExportedStringVariables, 390 stringListScope ExportedStringListVariables, exportedVars ExportedConfigDependingVariables) ([]string, error) { 391 392 // Internal recursive function. 393 var expandVarInternal func(string, map[string]bool) (string, error) 394 expandVarInternal = func(toExpand string, seenVars map[string]bool) (string, error) { 395 var ret string 396 remainingString := toExpand 397 for len(remainingString) > 0 { 398 matches, err := variableReference(remainingString) 399 if err != nil { 400 panic(err) 401 } 402 if !matches.matches { 403 return ret + remainingString, nil 404 } 405 matchIndex := strings.Index(remainingString, matches.fullVariableReference) 406 ret += remainingString[:matchIndex] 407 remainingString = remainingString[matchIndex+len(matches.fullVariableReference):] 408 409 variable := matches.variable 410 // toExpand contains a variable. 411 if _, ok := seenVars[variable]; ok { 412 return ret, fmt.Errorf( 413 "Unbounded recursive interpolation of variable: %s", variable) 414 } 415 // A map is passed-by-reference. Create a new map for 416 // this scope to prevent variables seen in one depth-first expansion 417 // to be also treated as "seen" in other depth-first traversals. 418 newSeenVars := map[string]bool{} 419 for k := range seenVars { 420 newSeenVars[k] = true 421 } 422 newSeenVars[variable] = true 423 if unexpandedVars, ok := stringListScope[variable]; ok { 424 expandedVars := []string{} 425 for _, unexpandedVar := range unexpandedVars { 426 expandedVar, err := expandVarInternal(unexpandedVar, newSeenVars) 427 if err != nil { 428 return ret, err 429 } 430 expandedVars = append(expandedVars, expandedVar) 431 } 432 ret += strings.Join(expandedVars, " ") 433 } else if unexpandedVar, ok := stringScope[variable]; ok { 434 expandedVar, err := expandVarInternal(unexpandedVar, newSeenVars) 435 if err != nil { 436 return ret, err 437 } 438 ret += expandedVar 439 } else if unevaluatedVar, ok := exportedVars[variable]; ok { 440 evalFunc := reflect.ValueOf(unevaluatedVar) 441 validateVariableMethod(variable, evalFunc) 442 evaluatedResult := evalFunc.Call([]reflect.Value{reflect.ValueOf(config)}) 443 evaluatedValue := evaluatedResult[0].Interface().(string) 444 expandedVar, err := expandVarInternal(evaluatedValue, newSeenVars) 445 if err != nil { 446 return ret, err 447 } 448 ret += expandedVar 449 } else { 450 return "", fmt.Errorf("Unbound config variable %s", variable) 451 } 452 } 453 return ret, nil 454 } 455 var ret []string 456 stringFields := splitStringKeepingQuotedSubstring(toExpand, ' ') 457 for _, v := range stringFields { 458 val, err := expandVarInternal(v, map[string]bool{}) 459 if err != nil { 460 return ret, err 461 } 462 ret = append(ret, val) 463 } 464 465 return ret, nil 466} 467 468// splitStringKeepingQuotedSubstring splits a string on a provided separator, 469// but it will not split substrings inside unescaped double quotes. If the double 470// quotes are escaped, then the returned string will only include the quote, and 471// not the escape. 472func splitStringKeepingQuotedSubstring(s string, delimiter byte) []string { 473 var ret []string 474 quote := byte('"') 475 476 var substring []byte 477 quoted := false 478 escaped := false 479 480 for i := range s { 481 if !quoted && s[i] == delimiter { 482 ret = append(ret, string(substring)) 483 substring = []byte{} 484 continue 485 } 486 487 characterIsEscape := i < len(s)-1 && s[i] == '\\' && s[i+1] == quote 488 if characterIsEscape { 489 escaped = true 490 continue 491 } 492 493 if s[i] == quote { 494 if !escaped { 495 quoted = !quoted 496 } 497 escaped = false 498 } 499 500 substring = append(substring, s[i]) 501 } 502 503 ret = append(ret, string(substring)) 504 505 return ret 506} 507 508func validateVariableMethod(name string, methodValue reflect.Value) { 509 methodType := methodValue.Type() 510 if methodType.Kind() != reflect.Func { 511 panic(fmt.Errorf("method given for variable %s is not a function", 512 name)) 513 } 514 if n := methodType.NumIn(); n != 1 { 515 panic(fmt.Errorf("method for variable %s has %d inputs (should be 1)", 516 name, n)) 517 } 518 if n := methodType.NumOut(); n != 1 { 519 panic(fmt.Errorf("method for variable %s has %d outputs (should be 1)", 520 name, n)) 521 } 522 if kind := methodType.Out(0).Kind(); kind != reflect.String { 523 panic(fmt.Errorf("method for variable %s does not return a string", 524 name)) 525 } 526} 527