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