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