Jingwen Chen | bf61afb | 2021-05-06 13:31:18 +0000 | [diff] [blame] | 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 | |
| 15 | package config |
| 16 | |
| 17 | import ( |
Jingwen Chen | bf61afb | 2021-05-06 13:31:18 +0000 | [diff] [blame] | 18 | "fmt" |
Chris Parsons | 3b1f83d | 2021-10-14 14:08:38 -0400 | [diff] [blame] | 19 | "reflect" |
Jingwen Chen | bf61afb | 2021-05-06 13:31:18 +0000 | [diff] [blame] | 20 | "regexp" |
Liz Kammer | 82ad8cc | 2021-08-02 10:41:48 -0400 | [diff] [blame] | 21 | "sort" |
Jingwen Chen | bf61afb | 2021-05-06 13:31:18 +0000 | [diff] [blame] | 22 | "strings" |
Chris Parsons | 3b1f83d | 2021-10-14 14:08:38 -0400 | [diff] [blame] | 23 | |
| 24 | "android/soong/android" |
Liz Kammer | 72beb34 | 2022-02-03 08:42:10 -0500 | [diff] [blame] | 25 | "android/soong/starlark_fmt" |
Jingwen Chen | 341f735 | 2022-01-11 05:42:49 +0000 | [diff] [blame] | 26 | |
Chris Parsons | 3b1f83d | 2021-10-14 14:08:38 -0400 | [diff] [blame] | 27 | "github.com/google/blueprint" |
Jingwen Chen | bf61afb | 2021-05-06 13:31:18 +0000 | [diff] [blame] | 28 | ) |
| 29 | |
Liz Kammer | 82ad8cc | 2021-08-02 10:41:48 -0400 | [diff] [blame] | 30 | type bazelVarExporter interface { |
Chris Parsons | 3b1f83d | 2021-10-14 14:08:38 -0400 | [diff] [blame] | 31 | asBazel(android.Config, exportedStringVariables, exportedStringListVariables, exportedConfigDependingVariables) []bazelConstant |
Liz Kammer | 82ad8cc | 2021-08-02 10:41:48 -0400 | [diff] [blame] | 32 | } |
| 33 | |
Jingwen Chen | bf61afb | 2021-05-06 13:31:18 +0000 | [diff] [blame] | 34 | // Helpers for exporting cc configuration information to Bazel. |
Jingwen Chen | bf61afb | 2021-05-06 13:31:18 +0000 | [diff] [blame] | 35 | var ( |
Chris Parsons | 3b1f83d | 2021-10-14 14:08:38 -0400 | [diff] [blame] | 36 | // Maps containing toolchain variables that are independent of the |
Jingwen Chen | bf61afb | 2021-05-06 13:31:18 +0000 | [diff] [blame] | 37 | // environment variables of the build. |
Liz Kammer | 82ad8cc | 2021-08-02 10:41:48 -0400 | [diff] [blame] | 38 | exportedStringListVars = exportedStringListVariables{} |
| 39 | exportedStringVars = exportedStringVariables{} |
| 40 | exportedStringListDictVars = exportedStringListDictVariables{} |
Liz Kammer | e8303bd | 2022-02-16 09:02:48 -0500 | [diff] [blame] | 41 | // Note: these can only contain references to other variables and must be printed last |
| 42 | exportedVariableReferenceDictVars = exportedVariableReferenceDictVariables{} |
Chris Parsons | 3b1f83d | 2021-10-14 14:08:38 -0400 | [diff] [blame] | 43 | |
| 44 | /// Maps containing variables that are dependent on the build config. |
| 45 | exportedConfigDependingVars = exportedConfigDependingVariables{} |
Jingwen Chen | bf61afb | 2021-05-06 13:31:18 +0000 | [diff] [blame] | 46 | ) |
| 47 | |
Chris Parsons | 3b1f83d | 2021-10-14 14:08:38 -0400 | [diff] [blame] | 48 | type exportedConfigDependingVariables map[string]interface{} |
| 49 | |
| 50 | func (m exportedConfigDependingVariables) Set(k string, v interface{}) { |
| 51 | m[k] = v |
| 52 | } |
| 53 | |
Liz Kammer | 82ad8cc | 2021-08-02 10:41:48 -0400 | [diff] [blame] | 54 | // Ensure that string s has no invalid characters to be generated into the bzl file. |
| 55 | func 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 | |
| 64 | type bazelConstant struct { |
| 65 | variableName string |
| 66 | internalDefinition string |
Liz Kammer | e8303bd | 2022-02-16 09:02:48 -0500 | [diff] [blame] | 67 | sortLast bool |
Liz Kammer | 82ad8cc | 2021-08-02 10:41:48 -0400 | [diff] [blame] | 68 | } |
| 69 | |
Jingwen Chen | 51a1e1c | 2021-05-20 13:40:14 +0000 | [diff] [blame] | 70 | type exportedStringVariables map[string]string |
Jingwen Chen | bf61afb | 2021-05-06 13:31:18 +0000 | [diff] [blame] | 71 | |
Jingwen Chen | 51a1e1c | 2021-05-20 13:40:14 +0000 | [diff] [blame] | 72 | func (m exportedStringVariables) Set(k string, v string) { |
Jingwen Chen | bf61afb | 2021-05-06 13:31:18 +0000 | [diff] [blame] | 73 | m[k] = v |
| 74 | } |
| 75 | |
Chris Parsons | 3b1f83d | 2021-10-14 14:08:38 -0400 | [diff] [blame] | 76 | func (m exportedStringVariables) asBazel(config android.Config, |
| 77 | stringVars exportedStringVariables, stringListVars exportedStringListVariables, cfgDepVars exportedConfigDependingVariables) []bazelConstant { |
Liz Kammer | 82ad8cc | 2021-08-02 10:41:48 -0400 | [diff] [blame] | 78 | ret := make([]bazelConstant, 0, len(m)) |
| 79 | for k, variableValue := range m { |
Chris Parsons | 3b1f83d | 2021-10-14 14:08:38 -0400 | [diff] [blame] | 80 | expandedVar, err := expandVar(config, variableValue, stringVars, stringListVars, cfgDepVars) |
| 81 | if err != nil { |
| 82 | panic(fmt.Errorf("error expanding config variable %s: %s", k, err)) |
| 83 | } |
Liz Kammer | 82ad8cc | 2021-08-02 10:41:48 -0400 | [diff] [blame] | 84 | if len(expandedVar) > 1 { |
| 85 | panic(fmt.Errorf("%s expands to more than one string value: %s", variableValue, expandedVar)) |
| 86 | } |
| 87 | ret = append(ret, bazelConstant{ |
| 88 | variableName: k, |
| 89 | internalDefinition: fmt.Sprintf(`"%s"`, validateCharacters(expandedVar[0])), |
| 90 | }) |
| 91 | } |
| 92 | return ret |
| 93 | } |
| 94 | |
Jingwen Chen | bf61afb | 2021-05-06 13:31:18 +0000 | [diff] [blame] | 95 | // Convenience function to declare a static variable and export it to Bazel's cc_toolchain. |
Jingwen Chen | 51a1e1c | 2021-05-20 13:40:14 +0000 | [diff] [blame] | 96 | func exportStringStaticVariable(name string, value string) { |
| 97 | pctx.StaticVariable(name, value) |
| 98 | exportedStringVars.Set(name, value) |
| 99 | } |
| 100 | |
Liz Kammer | 82ad8cc | 2021-08-02 10:41:48 -0400 | [diff] [blame] | 101 | type exportedStringListVariables map[string][]string |
| 102 | |
Jingwen Chen | 51a1e1c | 2021-05-20 13:40:14 +0000 | [diff] [blame] | 103 | func (m exportedStringListVariables) Set(k string, v []string) { |
| 104 | m[k] = v |
| 105 | } |
| 106 | |
Chris Parsons | 3b1f83d | 2021-10-14 14:08:38 -0400 | [diff] [blame] | 107 | func (m exportedStringListVariables) asBazel(config android.Config, |
| 108 | stringScope exportedStringVariables, stringListScope exportedStringListVariables, |
| 109 | exportedVars exportedConfigDependingVariables) []bazelConstant { |
Liz Kammer | 82ad8cc | 2021-08-02 10:41:48 -0400 | [diff] [blame] | 110 | ret := make([]bazelConstant, 0, len(m)) |
| 111 | // For each exported variable, recursively expand elements in the variableValue |
| 112 | // list to ensure that interpolated variables are expanded according to their values |
| 113 | // in the variable scope. |
| 114 | for k, variableValue := range m { |
| 115 | var expandedVars []string |
| 116 | for _, v := range variableValue { |
Chris Parsons | 3b1f83d | 2021-10-14 14:08:38 -0400 | [diff] [blame] | 117 | expandedVar, err := expandVar(config, v, stringScope, stringListScope, exportedVars) |
| 118 | if err != nil { |
| 119 | panic(fmt.Errorf("Error expanding config variable %s=%s: %s", k, v, err)) |
| 120 | } |
| 121 | expandedVars = append(expandedVars, expandedVar...) |
Liz Kammer | 82ad8cc | 2021-08-02 10:41:48 -0400 | [diff] [blame] | 122 | } |
| 123 | // Assign the list as a bzl-private variable; this variable will be exported |
| 124 | // out through a constants struct later. |
| 125 | ret = append(ret, bazelConstant{ |
| 126 | variableName: k, |
Liz Kammer | 72beb34 | 2022-02-03 08:42:10 -0500 | [diff] [blame] | 127 | internalDefinition: starlark_fmt.PrintStringList(expandedVars, 0), |
Liz Kammer | 82ad8cc | 2021-08-02 10:41:48 -0400 | [diff] [blame] | 128 | }) |
| 129 | } |
| 130 | return ret |
| 131 | } |
| 132 | |
Chris Parsons | 3b1f83d | 2021-10-14 14:08:38 -0400 | [diff] [blame] | 133 | // Convenience function to declare a static "source path" variable and export it to Bazel's cc_toolchain. |
| 134 | func exportVariableConfigMethod(name string, method interface{}) blueprint.Variable { |
| 135 | exportedConfigDependingVars.Set(name, method) |
| 136 | return pctx.VariableConfigMethod(name, method) |
| 137 | } |
| 138 | |
| 139 | // Convenience function to declare a static "source path" variable and export it to Bazel's cc_toolchain. |
| 140 | func exportSourcePathVariable(name string, value string) { |
| 141 | pctx.SourcePathVariable(name, value) |
| 142 | exportedStringVars.Set(name, value) |
| 143 | } |
| 144 | |
Jingwen Chen | 51a1e1c | 2021-05-20 13:40:14 +0000 | [diff] [blame] | 145 | // Convenience function to declare a static variable and export it to Bazel's cc_toolchain. |
| 146 | func exportStringListStaticVariable(name string, value []string) { |
Jingwen Chen | bf61afb | 2021-05-06 13:31:18 +0000 | [diff] [blame] | 147 | pctx.StaticVariable(name, strings.Join(value, " ")) |
Jingwen Chen | 51a1e1c | 2021-05-20 13:40:14 +0000 | [diff] [blame] | 148 | exportedStringListVars.Set(name, value) |
Jingwen Chen | bf61afb | 2021-05-06 13:31:18 +0000 | [diff] [blame] | 149 | } |
| 150 | |
Jingwen Chen | 341f735 | 2022-01-11 05:42:49 +0000 | [diff] [blame] | 151 | func ExportStringList(name string, value []string) { |
| 152 | exportedStringListVars.Set(name, value) |
| 153 | } |
| 154 | |
Liz Kammer | 82ad8cc | 2021-08-02 10:41:48 -0400 | [diff] [blame] | 155 | type exportedStringListDictVariables map[string]map[string][]string |
| 156 | |
| 157 | func (m exportedStringListDictVariables) Set(k string, v map[string][]string) { |
| 158 | m[k] = v |
| 159 | } |
| 160 | |
Liz Kammer | 82ad8cc | 2021-08-02 10:41:48 -0400 | [diff] [blame] | 161 | // Since dictionaries are not supported in Ninja, we do not expand variables for dictionaries |
Chris Parsons | 3b1f83d | 2021-10-14 14:08:38 -0400 | [diff] [blame] | 162 | func (m exportedStringListDictVariables) asBazel(_ android.Config, _ exportedStringVariables, |
| 163 | _ exportedStringListVariables, _ exportedConfigDependingVariables) []bazelConstant { |
Liz Kammer | 82ad8cc | 2021-08-02 10:41:48 -0400 | [diff] [blame] | 164 | ret := make([]bazelConstant, 0, len(m)) |
| 165 | for k, dict := range m { |
| 166 | ret = append(ret, bazelConstant{ |
| 167 | variableName: k, |
Liz Kammer | 72beb34 | 2022-02-03 08:42:10 -0500 | [diff] [blame] | 168 | internalDefinition: starlark_fmt.PrintStringListDict(dict, 0), |
Liz Kammer | 82ad8cc | 2021-08-02 10:41:48 -0400 | [diff] [blame] | 169 | }) |
| 170 | } |
| 171 | return ret |
| 172 | } |
| 173 | |
Liz Kammer | e8303bd | 2022-02-16 09:02:48 -0500 | [diff] [blame] | 174 | type exportedVariableReferenceDictVariables map[string]map[string]string |
| 175 | |
| 176 | func (m exportedVariableReferenceDictVariables) Set(k string, v map[string]string) { |
| 177 | m[k] = v |
| 178 | } |
| 179 | |
| 180 | func (m exportedVariableReferenceDictVariables) asBazel(_ android.Config, _ exportedStringVariables, |
| 181 | _ exportedStringListVariables, _ exportedConfigDependingVariables) []bazelConstant { |
| 182 | ret := make([]bazelConstant, 0, len(m)) |
| 183 | for n, dict := range m { |
| 184 | for k, v := range dict { |
| 185 | matches, err := variableReference(v) |
| 186 | if err != nil { |
| 187 | panic(err) |
| 188 | } else if !matches.matches { |
| 189 | panic(fmt.Errorf("Expected a variable reference, got %q", v)) |
| 190 | } else if len(matches.fullVariableReference) != len(v) { |
| 191 | panic(fmt.Errorf("Expected only a variable reference, got %q", v)) |
| 192 | } |
| 193 | dict[k] = "_" + matches.variable |
| 194 | } |
| 195 | ret = append(ret, bazelConstant{ |
| 196 | variableName: n, |
| 197 | internalDefinition: starlark_fmt.PrintDict(dict, 0), |
| 198 | sortLast: true, |
| 199 | }) |
| 200 | } |
| 201 | return ret |
| 202 | } |
| 203 | |
Jingwen Chen | bf61afb | 2021-05-06 13:31:18 +0000 | [diff] [blame] | 204 | // BazelCcToolchainVars generates bzl file content containing variables for |
| 205 | // Bazel's cc_toolchain configuration. |
Chris Parsons | 3b1f83d | 2021-10-14 14:08:38 -0400 | [diff] [blame] | 206 | func BazelCcToolchainVars(config android.Config) string { |
Liz Kammer | 82ad8cc | 2021-08-02 10:41:48 -0400 | [diff] [blame] | 207 | return bazelToolchainVars( |
Chris Parsons | 3b1f83d | 2021-10-14 14:08:38 -0400 | [diff] [blame] | 208 | config, |
Liz Kammer | 82ad8cc | 2021-08-02 10:41:48 -0400 | [diff] [blame] | 209 | exportedStringListDictVars, |
| 210 | exportedStringListVars, |
Liz Kammer | e8303bd | 2022-02-16 09:02:48 -0500 | [diff] [blame] | 211 | exportedStringVars, |
| 212 | exportedVariableReferenceDictVars) |
Liz Kammer | 82ad8cc | 2021-08-02 10:41:48 -0400 | [diff] [blame] | 213 | } |
| 214 | |
Chris Parsons | 3b1f83d | 2021-10-14 14:08:38 -0400 | [diff] [blame] | 215 | func bazelToolchainVars(config android.Config, vars ...bazelVarExporter) string { |
Jingwen Chen | bf61afb | 2021-05-06 13:31:18 +0000 | [diff] [blame] | 216 | ret := "# GENERATED FOR BAZEL FROM SOONG. DO NOT EDIT.\n\n" |
| 217 | |
Liz Kammer | 82ad8cc | 2021-08-02 10:41:48 -0400 | [diff] [blame] | 218 | results := []bazelConstant{} |
| 219 | for _, v := range vars { |
Chris Parsons | 3b1f83d | 2021-10-14 14:08:38 -0400 | [diff] [blame] | 220 | results = append(results, v.asBazel(config, exportedStringVars, exportedStringListVars, exportedConfigDependingVars)...) |
Jingwen Chen | bf61afb | 2021-05-06 13:31:18 +0000 | [diff] [blame] | 221 | } |
| 222 | |
Liz Kammer | e8303bd | 2022-02-16 09:02:48 -0500 | [diff] [blame] | 223 | sort.Slice(results, func(i, j int) bool { |
| 224 | if results[i].sortLast != results[j].sortLast { |
| 225 | return !results[i].sortLast |
| 226 | } |
| 227 | return results[i].variableName < results[j].variableName |
| 228 | }) |
Jingwen Chen | bf61afb | 2021-05-06 13:31:18 +0000 | [diff] [blame] | 229 | |
Liz Kammer | 82ad8cc | 2021-08-02 10:41:48 -0400 | [diff] [blame] | 230 | definitions := make([]string, 0, len(results)) |
| 231 | constants := make([]string, 0, len(results)) |
| 232 | for _, b := range results { |
| 233 | definitions = append(definitions, |
| 234 | fmt.Sprintf("_%s = %s", b.variableName, b.internalDefinition)) |
| 235 | constants = append(constants, |
Liz Kammer | 72beb34 | 2022-02-03 08:42:10 -0500 | [diff] [blame] | 236 | fmt.Sprintf("%[1]s%[2]s = _%[2]s,", starlark_fmt.Indention(1), b.variableName)) |
Jingwen Chen | 51a1e1c | 2021-05-20 13:40:14 +0000 | [diff] [blame] | 237 | } |
| 238 | |
Jingwen Chen | bf61afb | 2021-05-06 13:31:18 +0000 | [diff] [blame] | 239 | // Build the exported constants struct. |
Liz Kammer | 82ad8cc | 2021-08-02 10:41:48 -0400 | [diff] [blame] | 240 | ret += strings.Join(definitions, "\n\n") |
| 241 | ret += "\n\n" |
Jingwen Chen | bf61afb | 2021-05-06 13:31:18 +0000 | [diff] [blame] | 242 | ret += "constants = struct(\n" |
Liz Kammer | 82ad8cc | 2021-08-02 10:41:48 -0400 | [diff] [blame] | 243 | ret += strings.Join(constants, "\n") |
| 244 | ret += "\n)" |
| 245 | |
Jingwen Chen | bf61afb | 2021-05-06 13:31:18 +0000 | [diff] [blame] | 246 | return ret |
| 247 | } |
| 248 | |
Liz Kammer | e8303bd | 2022-02-16 09:02:48 -0500 | [diff] [blame] | 249 | type match struct { |
| 250 | matches bool |
| 251 | fullVariableReference string |
| 252 | variable string |
| 253 | } |
| 254 | |
| 255 | func variableReference(input string) (match, error) { |
| 256 | // e.g. "${ExternalCflags}" |
| 257 | r := regexp.MustCompile(`\${(?:config\.)?([a-zA-Z0-9_]+)}`) |
| 258 | |
| 259 | matches := r.FindStringSubmatch(input) |
| 260 | if len(matches) == 0 { |
| 261 | return match{}, nil |
| 262 | } |
| 263 | if len(matches) != 2 { |
| 264 | return match{}, fmt.Errorf("Expected to only match 1 subexpression in %s, got %d", input, len(matches)-1) |
| 265 | } |
| 266 | return match{ |
| 267 | matches: true, |
| 268 | fullVariableReference: matches[0], |
| 269 | // Index 1 of FindStringSubmatch contains the subexpression match |
| 270 | // (variable name) of the capture group. |
| 271 | variable: matches[1], |
| 272 | }, nil |
| 273 | } |
| 274 | |
Jingwen Chen | bf61afb | 2021-05-06 13:31:18 +0000 | [diff] [blame] | 275 | // expandVar recursively expand interpolated variables in the exportedVars scope. |
| 276 | // |
| 277 | // We're using a string slice to track the seen variables to avoid |
| 278 | // stackoverflow errors with infinite recursion. it's simpler to use a |
| 279 | // string slice than to handle a pass-by-referenced map, which would make it |
| 280 | // quite complex to track depth-first interpolations. It's also unlikely the |
| 281 | // interpolation stacks are deep (n > 1). |
Chris Parsons | 3b1f83d | 2021-10-14 14:08:38 -0400 | [diff] [blame] | 282 | func expandVar(config android.Config, toExpand string, stringScope exportedStringVariables, |
| 283 | stringListScope exportedStringListVariables, exportedVars exportedConfigDependingVariables) ([]string, error) { |
Jingwen Chen | bf61afb | 2021-05-06 13:31:18 +0000 | [diff] [blame] | 284 | |
| 285 | // Internal recursive function. |
Chris Parsons | 3b1f83d | 2021-10-14 14:08:38 -0400 | [diff] [blame] | 286 | var expandVarInternal func(string, map[string]bool) (string, error) |
| 287 | expandVarInternal = func(toExpand string, seenVars map[string]bool) (string, error) { |
| 288 | var ret string |
| 289 | remainingString := toExpand |
| 290 | for len(remainingString) > 0 { |
Liz Kammer | e8303bd | 2022-02-16 09:02:48 -0500 | [diff] [blame] | 291 | matches, err := variableReference(remainingString) |
| 292 | if err != nil { |
| 293 | panic(err) |
| 294 | } |
| 295 | if !matches.matches { |
Chris Parsons | 3b1f83d | 2021-10-14 14:08:38 -0400 | [diff] [blame] | 296 | return ret + remainingString, nil |
Jingwen Chen | bf61afb | 2021-05-06 13:31:18 +0000 | [diff] [blame] | 297 | } |
Liz Kammer | e8303bd | 2022-02-16 09:02:48 -0500 | [diff] [blame] | 298 | matchIndex := strings.Index(remainingString, matches.fullVariableReference) |
Chris Parsons | 3b1f83d | 2021-10-14 14:08:38 -0400 | [diff] [blame] | 299 | ret += remainingString[:matchIndex] |
Liz Kammer | e8303bd | 2022-02-16 09:02:48 -0500 | [diff] [blame] | 300 | remainingString = remainingString[matchIndex+len(matches.fullVariableReference):] |
Jingwen Chen | bf61afb | 2021-05-06 13:31:18 +0000 | [diff] [blame] | 301 | |
Liz Kammer | e8303bd | 2022-02-16 09:02:48 -0500 | [diff] [blame] | 302 | variable := matches.variable |
Jingwen Chen | bf61afb | 2021-05-06 13:31:18 +0000 | [diff] [blame] | 303 | // toExpand contains a variable. |
| 304 | if _, ok := seenVars[variable]; ok { |
Chris Parsons | 3b1f83d | 2021-10-14 14:08:38 -0400 | [diff] [blame] | 305 | return ret, fmt.Errorf( |
| 306 | "Unbounded recursive interpolation of variable: %s", variable) |
Jingwen Chen | bf61afb | 2021-05-06 13:31:18 +0000 | [diff] [blame] | 307 | } |
| 308 | // A map is passed-by-reference. Create a new map for |
| 309 | // this scope to prevent variables seen in one depth-first expansion |
| 310 | // to be also treated as "seen" in other depth-first traversals. |
| 311 | newSeenVars := map[string]bool{} |
| 312 | for k := range seenVars { |
| 313 | newSeenVars[k] = true |
| 314 | } |
| 315 | newSeenVars[variable] = true |
Jingwen Chen | 51a1e1c | 2021-05-20 13:40:14 +0000 | [diff] [blame] | 316 | if unexpandedVars, ok := stringListScope[variable]; ok { |
Chris Parsons | 3b1f83d | 2021-10-14 14:08:38 -0400 | [diff] [blame] | 317 | expandedVars := []string{} |
Jingwen Chen | 51a1e1c | 2021-05-20 13:40:14 +0000 | [diff] [blame] | 318 | for _, unexpandedVar := range unexpandedVars { |
Chris Parsons | 3b1f83d | 2021-10-14 14:08:38 -0400 | [diff] [blame] | 319 | expandedVar, err := expandVarInternal(unexpandedVar, newSeenVars) |
| 320 | if err != nil { |
| 321 | return ret, err |
| 322 | } |
| 323 | expandedVars = append(expandedVars, expandedVar) |
Jingwen Chen | 51a1e1c | 2021-05-20 13:40:14 +0000 | [diff] [blame] | 324 | } |
Chris Parsons | 3b1f83d | 2021-10-14 14:08:38 -0400 | [diff] [blame] | 325 | ret += strings.Join(expandedVars, " ") |
Jingwen Chen | 51a1e1c | 2021-05-20 13:40:14 +0000 | [diff] [blame] | 326 | } else if unexpandedVar, ok := stringScope[variable]; ok { |
Chris Parsons | 3b1f83d | 2021-10-14 14:08:38 -0400 | [diff] [blame] | 327 | expandedVar, err := expandVarInternal(unexpandedVar, newSeenVars) |
| 328 | if err != nil { |
| 329 | return ret, err |
| 330 | } |
| 331 | ret += expandedVar |
| 332 | } else if unevaluatedVar, ok := exportedVars[variable]; ok { |
| 333 | evalFunc := reflect.ValueOf(unevaluatedVar) |
| 334 | validateVariableMethod(variable, evalFunc) |
| 335 | evaluatedResult := evalFunc.Call([]reflect.Value{reflect.ValueOf(config)}) |
| 336 | evaluatedValue := evaluatedResult[0].Interface().(string) |
| 337 | expandedVar, err := expandVarInternal(evaluatedValue, newSeenVars) |
| 338 | if err != nil { |
| 339 | return ret, err |
| 340 | } |
| 341 | ret += expandedVar |
| 342 | } else { |
| 343 | return "", fmt.Errorf("Unbound config variable %s", variable) |
Jingwen Chen | bf61afb | 2021-05-06 13:31:18 +0000 | [diff] [blame] | 344 | } |
| 345 | } |
Chris Parsons | 3b1f83d | 2021-10-14 14:08:38 -0400 | [diff] [blame] | 346 | return ret, nil |
| 347 | } |
| 348 | var ret []string |
| 349 | for _, v := range strings.Split(toExpand, " ") { |
| 350 | val, err := expandVarInternal(v, map[string]bool{}) |
| 351 | if err != nil { |
| 352 | return ret, err |
| 353 | } |
| 354 | ret = append(ret, val) |
Jingwen Chen | bf61afb | 2021-05-06 13:31:18 +0000 | [diff] [blame] | 355 | } |
| 356 | |
Chris Parsons | 3b1f83d | 2021-10-14 14:08:38 -0400 | [diff] [blame] | 357 | return ret, nil |
| 358 | } |
| 359 | |
| 360 | func validateVariableMethod(name string, methodValue reflect.Value) { |
| 361 | methodType := methodValue.Type() |
| 362 | if methodType.Kind() != reflect.Func { |
| 363 | panic(fmt.Errorf("method given for variable %s is not a function", |
| 364 | name)) |
| 365 | } |
| 366 | if n := methodType.NumIn(); n != 1 { |
| 367 | panic(fmt.Errorf("method for variable %s has %d inputs (should be 1)", |
| 368 | name, n)) |
| 369 | } |
| 370 | if n := methodType.NumOut(); n != 1 { |
| 371 | panic(fmt.Errorf("method for variable %s has %d outputs (should be 1)", |
| 372 | name, n)) |
| 373 | } |
| 374 | if kind := methodType.Out(0).Kind(); kind != reflect.String { |
| 375 | panic(fmt.Errorf("method for variable %s does not return a string", |
| 376 | name)) |
| 377 | } |
Jingwen Chen | bf61afb | 2021-05-06 13:31:18 +0000 | [diff] [blame] | 378 | } |