blob: d19f5ac8e2674655ff0417f1e6444217e81eb292 [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"
19 "regexp"
Liz Kammer82ad8cc2021-08-02 10:41:48 -040020 "sort"
Jingwen Chenbf61afb2021-05-06 13:31:18 +000021 "strings"
22)
23
Liz Kammer82ad8cc2021-08-02 10:41:48 -040024const (
25 bazelIndent = 4
26)
27
28type bazelVarExporter interface {
29 asBazel(exportedStringVariables, exportedStringListVariables) []bazelConstant
30}
31
Jingwen Chenbf61afb2021-05-06 13:31:18 +000032// Helpers for exporting cc configuration information to Bazel.
Jingwen Chenbf61afb2021-05-06 13:31:18 +000033var (
34 // Map containing toolchain variables that are independent of the
35 // environment variables of the build.
Liz Kammer82ad8cc2021-08-02 10:41:48 -040036 exportedStringListVars = exportedStringListVariables{}
37 exportedStringVars = exportedStringVariables{}
38 exportedStringListDictVars = exportedStringListDictVariables{}
Jingwen Chenbf61afb2021-05-06 13:31:18 +000039)
40
Liz Kammer82ad8cc2021-08-02 10:41:48 -040041// Ensure that string s has no invalid characters to be generated into the bzl file.
42func validateCharacters(s string) string {
43 for _, c := range []string{`\n`, `"`, `\`} {
44 if strings.Contains(s, c) {
45 panic(fmt.Errorf("%s contains illegal character %s", s, c))
46 }
47 }
48 return s
49}
50
51type bazelConstant struct {
52 variableName string
53 internalDefinition string
54}
55
Jingwen Chen51a1e1c2021-05-20 13:40:14 +000056type exportedStringVariables map[string]string
Jingwen Chenbf61afb2021-05-06 13:31:18 +000057
Jingwen Chen51a1e1c2021-05-20 13:40:14 +000058func (m exportedStringVariables) Set(k string, v string) {
Jingwen Chenbf61afb2021-05-06 13:31:18 +000059 m[k] = v
60}
61
Liz Kammer82ad8cc2021-08-02 10:41:48 -040062func bazelIndention(level int) string {
63 return strings.Repeat(" ", level*bazelIndent)
64}
65
66func printBazelList(items []string, indentLevel int) string {
67 list := make([]string, 0, len(items)+2)
68 list = append(list, "[")
69 innerIndent := bazelIndention(indentLevel + 1)
70 for _, item := range items {
71 list = append(list, fmt.Sprintf(`%s"%s",`, innerIndent, item))
72 }
73 list = append(list, bazelIndention(indentLevel)+"]")
74 return strings.Join(list, "\n")
75}
76
77func (m exportedStringVariables) asBazel(stringScope exportedStringVariables, stringListScope exportedStringListVariables) []bazelConstant {
78 ret := make([]bazelConstant, 0, len(m))
79 for k, variableValue := range m {
80 expandedVar := expandVar(variableValue, exportedStringVars, exportedStringListVars)
81 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
Liz Kammer82ad8cc2021-08-02 10:41:48 -0400104func (m exportedStringListVariables) asBazel(stringScope exportedStringVariables, stringListScope exportedStringListVariables) []bazelConstant {
105 ret := make([]bazelConstant, 0, len(m))
106 // For each exported variable, recursively expand elements in the variableValue
107 // list to ensure that interpolated variables are expanded according to their values
108 // in the variable scope.
109 for k, variableValue := range m {
110 var expandedVars []string
111 for _, v := range variableValue {
112 expandedVars = append(expandedVars, expandVar(v, stringScope, stringListScope)...)
113 }
114 // Assign the list as a bzl-private variable; this variable will be exported
115 // out through a constants struct later.
116 ret = append(ret, bazelConstant{
117 variableName: k,
118 internalDefinition: printBazelList(expandedVars, 0),
119 })
120 }
121 return ret
122}
123
Jingwen Chen51a1e1c2021-05-20 13:40:14 +0000124// Convenience function to declare a static variable and export it to Bazel's cc_toolchain.
125func exportStringListStaticVariable(name string, value []string) {
Jingwen Chenbf61afb2021-05-06 13:31:18 +0000126 pctx.StaticVariable(name, strings.Join(value, " "))
Jingwen Chen51a1e1c2021-05-20 13:40:14 +0000127 exportedStringListVars.Set(name, value)
Jingwen Chenbf61afb2021-05-06 13:31:18 +0000128}
129
Liz Kammer82ad8cc2021-08-02 10:41:48 -0400130type exportedStringListDictVariables map[string]map[string][]string
131
132func (m exportedStringListDictVariables) Set(k string, v map[string][]string) {
133 m[k] = v
134}
135
136func printBazelStringListDict(dict map[string][]string) string {
137 bazelDict := make([]string, 0, len(dict)+2)
138 bazelDict = append(bazelDict, "{")
139 for k, v := range dict {
140 bazelDict = append(bazelDict,
141 fmt.Sprintf(`%s"%s": %s,`, bazelIndention(1), k, printBazelList(v, 1)))
142 }
143 bazelDict = append(bazelDict, "}")
144 return strings.Join(bazelDict, "\n")
145}
146
147// Since dictionaries are not supported in Ninja, we do not expand variables for dictionaries
148func (m exportedStringListDictVariables) asBazel(_ exportedStringVariables, _ exportedStringListVariables) []bazelConstant {
149 ret := make([]bazelConstant, 0, len(m))
150 for k, dict := range m {
151 ret = append(ret, bazelConstant{
152 variableName: k,
153 internalDefinition: printBazelStringListDict(dict),
154 })
155 }
156 return ret
157}
158
Jingwen Chenbf61afb2021-05-06 13:31:18 +0000159// BazelCcToolchainVars generates bzl file content containing variables for
160// Bazel's cc_toolchain configuration.
161func BazelCcToolchainVars() string {
Liz Kammer82ad8cc2021-08-02 10:41:48 -0400162 return bazelToolchainVars(
163 exportedStringListDictVars,
164 exportedStringListVars,
165 exportedStringVars)
166}
167
168func bazelToolchainVars(vars ...bazelVarExporter) string {
Jingwen Chenbf61afb2021-05-06 13:31:18 +0000169 ret := "# GENERATED FOR BAZEL FROM SOONG. DO NOT EDIT.\n\n"
170
Liz Kammer82ad8cc2021-08-02 10:41:48 -0400171 results := []bazelConstant{}
172 for _, v := range vars {
173 results = append(results, v.asBazel(exportedStringVars, exportedStringListVars)...)
Jingwen Chenbf61afb2021-05-06 13:31:18 +0000174 }
175
Liz Kammer82ad8cc2021-08-02 10:41:48 -0400176 sort.Slice(results, func(i, j int) bool { return results[i].variableName < results[j].variableName })
Jingwen Chenbf61afb2021-05-06 13:31:18 +0000177
Liz Kammer82ad8cc2021-08-02 10:41:48 -0400178 definitions := make([]string, 0, len(results))
179 constants := make([]string, 0, len(results))
180 for _, b := range results {
181 definitions = append(definitions,
182 fmt.Sprintf("_%s = %s", b.variableName, b.internalDefinition))
183 constants = append(constants,
184 fmt.Sprintf("%[1]s%[2]s = _%[2]s,", bazelIndention(1), b.variableName))
Jingwen Chen51a1e1c2021-05-20 13:40:14 +0000185 }
186
Jingwen Chenbf61afb2021-05-06 13:31:18 +0000187 // Build the exported constants struct.
Liz Kammer82ad8cc2021-08-02 10:41:48 -0400188 ret += strings.Join(definitions, "\n\n")
189 ret += "\n\n"
Jingwen Chenbf61afb2021-05-06 13:31:18 +0000190 ret += "constants = struct(\n"
Liz Kammer82ad8cc2021-08-02 10:41:48 -0400191 ret += strings.Join(constants, "\n")
192 ret += "\n)"
193
Jingwen Chenbf61afb2021-05-06 13:31:18 +0000194 return ret
195}
196
197// expandVar recursively expand interpolated variables in the exportedVars scope.
198//
199// We're using a string slice to track the seen variables to avoid
200// stackoverflow errors with infinite recursion. it's simpler to use a
201// string slice than to handle a pass-by-referenced map, which would make it
202// quite complex to track depth-first interpolations. It's also unlikely the
203// interpolation stacks are deep (n > 1).
Jingwen Chen51a1e1c2021-05-20 13:40:14 +0000204func expandVar(toExpand string, stringScope exportedStringVariables, stringListScope exportedStringListVariables) []string {
Colin Cross0523ba22021-07-14 18:45:05 -0700205 // e.g. "${ExternalCflags}"
Jingwen Chenbf61afb2021-05-06 13:31:18 +0000206 r := regexp.MustCompile(`\${([a-zA-Z0-9_]+)}`)
207
208 // Internal recursive function.
209 var expandVarInternal func(string, map[string]bool) []string
210 expandVarInternal = func(toExpand string, seenVars map[string]bool) []string {
211 var ret []string
212 for _, v := range strings.Split(toExpand, " ") {
213 matches := r.FindStringSubmatch(v)
214 if len(matches) == 0 {
215 return []string{v}
216 }
217
218 if len(matches) != 2 {
219 panic(fmt.Errorf(
220 "Expected to only match 1 subexpression in %s, got %d",
221 v,
222 len(matches)-1))
223 }
224
225 // Index 1 of FindStringSubmatch contains the subexpression match
226 // (variable name) of the capture group.
227 variable := matches[1]
228 // toExpand contains a variable.
229 if _, ok := seenVars[variable]; ok {
230 panic(fmt.Errorf(
231 "Unbounded recursive interpolation of variable: %s", variable))
232 }
233 // A map is passed-by-reference. Create a new map for
234 // this scope to prevent variables seen in one depth-first expansion
235 // to be also treated as "seen" in other depth-first traversals.
236 newSeenVars := map[string]bool{}
237 for k := range seenVars {
238 newSeenVars[k] = true
239 }
240 newSeenVars[variable] = true
Jingwen Chen51a1e1c2021-05-20 13:40:14 +0000241 if unexpandedVars, ok := stringListScope[variable]; ok {
242 for _, unexpandedVar := range unexpandedVars {
243 ret = append(ret, expandVarInternal(unexpandedVar, newSeenVars)...)
244 }
245 } else if unexpandedVar, ok := stringScope[variable]; ok {
Jingwen Chenbf61afb2021-05-06 13:31:18 +0000246 ret = append(ret, expandVarInternal(unexpandedVar, newSeenVars)...)
247 }
248 }
249 return ret
250 }
251
252 return expandVarInternal(toExpand, map[string]bool{})
253}