blob: eca5161073801d47f732fc842fa3fcf2ecfaaa7f [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"
Liz Kammer72beb342022-02-03 08:42:10 -050025 "android/soong/starlark_fmt"
Jingwen Chen341f7352022-01-11 05:42:49 +000026
Chris Parsons3b1f83d2021-10-14 14:08:38 -040027 "github.com/google/blueprint"
Jingwen Chenbf61afb2021-05-06 13:31:18 +000028)
29
Liz Kammer82ad8cc2021-08-02 10:41:48 -040030type bazelVarExporter interface {
Chris Parsons3b1f83d2021-10-14 14:08:38 -040031 asBazel(android.Config, exportedStringVariables, exportedStringListVariables, exportedConfigDependingVariables) []bazelConstant
Liz Kammer82ad8cc2021-08-02 10:41:48 -040032}
33
Jingwen Chenbf61afb2021-05-06 13:31:18 +000034// Helpers for exporting cc configuration information to Bazel.
Jingwen Chenbf61afb2021-05-06 13:31:18 +000035var (
Chris Parsons3b1f83d2021-10-14 14:08:38 -040036 // Maps containing toolchain variables that are independent of the
Jingwen Chenbf61afb2021-05-06 13:31:18 +000037 // environment variables of the build.
Liz Kammer82ad8cc2021-08-02 10:41:48 -040038 exportedStringListVars = exportedStringListVariables{}
39 exportedStringVars = exportedStringVariables{}
40 exportedStringListDictVars = exportedStringListDictVariables{}
Chris Parsons3b1f83d2021-10-14 14:08:38 -040041
42 /// Maps containing variables that are dependent on the build config.
43 exportedConfigDependingVars = exportedConfigDependingVariables{}
Jingwen Chenbf61afb2021-05-06 13:31:18 +000044)
45
Chris Parsons3b1f83d2021-10-14 14:08:38 -040046type exportedConfigDependingVariables map[string]interface{}
47
48func (m exportedConfigDependingVariables) Set(k string, v interface{}) {
49 m[k] = v
50}
51
Liz Kammer82ad8cc2021-08-02 10:41:48 -040052// Ensure that string s has no invalid characters to be generated into the bzl file.
53func validateCharacters(s string) string {
54 for _, c := range []string{`\n`, `"`, `\`} {
55 if strings.Contains(s, c) {
56 panic(fmt.Errorf("%s contains illegal character %s", s, c))
57 }
58 }
59 return s
60}
61
62type bazelConstant struct {
63 variableName string
64 internalDefinition string
65}
66
Jingwen Chen51a1e1c2021-05-20 13:40:14 +000067type exportedStringVariables map[string]string
Jingwen Chenbf61afb2021-05-06 13:31:18 +000068
Jingwen Chen51a1e1c2021-05-20 13:40:14 +000069func (m exportedStringVariables) Set(k string, v string) {
Jingwen Chenbf61afb2021-05-06 13:31:18 +000070 m[k] = v
71}
72
Chris Parsons3b1f83d2021-10-14 14:08:38 -040073func (m exportedStringVariables) asBazel(config android.Config,
74 stringVars exportedStringVariables, stringListVars exportedStringListVariables, cfgDepVars exportedConfigDependingVariables) []bazelConstant {
Liz Kammer82ad8cc2021-08-02 10:41:48 -040075 ret := make([]bazelConstant, 0, len(m))
76 for k, variableValue := range m {
Chris Parsons3b1f83d2021-10-14 14:08:38 -040077 expandedVar, err := expandVar(config, variableValue, stringVars, stringListVars, cfgDepVars)
78 if err != nil {
79 panic(fmt.Errorf("error expanding config variable %s: %s", k, err))
80 }
Liz Kammer82ad8cc2021-08-02 10:41:48 -040081 if len(expandedVar) > 1 {
82 panic(fmt.Errorf("%s expands to more than one string value: %s", variableValue, expandedVar))
83 }
84 ret = append(ret, bazelConstant{
85 variableName: k,
86 internalDefinition: fmt.Sprintf(`"%s"`, validateCharacters(expandedVar[0])),
87 })
88 }
89 return ret
90}
91
Jingwen Chenbf61afb2021-05-06 13:31:18 +000092// Convenience function to declare a static variable and export it to Bazel's cc_toolchain.
Jingwen Chen51a1e1c2021-05-20 13:40:14 +000093func exportStringStaticVariable(name string, value string) {
94 pctx.StaticVariable(name, value)
95 exportedStringVars.Set(name, value)
96}
97
Liz Kammer82ad8cc2021-08-02 10:41:48 -040098type exportedStringListVariables map[string][]string
99
Jingwen Chen51a1e1c2021-05-20 13:40:14 +0000100func (m exportedStringListVariables) Set(k string, v []string) {
101 m[k] = v
102}
103
Chris Parsons3b1f83d2021-10-14 14:08:38 -0400104func (m exportedStringListVariables) asBazel(config android.Config,
105 stringScope exportedStringVariables, stringListScope exportedStringListVariables,
106 exportedVars exportedConfigDependingVariables) []bazelConstant {
Liz Kammer82ad8cc2021-08-02 10:41:48 -0400107 ret := make([]bazelConstant, 0, len(m))
108 // For each exported variable, recursively expand elements in the variableValue
109 // list to ensure that interpolated variables are expanded according to their values
110 // in the variable scope.
111 for k, variableValue := range m {
112 var expandedVars []string
113 for _, v := range variableValue {
Chris Parsons3b1f83d2021-10-14 14:08:38 -0400114 expandedVar, err := expandVar(config, v, stringScope, stringListScope, exportedVars)
115 if err != nil {
116 panic(fmt.Errorf("Error expanding config variable %s=%s: %s", k, v, err))
117 }
118 expandedVars = append(expandedVars, expandedVar...)
Liz Kammer82ad8cc2021-08-02 10:41:48 -0400119 }
120 // Assign the list as a bzl-private variable; this variable will be exported
121 // out through a constants struct later.
122 ret = append(ret, bazelConstant{
123 variableName: k,
Liz Kammer72beb342022-02-03 08:42:10 -0500124 internalDefinition: starlark_fmt.PrintStringList(expandedVars, 0),
Liz Kammer82ad8cc2021-08-02 10:41:48 -0400125 })
126 }
127 return ret
128}
129
Chris Parsons3b1f83d2021-10-14 14:08:38 -0400130// Convenience function to declare a static "source path" variable and export it to Bazel's cc_toolchain.
131func exportVariableConfigMethod(name string, method interface{}) blueprint.Variable {
132 exportedConfigDependingVars.Set(name, method)
133 return pctx.VariableConfigMethod(name, method)
134}
135
136// Convenience function to declare a static "source path" variable and export it to Bazel's cc_toolchain.
137func exportSourcePathVariable(name string, value string) {
138 pctx.SourcePathVariable(name, value)
139 exportedStringVars.Set(name, value)
140}
141
Jingwen Chen51a1e1c2021-05-20 13:40:14 +0000142// Convenience function to declare a static variable and export it to Bazel's cc_toolchain.
143func exportStringListStaticVariable(name string, value []string) {
Jingwen Chenbf61afb2021-05-06 13:31:18 +0000144 pctx.StaticVariable(name, strings.Join(value, " "))
Jingwen Chen51a1e1c2021-05-20 13:40:14 +0000145 exportedStringListVars.Set(name, value)
Jingwen Chenbf61afb2021-05-06 13:31:18 +0000146}
147
Jingwen Chen341f7352022-01-11 05:42:49 +0000148func ExportStringList(name string, value []string) {
149 exportedStringListVars.Set(name, value)
150}
151
Liz Kammer82ad8cc2021-08-02 10:41:48 -0400152type exportedStringListDictVariables map[string]map[string][]string
153
154func (m exportedStringListDictVariables) Set(k string, v map[string][]string) {
155 m[k] = v
156}
157
Liz Kammer82ad8cc2021-08-02 10:41:48 -0400158// Since dictionaries are not supported in Ninja, we do not expand variables for dictionaries
Chris Parsons3b1f83d2021-10-14 14:08:38 -0400159func (m exportedStringListDictVariables) asBazel(_ android.Config, _ exportedStringVariables,
160 _ exportedStringListVariables, _ exportedConfigDependingVariables) []bazelConstant {
Liz Kammer82ad8cc2021-08-02 10:41:48 -0400161 ret := make([]bazelConstant, 0, len(m))
162 for k, dict := range m {
163 ret = append(ret, bazelConstant{
164 variableName: k,
Liz Kammer72beb342022-02-03 08:42:10 -0500165 internalDefinition: starlark_fmt.PrintStringListDict(dict, 0),
Liz Kammer82ad8cc2021-08-02 10:41:48 -0400166 })
167 }
168 return ret
169}
170
Jingwen Chenbf61afb2021-05-06 13:31:18 +0000171// BazelCcToolchainVars generates bzl file content containing variables for
172// Bazel's cc_toolchain configuration.
Chris Parsons3b1f83d2021-10-14 14:08:38 -0400173func BazelCcToolchainVars(config android.Config) string {
Liz Kammer82ad8cc2021-08-02 10:41:48 -0400174 return bazelToolchainVars(
Chris Parsons3b1f83d2021-10-14 14:08:38 -0400175 config,
Liz Kammer82ad8cc2021-08-02 10:41:48 -0400176 exportedStringListDictVars,
177 exportedStringListVars,
178 exportedStringVars)
179}
180
Chris Parsons3b1f83d2021-10-14 14:08:38 -0400181func bazelToolchainVars(config android.Config, vars ...bazelVarExporter) string {
Jingwen Chenbf61afb2021-05-06 13:31:18 +0000182 ret := "# GENERATED FOR BAZEL FROM SOONG. DO NOT EDIT.\n\n"
183
Liz Kammer82ad8cc2021-08-02 10:41:48 -0400184 results := []bazelConstant{}
185 for _, v := range vars {
Chris Parsons3b1f83d2021-10-14 14:08:38 -0400186 results = append(results, v.asBazel(config, exportedStringVars, exportedStringListVars, exportedConfigDependingVars)...)
Jingwen Chenbf61afb2021-05-06 13:31:18 +0000187 }
188
Liz Kammer82ad8cc2021-08-02 10:41:48 -0400189 sort.Slice(results, func(i, j int) bool { return results[i].variableName < results[j].variableName })
Jingwen Chenbf61afb2021-05-06 13:31:18 +0000190
Liz Kammer82ad8cc2021-08-02 10:41:48 -0400191 definitions := make([]string, 0, len(results))
192 constants := make([]string, 0, len(results))
193 for _, b := range results {
194 definitions = append(definitions,
195 fmt.Sprintf("_%s = %s", b.variableName, b.internalDefinition))
196 constants = append(constants,
Liz Kammer72beb342022-02-03 08:42:10 -0500197 fmt.Sprintf("%[1]s%[2]s = _%[2]s,", starlark_fmt.Indention(1), b.variableName))
Jingwen Chen51a1e1c2021-05-20 13:40:14 +0000198 }
199
Jingwen Chenbf61afb2021-05-06 13:31:18 +0000200 // Build the exported constants struct.
Liz Kammer82ad8cc2021-08-02 10:41:48 -0400201 ret += strings.Join(definitions, "\n\n")
202 ret += "\n\n"
Jingwen Chenbf61afb2021-05-06 13:31:18 +0000203 ret += "constants = struct(\n"
Liz Kammer82ad8cc2021-08-02 10:41:48 -0400204 ret += strings.Join(constants, "\n")
205 ret += "\n)"
206
Jingwen Chenbf61afb2021-05-06 13:31:18 +0000207 return ret
208}
209
210// expandVar recursively expand interpolated variables in the exportedVars scope.
211//
212// We're using a string slice to track the seen variables to avoid
213// stackoverflow errors with infinite recursion. it's simpler to use a
214// string slice than to handle a pass-by-referenced map, which would make it
215// quite complex to track depth-first interpolations. It's also unlikely the
216// interpolation stacks are deep (n > 1).
Chris Parsons3b1f83d2021-10-14 14:08:38 -0400217func expandVar(config android.Config, toExpand string, stringScope exportedStringVariables,
218 stringListScope exportedStringListVariables, exportedVars exportedConfigDependingVariables) ([]string, error) {
Colin Cross0523ba22021-07-14 18:45:05 -0700219 // e.g. "${ExternalCflags}"
Jingwen Chenbf61afb2021-05-06 13:31:18 +0000220 r := regexp.MustCompile(`\${([a-zA-Z0-9_]+)}`)
221
222 // Internal recursive function.
Chris Parsons3b1f83d2021-10-14 14:08:38 -0400223 var expandVarInternal func(string, map[string]bool) (string, error)
224 expandVarInternal = func(toExpand string, seenVars map[string]bool) (string, error) {
225 var ret string
226 remainingString := toExpand
227 for len(remainingString) > 0 {
228 matches := r.FindStringSubmatch(remainingString)
Jingwen Chenbf61afb2021-05-06 13:31:18 +0000229 if len(matches) == 0 {
Chris Parsons3b1f83d2021-10-14 14:08:38 -0400230 return ret + remainingString, nil
Jingwen Chenbf61afb2021-05-06 13:31:18 +0000231 }
Jingwen Chenbf61afb2021-05-06 13:31:18 +0000232 if len(matches) != 2 {
Chris Parsons3b1f83d2021-10-14 14:08:38 -0400233 panic(fmt.Errorf("Expected to only match 1 subexpression in %s, got %d", remainingString, len(matches)-1))
Jingwen Chenbf61afb2021-05-06 13:31:18 +0000234 }
Chris Parsons3b1f83d2021-10-14 14:08:38 -0400235 matchIndex := strings.Index(remainingString, matches[0])
236 ret += remainingString[:matchIndex]
237 remainingString = remainingString[matchIndex+len(matches[0]):]
Jingwen Chenbf61afb2021-05-06 13:31:18 +0000238
239 // Index 1 of FindStringSubmatch contains the subexpression match
240 // (variable name) of the capture group.
241 variable := matches[1]
242 // toExpand contains a variable.
243 if _, ok := seenVars[variable]; ok {
Chris Parsons3b1f83d2021-10-14 14:08:38 -0400244 return ret, fmt.Errorf(
245 "Unbounded recursive interpolation of variable: %s", variable)
Jingwen Chenbf61afb2021-05-06 13:31:18 +0000246 }
247 // A map is passed-by-reference. Create a new map for
248 // this scope to prevent variables seen in one depth-first expansion
249 // to be also treated as "seen" in other depth-first traversals.
250 newSeenVars := map[string]bool{}
251 for k := range seenVars {
252 newSeenVars[k] = true
253 }
254 newSeenVars[variable] = true
Jingwen Chen51a1e1c2021-05-20 13:40:14 +0000255 if unexpandedVars, ok := stringListScope[variable]; ok {
Chris Parsons3b1f83d2021-10-14 14:08:38 -0400256 expandedVars := []string{}
Jingwen Chen51a1e1c2021-05-20 13:40:14 +0000257 for _, unexpandedVar := range unexpandedVars {
Chris Parsons3b1f83d2021-10-14 14:08:38 -0400258 expandedVar, err := expandVarInternal(unexpandedVar, newSeenVars)
259 if err != nil {
260 return ret, err
261 }
262 expandedVars = append(expandedVars, expandedVar)
Jingwen Chen51a1e1c2021-05-20 13:40:14 +0000263 }
Chris Parsons3b1f83d2021-10-14 14:08:38 -0400264 ret += strings.Join(expandedVars, " ")
Jingwen Chen51a1e1c2021-05-20 13:40:14 +0000265 } else if unexpandedVar, ok := stringScope[variable]; ok {
Chris Parsons3b1f83d2021-10-14 14:08:38 -0400266 expandedVar, err := expandVarInternal(unexpandedVar, newSeenVars)
267 if err != nil {
268 return ret, err
269 }
270 ret += expandedVar
271 } else if unevaluatedVar, ok := exportedVars[variable]; ok {
272 evalFunc := reflect.ValueOf(unevaluatedVar)
273 validateVariableMethod(variable, evalFunc)
274 evaluatedResult := evalFunc.Call([]reflect.Value{reflect.ValueOf(config)})
275 evaluatedValue := evaluatedResult[0].Interface().(string)
276 expandedVar, err := expandVarInternal(evaluatedValue, newSeenVars)
277 if err != nil {
278 return ret, err
279 }
280 ret += expandedVar
281 } else {
282 return "", fmt.Errorf("Unbound config variable %s", variable)
Jingwen Chenbf61afb2021-05-06 13:31:18 +0000283 }
284 }
Chris Parsons3b1f83d2021-10-14 14:08:38 -0400285 return ret, nil
286 }
287 var ret []string
288 for _, v := range strings.Split(toExpand, " ") {
289 val, err := expandVarInternal(v, map[string]bool{})
290 if err != nil {
291 return ret, err
292 }
293 ret = append(ret, val)
Jingwen Chenbf61afb2021-05-06 13:31:18 +0000294 }
295
Chris Parsons3b1f83d2021-10-14 14:08:38 -0400296 return ret, nil
297}
298
299func validateVariableMethod(name string, methodValue reflect.Value) {
300 methodType := methodValue.Type()
301 if methodType.Kind() != reflect.Func {
302 panic(fmt.Errorf("method given for variable %s is not a function",
303 name))
304 }
305 if n := methodType.NumIn(); n != 1 {
306 panic(fmt.Errorf("method for variable %s has %d inputs (should be 1)",
307 name, n))
308 }
309 if n := methodType.NumOut(); n != 1 {
310 panic(fmt.Errorf("method for variable %s has %d outputs (should be 1)",
311 name, n))
312 }
313 if kind := methodType.Out(0).Kind(); kind != reflect.String {
314 panic(fmt.Errorf("method for variable %s does not return a string",
315 name))
316 }
Jingwen Chenbf61afb2021-05-06 13:31:18 +0000317}