blob: 982b4364886dddedddeb512d6461b9b9984d46a1 [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"
Jingwen Chen341f7352022-01-11 05:42:49 +000025
Chris Parsons3b1f83d2021-10-14 14:08:38 -040026 "github.com/google/blueprint"
Jingwen Chenbf61afb2021-05-06 13:31:18 +000027)
28
Liz Kammer82ad8cc2021-08-02 10:41:48 -040029const (
30 bazelIndent = 4
31)
32
33type bazelVarExporter interface {
Chris Parsons3b1f83d2021-10-14 14:08:38 -040034 asBazel(android.Config, exportedStringVariables, exportedStringListVariables, exportedConfigDependingVariables) []bazelConstant
Liz Kammer82ad8cc2021-08-02 10:41:48 -040035}
36
Jingwen Chenbf61afb2021-05-06 13:31:18 +000037// Helpers for exporting cc configuration information to Bazel.
Jingwen Chenbf61afb2021-05-06 13:31:18 +000038var (
Chris Parsons3b1f83d2021-10-14 14:08:38 -040039 // Maps containing toolchain variables that are independent of the
Jingwen Chenbf61afb2021-05-06 13:31:18 +000040 // environment variables of the build.
Liz Kammer82ad8cc2021-08-02 10:41:48 -040041 exportedStringListVars = exportedStringListVariables{}
42 exportedStringVars = exportedStringVariables{}
43 exportedStringListDictVars = exportedStringListDictVariables{}
Chris Parsons3b1f83d2021-10-14 14:08:38 -040044
45 /// Maps containing variables that are dependent on the build config.
46 exportedConfigDependingVars = exportedConfigDependingVariables{}
Jingwen Chenbf61afb2021-05-06 13:31:18 +000047)
48
Chris Parsons3b1f83d2021-10-14 14:08:38 -040049type exportedConfigDependingVariables map[string]interface{}
50
51func (m exportedConfigDependingVariables) Set(k string, v interface{}) {
52 m[k] = v
53}
54
Liz Kammer82ad8cc2021-08-02 10:41:48 -040055// Ensure that string s has no invalid characters to be generated into the bzl file.
56func validateCharacters(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
65type bazelConstant struct {
66 variableName string
67 internalDefinition string
68}
69
Jingwen Chen51a1e1c2021-05-20 13:40:14 +000070type exportedStringVariables map[string]string
Jingwen Chenbf61afb2021-05-06 13:31:18 +000071
Jingwen Chen51a1e1c2021-05-20 13:40:14 +000072func (m exportedStringVariables) Set(k string, v string) {
Jingwen Chenbf61afb2021-05-06 13:31:18 +000073 m[k] = v
74}
75
Liz Kammer82ad8cc2021-08-02 10:41:48 -040076func bazelIndention(level int) string {
77 return strings.Repeat(" ", level*bazelIndent)
78}
79
80func printBazelList(items []string, indentLevel int) string {
81 list := make([]string, 0, len(items)+2)
82 list = append(list, "[")
83 innerIndent := bazelIndention(indentLevel + 1)
84 for _, item := range items {
85 list = append(list, fmt.Sprintf(`%s"%s",`, innerIndent, item))
86 }
87 list = append(list, bazelIndention(indentLevel)+"]")
88 return strings.Join(list, "\n")
89}
90
Chris Parsons3b1f83d2021-10-14 14:08:38 -040091func (m exportedStringVariables) asBazel(config android.Config,
92 stringVars exportedStringVariables, stringListVars exportedStringListVariables, cfgDepVars exportedConfigDependingVariables) []bazelConstant {
Liz Kammer82ad8cc2021-08-02 10:41:48 -040093 ret := make([]bazelConstant, 0, len(m))
94 for k, variableValue := range m {
Chris Parsons3b1f83d2021-10-14 14:08:38 -040095 expandedVar, err := expandVar(config, variableValue, stringVars, stringListVars, cfgDepVars)
96 if err != nil {
97 panic(fmt.Errorf("error expanding config variable %s: %s", k, err))
98 }
Liz Kammer82ad8cc2021-08-02 10:41:48 -040099 if len(expandedVar) > 1 {
100 panic(fmt.Errorf("%s expands to more than one string value: %s", variableValue, expandedVar))
101 }
102 ret = append(ret, bazelConstant{
103 variableName: k,
104 internalDefinition: fmt.Sprintf(`"%s"`, validateCharacters(expandedVar[0])),
105 })
106 }
107 return ret
108}
109
Jingwen Chenbf61afb2021-05-06 13:31:18 +0000110// Convenience function to declare a static variable and export it to Bazel's cc_toolchain.
Jingwen Chen51a1e1c2021-05-20 13:40:14 +0000111func exportStringStaticVariable(name string, value string) {
112 pctx.StaticVariable(name, value)
113 exportedStringVars.Set(name, value)
114}
115
Liz Kammer82ad8cc2021-08-02 10:41:48 -0400116type exportedStringListVariables map[string][]string
117
Jingwen Chen51a1e1c2021-05-20 13:40:14 +0000118func (m exportedStringListVariables) Set(k string, v []string) {
119 m[k] = v
120}
121
Chris Parsons3b1f83d2021-10-14 14:08:38 -0400122func (m exportedStringListVariables) asBazel(config android.Config,
123 stringScope exportedStringVariables, stringListScope exportedStringListVariables,
124 exportedVars exportedConfigDependingVariables) []bazelConstant {
Liz Kammer82ad8cc2021-08-02 10:41:48 -0400125 ret := make([]bazelConstant, 0, len(m))
126 // For each exported variable, recursively expand elements in the variableValue
127 // list to ensure that interpolated variables are expanded according to their values
128 // in the variable scope.
129 for k, variableValue := range m {
130 var expandedVars []string
131 for _, v := range variableValue {
Chris Parsons3b1f83d2021-10-14 14:08:38 -0400132 expandedVar, err := expandVar(config, v, stringScope, stringListScope, exportedVars)
133 if err != nil {
134 panic(fmt.Errorf("Error expanding config variable %s=%s: %s", k, v, err))
135 }
136 expandedVars = append(expandedVars, expandedVar...)
Liz Kammer82ad8cc2021-08-02 10:41:48 -0400137 }
138 // Assign the list as a bzl-private variable; this variable will be exported
139 // out through a constants struct later.
140 ret = append(ret, bazelConstant{
141 variableName: k,
142 internalDefinition: printBazelList(expandedVars, 0),
143 })
144 }
145 return ret
146}
147
Chris Parsons3b1f83d2021-10-14 14:08:38 -0400148// Convenience function to declare a static "source path" variable and export it to Bazel's cc_toolchain.
149func exportVariableConfigMethod(name string, method interface{}) blueprint.Variable {
150 exportedConfigDependingVars.Set(name, method)
151 return pctx.VariableConfigMethod(name, method)
152}
153
154// Convenience function to declare a static "source path" variable and export it to Bazel's cc_toolchain.
155func exportSourcePathVariable(name string, value string) {
156 pctx.SourcePathVariable(name, value)
157 exportedStringVars.Set(name, value)
158}
159
Jingwen Chen51a1e1c2021-05-20 13:40:14 +0000160// Convenience function to declare a static variable and export it to Bazel's cc_toolchain.
161func exportStringListStaticVariable(name string, value []string) {
Jingwen Chenbf61afb2021-05-06 13:31:18 +0000162 pctx.StaticVariable(name, strings.Join(value, " "))
Jingwen Chen51a1e1c2021-05-20 13:40:14 +0000163 exportedStringListVars.Set(name, value)
Jingwen Chenbf61afb2021-05-06 13:31:18 +0000164}
165
Jingwen Chen341f7352022-01-11 05:42:49 +0000166func ExportStringList(name string, value []string) {
167 exportedStringListVars.Set(name, value)
168}
169
Liz Kammer82ad8cc2021-08-02 10:41:48 -0400170type exportedStringListDictVariables map[string]map[string][]string
171
172func (m exportedStringListDictVariables) Set(k string, v map[string][]string) {
173 m[k] = v
174}
175
176func printBazelStringListDict(dict map[string][]string) string {
177 bazelDict := make([]string, 0, len(dict)+2)
178 bazelDict = append(bazelDict, "{")
179 for k, v := range dict {
180 bazelDict = append(bazelDict,
181 fmt.Sprintf(`%s"%s": %s,`, bazelIndention(1), k, printBazelList(v, 1)))
182 }
183 bazelDict = append(bazelDict, "}")
184 return strings.Join(bazelDict, "\n")
185}
186
187// Since dictionaries are not supported in Ninja, we do not expand variables for dictionaries
Chris Parsons3b1f83d2021-10-14 14:08:38 -0400188func (m exportedStringListDictVariables) asBazel(_ android.Config, _ exportedStringVariables,
189 _ exportedStringListVariables, _ exportedConfigDependingVariables) []bazelConstant {
Liz Kammer82ad8cc2021-08-02 10:41:48 -0400190 ret := make([]bazelConstant, 0, len(m))
191 for k, dict := range m {
192 ret = append(ret, bazelConstant{
193 variableName: k,
194 internalDefinition: printBazelStringListDict(dict),
195 })
196 }
197 return ret
198}
199
Jingwen Chenbf61afb2021-05-06 13:31:18 +0000200// BazelCcToolchainVars generates bzl file content containing variables for
201// Bazel's cc_toolchain configuration.
Chris Parsons3b1f83d2021-10-14 14:08:38 -0400202func BazelCcToolchainVars(config android.Config) string {
Liz Kammer82ad8cc2021-08-02 10:41:48 -0400203 return bazelToolchainVars(
Chris Parsons3b1f83d2021-10-14 14:08:38 -0400204 config,
Liz Kammer82ad8cc2021-08-02 10:41:48 -0400205 exportedStringListDictVars,
206 exportedStringListVars,
207 exportedStringVars)
208}
209
Chris Parsons3b1f83d2021-10-14 14:08:38 -0400210func bazelToolchainVars(config android.Config, vars ...bazelVarExporter) string {
Jingwen Chenbf61afb2021-05-06 13:31:18 +0000211 ret := "# GENERATED FOR BAZEL FROM SOONG. DO NOT EDIT.\n\n"
212
Liz Kammer82ad8cc2021-08-02 10:41:48 -0400213 results := []bazelConstant{}
214 for _, v := range vars {
Chris Parsons3b1f83d2021-10-14 14:08:38 -0400215 results = append(results, v.asBazel(config, exportedStringVars, exportedStringListVars, exportedConfigDependingVars)...)
Jingwen Chenbf61afb2021-05-06 13:31:18 +0000216 }
217
Liz Kammer82ad8cc2021-08-02 10:41:48 -0400218 sort.Slice(results, func(i, j int) bool { return results[i].variableName < results[j].variableName })
Jingwen Chenbf61afb2021-05-06 13:31:18 +0000219
Liz Kammer82ad8cc2021-08-02 10:41:48 -0400220 definitions := make([]string, 0, len(results))
221 constants := make([]string, 0, len(results))
222 for _, b := range results {
223 definitions = append(definitions,
224 fmt.Sprintf("_%s = %s", b.variableName, b.internalDefinition))
225 constants = append(constants,
226 fmt.Sprintf("%[1]s%[2]s = _%[2]s,", bazelIndention(1), b.variableName))
Jingwen Chen51a1e1c2021-05-20 13:40:14 +0000227 }
228
Jingwen Chenbf61afb2021-05-06 13:31:18 +0000229 // Build the exported constants struct.
Liz Kammer82ad8cc2021-08-02 10:41:48 -0400230 ret += strings.Join(definitions, "\n\n")
231 ret += "\n\n"
Jingwen Chenbf61afb2021-05-06 13:31:18 +0000232 ret += "constants = struct(\n"
Liz Kammer82ad8cc2021-08-02 10:41:48 -0400233 ret += strings.Join(constants, "\n")
234 ret += "\n)"
235
Jingwen Chenbf61afb2021-05-06 13:31:18 +0000236 return ret
237}
238
239// expandVar recursively expand interpolated variables in the exportedVars scope.
240//
241// We're using a string slice to track the seen variables to avoid
242// stackoverflow errors with infinite recursion. it's simpler to use a
243// string slice than to handle a pass-by-referenced map, which would make it
244// quite complex to track depth-first interpolations. It's also unlikely the
245// interpolation stacks are deep (n > 1).
Chris Parsons3b1f83d2021-10-14 14:08:38 -0400246func expandVar(config android.Config, toExpand string, stringScope exportedStringVariables,
247 stringListScope exportedStringListVariables, exportedVars exportedConfigDependingVariables) ([]string, error) {
Colin Cross0523ba22021-07-14 18:45:05 -0700248 // e.g. "${ExternalCflags}"
Jingwen Chenbf61afb2021-05-06 13:31:18 +0000249 r := regexp.MustCompile(`\${([a-zA-Z0-9_]+)}`)
250
251 // Internal recursive function.
Chris Parsons3b1f83d2021-10-14 14:08:38 -0400252 var expandVarInternal func(string, map[string]bool) (string, error)
253 expandVarInternal = func(toExpand string, seenVars map[string]bool) (string, error) {
254 var ret string
255 remainingString := toExpand
256 for len(remainingString) > 0 {
257 matches := r.FindStringSubmatch(remainingString)
Jingwen Chenbf61afb2021-05-06 13:31:18 +0000258 if len(matches) == 0 {
Chris Parsons3b1f83d2021-10-14 14:08:38 -0400259 return ret + remainingString, nil
Jingwen Chenbf61afb2021-05-06 13:31:18 +0000260 }
Jingwen Chenbf61afb2021-05-06 13:31:18 +0000261 if len(matches) != 2 {
Chris Parsons3b1f83d2021-10-14 14:08:38 -0400262 panic(fmt.Errorf("Expected to only match 1 subexpression in %s, got %d", remainingString, len(matches)-1))
Jingwen Chenbf61afb2021-05-06 13:31:18 +0000263 }
Chris Parsons3b1f83d2021-10-14 14:08:38 -0400264 matchIndex := strings.Index(remainingString, matches[0])
265 ret += remainingString[:matchIndex]
266 remainingString = remainingString[matchIndex+len(matches[0]):]
Jingwen Chenbf61afb2021-05-06 13:31:18 +0000267
268 // Index 1 of FindStringSubmatch contains the subexpression match
269 // (variable name) of the capture group.
270 variable := matches[1]
271 // toExpand contains a variable.
272 if _, ok := seenVars[variable]; ok {
Chris Parsons3b1f83d2021-10-14 14:08:38 -0400273 return ret, fmt.Errorf(
274 "Unbounded recursive interpolation of variable: %s", variable)
Jingwen Chenbf61afb2021-05-06 13:31:18 +0000275 }
276 // A map is passed-by-reference. Create a new map for
277 // this scope to prevent variables seen in one depth-first expansion
278 // to be also treated as "seen" in other depth-first traversals.
279 newSeenVars := map[string]bool{}
280 for k := range seenVars {
281 newSeenVars[k] = true
282 }
283 newSeenVars[variable] = true
Jingwen Chen51a1e1c2021-05-20 13:40:14 +0000284 if unexpandedVars, ok := stringListScope[variable]; ok {
Chris Parsons3b1f83d2021-10-14 14:08:38 -0400285 expandedVars := []string{}
Jingwen Chen51a1e1c2021-05-20 13:40:14 +0000286 for _, unexpandedVar := range unexpandedVars {
Chris Parsons3b1f83d2021-10-14 14:08:38 -0400287 expandedVar, err := expandVarInternal(unexpandedVar, newSeenVars)
288 if err != nil {
289 return ret, err
290 }
291 expandedVars = append(expandedVars, expandedVar)
Jingwen Chen51a1e1c2021-05-20 13:40:14 +0000292 }
Chris Parsons3b1f83d2021-10-14 14:08:38 -0400293 ret += strings.Join(expandedVars, " ")
Jingwen Chen51a1e1c2021-05-20 13:40:14 +0000294 } else if unexpandedVar, ok := stringScope[variable]; ok {
Chris Parsons3b1f83d2021-10-14 14:08:38 -0400295 expandedVar, err := expandVarInternal(unexpandedVar, newSeenVars)
296 if err != nil {
297 return ret, err
298 }
299 ret += expandedVar
300 } else if unevaluatedVar, ok := exportedVars[variable]; ok {
301 evalFunc := reflect.ValueOf(unevaluatedVar)
302 validateVariableMethod(variable, evalFunc)
303 evaluatedResult := evalFunc.Call([]reflect.Value{reflect.ValueOf(config)})
304 evaluatedValue := evaluatedResult[0].Interface().(string)
305 expandedVar, err := expandVarInternal(evaluatedValue, newSeenVars)
306 if err != nil {
307 return ret, err
308 }
309 ret += expandedVar
310 } else {
311 return "", fmt.Errorf("Unbound config variable %s", variable)
Jingwen Chenbf61afb2021-05-06 13:31:18 +0000312 }
313 }
Chris Parsons3b1f83d2021-10-14 14:08:38 -0400314 return ret, nil
315 }
316 var ret []string
317 for _, v := range strings.Split(toExpand, " ") {
318 val, err := expandVarInternal(v, map[string]bool{})
319 if err != nil {
320 return ret, err
321 }
322 ret = append(ret, val)
Jingwen Chenbf61afb2021-05-06 13:31:18 +0000323 }
324
Chris Parsons3b1f83d2021-10-14 14:08:38 -0400325 return ret, nil
326}
327
328func validateVariableMethod(name string, methodValue reflect.Value) {
329 methodType := methodValue.Type()
330 if methodType.Kind() != reflect.Func {
331 panic(fmt.Errorf("method given for variable %s is not a function",
332 name))
333 }
334 if n := methodType.NumIn(); n != 1 {
335 panic(fmt.Errorf("method for variable %s has %d inputs (should be 1)",
336 name, n))
337 }
338 if n := methodType.NumOut(); n != 1 {
339 panic(fmt.Errorf("method for variable %s has %d outputs (should be 1)",
340 name, n))
341 }
342 if kind := methodType.Out(0).Kind(); kind != reflect.String {
343 panic(fmt.Errorf("method for variable %s does not return a string",
344 name))
345 }
Jingwen Chenbf61afb2021-05-06 13:31:18 +0000346}