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 config 16 17import ( 18 "android/soong/android" 19 "fmt" 20 "regexp" 21 "strings" 22) 23 24// Helpers for exporting cc configuration information to Bazel. 25 26var ( 27 // Map containing toolchain variables that are independent of the 28 // environment variables of the build. 29 exportedVars = exportedVariablesMap{} 30) 31 32// variableValue is a string slice because the exported variables are all lists 33// of string, and it's simpler to manipulate string lists before joining them 34// into their final string representation. 35type variableValue []string 36 37// envDependentVariable is a toolchain variable computed based on an environment variable. 38type exportedVariablesMap map[string]variableValue 39 40func (m exportedVariablesMap) Set(k string, v variableValue) { 41 m[k] = v 42} 43 44// Convenience function to declare a static variable and export it to Bazel's cc_toolchain. 45func staticVariableExportedToBazel(name string, value []string) { 46 pctx.StaticVariable(name, strings.Join(value, " ")) 47 exportedVars.Set(name, variableValue(value)) 48} 49 50// BazelCcToolchainVars generates bzl file content containing variables for 51// Bazel's cc_toolchain configuration. 52func BazelCcToolchainVars() string { 53 ret := "# GENERATED FOR BAZEL FROM SOONG. DO NOT EDIT.\n\n" 54 55 // Ensure that string s has no invalid characters to be generated into the bzl file. 56 validateCharacters := func(s string) string { 57 for _, c := range []string{`\n`, `"`, `\`} { 58 if strings.Contains(s, c) { 59 panic(fmt.Errorf("%s contains illegal character %s", s, c)) 60 } 61 } 62 return s 63 } 64 65 // For each exported variable, recursively expand elements in the variableValue 66 // list to ensure that interpolated variables are expanded according to their values 67 // in the exportedVars scope. 68 for _, k := range android.SortedStringKeys(exportedVars) { 69 variableValue := exportedVars[k] 70 var expandedVars []string 71 for _, v := range variableValue { 72 expandedVars = append(expandedVars, expandVar(v, exportedVars)...) 73 } 74 // Build the list for this variable. 75 list := "[" 76 for _, flag := range expandedVars { 77 list += fmt.Sprintf("\n \"%s\",", validateCharacters(flag)) 78 } 79 list += "\n]" 80 // Assign the list as a bzl-private variable; this variable will be exported 81 // out through a constants struct later. 82 ret += fmt.Sprintf("_%s = %s\n", k, list) 83 ret += "\n" 84 } 85 86 // Build the exported constants struct. 87 ret += "constants = struct(\n" 88 for _, k := range android.SortedStringKeys(exportedVars) { 89 ret += fmt.Sprintf(" %s = _%s,\n", k, k) 90 } 91 ret += ")" 92 return ret 93} 94 95// expandVar recursively expand interpolated variables in the exportedVars scope. 96// 97// We're using a string slice to track the seen variables to avoid 98// stackoverflow errors with infinite recursion. it's simpler to use a 99// string slice than to handle a pass-by-referenced map, which would make it 100// quite complex to track depth-first interpolations. It's also unlikely the 101// interpolation stacks are deep (n > 1). 102func expandVar(toExpand string, exportedVars map[string]variableValue) []string { 103 // e.g. "${ClangExternalCflags}" 104 r := regexp.MustCompile(`\${([a-zA-Z0-9_]+)}`) 105 106 // Internal recursive function. 107 var expandVarInternal func(string, map[string]bool) []string 108 expandVarInternal = func(toExpand string, seenVars map[string]bool) []string { 109 var ret []string 110 for _, v := range strings.Split(toExpand, " ") { 111 matches := r.FindStringSubmatch(v) 112 if len(matches) == 0 { 113 return []string{v} 114 } 115 116 if len(matches) != 2 { 117 panic(fmt.Errorf( 118 "Expected to only match 1 subexpression in %s, got %d", 119 v, 120 len(matches)-1)) 121 } 122 123 // Index 1 of FindStringSubmatch contains the subexpression match 124 // (variable name) of the capture group. 125 variable := matches[1] 126 // toExpand contains a variable. 127 if _, ok := seenVars[variable]; ok { 128 panic(fmt.Errorf( 129 "Unbounded recursive interpolation of variable: %s", variable)) 130 } 131 // A map is passed-by-reference. Create a new map for 132 // this scope to prevent variables seen in one depth-first expansion 133 // to be also treated as "seen" in other depth-first traversals. 134 newSeenVars := map[string]bool{} 135 for k := range seenVars { 136 newSeenVars[k] = true 137 } 138 newSeenVars[variable] = true 139 unexpandedVars := exportedVars[variable] 140 for _, unexpandedVar := range unexpandedVars { 141 ret = append(ret, expandVarInternal(unexpandedVar, newSeenVars)...) 142 } 143 } 144 return ret 145 } 146 147 return expandVarInternal(toExpand, map[string]bool{}) 148} 149