• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1// 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
17/*
18For shareable/common functionality for conversion from soong-module to build files
19for queryview/bp2build
20*/
21
22import (
23	"fmt"
24	"reflect"
25	"sort"
26	"strings"
27
28	"android/soong/android"
29	"android/soong/bazel"
30	"android/soong/starlark_fmt"
31
32	"github.com/google/blueprint"
33	"github.com/google/blueprint/proptools"
34)
35
36type BazelAttributes struct {
37	Attrs map[string]string
38}
39
40type BazelTarget struct {
41	name            string
42	packageName     string
43	content         string
44	ruleClass       string
45	bzlLoadLocation string
46	handcrafted     bool
47}
48
49// IsLoadedFromStarlark determines if the BazelTarget's rule class is loaded from a .bzl file,
50// as opposed to a native rule built into Bazel.
51func (t BazelTarget) IsLoadedFromStarlark() bool {
52	return t.bzlLoadLocation != ""
53}
54
55// Label is the fully qualified Bazel label constructed from the BazelTarget's
56// package name and target name.
57func (t BazelTarget) Label() string {
58	if t.packageName == "." {
59		return "//:" + t.name
60	} else {
61		return "//" + t.packageName + ":" + t.name
62	}
63}
64
65// BazelTargets is a typedef for a slice of BazelTarget objects.
66type BazelTargets []BazelTarget
67
68// HasHandcraftedTargetsreturns true if a set of bazel targets contain
69// handcrafted ones.
70func (targets BazelTargets) hasHandcraftedTargets() bool {
71	for _, target := range targets {
72		if target.handcrafted {
73			return true
74		}
75	}
76	return false
77}
78
79// sort a list of BazelTargets in-place, by name, and by generated/handcrafted types.
80func (targets BazelTargets) sort() {
81	sort.Slice(targets, func(i, j int) bool {
82		if targets[i].handcrafted != targets[j].handcrafted {
83			// Handcrafted targets will be generated after the bp2build generated targets.
84			return targets[j].handcrafted
85		}
86		// This will cover all bp2build generated targets.
87		return targets[i].name < targets[j].name
88	})
89}
90
91// String returns the string representation of BazelTargets, without load
92// statements (use LoadStatements for that), since the targets are usually not
93// adjacent to the load statements at the top of the BUILD file.
94func (targets BazelTargets) String() string {
95	var res string
96	for i, target := range targets {
97		// There is only at most 1 handcrafted "target", because its contents
98		// represent the entire BUILD file content from the tree. See
99		// build_conversion.go#getHandcraftedBuildContent for more information.
100		//
101		// Add a header to make it easy to debug where the handcrafted targets
102		// are in a generated BUILD file.
103		if target.handcrafted {
104			res += "# -----------------------------\n"
105			res += "# Section: Handcrafted targets. \n"
106			res += "# -----------------------------\n\n"
107		}
108
109		res += target.content
110		if i != len(targets)-1 {
111			res += "\n\n"
112		}
113	}
114	return res
115}
116
117// LoadStatements return the string representation of the sorted and deduplicated
118// Starlark rule load statements needed by a group of BazelTargets.
119func (targets BazelTargets) LoadStatements() string {
120	bzlToLoadedSymbols := map[string][]string{}
121	for _, target := range targets {
122		if target.IsLoadedFromStarlark() {
123			bzlToLoadedSymbols[target.bzlLoadLocation] =
124				append(bzlToLoadedSymbols[target.bzlLoadLocation], target.ruleClass)
125		}
126	}
127
128	var loadStatements []string
129	for bzl, ruleClasses := range bzlToLoadedSymbols {
130		loadStatement := "load(\""
131		loadStatement += bzl
132		loadStatement += "\", "
133		ruleClasses = android.SortedUniqueStrings(ruleClasses)
134		for i, ruleClass := range ruleClasses {
135			loadStatement += "\"" + ruleClass + "\""
136			if i != len(ruleClasses)-1 {
137				loadStatement += ", "
138			}
139		}
140		loadStatement += ")"
141		loadStatements = append(loadStatements, loadStatement)
142	}
143	return strings.Join(android.SortedUniqueStrings(loadStatements), "\n")
144}
145
146type bpToBuildContext interface {
147	ModuleName(module blueprint.Module) string
148	ModuleDir(module blueprint.Module) string
149	ModuleSubDir(module blueprint.Module) string
150	ModuleType(module blueprint.Module) string
151
152	VisitAllModules(visit func(blueprint.Module))
153	VisitDirectDeps(module blueprint.Module, visit func(blueprint.Module))
154}
155
156type CodegenContext struct {
157	config             android.Config
158	context            android.Context
159	mode               CodegenMode
160	additionalDeps     []string
161	unconvertedDepMode unconvertedDepsMode
162}
163
164func (c *CodegenContext) Mode() CodegenMode {
165	return c.mode
166}
167
168// CodegenMode is an enum to differentiate code-generation modes.
169type CodegenMode int
170
171const (
172	// Bp2Build: generate BUILD files with targets buildable by Bazel directly.
173	//
174	// This mode is used for the Soong->Bazel build definition conversion.
175	Bp2Build CodegenMode = iota
176
177	// QueryView: generate BUILD files with targets representing fully mutated
178	// Soong modules, representing the fully configured Soong module graph with
179	// variants and dependency endges.
180	//
181	// This mode is used for discovering and introspecting the existing Soong
182	// module graph.
183	QueryView
184)
185
186type unconvertedDepsMode int
187
188const (
189	// Include a warning in conversion metrics about converted modules with unconverted direct deps
190	warnUnconvertedDeps unconvertedDepsMode = iota
191	// Error and fail conversion if encountering a module with unconverted direct deps
192	// Enabled by setting environment variable `BP2BUILD_ERROR_UNCONVERTED`
193	errorModulesUnconvertedDeps
194)
195
196func (mode CodegenMode) String() string {
197	switch mode {
198	case Bp2Build:
199		return "Bp2Build"
200	case QueryView:
201		return "QueryView"
202	default:
203		return fmt.Sprintf("%d", mode)
204	}
205}
206
207// AddNinjaFileDeps adds dependencies on the specified files to be added to the ninja manifest. The
208// primary builder will be rerun whenever the specified files are modified. Allows us to fulfill the
209// PathContext interface in order to add dependencies on hand-crafted BUILD files. Note: must also
210// call AdditionalNinjaDeps and add them manually to the ninja file.
211func (ctx *CodegenContext) AddNinjaFileDeps(deps ...string) {
212	ctx.additionalDeps = append(ctx.additionalDeps, deps...)
213}
214
215// AdditionalNinjaDeps returns additional ninja deps added by CodegenContext
216func (ctx *CodegenContext) AdditionalNinjaDeps() []string {
217	return ctx.additionalDeps
218}
219
220func (ctx *CodegenContext) Config() android.Config   { return ctx.config }
221func (ctx *CodegenContext) Context() android.Context { return ctx.context }
222
223// NewCodegenContext creates a wrapper context that conforms to PathContext for
224// writing BUILD files in the output directory.
225func NewCodegenContext(config android.Config, context android.Context, mode CodegenMode) *CodegenContext {
226	var unconvertedDeps unconvertedDepsMode
227	if config.IsEnvTrue("BP2BUILD_ERROR_UNCONVERTED") {
228		unconvertedDeps = errorModulesUnconvertedDeps
229	}
230	return &CodegenContext{
231		context:            context,
232		config:             config,
233		mode:               mode,
234		unconvertedDepMode: unconvertedDeps,
235	}
236}
237
238// props is an unsorted map. This function ensures that
239// the generated attributes are sorted to ensure determinism.
240func propsToAttributes(props map[string]string) string {
241	var attributes string
242	for _, propName := range android.SortedStringKeys(props) {
243		attributes += fmt.Sprintf("    %s = %s,\n", propName, props[propName])
244	}
245	return attributes
246}
247
248type conversionResults struct {
249	buildFileToTargets map[string]BazelTargets
250	metrics            CodegenMetrics
251}
252
253func (r conversionResults) BuildDirToTargets() map[string]BazelTargets {
254	return r.buildFileToTargets
255}
256
257func GenerateBazelTargets(ctx *CodegenContext, generateFilegroups bool) (conversionResults, []error) {
258	buildFileToTargets := make(map[string]BazelTargets)
259	buildFileToAppend := make(map[string]bool)
260
261	// Simple metrics tracking for bp2build
262	metrics := CodegenMetrics{
263		ruleClassCount:           make(map[string]uint64),
264		convertedModuleTypeCount: make(map[string]uint64),
265		totalModuleTypeCount:     make(map[string]uint64),
266	}
267
268	dirs := make(map[string]bool)
269
270	var errs []error
271
272	bpCtx := ctx.Context()
273	bpCtx.VisitAllModules(func(m blueprint.Module) {
274		dir := bpCtx.ModuleDir(m)
275		moduleType := bpCtx.ModuleType(m)
276		dirs[dir] = true
277
278		var targets []BazelTarget
279
280		switch ctx.Mode() {
281		case Bp2Build:
282			// There are two main ways of converting a Soong module to Bazel:
283			// 1) Manually handcrafting a Bazel target and associating the module with its label
284			// 2) Automatically generating with bp2build converters
285			//
286			// bp2build converters are used for the majority of modules.
287			if b, ok := m.(android.Bazelable); ok && b.HasHandcraftedLabel() {
288				// Handle modules converted to handcrafted targets.
289				//
290				// Since these modules are associated with some handcrafted
291				// target in a BUILD file, we simply append the entire contents
292				// of that BUILD file to the generated BUILD file.
293				//
294				// The append operation is only done once, even if there are
295				// multiple modules from the same directory associated to
296				// targets in the same BUILD file (or package).
297
298				// Log the module.
299				metrics.AddConvertedModule(m, moduleType, Handcrafted)
300
301				pathToBuildFile := getBazelPackagePath(b)
302				if _, exists := buildFileToAppend[pathToBuildFile]; exists {
303					// Append the BUILD file content once per package, at most.
304					return
305				}
306				t, err := getHandcraftedBuildContent(ctx, b, pathToBuildFile)
307				if err != nil {
308					errs = append(errs, fmt.Errorf("Error converting %s: %s", bpCtx.ModuleName(m), err))
309					return
310				}
311				targets = append(targets, t)
312				// TODO(b/181575318): currently we append the whole BUILD file, let's change that to do
313				// something more targeted based on the rule type and target
314				buildFileToAppend[pathToBuildFile] = true
315			} else if aModule, ok := m.(android.Module); ok && aModule.IsConvertedByBp2build() {
316				// Handle modules converted to generated targets.
317
318				// Log the module.
319				metrics.AddConvertedModule(aModule, moduleType, Generated)
320
321				// Handle modules with unconverted deps. By default, emit a warning.
322				if unconvertedDeps := aModule.GetUnconvertedBp2buildDeps(); len(unconvertedDeps) > 0 {
323					msg := fmt.Sprintf("%q depends on unconverted modules: %s", m.Name(), strings.Join(unconvertedDeps, ", "))
324					if ctx.unconvertedDepMode == warnUnconvertedDeps {
325						metrics.moduleWithUnconvertedDepsMsgs = append(metrics.moduleWithUnconvertedDepsMsgs, msg)
326					} else if ctx.unconvertedDepMode == errorModulesUnconvertedDeps {
327						errs = append(errs, fmt.Errorf(msg))
328						return
329					}
330				}
331				if unconvertedDeps := aModule.GetMissingBp2buildDeps(); len(unconvertedDeps) > 0 {
332					msg := fmt.Sprintf("%q depends on missing modules: %s", m.Name(), strings.Join(unconvertedDeps, ", "))
333					if ctx.unconvertedDepMode == warnUnconvertedDeps {
334						metrics.moduleWithMissingDepsMsgs = append(metrics.moduleWithMissingDepsMsgs, msg)
335					} else if ctx.unconvertedDepMode == errorModulesUnconvertedDeps {
336						errs = append(errs, fmt.Errorf(msg))
337						return
338					}
339				}
340				targets = generateBazelTargets(bpCtx, aModule)
341				for _, t := range targets {
342					// A module can potentially generate more than 1 Bazel
343					// target, each of a different rule class.
344					metrics.IncrementRuleClassCount(t.ruleClass)
345				}
346			} else {
347				metrics.AddUnconvertedModule(moduleType)
348				return
349			}
350		case QueryView:
351			// Blocklist certain module types from being generated.
352			if canonicalizeModuleType(bpCtx.ModuleType(m)) == "package" {
353				// package module name contain slashes, and thus cannot
354				// be mapped cleanly to a bazel label.
355				return
356			}
357			t := generateSoongModuleTarget(bpCtx, m)
358			targets = append(targets, t)
359		default:
360			errs = append(errs, fmt.Errorf("Unknown code-generation mode: %s", ctx.Mode()))
361			return
362		}
363
364		buildFileToTargets[dir] = append(buildFileToTargets[dir], targets...)
365	})
366
367	if len(errs) > 0 {
368		return conversionResults{}, errs
369	}
370
371	if generateFilegroups {
372		// Add a filegroup target that exposes all sources in the subtree of this package
373		// NOTE: This also means we generate a BUILD file for every Android.bp file (as long as it has at least one module)
374		for dir, _ := range dirs {
375			buildFileToTargets[dir] = append(buildFileToTargets[dir], BazelTarget{
376				name:      "bp2build_all_srcs",
377				content:   `filegroup(name = "bp2build_all_srcs", srcs = glob(["**/*"]))`,
378				ruleClass: "filegroup",
379			})
380		}
381	}
382
383	return conversionResults{
384		buildFileToTargets: buildFileToTargets,
385		metrics:            metrics,
386	}, errs
387}
388
389func getBazelPackagePath(b android.Bazelable) string {
390	label := b.HandcraftedLabel()
391	pathToBuildFile := strings.TrimPrefix(label, "//")
392	pathToBuildFile = strings.Split(pathToBuildFile, ":")[0]
393	return pathToBuildFile
394}
395
396func getHandcraftedBuildContent(ctx *CodegenContext, b android.Bazelable, pathToBuildFile string) (BazelTarget, error) {
397	p := android.ExistentPathForSource(ctx, pathToBuildFile, HandcraftedBuildFileName)
398	if !p.Valid() {
399		return BazelTarget{}, fmt.Errorf("Could not find file %q for handcrafted target.", pathToBuildFile)
400	}
401	c, err := b.GetBazelBuildFileContents(ctx.Config(), pathToBuildFile, HandcraftedBuildFileName)
402	if err != nil {
403		return BazelTarget{}, err
404	}
405	// TODO(b/181575318): once this is more targeted, we need to include name, rule class, etc
406	return BazelTarget{
407		content:     c,
408		handcrafted: true,
409	}, nil
410}
411
412func generateBazelTargets(ctx bpToBuildContext, m android.Module) []BazelTarget {
413	var targets []BazelTarget
414	for _, m := range m.Bp2buildTargets() {
415		targets = append(targets, generateBazelTarget(ctx, m))
416	}
417	return targets
418}
419
420type bp2buildModule interface {
421	TargetName() string
422	TargetPackage() string
423	BazelRuleClass() string
424	BazelRuleLoadLocation() string
425	BazelAttributes() []interface{}
426}
427
428func generateBazelTarget(ctx bpToBuildContext, m bp2buildModule) BazelTarget {
429	ruleClass := m.BazelRuleClass()
430	bzlLoadLocation := m.BazelRuleLoadLocation()
431
432	// extract the bazel attributes from the module.
433	attrs := m.BazelAttributes()
434	props := extractModuleProperties(attrs, true)
435
436	// name is handled in a special manner
437	delete(props.Attrs, "name")
438
439	// Return the Bazel target with rule class and attributes, ready to be
440	// code-generated.
441	attributes := propsToAttributes(props.Attrs)
442	targetName := m.TargetName()
443	return BazelTarget{
444		name:            targetName,
445		packageName:     m.TargetPackage(),
446		ruleClass:       ruleClass,
447		bzlLoadLocation: bzlLoadLocation,
448		content: fmt.Sprintf(
449			bazelTarget,
450			ruleClass,
451			targetName,
452			attributes,
453		),
454		handcrafted: false,
455	}
456}
457
458// Convert a module and its deps and props into a Bazel macro/rule
459// representation in the BUILD file.
460func generateSoongModuleTarget(ctx bpToBuildContext, m blueprint.Module) BazelTarget {
461	props := getBuildProperties(ctx, m)
462
463	// TODO(b/163018919): DirectDeps can have duplicate (module, variant)
464	// items, if the modules are added using different DependencyTag. Figure
465	// out the implications of that.
466	depLabels := map[string]bool{}
467	if aModule, ok := m.(android.Module); ok {
468		ctx.VisitDirectDeps(aModule, func(depModule blueprint.Module) {
469			depLabels[qualifiedTargetLabel(ctx, depModule)] = true
470		})
471	}
472
473	for p, _ := range ignoredPropNames {
474		delete(props.Attrs, p)
475	}
476	attributes := propsToAttributes(props.Attrs)
477
478	depLabelList := "[\n"
479	for depLabel, _ := range depLabels {
480		depLabelList += fmt.Sprintf("        %q,\n", depLabel)
481	}
482	depLabelList += "    ]"
483
484	targetName := targetNameWithVariant(ctx, m)
485	return BazelTarget{
486		name: targetName,
487		content: fmt.Sprintf(
488			soongModuleTarget,
489			targetName,
490			ctx.ModuleName(m),
491			canonicalizeModuleType(ctx.ModuleType(m)),
492			ctx.ModuleSubDir(m),
493			depLabelList,
494			attributes),
495	}
496}
497
498func getBuildProperties(ctx bpToBuildContext, m blueprint.Module) BazelAttributes {
499	// TODO: this omits properties for blueprint modules (blueprint_go_binary,
500	// bootstrap_go_binary, bootstrap_go_package), which will have to be handled separately.
501	if aModule, ok := m.(android.Module); ok {
502		return extractModuleProperties(aModule.GetProperties(), false)
503	}
504
505	return BazelAttributes{}
506}
507
508// Generically extract module properties and types into a map, keyed by the module property name.
509func extractModuleProperties(props []interface{}, checkForDuplicateProperties bool) BazelAttributes {
510	ret := map[string]string{}
511
512	// Iterate over this android.Module's property structs.
513	for _, properties := range props {
514		propertiesValue := reflect.ValueOf(properties)
515		// Check that propertiesValue is a pointer to the Properties struct, like
516		// *cc.BaseLinkerProperties or *java.CompilerProperties.
517		//
518		// propertiesValue can also be type-asserted to the structs to
519		// manipulate internal props, if needed.
520		if isStructPtr(propertiesValue.Type()) {
521			structValue := propertiesValue.Elem()
522			for k, v := range extractStructProperties(structValue, 0) {
523				if existing, exists := ret[k]; checkForDuplicateProperties && exists {
524					panic(fmt.Errorf(
525						"%s (%v) is present in properties whereas it should be consolidated into a commonAttributes",
526						k, existing))
527				}
528				ret[k] = v
529			}
530		} else {
531			panic(fmt.Errorf(
532				"properties must be a pointer to a struct, got %T",
533				propertiesValue.Interface()))
534		}
535	}
536
537	return BazelAttributes{
538		Attrs: ret,
539	}
540}
541
542func isStructPtr(t reflect.Type) bool {
543	return t.Kind() == reflect.Ptr && t.Elem().Kind() == reflect.Struct
544}
545
546// prettyPrint a property value into the equivalent Starlark representation
547// recursively.
548func prettyPrint(propertyValue reflect.Value, indent int, emitZeroValues bool) (string, error) {
549	if !emitZeroValues && isZero(propertyValue) {
550		// A property value being set or unset actually matters -- Soong does set default
551		// values for unset properties, like system_shared_libs = ["libc", "libm", "libdl"] at
552		// https://cs.android.com/android/platform/superproject/+/master:build/soong/cc/linker.go;l=281-287;drc=f70926eef0b9b57faf04c17a1062ce50d209e480
553		//
554		// In Bazel-parlance, we would use "attr.<type>(default = <default
555		// value>)" to set the default value of unset attributes. In the cases
556		// where the bp2build converter didn't set the default value within the
557		// mutator when creating the BazelTargetModule, this would be a zero
558		// value. For those cases, we return an empty string so we don't
559		// unnecessarily generate empty values.
560		return "", nil
561	}
562
563	switch propertyValue.Kind() {
564	case reflect.String:
565		return fmt.Sprintf("\"%v\"", escapeString(propertyValue.String())), nil
566	case reflect.Bool:
567		return starlark_fmt.PrintBool(propertyValue.Bool()), nil
568	case reflect.Int, reflect.Uint, reflect.Int64:
569		return fmt.Sprintf("%v", propertyValue.Interface()), nil
570	case reflect.Ptr:
571		return prettyPrint(propertyValue.Elem(), indent, emitZeroValues)
572	case reflect.Slice:
573		elements := make([]string, 0, propertyValue.Len())
574		for i := 0; i < propertyValue.Len(); i++ {
575			val, err := prettyPrint(propertyValue.Index(i), indent, emitZeroValues)
576			if err != nil {
577				return "", err
578			}
579			if val != "" {
580				elements = append(elements, val)
581			}
582		}
583		return starlark_fmt.PrintList(elements, indent, func(s string) string {
584			return "%s"
585		}), nil
586
587	case reflect.Struct:
588		// Special cases where the bp2build sends additional information to the codegenerator
589		// by wrapping the attributes in a custom struct type.
590		if attr, ok := propertyValue.Interface().(bazel.Attribute); ok {
591			return prettyPrintAttribute(attr, indent)
592		} else if label, ok := propertyValue.Interface().(bazel.Label); ok {
593			return fmt.Sprintf("%q", label.Label), nil
594		}
595
596		// Sort and print the struct props by the key.
597		structProps := extractStructProperties(propertyValue, indent)
598		if len(structProps) == 0 {
599			return "", nil
600		}
601		return starlark_fmt.PrintDict(structProps, indent), nil
602	case reflect.Interface:
603		// TODO(b/164227191): implement pretty print for interfaces.
604		// Interfaces are used for for arch, multilib and target properties.
605		return "", nil
606	default:
607		return "", fmt.Errorf(
608			"unexpected kind for property struct field: %s", propertyValue.Kind())
609	}
610}
611
612// Converts a reflected property struct value into a map of property names and property values,
613// which each property value correctly pretty-printed and indented at the right nest level,
614// since property structs can be nested. In Starlark, nested structs are represented as nested
615// dicts: https://docs.bazel.build/skylark/lib/dict.html
616func extractStructProperties(structValue reflect.Value, indent int) map[string]string {
617	if structValue.Kind() != reflect.Struct {
618		panic(fmt.Errorf("Expected a reflect.Struct type, but got %s", structValue.Kind()))
619	}
620
621	ret := map[string]string{}
622	structType := structValue.Type()
623	for i := 0; i < structValue.NumField(); i++ {
624		field := structType.Field(i)
625		if shouldSkipStructField(field) {
626			continue
627		}
628
629		fieldValue := structValue.Field(i)
630		if isZero(fieldValue) {
631			// Ignore zero-valued fields
632			continue
633		}
634
635		// if the struct is embedded (anonymous), flatten the properties into the containing struct
636		if field.Anonymous {
637			if field.Type.Kind() == reflect.Ptr {
638				fieldValue = fieldValue.Elem()
639			}
640			if fieldValue.Type().Kind() == reflect.Struct {
641				propsToMerge := extractStructProperties(fieldValue, indent)
642				for prop, value := range propsToMerge {
643					ret[prop] = value
644				}
645				continue
646			}
647		}
648
649		propertyName := proptools.PropertyNameForField(field.Name)
650		prettyPrintedValue, err := prettyPrint(fieldValue, indent+1, false)
651		if err != nil {
652			panic(
653				fmt.Errorf(
654					"Error while parsing property: %q. %s",
655					propertyName,
656					err))
657		}
658		if prettyPrintedValue != "" {
659			ret[propertyName] = prettyPrintedValue
660		}
661	}
662
663	return ret
664}
665
666func isZero(value reflect.Value) bool {
667	switch value.Kind() {
668	case reflect.Func, reflect.Map, reflect.Slice:
669		return value.IsNil()
670	case reflect.Array:
671		valueIsZero := true
672		for i := 0; i < value.Len(); i++ {
673			valueIsZero = valueIsZero && isZero(value.Index(i))
674		}
675		return valueIsZero
676	case reflect.Struct:
677		valueIsZero := true
678		for i := 0; i < value.NumField(); i++ {
679			valueIsZero = valueIsZero && isZero(value.Field(i))
680		}
681		return valueIsZero
682	case reflect.Ptr:
683		if !value.IsNil() {
684			return isZero(reflect.Indirect(value))
685		} else {
686			return true
687		}
688	// Always print bool/strings, if you want a bool/string attribute to be able to take the default value, use a
689	// pointer instead
690	case reflect.Bool, reflect.String:
691		return false
692	default:
693		if !value.IsValid() {
694			return true
695		}
696		zeroValue := reflect.Zero(value.Type())
697		result := value.Interface() == zeroValue.Interface()
698		return result
699	}
700}
701
702func escapeString(s string) string {
703	s = strings.ReplaceAll(s, "\\", "\\\\")
704
705	// b/184026959: Reverse the application of some common control sequences.
706	// These must be generated literally in the BUILD file.
707	s = strings.ReplaceAll(s, "\t", "\\t")
708	s = strings.ReplaceAll(s, "\n", "\\n")
709	s = strings.ReplaceAll(s, "\r", "\\r")
710
711	return strings.ReplaceAll(s, "\"", "\\\"")
712}
713
714func targetNameWithVariant(c bpToBuildContext, logicModule blueprint.Module) string {
715	name := ""
716	if c.ModuleSubDir(logicModule) != "" {
717		// TODO(b/162720883): Figure out a way to drop the "--" variant suffixes.
718		name = c.ModuleName(logicModule) + "--" + c.ModuleSubDir(logicModule)
719	} else {
720		name = c.ModuleName(logicModule)
721	}
722
723	return strings.Replace(name, "//", "", 1)
724}
725
726func qualifiedTargetLabel(c bpToBuildContext, logicModule blueprint.Module) string {
727	return fmt.Sprintf("//%s:%s", c.ModuleDir(logicModule), targetNameWithVariant(c, logicModule))
728}
729