blob: 4797acca14b1feafa09661626189173040010d84 [file] [log] [blame]
Jingwen Chenbf61afb2021-05-06 13:31:18 +00001// 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 (
Jingwen Chenbf61afb2021-05-06 13:31:18 +000018 "fmt"
Chris Parsons3b1f83d2021-10-14 14:08:38 -040019 "reflect"
Jingwen Chenbf61afb2021-05-06 13:31:18 +000020 "regexp"
Liz Kammer82ad8cc2021-08-02 10:41:48 -040021 "sort"
Jingwen Chenbf61afb2021-05-06 13:31:18 +000022 "strings"
Chris Parsons3b1f83d2021-10-14 14:08:38 -040023
24 "android/soong/android"
25 "github.com/google/blueprint"
Jingwen Chenbf61afb2021-05-06 13:31:18 +000026)
27
Liz Kammer82ad8cc2021-08-02 10:41:48 -040028const (
29 bazelIndent = 4
30)
31
32type bazelVarExporter interface {
Chris Parsons3b1f83d2021-10-14 14:08:38 -040033 asBazel(android.Config, exportedStringVariables, exportedStringListVariables, exportedConfigDependingVariables) []bazelConstant
Liz Kammer82ad8cc2021-08-02 10:41:48 -040034}
35
Jingwen Chenbf61afb2021-05-06 13:31:18 +000036// Helpers for exporting cc configuration information to Bazel.
Jingwen Chenbf61afb2021-05-06 13:31:18 +000037var (
Chris Parsons3b1f83d2021-10-14 14:08:38 -040038 // Maps containing toolchain variables that are independent of the
Jingwen Chenbf61afb2021-05-06 13:31:18 +000039 // environment variables of the build.
Liz Kammer82ad8cc2021-08-02 10:41:48 -040040 exportedStringListVars = exportedStringListVariables{}
41 exportedStringVars = exportedStringVariables{}
42 exportedStringListDictVars = exportedStringListDictVariables{}
Chris Parsons3b1f83d2021-10-14 14:08:38 -040043
44 /// Maps containing variables that are dependent on the build config.
45 exportedConfigDependingVars = exportedConfigDependingVariables{}
Jingwen Chenbf61afb2021-05-06 13:31:18 +000046)
47
Chris Parsons3b1f83d2021-10-14 14:08:38 -040048type exportedConfigDependingVariables map[string]interface{}
49
50func (m exportedConfigDependingVariables) Set(k string, v interface{}) {
51 m[k] = v
52}
53
Liz Kammer82ad8cc2021-08-02 10:41:48 -040054// Ensure that string s has no invalid characters to be generated into the bzl file.
55func validateCharacters(s string) string {
56 for _, c := range []string{`\n`, `"`, `\`} {
57 if strings.Contains(s, c) {
58 panic(fmt.Errorf("%s contains illegal character %s", s, c))
59 }
60 }
61 return s
62}
63
64type bazelConstant struct {
65 variableName string
66 internalDefinition string
67}
68
Jingwen Chen51a1e1c2021-05-20 13:40:14 +000069type exportedStringVariables map[string]string
Jingwen Chenbf61afb2021-05-06 13:31:18 +000070
Jingwen Chen51a1e1c2021-05-20 13:40:14 +000071func (m exportedStringVariables) Set(k string, v string) {
Jingwen Chenbf61afb2021-05-06 13:31:18 +000072 m[k] = v
73}
74
Liz Kammer82ad8cc2021-08-02 10:41:48 -040075func bazelIndention(level int) string {
76 return strings.Repeat(" ", level*bazelIndent)
77}
78
79func printBazelList(items []string, indentLevel int) string {
80 list := make([]string, 0, len(items)+2)
81 list = append(list, "[")
82 innerIndent := bazelIndention(indentLevel + 1)
83 for _, item := range items {
84 list = append(list, fmt.Sprintf(`%s"%s",`, innerIndent, item))
85 }
86 list = append(list, bazelIndention(indentLevel)+"]")
87 return strings.Join(list, "\n")
88}
89
Chris Parsons3b1f83d2021-10-14 14:08:38 -040090func (m exportedStringVariables) asBazel(config android.Config,
91 stringVars exportedStringVariables, stringListVars exportedStringListVariables, cfgDepVars exportedConfigDependingVariables) []bazelConstant {
Liz Kammer82ad8cc2021-08-02 10:41:48 -040092 ret := make([]bazelConstant, 0, len(m))
93 for k, variableValue := range m {
Chris Parsons3b1f83d2021-10-14 14:08:38 -040094 expandedVar, err := expandVar(config, variableValue, stringVars, stringListVars, cfgDepVars)
95 if err != nil {
96 panic(fmt.Errorf("error expanding config variable %s: %s", k, err))
97 }
Liz Kammer82ad8cc2021-08-02 10:41:48 -040098 if len(expandedVar) > 1 {
99 panic(fmt.Errorf("%s expands to more than one string value: %s", variableValue, expandedVar))
100 }
101 ret = append(ret, bazelConstant{
102 variableName: k,
103 internalDefinition: fmt.Sprintf(`"%s"`, validateCharacters(expandedVar[0])),
104 })
105 }
106 return ret
107}
108
Jingwen Chenbf61afb2021-05-06 13:31:18 +0000109// Convenience function to declare a static variable and export it to Bazel's cc_toolchain.
Jingwen Chen51a1e1c2021-05-20 13:40:14 +0000110func exportStringStaticVariable(name string, value string) {
111 pctx.StaticVariable(name, value)
112 exportedStringVars.Set(name, value)
113}
114
Liz Kammer82ad8cc2021-08-02 10:41:48 -0400115type exportedStringListVariables map[string][]string
116
Jingwen Chen51a1e1c2021-05-20 13:40:14 +0000117func (m exportedStringListVariables) Set(k string, v []string) {
118 m[k] = v
119}
120
Chris Parsons3b1f83d2021-10-14 14:08:38 -0400121func (m exportedStringListVariables) asBazel(config android.Config,
122 stringScope exportedStringVariables, stringListScope exportedStringListVariables,
123 exportedVars exportedConfigDependingVariables) []bazelConstant {
Liz Kammer82ad8cc2021-08-02 10:41:48 -0400124 ret := make([]bazelConstant, 0, len(m))
125 // For each exported variable, recursively expand elements in the variableValue
126 // list to ensure that interpolated variables are expanded according to their values
127 // in the variable scope.
128 for k, variableValue := range m {
129 var expandedVars []string
130 for _, v := range variableValue {
Chris Parsons3b1f83d2021-10-14 14:08:38 -0400131 expandedVar, err := expandVar(config, v, stringScope, stringListScope, exportedVars)
132 if err != nil {
133 panic(fmt.Errorf("Error expanding config variable %s=%s: %s", k, v, err))
134 }
135 expandedVars = append(expandedVars, expandedVar...)
Liz Kammer82ad8cc2021-08-02 10:41:48 -0400136 }
137 // Assign the list as a bzl-private variable; this variable will be exported
138 // out through a constants struct later.
139 ret = append(ret, bazelConstant{
140 variableName: k,
141 internalDefinition: printBazelList(expandedVars, 0),
142 })
143 }
144 return ret
145}
146
Chris Parsons3b1f83d2021-10-14 14:08:38 -0400147// Convenience function to declare a static "source path" variable and export it to Bazel's cc_toolchain.
148func exportVariableConfigMethod(name string, method interface{}) blueprint.Variable {
149 exportedConfigDependingVars.Set(name, method)
150 return pctx.VariableConfigMethod(name, method)
151}
152
153// Convenience function to declare a static "source path" variable and export it to Bazel's cc_toolchain.
154func exportSourcePathVariable(name string, value string) {
155 pctx.SourcePathVariable(name, value)
156 exportedStringVars.Set(name, value)
157}
158
Jingwen Chen51a1e1c2021-05-20 13:40:14 +0000159// Convenience function to declare a static variable and export it to Bazel's cc_toolchain.
160func exportStringListStaticVariable(name string, value []string) {
Jingwen Chenbf61afb2021-05-06 13:31:18 +0000161 pctx.StaticVariable(name, strings.Join(value, " "))
Jingwen Chen51a1e1c2021-05-20 13:40:14 +0000162 exportedStringListVars.Set(name, value)
Jingwen Chenbf61afb2021-05-06 13:31:18 +0000163}
164
Liz Kammer82ad8cc2021-08-02 10:41:48 -0400165type exportedStringListDictVariables map[string]map[string][]string
166
167func (m exportedStringListDictVariables) Set(k string, v map[string][]string) {
168 m[k] = v
169}
170
171func printBazelStringListDict(dict map[string][]string) string {
172 bazelDict := make([]string, 0, len(dict)+2)
173 bazelDict = append(bazelDict, "{")
174 for k, v := range dict {
175 bazelDict = append(bazelDict,
176 fmt.Sprintf(`%s"%s": %s,`, bazelIndention(1), k, printBazelList(v, 1)))
177 }
178 bazelDict = append(bazelDict, "}")
179 return strings.Join(bazelDict, "\n")
180}
181
182// Since dictionaries are not supported in Ninja, we do not expand variables for dictionaries
Chris Parsons3b1f83d2021-10-14 14:08:38 -0400183func (m exportedStringListDictVariables) asBazel(_ android.Config, _ exportedStringVariables,
184 _ exportedStringListVariables, _ exportedConfigDependingVariables) []bazelConstant {
Liz Kammer82ad8cc2021-08-02 10:41:48 -0400185 ret := make([]bazelConstant, 0, len(m))
186 for k, dict := range m {
187 ret = append(ret, bazelConstant{
188 variableName: k,
189 internalDefinition: printBazelStringListDict(dict),
190 })
191 }
192 return ret
193}
194
Jingwen Chenbf61afb2021-05-06 13:31:18 +0000195// BazelCcToolchainVars generates bzl file content containing variables for
196// Bazel's cc_toolchain configuration.
Chris Parsons3b1f83d2021-10-14 14:08:38 -0400197func BazelCcToolchainVars(config android.Config) string {
Liz Kammer82ad8cc2021-08-02 10:41:48 -0400198 return bazelToolchainVars(
Chris Parsons3b1f83d2021-10-14 14:08:38 -0400199 config,
Liz Kammer82ad8cc2021-08-02 10:41:48 -0400200 exportedStringListDictVars,
201 exportedStringListVars,
202 exportedStringVars)
203}
204
Chris Parsons3b1f83d2021-10-14 14:08:38 -0400205func bazelToolchainVars(config android.Config, vars ...bazelVarExporter) string {
Jingwen Chenbf61afb2021-05-06 13:31:18 +0000206 ret := "# GENERATED FOR BAZEL FROM SOONG. DO NOT EDIT.\n\n"
207
Liz Kammer82ad8cc2021-08-02 10:41:48 -0400208 results := []bazelConstant{}
209 for _, v := range vars {
Chris Parsons3b1f83d2021-10-14 14:08:38 -0400210 results = append(results, v.asBazel(config, exportedStringVars, exportedStringListVars, exportedConfigDependingVars)...)
Jingwen Chenbf61afb2021-05-06 13:31:18 +0000211 }
212
Liz Kammer82ad8cc2021-08-02 10:41:48 -0400213 sort.Slice(results, func(i, j int) bool { return results[i].variableName < results[j].variableName })
Jingwen Chenbf61afb2021-05-06 13:31:18 +0000214
Liz Kammer82ad8cc2021-08-02 10:41:48 -0400215 definitions := make([]string, 0, len(results))
216 constants := make([]string, 0, len(results))
217 for _, b := range results {
218 definitions = append(definitions,
219 fmt.Sprintf("_%s = %s", b.variableName, b.internalDefinition))
220 constants = append(constants,
221 fmt.Sprintf("%[1]s%[2]s = _%[2]s,", bazelIndention(1), b.variableName))
Jingwen Chen51a1e1c2021-05-20 13:40:14 +0000222 }
223
Jingwen Chenbf61afb2021-05-06 13:31:18 +0000224 // Build the exported constants struct.
Liz Kammer82ad8cc2021-08-02 10:41:48 -0400225 ret += strings.Join(definitions, "\n\n")
226 ret += "\n\n"
Jingwen Chenbf61afb2021-05-06 13:31:18 +0000227 ret += "constants = struct(\n"
Liz Kammer82ad8cc2021-08-02 10:41:48 -0400228 ret += strings.Join(constants, "\n")
229 ret += "\n)"
230
Jingwen Chenbf61afb2021-05-06 13:31:18 +0000231 return ret
232}
233
234// expandVar recursively expand interpolated variables in the exportedVars scope.
235//
236// We're using a string slice to track the seen variables to avoid
237// stackoverflow errors with infinite recursion. it's simpler to use a
238// string slice than to handle a pass-by-referenced map, which would make it
239// quite complex to track depth-first interpolations. It's also unlikely the
240// interpolation stacks are deep (n > 1).
Chris Parsons3b1f83d2021-10-14 14:08:38 -0400241func expandVar(config android.Config, toExpand string, stringScope exportedStringVariables,
242 stringListScope exportedStringListVariables, exportedVars exportedConfigDependingVariables) ([]string, error) {
Colin Cross0523ba22021-07-14 18:45:05 -0700243 // e.g. "${ExternalCflags}"
Jingwen Chenbf61afb2021-05-06 13:31:18 +0000244 r := regexp.MustCompile(`\${([a-zA-Z0-9_]+)}`)
245
246 // Internal recursive function.
Chris Parsons3b1f83d2021-10-14 14:08:38 -0400247 var expandVarInternal func(string, map[string]bool) (string, error)
248 expandVarInternal = func(toExpand string, seenVars map[string]bool) (string, error) {
249 var ret string
250 remainingString := toExpand
251 for len(remainingString) > 0 {
252 matches := r.FindStringSubmatch(remainingString)
Jingwen Chenbf61afb2021-05-06 13:31:18 +0000253 if len(matches) == 0 {
Chris Parsons3b1f83d2021-10-14 14:08:38 -0400254 return ret + remainingString, nil
Jingwen Chenbf61afb2021-05-06 13:31:18 +0000255 }
Jingwen Chenbf61afb2021-05-06 13:31:18 +0000256 if len(matches) != 2 {
Chris Parsons3b1f83d2021-10-14 14:08:38 -0400257 panic(fmt.Errorf("Expected to only match 1 subexpression in %s, got %d", remainingString, len(matches)-1))
Jingwen Chenbf61afb2021-05-06 13:31:18 +0000258 }
Chris Parsons3b1f83d2021-10-14 14:08:38 -0400259 matchIndex := strings.Index(remainingString, matches[0])
260 ret += remainingString[:matchIndex]
261 remainingString = remainingString[matchIndex+len(matches[0]):]
Jingwen Chenbf61afb2021-05-06 13:31:18 +0000262
263 // Index 1 of FindStringSubmatch contains the subexpression match
264 // (variable name) of the capture group.
265 variable := matches[1]
266 // toExpand contains a variable.
267 if _, ok := seenVars[variable]; ok {
Chris Parsons3b1f83d2021-10-14 14:08:38 -0400268 return ret, fmt.Errorf(
269 "Unbounded recursive interpolation of variable: %s", variable)
Jingwen Chenbf61afb2021-05-06 13:31:18 +0000270 }
271 // A map is passed-by-reference. Create a new map for
272 // this scope to prevent variables seen in one depth-first expansion
273 // to be also treated as "seen" in other depth-first traversals.
274 newSeenVars := map[string]bool{}
275 for k := range seenVars {
276 newSeenVars[k] = true
277 }
278 newSeenVars[variable] = true
Jingwen Chen51a1e1c2021-05-20 13:40:14 +0000279 if unexpandedVars, ok := stringListScope[variable]; ok {
Chris Parsons3b1f83d2021-10-14 14:08:38 -0400280 expandedVars := []string{}
Jingwen Chen51a1e1c2021-05-20 13:40:14 +0000281 for _, unexpandedVar := range unexpandedVars {
Chris Parsons3b1f83d2021-10-14 14:08:38 -0400282 expandedVar, err := expandVarInternal(unexpandedVar, newSeenVars)
283 if err != nil {
284 return ret, err
285 }
286 expandedVars = append(expandedVars, expandedVar)
Jingwen Chen51a1e1c2021-05-20 13:40:14 +0000287 }
Chris Parsons3b1f83d2021-10-14 14:08:38 -0400288 ret += strings.Join(expandedVars, " ")
Jingwen Chen51a1e1c2021-05-20 13:40:14 +0000289 } else if unexpandedVar, ok := stringScope[variable]; ok {
Chris Parsons3b1f83d2021-10-14 14:08:38 -0400290 expandedVar, err := expandVarInternal(unexpandedVar, newSeenVars)
291 if err != nil {
292 return ret, err
293 }
294 ret += expandedVar
295 } else if unevaluatedVar, ok := exportedVars[variable]; ok {
296 evalFunc := reflect.ValueOf(unevaluatedVar)
297 validateVariableMethod(variable, evalFunc)
298 evaluatedResult := evalFunc.Call([]reflect.Value{reflect.ValueOf(config)})
299 evaluatedValue := evaluatedResult[0].Interface().(string)
300 expandedVar, err := expandVarInternal(evaluatedValue, newSeenVars)
301 if err != nil {
302 return ret, err
303 }
304 ret += expandedVar
305 } else {
306 return "", fmt.Errorf("Unbound config variable %s", variable)
Jingwen Chenbf61afb2021-05-06 13:31:18 +0000307 }
308 }
Chris Parsons3b1f83d2021-10-14 14:08:38 -0400309 return ret, nil
310 }
311 var ret []string
312 for _, v := range strings.Split(toExpand, " ") {
313 val, err := expandVarInternal(v, map[string]bool{})
314 if err != nil {
315 return ret, err
316 }
317 ret = append(ret, val)
Jingwen Chenbf61afb2021-05-06 13:31:18 +0000318 }
319
Chris Parsons3b1f83d2021-10-14 14:08:38 -0400320 return ret, nil
321}
322
323func validateVariableMethod(name string, methodValue reflect.Value) {
324 methodType := methodValue.Type()
325 if methodType.Kind() != reflect.Func {
326 panic(fmt.Errorf("method given for variable %s is not a function",
327 name))
328 }
329 if n := methodType.NumIn(); n != 1 {
330 panic(fmt.Errorf("method for variable %s has %d inputs (should be 1)",
331 name, n))
332 }
333 if n := methodType.NumOut(); n != 1 {
334 panic(fmt.Errorf("method for variable %s has %d outputs (should be 1)",
335 name, n))
336 }
337 if kind := methodType.Out(0).Kind(); kind != reflect.String {
338 panic(fmt.Errorf("method for variable %s does not return a string",
339 name))
340 }
Jingwen Chenbf61afb2021-05-06 13:31:18 +0000341}