• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1// Copyright 2021 Google Inc. All rights reserved.
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7//     http://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14
15package android
16
17import (
18	"bufio"
19	"errors"
20	"strings"
21
22	"github.com/google/blueprint"
23	"github.com/google/blueprint/proptools"
24
25	"android/soong/android/allowlists"
26)
27
28const (
29	// A sentinel value to be used as a key in Bp2BuildConfig for modules with
30	// no package path. This is also the module dir for top level Android.bp
31	// modules.
32	Bp2BuildTopLevel = "."
33)
34
35// FileGroupAsLibrary describes a filegroup module that is converted to some library
36// such as aidl_library or proto_library.
37type FileGroupAsLibrary interface {
38	ShouldConvertToAidlLibrary(ctx BazelConversionPathContext) bool
39	ShouldConvertToProtoLibrary(ctx BazelConversionPathContext) bool
40	GetAidlLibraryLabel(ctx BazelConversionPathContext) string
41	GetProtoLibraryLabel(ctx BazelConversionPathContext) string
42}
43
44type BazelConversionStatus struct {
45	// Information about _all_ bp2build targets generated by this module. Multiple targets are
46	// supported as Soong handles some things within a single target that we may choose to split into
47	// multiple targets, e.g. renderscript, protos, yacc within a cc module.
48	Bp2buildInfo []bp2buildInfo `blueprint:"mutated"`
49
50	// UnconvertedBp2buildDep stores the module names of direct dependency that were not converted to
51	// Bazel
52	UnconvertedDeps []string `blueprint:"mutated"`
53
54	// MissingBp2buildDep stores the module names of direct dependency that were not found
55	MissingDeps []string `blueprint:"mutated"`
56}
57
58type bazelModuleProperties struct {
59	// The label of the Bazel target replacing this Soong module. When run in conversion mode, this
60	// will import the handcrafted build target into the autogenerated file. Note: this may result in
61	// a conflict due to duplicate targets if bp2build_available is also set.
62	Label *string
63
64	// If true, bp2build will generate the converted Bazel target for this module. Note: this may
65	// cause a conflict due to the duplicate targets if label is also set.
66	//
67	// This is a bool pointer to support tristates: true, false, not set.
68	//
69	// To opt in a module, set bazel_module: { bp2build_available: true }
70	// To opt out a module, set bazel_module: { bp2build_available: false }
71	// To defer the default setting for the directory, do not set the value.
72	Bp2build_available *bool
73
74	// CanConvertToBazel is set via InitBazelModule to indicate that a module type can be converted to
75	// Bazel with Bp2build.
76	CanConvertToBazel bool `blueprint:"mutated"`
77}
78
79// Properties contains common module properties for Bazel migration purposes.
80type properties struct {
81	// In "Bazel mixed build" mode, this represents the Bazel target replacing
82	// this Soong module.
83	Bazel_module bazelModuleProperties
84}
85
86// namespacedVariableProperties is a map from a string representing a Soong
87// config variable namespace, like "android" or "vendor_name" to a slice of
88// pointer to a struct containing a single field called Soong_config_variables
89// whose value mirrors the structure in the Blueprint file.
90type namespacedVariableProperties map[string][]interface{}
91
92// BazelModuleBase contains the property structs with metadata for modules which can be converted to
93// Bazel.
94type BazelModuleBase struct {
95	bazelProperties properties
96
97	// namespacedVariableProperties is used for soong_config_module_type support
98	// in bp2build. Soong config modules allow users to set module properties
99	// based on custom product variables defined in Android.bp files. These
100	// variables are namespaced to prevent clobbering, especially when set from
101	// Makefiles.
102	namespacedVariableProperties namespacedVariableProperties
103
104	// baseModuleType is set when this module was created from a module type
105	// defined by a soong_config_module_type. Every soong_config_module_type
106	// "wraps" another module type, e.g. a soong_config_module_type can wrap a
107	// cc_defaults to a custom_cc_defaults, or cc_binary to a custom_cc_binary.
108	// This baseModuleType is set to the wrapped module type.
109	baseModuleType string
110}
111
112// Bazelable is specifies the interface for modules that can be converted to Bazel.
113type Bazelable interface {
114	bazelProps() *properties
115	HasHandcraftedLabel() bool
116	HandcraftedLabel() string
117	GetBazelLabel(ctx BazelConversionPathContext, module blueprint.Module) string
118	ShouldConvertWithBp2build(ctx BazelConversionContext) bool
119	shouldConvertWithBp2build(ctx bazelOtherModuleContext, module blueprint.Module) bool
120	ConvertWithBp2build(ctx TopDownMutatorContext)
121
122	// namespacedVariableProps is a map from a soong config variable namespace
123	// (e.g. acme, android) to a map of interfaces{}, which are really
124	// reflect.Struct pointers, representing the value of the
125	// soong_config_variables property of a module. The struct pointer is the
126	// one with the single member called Soong_config_variables, which itself is
127	// a struct containing fields for each supported feature in that namespace.
128	//
129	// The reason for using a slice of interface{} is to support defaults
130	// propagation of the struct pointers.
131	namespacedVariableProps() namespacedVariableProperties
132	setNamespacedVariableProps(props namespacedVariableProperties)
133	BaseModuleType() string
134	SetBaseModuleType(baseModuleType string)
135}
136
137// ApiProvider is implemented by modules that contribute to an API surface
138type ApiProvider interface {
139	ConvertWithApiBp2build(ctx TopDownMutatorContext)
140}
141
142// MixedBuildBuildable is an interface that module types should implement in order
143// to be "handled by Bazel" in a mixed build.
144type MixedBuildBuildable interface {
145	// IsMixedBuildSupported returns true if and only if this module should be
146	// "handled by Bazel" in a mixed build.
147	// This "escape hatch" allows modules with corner-case scenarios to opt out
148	// of being built with Bazel.
149	IsMixedBuildSupported(ctx BaseModuleContext) bool
150
151	// QueueBazelCall invokes request-queueing functions on the BazelContext
152	// so that these requests are handled when Bazel's cquery is invoked.
153	QueueBazelCall(ctx BaseModuleContext)
154
155	// ProcessBazelQueryResponse uses Bazel information (obtained from the BazelContext)
156	// to set module fields and providers to propagate this module's metadata upstream.
157	// This effectively "bridges the gap" between Bazel and Soong in a mixed build.
158	// Soong modules depending on this module should be oblivious to the fact that
159	// this module was handled by Bazel.
160	ProcessBazelQueryResponse(ctx ModuleContext)
161}
162
163// BazelModule is a lightweight wrapper interface around Module for Bazel-convertible modules.
164type BazelModule interface {
165	Module
166	Bazelable
167}
168
169// InitBazelModule is a wrapper function that decorates a BazelModule with Bazel-conversion
170// properties.
171func InitBazelModule(module BazelModule) {
172	module.AddProperties(module.bazelProps())
173	module.bazelProps().Bazel_module.CanConvertToBazel = true
174}
175
176// bazelProps returns the Bazel properties for the given BazelModuleBase.
177func (b *BazelModuleBase) bazelProps() *properties {
178	return &b.bazelProperties
179}
180
181func (b *BazelModuleBase) namespacedVariableProps() namespacedVariableProperties {
182	return b.namespacedVariableProperties
183}
184
185func (b *BazelModuleBase) setNamespacedVariableProps(props namespacedVariableProperties) {
186	b.namespacedVariableProperties = props
187}
188
189func (b *BazelModuleBase) BaseModuleType() string {
190	return b.baseModuleType
191}
192
193func (b *BazelModuleBase) SetBaseModuleType(baseModuleType string) {
194	b.baseModuleType = baseModuleType
195}
196
197// HasHandcraftedLabel returns whether this module has a handcrafted Bazel label.
198func (b *BazelModuleBase) HasHandcraftedLabel() bool {
199	return b.bazelProperties.Bazel_module.Label != nil
200}
201
202// HandcraftedLabel returns the handcrafted label for this module, or empty string if there is none
203func (b *BazelModuleBase) HandcraftedLabel() string {
204	return proptools.String(b.bazelProperties.Bazel_module.Label)
205}
206
207// GetBazelLabel returns the Bazel label for the given BazelModuleBase.
208func (b *BazelModuleBase) GetBazelLabel(ctx BazelConversionPathContext, module blueprint.Module) string {
209	if b.HasHandcraftedLabel() {
210		return b.HandcraftedLabel()
211	}
212	if b.ShouldConvertWithBp2build(ctx) {
213		return bp2buildModuleLabel(ctx, module)
214	}
215	return "" // no label for unconverted module
216}
217
218type Bp2BuildConversionAllowlist struct {
219	// Configure modules in these directories to enable bp2build_available: true or false by default.
220	defaultConfig allowlists.Bp2BuildConfig
221
222	// Keep any existing BUILD files (and do not generate new BUILD files) for these directories
223	// in the synthetic Bazel workspace.
224	keepExistingBuildFile map[string]bool
225
226	// Per-module allowlist to always opt modules into both bp2build and Bazel Dev Mode mixed
227	// builds. These modules are usually in directories with many other modules that are not ready
228	// for conversion.
229	//
230	// A module can either be in this list or its directory allowlisted entirely
231	// in bp2buildDefaultConfig, but not both at the same time.
232	moduleAlwaysConvert map[string]bool
233
234	// Per-module-type allowlist to always opt modules in to both bp2build and
235	// Bazel Dev Mode mixed builds when they have the same type as one listed.
236	moduleTypeAlwaysConvert map[string]bool
237
238	// Per-module denylist to always opt modules out of bp2build conversion.
239	moduleDoNotConvert map[string]bool
240}
241
242// NewBp2BuildAllowlist creates a new, empty Bp2BuildConversionAllowlist
243// which can be populated using builder pattern Set* methods
244func NewBp2BuildAllowlist() Bp2BuildConversionAllowlist {
245	return Bp2BuildConversionAllowlist{
246		allowlists.Bp2BuildConfig{},
247		map[string]bool{},
248		map[string]bool{},
249		map[string]bool{},
250		map[string]bool{},
251	}
252}
253
254// SetDefaultConfig copies the entries from defaultConfig into the allowlist
255func (a Bp2BuildConversionAllowlist) SetDefaultConfig(defaultConfig allowlists.Bp2BuildConfig) Bp2BuildConversionAllowlist {
256	if a.defaultConfig == nil {
257		a.defaultConfig = allowlists.Bp2BuildConfig{}
258	}
259	for k, v := range defaultConfig {
260		a.defaultConfig[k] = v
261	}
262
263	return a
264}
265
266// SetKeepExistingBuildFile copies the entries from keepExistingBuildFile into the allowlist
267func (a Bp2BuildConversionAllowlist) SetKeepExistingBuildFile(keepExistingBuildFile map[string]bool) Bp2BuildConversionAllowlist {
268	if a.keepExistingBuildFile == nil {
269		a.keepExistingBuildFile = map[string]bool{}
270	}
271	for k, v := range keepExistingBuildFile {
272		a.keepExistingBuildFile[k] = v
273	}
274
275	return a
276}
277
278// SetModuleAlwaysConvertList copies the entries from moduleAlwaysConvert into the allowlist
279func (a Bp2BuildConversionAllowlist) SetModuleAlwaysConvertList(moduleAlwaysConvert []string) Bp2BuildConversionAllowlist {
280	if a.moduleAlwaysConvert == nil {
281		a.moduleAlwaysConvert = map[string]bool{}
282	}
283	for _, m := range moduleAlwaysConvert {
284		a.moduleAlwaysConvert[m] = true
285	}
286
287	return a
288}
289
290// SetModuleTypeAlwaysConvertList copies the entries from moduleTypeAlwaysConvert into the allowlist
291func (a Bp2BuildConversionAllowlist) SetModuleTypeAlwaysConvertList(moduleTypeAlwaysConvert []string) Bp2BuildConversionAllowlist {
292	if a.moduleTypeAlwaysConvert == nil {
293		a.moduleTypeAlwaysConvert = map[string]bool{}
294	}
295	for _, m := range moduleTypeAlwaysConvert {
296		a.moduleTypeAlwaysConvert[m] = true
297	}
298
299	return a
300}
301
302// SetModuleDoNotConvertList copies the entries from moduleDoNotConvert into the allowlist
303func (a Bp2BuildConversionAllowlist) SetModuleDoNotConvertList(moduleDoNotConvert []string) Bp2BuildConversionAllowlist {
304	if a.moduleDoNotConvert == nil {
305		a.moduleDoNotConvert = map[string]bool{}
306	}
307	for _, m := range moduleDoNotConvert {
308		a.moduleDoNotConvert[m] = true
309	}
310
311	return a
312}
313
314// ShouldKeepExistingBuildFileForDir returns whether an existing BUILD file should be
315// added to the build symlink forest based on the current global configuration.
316func (a Bp2BuildConversionAllowlist) ShouldKeepExistingBuildFileForDir(dir string) bool {
317	if _, ok := a.keepExistingBuildFile[dir]; ok {
318		// Exact dir match
319		return true
320	}
321	var i int
322	// Check if subtree match
323	for {
324		j := strings.Index(dir[i:], "/")
325		if j == -1 {
326			return false //default
327		}
328		prefix := dir[0 : i+j]
329		i = i + j + 1 // skip the "/"
330		if recursive, ok := a.keepExistingBuildFile[prefix]; ok && recursive {
331			return true
332		}
333	}
334}
335
336var bp2BuildAllowListKey = NewOnceKey("Bp2BuildAllowlist")
337var bp2buildAllowlist OncePer
338
339func GetBp2BuildAllowList() Bp2BuildConversionAllowlist {
340	return bp2buildAllowlist.Once(bp2BuildAllowListKey, func() interface{} {
341		return NewBp2BuildAllowlist().SetDefaultConfig(allowlists.Bp2buildDefaultConfig).
342			SetKeepExistingBuildFile(allowlists.Bp2buildKeepExistingBuildFile).
343			SetModuleAlwaysConvertList(allowlists.Bp2buildModuleAlwaysConvertList).
344			SetModuleTypeAlwaysConvertList(allowlists.Bp2buildModuleTypeAlwaysConvertList).
345			SetModuleDoNotConvertList(allowlists.Bp2buildModuleDoNotConvertList)
346	}).(Bp2BuildConversionAllowlist)
347}
348
349// MixedBuildsEnabled returns true if a module is ready to be replaced by a
350// converted or handcrafted Bazel target. As a side effect, calling this
351// method will also log whether this module is mixed build enabled for
352// metrics reporting.
353func MixedBuildsEnabled(ctx BaseModuleContext) bool {
354	module := ctx.Module()
355	apexInfo := ctx.Provider(ApexInfoProvider).(ApexInfo)
356	withinApex := !apexInfo.IsForPlatform()
357	mixedBuildEnabled := ctx.Config().IsMixedBuildsEnabled() &&
358		ctx.Os() != Windows && // Windows toolchains are not currently supported.
359		ctx.Os() != LinuxBionic && // Linux Bionic toolchains are not currently supported.
360		ctx.Os() != LinuxMusl && // Linux musl toolchains are not currently supported (b/259266326).
361		ctx.Arch().ArchType != Riscv64 && // TODO(b/262192655) Riscv64 toolchains are not currently supported.
362		module.Enabled() &&
363		convertedToBazel(ctx, module) &&
364		ctx.Config().BazelContext.IsModuleNameAllowed(module.Name(), withinApex)
365	ctx.Config().LogMixedBuild(ctx, mixedBuildEnabled)
366	return mixedBuildEnabled
367}
368
369// ConvertedToBazel returns whether this module has been converted (with bp2build or manually) to Bazel.
370func convertedToBazel(ctx BazelConversionContext, module blueprint.Module) bool {
371	b, ok := module.(Bazelable)
372	if !ok {
373		return false
374	}
375	return b.shouldConvertWithBp2build(ctx, module) || b.HasHandcraftedLabel()
376}
377
378// ShouldConvertWithBp2build returns whether the given BazelModuleBase should be converted with bp2build
379func (b *BazelModuleBase) ShouldConvertWithBp2build(ctx BazelConversionContext) bool {
380	return b.shouldConvertWithBp2build(ctx, ctx.Module())
381}
382
383type bazelOtherModuleContext interface {
384	ModuleErrorf(format string, args ...interface{})
385	Config() Config
386	OtherModuleType(m blueprint.Module) string
387	OtherModuleName(m blueprint.Module) string
388	OtherModuleDir(m blueprint.Module) string
389}
390
391func (b *BazelModuleBase) shouldConvertWithBp2build(ctx bazelOtherModuleContext, module blueprint.Module) bool {
392	if !b.bazelProps().Bazel_module.CanConvertToBazel {
393		return false
394	}
395
396	// In api_bp2build mode, all soong modules that can provide API contributions should be converted
397	// This is irrespective of its presence/absence in bp2build allowlists
398	if ctx.Config().BuildMode == ApiBp2build {
399		_, providesApis := module.(ApiProvider)
400		return providesApis
401	}
402
403	propValue := b.bazelProperties.Bazel_module.Bp2build_available
404	packagePath := ctx.OtherModuleDir(module)
405
406	// Modules in unit tests which are enabled in the allowlist by type or name
407	// trigger this conditional because unit tests run under the "." package path
408	isTestModule := packagePath == Bp2BuildTopLevel && proptools.BoolDefault(propValue, false)
409	if isTestModule {
410		return true
411	}
412
413	moduleName := module.Name()
414	allowlist := ctx.Config().Bp2buildPackageConfig
415	moduleNameAllowed := allowlist.moduleAlwaysConvert[moduleName]
416	moduleTypeAllowed := allowlist.moduleTypeAlwaysConvert[ctx.OtherModuleType(module)]
417	allowlistConvert := moduleNameAllowed || moduleTypeAllowed
418	if moduleNameAllowed && moduleTypeAllowed {
419		ctx.ModuleErrorf("A module cannot be in moduleAlwaysConvert and also be in moduleTypeAlwaysConvert")
420		return false
421	}
422
423	if allowlist.moduleDoNotConvert[moduleName] {
424		if moduleNameAllowed {
425			ctx.ModuleErrorf("a module cannot be in moduleDoNotConvert and also be in moduleAlwaysConvert")
426		}
427		return false
428	}
429
430	// This is a tristate value: true, false, or unset.
431	if ok, directoryPath := bp2buildDefaultTrueRecursively(packagePath, allowlist.defaultConfig); ok {
432		if moduleNameAllowed {
433			ctx.ModuleErrorf("A module cannot be in a directory marked Bp2BuildDefaultTrue"+
434				" or Bp2BuildDefaultTrueRecursively and also be in moduleAlwaysConvert. Directory: '%s'"+
435				" Module: '%s'", directoryPath, moduleName)
436			return false
437		}
438
439		// Allow modules to explicitly opt-out.
440		return proptools.BoolDefault(propValue, true)
441	}
442
443	// Allow modules to explicitly opt-in.
444	return proptools.BoolDefault(propValue, allowlistConvert)
445}
446
447// bp2buildDefaultTrueRecursively checks that the package contains a prefix from the
448// set of package prefixes where all modules must be converted. That is, if the
449// package is x/y/z, and the list contains either x, x/y, or x/y/z, this function will
450// return true.
451//
452// However, if the package is x/y, and it matches a Bp2BuildDefaultFalse "x/y" entry
453// exactly, this module will return false early.
454//
455// This function will also return false if the package doesn't match anything in
456// the config.
457//
458// This function will also return the allowlist entry which caused a particular
459// package to be enabled. Since packages can be enabled via a recursive declaration,
460// the path returned will not always be the same as the one provided.
461func bp2buildDefaultTrueRecursively(packagePath string, config allowlists.Bp2BuildConfig) (bool, string) {
462	// Check if the package path has an exact match in the config.
463	if config[packagePath] == allowlists.Bp2BuildDefaultTrue || config[packagePath] == allowlists.Bp2BuildDefaultTrueRecursively {
464		return true, packagePath
465	} else if config[packagePath] == allowlists.Bp2BuildDefaultFalse || config[packagePath] == allowlists.Bp2BuildDefaultFalseRecursively {
466		return false, packagePath
467	}
468
469	// If not, check for the config recursively.
470	packagePrefix := packagePath
471
472	// e.g. for x/y/z, iterate over x/y, then x, taking the most-specific value from the allowlist.
473	for strings.Contains(packagePrefix, "/") {
474		dirIndex := strings.LastIndex(packagePrefix, "/")
475		packagePrefix = packagePrefix[:dirIndex]
476		switch value := config[packagePrefix]; value {
477		case allowlists.Bp2BuildDefaultTrueRecursively:
478			// package contains this prefix and this prefix should convert all modules
479			return true, packagePrefix
480		case allowlists.Bp2BuildDefaultFalseRecursively:
481			//package contains this prefix and this prefix should NOT convert any modules
482			return false, packagePrefix
483		}
484		// Continue to the next part of the package dir.
485
486	}
487
488	return false, packagePath
489}
490
491func registerBp2buildConversionMutator(ctx RegisterMutatorsContext) {
492	ctx.TopDown("bp2build_conversion", convertWithBp2build).Parallel()
493}
494
495func convertWithBp2build(ctx TopDownMutatorContext) {
496	bModule, ok := ctx.Module().(Bazelable)
497	if !ok || !bModule.shouldConvertWithBp2build(ctx, ctx.Module()) {
498		return
499	}
500
501	bModule.ConvertWithBp2build(ctx)
502}
503
504func registerApiBp2buildConversionMutator(ctx RegisterMutatorsContext) {
505	ctx.TopDown("apiBp2build_conversion", convertWithApiBp2build).Parallel()
506}
507
508// Generate API contribution targets if the Soong module provides APIs
509func convertWithApiBp2build(ctx TopDownMutatorContext) {
510	if m, ok := ctx.Module().(ApiProvider); ok {
511		m.ConvertWithApiBp2build(ctx)
512	}
513}
514
515// GetMainClassInManifest scans the manifest file specified in filepath and returns
516// the value of attribute Main-Class in the manifest file if it exists, or returns error.
517// WARNING: this is for bp2build converters of java_* modules only.
518func GetMainClassInManifest(c Config, filepath string) (string, error) {
519	file, err := c.fs.Open(filepath)
520	if err != nil {
521		return "", err
522	}
523	defer file.Close()
524	scanner := bufio.NewScanner(file)
525	for scanner.Scan() {
526		line := scanner.Text()
527		if strings.HasPrefix(line, "Main-Class:") {
528			return strings.TrimSpace(line[len("Main-Class:"):]), nil
529		}
530	}
531
532	return "", errors.New("Main-Class is not found.")
533}
534
535func AttachValidationActions(ctx ModuleContext, outputFilePath Path, validations Paths) ModuleOutPath {
536	validatedOutputFilePath := PathForModuleOut(ctx, "validated", outputFilePath.Base())
537	ctx.Build(pctx, BuildParams{
538		Rule:        CpNoPreserveSymlink,
539		Description: "run validations " + outputFilePath.Base(),
540		Output:      validatedOutputFilePath,
541		Input:       outputFilePath,
542		Validations: validations,
543	})
544	return validatedOutputFilePath
545}
546