blob: a1e04248e455bf98d97e38951cb69ad84c4236ec [file] [log] [blame]
Liz Kammer2dd9ca42020-11-25 16:06:39 -08001// Copyright 2020 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 bp2build
16
17import (
18 "android/soong/android"
Liz Kammer356f7d42021-01-26 09:18:53 -050019 "android/soong/bazel"
Liz Kammer2dd9ca42020-11-25 16:06:39 -080020 "fmt"
21 "reflect"
Jingwen Chen49109762021-05-25 05:16:48 +000022 "sort"
Liz Kammer2dd9ca42020-11-25 16:06:39 -080023 "strings"
24
25 "github.com/google/blueprint"
26 "github.com/google/blueprint/proptools"
27)
28
29type BazelAttributes struct {
30 Attrs map[string]string
31}
32
33type BazelTarget struct {
Jingwen Chen40067de2021-01-26 21:58:43 -050034 name string
35 content string
36 ruleClass string
37 bzlLoadLocation string
Jingwen Chen49109762021-05-25 05:16:48 +000038 handcrafted bool
Jingwen Chen40067de2021-01-26 21:58:43 -050039}
40
41// IsLoadedFromStarlark determines if the BazelTarget's rule class is loaded from a .bzl file,
42// as opposed to a native rule built into Bazel.
43func (t BazelTarget) IsLoadedFromStarlark() bool {
44 return t.bzlLoadLocation != ""
45}
46
47// BazelTargets is a typedef for a slice of BazelTarget objects.
48type BazelTargets []BazelTarget
49
Jingwen Chen49109762021-05-25 05:16:48 +000050// HasHandcraftedTargetsreturns true if a set of bazel targets contain
51// handcrafted ones.
52func (targets BazelTargets) hasHandcraftedTargets() bool {
53 for _, target := range targets {
54 if target.handcrafted {
55 return true
56 }
57 }
58 return false
59}
60
61// sort a list of BazelTargets in-place, by name, and by generated/handcrafted types.
62func (targets BazelTargets) sort() {
63 sort.Slice(targets, func(i, j int) bool {
64 if targets[i].handcrafted != targets[j].handcrafted {
65 // Handcrafted targets will be generated after the bp2build generated targets.
66 return targets[j].handcrafted
67 }
68 // This will cover all bp2build generated targets.
69 return targets[i].name < targets[j].name
70 })
71}
72
Jingwen Chen40067de2021-01-26 21:58:43 -050073// String returns the string representation of BazelTargets, without load
74// statements (use LoadStatements for that), since the targets are usually not
75// adjacent to the load statements at the top of the BUILD file.
76func (targets BazelTargets) String() string {
77 var res string
78 for i, target := range targets {
Jingwen Chen49109762021-05-25 05:16:48 +000079 // There is only at most 1 handcrafted "target", because its contents
80 // represent the entire BUILD file content from the tree. See
81 // build_conversion.go#getHandcraftedBuildContent for more information.
82 //
83 // Add a header to make it easy to debug where the handcrafted targets
84 // are in a generated BUILD file.
85 if target.handcrafted {
86 res += "# -----------------------------\n"
87 res += "# Section: Handcrafted targets. \n"
88 res += "# -----------------------------\n\n"
89 }
90
Jingwen Chen40067de2021-01-26 21:58:43 -050091 res += target.content
92 if i != len(targets)-1 {
93 res += "\n\n"
94 }
95 }
96 return res
97}
98
99// LoadStatements return the string representation of the sorted and deduplicated
100// Starlark rule load statements needed by a group of BazelTargets.
101func (targets BazelTargets) LoadStatements() string {
102 bzlToLoadedSymbols := map[string][]string{}
103 for _, target := range targets {
104 if target.IsLoadedFromStarlark() {
105 bzlToLoadedSymbols[target.bzlLoadLocation] =
106 append(bzlToLoadedSymbols[target.bzlLoadLocation], target.ruleClass)
107 }
108 }
109
110 var loadStatements []string
111 for bzl, ruleClasses := range bzlToLoadedSymbols {
112 loadStatement := "load(\""
113 loadStatement += bzl
114 loadStatement += "\", "
115 ruleClasses = android.SortedUniqueStrings(ruleClasses)
116 for i, ruleClass := range ruleClasses {
117 loadStatement += "\"" + ruleClass + "\""
118 if i != len(ruleClasses)-1 {
119 loadStatement += ", "
120 }
121 }
122 loadStatement += ")"
123 loadStatements = append(loadStatements, loadStatement)
124 }
125 return strings.Join(android.SortedUniqueStrings(loadStatements), "\n")
Liz Kammer2dd9ca42020-11-25 16:06:39 -0800126}
127
128type bpToBuildContext interface {
129 ModuleName(module blueprint.Module) string
130 ModuleDir(module blueprint.Module) string
131 ModuleSubDir(module blueprint.Module) string
132 ModuleType(module blueprint.Module) string
133
Jingwen Chendaa54bc2020-12-14 02:58:54 -0500134 VisitAllModules(visit func(blueprint.Module))
135 VisitDirectDeps(module blueprint.Module, visit func(blueprint.Module))
136}
137
138type CodegenContext struct {
Liz Kammerba3ea162021-02-17 13:22:03 -0500139 config android.Config
140 context android.Context
141 mode CodegenMode
142 additionalDeps []string
Jingwen Chendaa54bc2020-12-14 02:58:54 -0500143}
144
Jingwen Chen164e0862021-02-19 00:48:40 -0500145func (c *CodegenContext) Mode() CodegenMode {
146 return c.mode
147}
148
Jingwen Chen33832f92021-01-24 22:55:54 -0500149// CodegenMode is an enum to differentiate code-generation modes.
150type CodegenMode int
151
152const (
153 // Bp2Build: generate BUILD files with targets buildable by Bazel directly.
154 //
155 // This mode is used for the Soong->Bazel build definition conversion.
156 Bp2Build CodegenMode = iota
157
158 // QueryView: generate BUILD files with targets representing fully mutated
159 // Soong modules, representing the fully configured Soong module graph with
160 // variants and dependency endges.
161 //
162 // This mode is used for discovering and introspecting the existing Soong
163 // module graph.
164 QueryView
165)
166
Jingwen Chendcc329a2021-01-26 02:49:03 -0500167func (mode CodegenMode) String() string {
168 switch mode {
169 case Bp2Build:
170 return "Bp2Build"
171 case QueryView:
172 return "QueryView"
173 default:
174 return fmt.Sprintf("%d", mode)
175 }
176}
177
Liz Kammerba3ea162021-02-17 13:22:03 -0500178// AddNinjaFileDeps adds dependencies on the specified files to be added to the ninja manifest. The
179// primary builder will be rerun whenever the specified files are modified. Allows us to fulfill the
180// PathContext interface in order to add dependencies on hand-crafted BUILD files. Note: must also
181// call AdditionalNinjaDeps and add them manually to the ninja file.
182func (ctx *CodegenContext) AddNinjaFileDeps(deps ...string) {
183 ctx.additionalDeps = append(ctx.additionalDeps, deps...)
184}
185
186// AdditionalNinjaDeps returns additional ninja deps added by CodegenContext
187func (ctx *CodegenContext) AdditionalNinjaDeps() []string {
188 return ctx.additionalDeps
189}
190
191func (ctx *CodegenContext) Config() android.Config { return ctx.config }
192func (ctx *CodegenContext) Context() android.Context { return ctx.context }
Jingwen Chendaa54bc2020-12-14 02:58:54 -0500193
194// NewCodegenContext creates a wrapper context that conforms to PathContext for
195// writing BUILD files in the output directory.
Liz Kammerba3ea162021-02-17 13:22:03 -0500196func NewCodegenContext(config android.Config, context android.Context, mode CodegenMode) *CodegenContext {
197 return &CodegenContext{
Jingwen Chendaa54bc2020-12-14 02:58:54 -0500198 context: context,
199 config: config,
Jingwen Chen33832f92021-01-24 22:55:54 -0500200 mode: mode,
Jingwen Chendaa54bc2020-12-14 02:58:54 -0500201 }
Liz Kammer2dd9ca42020-11-25 16:06:39 -0800202}
203
204// props is an unsorted map. This function ensures that
205// the generated attributes are sorted to ensure determinism.
206func propsToAttributes(props map[string]string) string {
207 var attributes string
208 for _, propName := range android.SortedStringKeys(props) {
209 if shouldGenerateAttribute(propName) {
210 attributes += fmt.Sprintf(" %s = %s,\n", propName, props[propName])
211 }
212 }
213 return attributes
214}
215
Rupert Shuttleworth2a4fc3e2021-04-21 07:10:09 -0400216func GenerateBazelTargets(ctx *CodegenContext, generateFilegroups bool) (map[string]BazelTargets, CodegenMetrics) {
Jingwen Chen40067de2021-01-26 21:58:43 -0500217 buildFileToTargets := make(map[string]BazelTargets)
Liz Kammerba3ea162021-02-17 13:22:03 -0500218 buildFileToAppend := make(map[string]bool)
Jingwen Chen164e0862021-02-19 00:48:40 -0500219
220 // Simple metrics tracking for bp2build
Liz Kammerba3ea162021-02-17 13:22:03 -0500221 metrics := CodegenMetrics{
222 RuleClassCount: make(map[string]int),
223 }
Jingwen Chen164e0862021-02-19 00:48:40 -0500224
Rupert Shuttleworth2a4fc3e2021-04-21 07:10:09 -0400225 dirs := make(map[string]bool)
226
Jingwen Chen164e0862021-02-19 00:48:40 -0500227 bpCtx := ctx.Context()
228 bpCtx.VisitAllModules(func(m blueprint.Module) {
229 dir := bpCtx.ModuleDir(m)
Rupert Shuttleworth2a4fc3e2021-04-21 07:10:09 -0400230 dirs[dir] = true
231
Jingwen Chen73850672020-12-14 08:25:34 -0500232 var t BazelTarget
233
Jingwen Chen164e0862021-02-19 00:48:40 -0500234 switch ctx.Mode() {
Jingwen Chen33832f92021-01-24 22:55:54 -0500235 case Bp2Build:
Liz Kammerba3ea162021-02-17 13:22:03 -0500236 if b, ok := m.(android.Bazelable); ok && b.HasHandcraftedLabel() {
237 metrics.handCraftedTargetCount += 1
238 metrics.TotalModuleCount += 1
239 pathToBuildFile := getBazelPackagePath(b)
240 // We are using the entire contents of handcrafted build file, so if multiple targets within
241 // a package have handcrafted targets, we only want to include the contents one time.
242 if _, exists := buildFileToAppend[pathToBuildFile]; exists {
243 return
244 }
245 var err error
246 t, err = getHandcraftedBuildContent(ctx, b, pathToBuildFile)
247 if err != nil {
248 panic(fmt.Errorf("Error converting %s: %s", bpCtx.ModuleName(m), err))
249 }
250 // TODO(b/181575318): currently we append the whole BUILD file, let's change that to do
251 // something more targeted based on the rule type and target
252 buildFileToAppend[pathToBuildFile] = true
253 } else if btm, ok := m.(android.BazelTargetModule); ok {
254 t = generateBazelTarget(bpCtx, m, btm)
255 metrics.RuleClassCount[t.ruleClass] += 1
Liz Kammerfc46bc12021-02-19 11:06:17 -0500256 } else {
Liz Kammerba3ea162021-02-17 13:22:03 -0500257 metrics.TotalModuleCount += 1
258 return
Jingwen Chen73850672020-12-14 08:25:34 -0500259 }
Jingwen Chen33832f92021-01-24 22:55:54 -0500260 case QueryView:
Jingwen Chen96af35b2021-02-08 00:49:32 -0500261 // Blocklist certain module types from being generated.
Jingwen Chen164e0862021-02-19 00:48:40 -0500262 if canonicalizeModuleType(bpCtx.ModuleType(m)) == "package" {
Jingwen Chen96af35b2021-02-08 00:49:32 -0500263 // package module name contain slashes, and thus cannot
264 // be mapped cleanly to a bazel label.
265 return
266 }
Jingwen Chen164e0862021-02-19 00:48:40 -0500267 t = generateSoongModuleTarget(bpCtx, m)
Jingwen Chen33832f92021-01-24 22:55:54 -0500268 default:
Jingwen Chen164e0862021-02-19 00:48:40 -0500269 panic(fmt.Errorf("Unknown code-generation mode: %s", ctx.Mode()))
Jingwen Chen73850672020-12-14 08:25:34 -0500270 }
271
Liz Kammer356f7d42021-01-26 09:18:53 -0500272 buildFileToTargets[dir] = append(buildFileToTargets[dir], t)
Liz Kammer2dd9ca42020-11-25 16:06:39 -0800273 })
Rupert Shuttleworth2a4fc3e2021-04-21 07:10:09 -0400274 if generateFilegroups {
275 // Add a filegroup target that exposes all sources in the subtree of this package
276 // NOTE: This also means we generate a BUILD file for every Android.bp file (as long as it has at least one module)
277 for dir, _ := range dirs {
278 buildFileToTargets[dir] = append(buildFileToTargets[dir], BazelTarget{
279 name: "bp2build_all_srcs",
280 content: `filegroup(name = "bp2build_all_srcs", srcs = glob(["**/*"]))`,
281 ruleClass: "filegroup",
282 })
283 }
284 }
Jingwen Chen164e0862021-02-19 00:48:40 -0500285
Jingwen Chen164e0862021-02-19 00:48:40 -0500286 return buildFileToTargets, metrics
287}
288
Liz Kammerba3ea162021-02-17 13:22:03 -0500289func getBazelPackagePath(b android.Bazelable) string {
Liz Kammerbdc60992021-02-24 16:55:11 -0500290 label := b.HandcraftedLabel()
Liz Kammerba3ea162021-02-17 13:22:03 -0500291 pathToBuildFile := strings.TrimPrefix(label, "//")
292 pathToBuildFile = strings.Split(pathToBuildFile, ":")[0]
293 return pathToBuildFile
294}
295
296func getHandcraftedBuildContent(ctx *CodegenContext, b android.Bazelable, pathToBuildFile string) (BazelTarget, error) {
297 p := android.ExistentPathForSource(ctx, pathToBuildFile, HandcraftedBuildFileName)
298 if !p.Valid() {
299 return BazelTarget{}, fmt.Errorf("Could not find file %q for handcrafted target.", pathToBuildFile)
300 }
301 c, err := b.GetBazelBuildFileContents(ctx.Config(), pathToBuildFile, HandcraftedBuildFileName)
302 if err != nil {
303 return BazelTarget{}, err
304 }
305 // TODO(b/181575318): once this is more targeted, we need to include name, rule class, etc
306 return BazelTarget{
Jingwen Chen49109762021-05-25 05:16:48 +0000307 content: c,
308 handcrafted: true,
Liz Kammerba3ea162021-02-17 13:22:03 -0500309 }, nil
310}
311
312func generateBazelTarget(ctx bpToBuildContext, m blueprint.Module, btm android.BazelTargetModule) BazelTarget {
313 ruleClass := btm.RuleClass()
314 bzlLoadLocation := btm.BzlLoadLocation()
Jingwen Chen40067de2021-01-26 21:58:43 -0500315
Jingwen Chen73850672020-12-14 08:25:34 -0500316 // extract the bazel attributes from the module.
317 props := getBuildProperties(ctx, m)
318
Jingwen Chen77e8b7b2021-02-05 03:03:24 -0500319 delete(props.Attrs, "bp2build_available")
320
Jingwen Chen73850672020-12-14 08:25:34 -0500321 // Return the Bazel target with rule class and attributes, ready to be
322 // code-generated.
323 attributes := propsToAttributes(props.Attrs)
324 targetName := targetNameForBp2Build(ctx, m)
325 return BazelTarget{
Jingwen Chen40067de2021-01-26 21:58:43 -0500326 name: targetName,
327 ruleClass: ruleClass,
328 bzlLoadLocation: bzlLoadLocation,
Jingwen Chen73850672020-12-14 08:25:34 -0500329 content: fmt.Sprintf(
330 bazelTarget,
331 ruleClass,
332 targetName,
333 attributes,
334 ),
Jingwen Chen49109762021-05-25 05:16:48 +0000335 handcrafted: false,
Jingwen Chen73850672020-12-14 08:25:34 -0500336 }
337}
338
Liz Kammer2dd9ca42020-11-25 16:06:39 -0800339// Convert a module and its deps and props into a Bazel macro/rule
340// representation in the BUILD file.
341func generateSoongModuleTarget(ctx bpToBuildContext, m blueprint.Module) BazelTarget {
342 props := getBuildProperties(ctx, m)
343
344 // TODO(b/163018919): DirectDeps can have duplicate (module, variant)
345 // items, if the modules are added using different DependencyTag. Figure
346 // out the implications of that.
347 depLabels := map[string]bool{}
348 if aModule, ok := m.(android.Module); ok {
Jingwen Chendaa54bc2020-12-14 02:58:54 -0500349 ctx.VisitDirectDeps(aModule, func(depModule blueprint.Module) {
Liz Kammer2dd9ca42020-11-25 16:06:39 -0800350 depLabels[qualifiedTargetLabel(ctx, depModule)] = true
351 })
352 }
353 attributes := propsToAttributes(props.Attrs)
354
355 depLabelList := "[\n"
356 for depLabel, _ := range depLabels {
357 depLabelList += fmt.Sprintf(" %q,\n", depLabel)
358 }
359 depLabelList += " ]"
360
361 targetName := targetNameWithVariant(ctx, m)
362 return BazelTarget{
363 name: targetName,
364 content: fmt.Sprintf(
365 soongModuleTarget,
366 targetName,
367 ctx.ModuleName(m),
368 canonicalizeModuleType(ctx.ModuleType(m)),
369 ctx.ModuleSubDir(m),
370 depLabelList,
371 attributes),
372 }
373}
374
375func getBuildProperties(ctx bpToBuildContext, m blueprint.Module) BazelAttributes {
376 var allProps map[string]string
377 // TODO: this omits properties for blueprint modules (blueprint_go_binary,
378 // bootstrap_go_binary, bootstrap_go_package), which will have to be handled separately.
379 if aModule, ok := m.(android.Module); ok {
380 allProps = ExtractModuleProperties(aModule)
381 }
382
383 return BazelAttributes{
384 Attrs: allProps,
385 }
386}
387
388// Generically extract module properties and types into a map, keyed by the module property name.
389func ExtractModuleProperties(aModule android.Module) map[string]string {
390 ret := map[string]string{}
391
392 // Iterate over this android.Module's property structs.
393 for _, properties := range aModule.GetProperties() {
394 propertiesValue := reflect.ValueOf(properties)
395 // Check that propertiesValue is a pointer to the Properties struct, like
396 // *cc.BaseLinkerProperties or *java.CompilerProperties.
397 //
398 // propertiesValue can also be type-asserted to the structs to
399 // manipulate internal props, if needed.
400 if isStructPtr(propertiesValue.Type()) {
401 structValue := propertiesValue.Elem()
402 for k, v := range extractStructProperties(structValue, 0) {
403 ret[k] = v
404 }
405 } else {
406 panic(fmt.Errorf(
407 "properties must be a pointer to a struct, got %T",
408 propertiesValue.Interface()))
409 }
410 }
411
412 return ret
413}
414
415func isStructPtr(t reflect.Type) bool {
416 return t.Kind() == reflect.Ptr && t.Elem().Kind() == reflect.Struct
417}
418
419// prettyPrint a property value into the equivalent Starlark representation
420// recursively.
421func prettyPrint(propertyValue reflect.Value, indent int) (string, error) {
422 if isZero(propertyValue) {
423 // A property value being set or unset actually matters -- Soong does set default
424 // values for unset properties, like system_shared_libs = ["libc", "libm", "libdl"] at
425 // https://cs.android.com/android/platform/superproject/+/master:build/soong/cc/linker.go;l=281-287;drc=f70926eef0b9b57faf04c17a1062ce50d209e480
426 //
Jingwen Chenfc490bd2021-03-30 10:24:19 +0000427 // In Bazel-parlance, we would use "attr.<type>(default = <default
428 // value>)" to set the default value of unset attributes. In the cases
429 // where the bp2build converter didn't set the default value within the
430 // mutator when creating the BazelTargetModule, this would be a zero
Jingwen Chen63930982021-03-24 10:04:33 -0400431 // value. For those cases, we return an empty string so we don't
432 // unnecessarily generate empty values.
433 return "", nil
Liz Kammer2dd9ca42020-11-25 16:06:39 -0800434 }
435
436 var ret string
437 switch propertyValue.Kind() {
438 case reflect.String:
439 ret = fmt.Sprintf("\"%v\"", escapeString(propertyValue.String()))
440 case reflect.Bool:
441 ret = strings.Title(fmt.Sprintf("%v", propertyValue.Interface()))
442 case reflect.Int, reflect.Uint, reflect.Int64:
443 ret = fmt.Sprintf("%v", propertyValue.Interface())
444 case reflect.Ptr:
445 return prettyPrint(propertyValue.Elem(), indent)
446 case reflect.Slice:
Jingwen Chen63930982021-03-24 10:04:33 -0400447 if propertyValue.Len() == 0 {
Liz Kammer2b07ec72021-05-26 15:08:27 -0400448 return "[]", nil
Jingwen Chen63930982021-03-24 10:04:33 -0400449 }
450
Jingwen Chenb4628eb2021-04-08 14:40:57 +0000451 if propertyValue.Len() == 1 {
452 // Single-line list for list with only 1 element
453 ret += "["
454 indexedValue, err := prettyPrint(propertyValue.Index(0), indent)
Liz Kammer2dd9ca42020-11-25 16:06:39 -0800455 if err != nil {
456 return "", err
457 }
Jingwen Chenb4628eb2021-04-08 14:40:57 +0000458 ret += indexedValue
459 ret += "]"
460 } else {
461 // otherwise, use a multiline list.
462 ret += "[\n"
463 for i := 0; i < propertyValue.Len(); i++ {
464 indexedValue, err := prettyPrint(propertyValue.Index(i), indent+1)
465 if err != nil {
466 return "", err
467 }
Liz Kammer2dd9ca42020-11-25 16:06:39 -0800468
Jingwen Chenb4628eb2021-04-08 14:40:57 +0000469 if indexedValue != "" {
470 ret += makeIndent(indent + 1)
471 ret += indexedValue
472 ret += ",\n"
473 }
Liz Kammer2dd9ca42020-11-25 16:06:39 -0800474 }
Jingwen Chenb4628eb2021-04-08 14:40:57 +0000475 ret += makeIndent(indent)
476 ret += "]"
Liz Kammer2dd9ca42020-11-25 16:06:39 -0800477 }
Jingwen Chenb4628eb2021-04-08 14:40:57 +0000478
Liz Kammer2dd9ca42020-11-25 16:06:39 -0800479 case reflect.Struct:
Jingwen Chen5d864492021-02-24 07:20:12 -0500480 // Special cases where the bp2build sends additional information to the codegenerator
481 // by wrapping the attributes in a custom struct type.
Jingwen Chenc1c26502021-04-05 10:35:13 +0000482 if attr, ok := propertyValue.Interface().(bazel.Attribute); ok {
483 return prettyPrintAttribute(attr, indent)
Liz Kammer356f7d42021-01-26 09:18:53 -0500484 } else if label, ok := propertyValue.Interface().(bazel.Label); ok {
485 return fmt.Sprintf("%q", label.Label), nil
486 }
487
Liz Kammer2dd9ca42020-11-25 16:06:39 -0800488 ret = "{\n"
489 // Sort and print the struct props by the key.
490 structProps := extractStructProperties(propertyValue, indent)
Jingwen Chen3d383bb2021-06-09 07:18:37 +0000491 if len(structProps) == 0 {
492 return "", nil
493 }
Liz Kammer2dd9ca42020-11-25 16:06:39 -0800494 for _, k := range android.SortedStringKeys(structProps) {
495 ret += makeIndent(indent + 1)
496 ret += fmt.Sprintf("%q: %s,\n", k, structProps[k])
497 }
498 ret += makeIndent(indent)
499 ret += "}"
500 case reflect.Interface:
501 // TODO(b/164227191): implement pretty print for interfaces.
502 // Interfaces are used for for arch, multilib and target properties.
503 return "", nil
504 default:
505 return "", fmt.Errorf(
506 "unexpected kind for property struct field: %s", propertyValue.Kind())
507 }
508 return ret, nil
509}
510
511// Converts a reflected property struct value into a map of property names and property values,
512// which each property value correctly pretty-printed and indented at the right nest level,
513// since property structs can be nested. In Starlark, nested structs are represented as nested
514// dicts: https://docs.bazel.build/skylark/lib/dict.html
515func extractStructProperties(structValue reflect.Value, indent int) map[string]string {
516 if structValue.Kind() != reflect.Struct {
517 panic(fmt.Errorf("Expected a reflect.Struct type, but got %s", structValue.Kind()))
518 }
519
520 ret := map[string]string{}
521 structType := structValue.Type()
522 for i := 0; i < structValue.NumField(); i++ {
523 field := structType.Field(i)
524 if shouldSkipStructField(field) {
525 continue
526 }
527
528 fieldValue := structValue.Field(i)
529 if isZero(fieldValue) {
530 // Ignore zero-valued fields
531 continue
532 }
533
534 propertyName := proptools.PropertyNameForField(field.Name)
535 prettyPrintedValue, err := prettyPrint(fieldValue, indent+1)
536 if err != nil {
537 panic(
538 fmt.Errorf(
539 "Error while parsing property: %q. %s",
540 propertyName,
541 err))
542 }
543 if prettyPrintedValue != "" {
544 ret[propertyName] = prettyPrintedValue
545 }
546 }
547
548 return ret
549}
550
551func isZero(value reflect.Value) bool {
552 switch value.Kind() {
553 case reflect.Func, reflect.Map, reflect.Slice:
554 return value.IsNil()
555 case reflect.Array:
556 valueIsZero := true
557 for i := 0; i < value.Len(); i++ {
558 valueIsZero = valueIsZero && isZero(value.Index(i))
559 }
560 return valueIsZero
561 case reflect.Struct:
562 valueIsZero := true
563 for i := 0; i < value.NumField(); i++ {
Lukacs T. Berki1353e592021-04-30 15:35:09 +0200564 valueIsZero = valueIsZero && isZero(value.Field(i))
Liz Kammer2dd9ca42020-11-25 16:06:39 -0800565 }
566 return valueIsZero
567 case reflect.Ptr:
568 if !value.IsNil() {
569 return isZero(reflect.Indirect(value))
570 } else {
571 return true
572 }
Liz Kammerd366c902021-06-03 13:43:01 -0400573 // Always print bools, if you want a bool attribute to be able to take the default value, use a
574 // bool pointer instead
575 case reflect.Bool:
576 return false
Liz Kammer2dd9ca42020-11-25 16:06:39 -0800577 default:
Rupert Shuttleworthc194ffb2021-05-19 06:49:02 -0400578 if !value.IsValid() {
579 return true
580 }
Liz Kammer2dd9ca42020-11-25 16:06:39 -0800581 zeroValue := reflect.Zero(value.Type())
582 result := value.Interface() == zeroValue.Interface()
583 return result
584 }
585}
586
587func escapeString(s string) string {
588 s = strings.ReplaceAll(s, "\\", "\\\\")
Jingwen Chen58a12b82021-03-30 13:08:36 +0000589
590 // b/184026959: Reverse the application of some common control sequences.
591 // These must be generated literally in the BUILD file.
592 s = strings.ReplaceAll(s, "\t", "\\t")
593 s = strings.ReplaceAll(s, "\n", "\\n")
594 s = strings.ReplaceAll(s, "\r", "\\r")
595
Liz Kammer2dd9ca42020-11-25 16:06:39 -0800596 return strings.ReplaceAll(s, "\"", "\\\"")
597}
598
599func makeIndent(indent int) string {
600 if indent < 0 {
601 panic(fmt.Errorf("indent column cannot be less than 0, but got %d", indent))
602 }
603 return strings.Repeat(" ", indent)
604}
605
Jingwen Chen73850672020-12-14 08:25:34 -0500606func targetNameForBp2Build(c bpToBuildContext, logicModule blueprint.Module) string {
Jingwen Chenfb4692a2021-02-07 10:05:16 -0500607 return strings.Replace(c.ModuleName(logicModule), bazel.BazelTargetModuleNamePrefix, "", 1)
Jingwen Chen73850672020-12-14 08:25:34 -0500608}
609
Liz Kammer2dd9ca42020-11-25 16:06:39 -0800610func targetNameWithVariant(c bpToBuildContext, logicModule blueprint.Module) string {
611 name := ""
612 if c.ModuleSubDir(logicModule) != "" {
613 // TODO(b/162720883): Figure out a way to drop the "--" variant suffixes.
614 name = c.ModuleName(logicModule) + "--" + c.ModuleSubDir(logicModule)
615 } else {
616 name = c.ModuleName(logicModule)
617 }
618
619 return strings.Replace(name, "//", "", 1)
620}
621
622func qualifiedTargetLabel(c bpToBuildContext, logicModule blueprint.Module) string {
623 return fmt.Sprintf("//%s:%s", c.ModuleDir(logicModule), targetNameWithVariant(c, logicModule))
624}