• 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	"fmt"
21	"io/ioutil"
22	"path/filepath"
23	"strings"
24
25	"github.com/google/blueprint"
26	"github.com/google/blueprint/proptools"
27
28	"android/soong/android/allowlists"
29)
30
31const (
32	// A sentinel value to be used as a key in Bp2BuildConfig for modules with
33	// no package path. This is also the module dir for top level Android.bp
34	// modules.
35	Bp2BuildTopLevel = "."
36)
37
38type bazelModuleProperties struct {
39	// The label of the Bazel target replacing this Soong module. When run in conversion mode, this
40	// will import the handcrafted build target into the autogenerated file. Note: this may result in
41	// a conflict due to duplicate targets if bp2build_available is also set.
42	Label *string
43
44	// If true, bp2build will generate the converted Bazel target for this module. Note: this may
45	// cause a conflict due to the duplicate targets if label is also set.
46	//
47	// This is a bool pointer to support tristates: true, false, not set.
48	//
49	// To opt-in a module, set bazel_module: { bp2build_available: true }
50	// To opt-out a module, set bazel_module: { bp2build_available: false }
51	// To defer the default setting for the directory, do not set the value.
52	Bp2build_available *bool
53
54	// CanConvertToBazel is set via InitBazelModule to indicate that a module type can be converted to
55	// Bazel with Bp2build.
56	CanConvertToBazel bool `blueprint:"mutated"`
57}
58
59// Properties contains common module properties for Bazel migration purposes.
60type properties struct {
61	// In USE_BAZEL_ANALYSIS=1 mode, this represents the Bazel target replacing
62	// this Soong module.
63	Bazel_module bazelModuleProperties
64}
65
66// namespacedVariableProperties is a map from a string representing a Soong
67// config variable namespace, like "android" or "vendor_name" to a slice of
68// pointer to a struct containing a single field called Soong_config_variables
69// whose value mirrors the structure in the Blueprint file.
70type namespacedVariableProperties map[string][]interface{}
71
72// BazelModuleBase contains the property structs with metadata for modules which can be converted to
73// Bazel.
74type BazelModuleBase struct {
75	bazelProperties properties
76
77	// namespacedVariableProperties is used for soong_config_module_type support
78	// in bp2build. Soong config modules allow users to set module properties
79	// based on custom product variables defined in Android.bp files. These
80	// variables are namespaced to prevent clobbering, especially when set from
81	// Makefiles.
82	namespacedVariableProperties namespacedVariableProperties
83
84	// baseModuleType is set when this module was created from a module type
85	// defined by a soong_config_module_type. Every soong_config_module_type
86	// "wraps" another module type, e.g. a soong_config_module_type can wrap a
87	// cc_defaults to a custom_cc_defaults, or cc_binary to a custom_cc_binary.
88	// This baseModuleType is set to the wrapped module type.
89	baseModuleType string
90}
91
92// Bazelable is specifies the interface for modules that can be converted to Bazel.
93type Bazelable interface {
94	bazelProps() *properties
95	HasHandcraftedLabel() bool
96	HandcraftedLabel() string
97	GetBazelLabel(ctx BazelConversionPathContext, module blueprint.Module) string
98	ShouldConvertWithBp2build(ctx BazelConversionContext) bool
99	shouldConvertWithBp2build(ctx bazelOtherModuleContext, module blueprint.Module) bool
100	GetBazelBuildFileContents(c Config, path, name string) (string, error)
101	ConvertWithBp2build(ctx TopDownMutatorContext)
102
103	// namespacedVariableProps is a map from a soong config variable namespace
104	// (e.g. acme, android) to a map of interfaces{}, which are really
105	// reflect.Struct pointers, representing the value of the
106	// soong_config_variables property of a module. The struct pointer is the
107	// one with the single member called Soong_config_variables, which itself is
108	// a struct containing fields for each supported feature in that namespace.
109	//
110	// The reason for using an slice of interface{} is to support defaults
111	// propagation of the struct pointers.
112	namespacedVariableProps() namespacedVariableProperties
113	setNamespacedVariableProps(props namespacedVariableProperties)
114	BaseModuleType() string
115	SetBaseModuleType(baseModuleType string)
116}
117
118// BazelModule is a lightweight wrapper interface around Module for Bazel-convertible modules.
119type BazelModule interface {
120	Module
121	Bazelable
122}
123
124// InitBazelModule is a wrapper function that decorates a BazelModule with Bazel-conversion
125// properties.
126func InitBazelModule(module BazelModule) {
127	module.AddProperties(module.bazelProps())
128	module.bazelProps().Bazel_module.CanConvertToBazel = true
129}
130
131// bazelProps returns the Bazel properties for the given BazelModuleBase.
132func (b *BazelModuleBase) bazelProps() *properties {
133	return &b.bazelProperties
134}
135
136func (b *BazelModuleBase) namespacedVariableProps() namespacedVariableProperties {
137	return b.namespacedVariableProperties
138}
139
140func (b *BazelModuleBase) setNamespacedVariableProps(props namespacedVariableProperties) {
141	b.namespacedVariableProperties = props
142}
143
144func (b *BazelModuleBase) BaseModuleType() string {
145	return b.baseModuleType
146}
147
148func (b *BazelModuleBase) SetBaseModuleType(baseModuleType string) {
149	b.baseModuleType = baseModuleType
150}
151
152// HasHandcraftedLabel returns whether this module has a handcrafted Bazel label.
153func (b *BazelModuleBase) HasHandcraftedLabel() bool {
154	return b.bazelProperties.Bazel_module.Label != nil
155}
156
157// HandcraftedLabel returns the handcrafted label for this module, or empty string if there is none
158func (b *BazelModuleBase) HandcraftedLabel() string {
159	return proptools.String(b.bazelProperties.Bazel_module.Label)
160}
161
162// GetBazelLabel returns the Bazel label for the given BazelModuleBase.
163func (b *BazelModuleBase) GetBazelLabel(ctx BazelConversionPathContext, module blueprint.Module) string {
164	if b.HasHandcraftedLabel() {
165		return b.HandcraftedLabel()
166	}
167	if b.ShouldConvertWithBp2build(ctx) {
168		return bp2buildModuleLabel(ctx, module)
169	}
170	return "" // no label for unconverted module
171}
172
173type bp2BuildConversionAllowlist struct {
174	// Configure modules in these directories to enable bp2build_available: true or false by default.
175	defaultConfig allowlists.Bp2BuildConfig
176
177	// Keep any existing BUILD files (and do not generate new BUILD files) for these directories
178	// in the synthetic Bazel workspace.
179	keepExistingBuildFile map[string]bool
180
181	// Per-module allowlist to always opt modules in of both bp2build and mixed builds.
182	// These modules are usually in directories with many other modules that are not ready for
183	// conversion.
184	//
185	// A module can either be in this list or its directory allowlisted entirely
186	// in bp2buildDefaultConfig, but not both at the same time.
187	moduleAlwaysConvert map[string]bool
188
189	// Per-module-type allowlist to always opt modules in to both bp2build and mixed builds
190	// when they have the same type as one listed.
191	moduleTypeAlwaysConvert map[string]bool
192
193	// Per-module denylist to always opt modules out of both bp2build and mixed builds.
194	moduleDoNotConvert map[string]bool
195
196	// Per-module denylist of cc_library modules to only generate the static
197	// variant if their shared variant isn't ready or buildable by Bazel.
198	ccLibraryStaticOnly map[string]bool
199
200	// Per-module denylist to opt modules out of mixed builds. Such modules will
201	// still be generated via bp2build.
202	mixedBuildsDisabled map[string]bool
203}
204
205// NewBp2BuildAllowlist creates a new, empty bp2BuildConversionAllowlist
206// which can be populated using builder pattern Set* methods
207func NewBp2BuildAllowlist() bp2BuildConversionAllowlist {
208	return bp2BuildConversionAllowlist{
209		allowlists.Bp2BuildConfig{},
210		map[string]bool{},
211		map[string]bool{},
212		map[string]bool{},
213		map[string]bool{},
214		map[string]bool{},
215		map[string]bool{},
216	}
217}
218
219// SetDefaultConfig copies the entries from defaultConfig into the allowlist
220func (a bp2BuildConversionAllowlist) SetDefaultConfig(defaultConfig allowlists.Bp2BuildConfig) bp2BuildConversionAllowlist {
221	if a.defaultConfig == nil {
222		a.defaultConfig = allowlists.Bp2BuildConfig{}
223	}
224	for k, v := range defaultConfig {
225		a.defaultConfig[k] = v
226	}
227
228	return a
229}
230
231// SetKeepExistingBuildFile copies the entries from keepExistingBuildFile into the allowlist
232func (a bp2BuildConversionAllowlist) SetKeepExistingBuildFile(keepExistingBuildFile map[string]bool) bp2BuildConversionAllowlist {
233	if a.keepExistingBuildFile == nil {
234		a.keepExistingBuildFile = map[string]bool{}
235	}
236	for k, v := range keepExistingBuildFile {
237		a.keepExistingBuildFile[k] = v
238	}
239
240	return a
241}
242
243// SetModuleAlwaysConvertList copies the entries from moduleAlwaysConvert into the allowlist
244func (a bp2BuildConversionAllowlist) SetModuleAlwaysConvertList(moduleAlwaysConvert []string) bp2BuildConversionAllowlist {
245	if a.moduleAlwaysConvert == nil {
246		a.moduleAlwaysConvert = map[string]bool{}
247	}
248	for _, m := range moduleAlwaysConvert {
249		a.moduleAlwaysConvert[m] = true
250	}
251
252	return a
253}
254
255// SetModuleTypeAlwaysConvertList copies the entries from moduleTypeAlwaysConvert into the allowlist
256func (a bp2BuildConversionAllowlist) SetModuleTypeAlwaysConvertList(moduleTypeAlwaysConvert []string) bp2BuildConversionAllowlist {
257	if a.moduleTypeAlwaysConvert == nil {
258		a.moduleTypeAlwaysConvert = map[string]bool{}
259	}
260	for _, m := range moduleTypeAlwaysConvert {
261		a.moduleTypeAlwaysConvert[m] = true
262	}
263
264	return a
265}
266
267// SetModuleDoNotConvertList copies the entries from moduleDoNotConvert into the allowlist
268func (a bp2BuildConversionAllowlist) SetModuleDoNotConvertList(moduleDoNotConvert []string) bp2BuildConversionAllowlist {
269	if a.moduleDoNotConvert == nil {
270		a.moduleDoNotConvert = map[string]bool{}
271	}
272	for _, m := range moduleDoNotConvert {
273		a.moduleDoNotConvert[m] = true
274	}
275
276	return a
277}
278
279// SetCcLibraryStaticOnlyList copies the entries from ccLibraryStaticOnly into the allowlist
280func (a bp2BuildConversionAllowlist) SetCcLibraryStaticOnlyList(ccLibraryStaticOnly []string) bp2BuildConversionAllowlist {
281	if a.ccLibraryStaticOnly == nil {
282		a.ccLibraryStaticOnly = map[string]bool{}
283	}
284	for _, m := range ccLibraryStaticOnly {
285		a.ccLibraryStaticOnly[m] = true
286	}
287
288	return a
289}
290
291// SetMixedBuildsDisabledList copies the entries from mixedBuildsDisabled into the allowlist
292func (a bp2BuildConversionAllowlist) SetMixedBuildsDisabledList(mixedBuildsDisabled []string) bp2BuildConversionAllowlist {
293	if a.mixedBuildsDisabled == nil {
294		a.mixedBuildsDisabled = map[string]bool{}
295	}
296	for _, m := range mixedBuildsDisabled {
297		a.mixedBuildsDisabled[m] = true
298	}
299
300	return a
301}
302
303var bp2buildAllowlist = NewBp2BuildAllowlist().
304	SetDefaultConfig(allowlists.Bp2buildDefaultConfig).
305	SetKeepExistingBuildFile(allowlists.Bp2buildKeepExistingBuildFile).
306	SetModuleAlwaysConvertList(allowlists.Bp2buildModuleAlwaysConvertList).
307	SetModuleTypeAlwaysConvertList(allowlists.Bp2buildModuleTypeAlwaysConvertList).
308	SetModuleDoNotConvertList(allowlists.Bp2buildModuleDoNotConvertList).
309	SetCcLibraryStaticOnlyList(allowlists.Bp2buildCcLibraryStaticOnlyList).
310	SetMixedBuildsDisabledList(allowlists.MixedBuildsDisabledList)
311
312// GenerateCcLibraryStaticOnly returns whether a cc_library module should only
313// generate a static version of itself based on the current global configuration.
314func GenerateCcLibraryStaticOnly(moduleName string) bool {
315	return bp2buildAllowlist.ccLibraryStaticOnly[moduleName]
316}
317
318// ShouldKeepExistingBuildFileForDir returns whether an existing BUILD file should be
319// added to the build symlink forest based on the current global configuration.
320func ShouldKeepExistingBuildFileForDir(dir string) bool {
321	return shouldKeepExistingBuildFileForDir(bp2buildAllowlist, dir)
322}
323
324func shouldKeepExistingBuildFileForDir(allowlist bp2BuildConversionAllowlist, dir string) bool {
325	if _, ok := allowlist.keepExistingBuildFile[dir]; ok {
326		// Exact dir match
327		return true
328	}
329	// Check if subtree match
330	for prefix, recursive := range allowlist.keepExistingBuildFile {
331		if recursive {
332			if strings.HasPrefix(dir, prefix+"/") {
333				return true
334			}
335		}
336	}
337	// Default
338	return false
339}
340
341// MixedBuildsEnabled checks that a module is ready to be replaced by a
342// converted or handcrafted Bazel target.
343func (b *BazelModuleBase) MixedBuildsEnabled(ctx ModuleContext) bool {
344	if ctx.Os() == Windows {
345		// Windows toolchains are not currently supported.
346		return false
347	}
348	if !ctx.Module().Enabled() {
349		return false
350	}
351	if !ctx.Config().BazelContext.BazelEnabled() {
352		return false
353	}
354	if !convertedToBazel(ctx, ctx.Module()) {
355		return false
356	}
357
358	if GenerateCcLibraryStaticOnly(ctx.Module().Name()) {
359		// Don't use partially-converted cc_library targets in mixed builds,
360		// since mixed builds would generally rely on both static and shared
361		// variants of a cc_library.
362		return false
363	}
364	return !bp2buildAllowlist.mixedBuildsDisabled[ctx.Module().Name()]
365}
366
367// ConvertedToBazel returns whether this module has been converted (with bp2build or manually) to Bazel.
368func convertedToBazel(ctx BazelConversionContext, module blueprint.Module) bool {
369	b, ok := module.(Bazelable)
370	if !ok {
371		return false
372	}
373	return b.shouldConvertWithBp2build(ctx, module) || b.HasHandcraftedLabel()
374}
375
376// ShouldConvertWithBp2build returns whether the given BazelModuleBase should be converted with bp2build
377func (b *BazelModuleBase) ShouldConvertWithBp2build(ctx BazelConversionContext) bool {
378	return b.shouldConvertWithBp2build(ctx, ctx.Module())
379}
380
381type bazelOtherModuleContext interface {
382	ModuleErrorf(format string, args ...interface{})
383	Config() Config
384	OtherModuleType(m blueprint.Module) string
385	OtherModuleName(m blueprint.Module) string
386	OtherModuleDir(m blueprint.Module) string
387}
388
389func (b *BazelModuleBase) shouldConvertWithBp2build(ctx bazelOtherModuleContext, module blueprint.Module) bool {
390	if !b.bazelProps().Bazel_module.CanConvertToBazel {
391		return false
392	}
393
394	propValue := b.bazelProperties.Bazel_module.Bp2build_available
395	packagePath := ctx.OtherModuleDir(module)
396
397	// Modules in unit tests which are enabled in the allowlist by type or name
398	// trigger this conditional because unit tests run under the "." package path
399	isTestModule := packagePath == Bp2BuildTopLevel && proptools.BoolDefault(propValue, false)
400	if isTestModule {
401		return true
402	}
403
404	moduleName := module.Name()
405	allowlist := ctx.Config().bp2buildPackageConfig
406	moduleNameAllowed := allowlist.moduleAlwaysConvert[moduleName]
407	moduleTypeAllowed := allowlist.moduleTypeAlwaysConvert[ctx.OtherModuleType(module)]
408	allowlistConvert := moduleNameAllowed || moduleTypeAllowed
409	if moduleNameAllowed && moduleTypeAllowed {
410		ctx.ModuleErrorf("A module cannot be in moduleAlwaysConvert and also be in moduleTypeAlwaysConvert")
411		return false
412	}
413
414	if allowlist.moduleDoNotConvert[moduleName] {
415		if moduleNameAllowed {
416			ctx.ModuleErrorf("a module cannot be in moduleDoNotConvert and also be in moduleAlwaysConvert")
417		}
418		return false
419	}
420
421	if allowlistConvert && shouldKeepExistingBuildFileForDir(allowlist, packagePath) {
422		if moduleNameAllowed {
423			ctx.ModuleErrorf("A module cannot be in a directory listed in keepExistingBuildFile"+
424				" and also be in moduleAlwaysConvert. Directory: '%s'", packagePath)
425			return false
426		}
427	}
428
429	// This is a tristate value: true, false, or unset.
430	if ok, directoryPath := bp2buildDefaultTrueRecursively(packagePath, allowlist.defaultConfig); ok {
431		if moduleNameAllowed {
432			ctx.ModuleErrorf("A module cannot be in a directory marked Bp2BuildDefaultTrue"+
433				" or Bp2BuildDefaultTrueRecursively and also be in moduleAlwaysConvert. Directory: '%s'",
434				directoryPath)
435			return false
436		}
437
438		// Allow modules to explicitly opt-out.
439		return proptools.BoolDefault(propValue, true)
440	}
441
442	// Allow modules to explicitly opt-in.
443	return proptools.BoolDefault(propValue, allowlistConvert)
444}
445
446// bp2buildDefaultTrueRecursively checks that the package contains a prefix from the
447// set of package prefixes where all modules must be converted. That is, if the
448// package is x/y/z, and the list contains either x, x/y, or x/y/z, this function will
449// return true.
450//
451// However, if the package is x/y, and it matches a Bp2BuildDefaultFalse "x/y" entry
452// exactly, this module will return false early.
453//
454// This function will also return false if the package doesn't match anything in
455// the config.
456//
457// This function will also return the allowlist entry which caused a particular
458// package to be enabled. Since packages can be enabled via a recursive declaration,
459// the path returned will not always be the same as the one provided.
460func bp2buildDefaultTrueRecursively(packagePath string, config allowlists.Bp2BuildConfig) (bool, string) {
461	// Check if the package path has an exact match in the config.
462	if config[packagePath] == allowlists.Bp2BuildDefaultTrue || config[packagePath] == allowlists.Bp2BuildDefaultTrueRecursively {
463		return true, packagePath
464	} else if config[packagePath] == allowlists.Bp2BuildDefaultFalse {
465		return false, packagePath
466	}
467
468	// If not, check for the config recursively.
469	packagePrefix := ""
470	// e.g. for x/y/z, iterate over x, x/y, then x/y/z, taking the final value from the allowlist.
471	for _, part := range strings.Split(packagePath, "/") {
472		packagePrefix += part
473		if config[packagePrefix] == allowlists.Bp2BuildDefaultTrueRecursively {
474			// package contains this prefix and this prefix should convert all modules
475			return true, packagePrefix
476		}
477		// Continue to the next part of the package dir.
478		packagePrefix += "/"
479	}
480
481	return false, packagePath
482}
483
484// GetBazelBuildFileContents returns the file contents of a hand-crafted BUILD file if available or
485// an error if there are errors reading the file.
486// TODO(b/181575318): currently we append the whole BUILD file, let's change that to do
487// something more targeted based on the rule type and target.
488func (b *BazelModuleBase) GetBazelBuildFileContents(c Config, path, name string) (string, error) {
489	if !strings.Contains(b.HandcraftedLabel(), path) {
490		return "", fmt.Errorf("%q not found in bazel_module.label %q", path, b.HandcraftedLabel())
491	}
492	name = filepath.Join(path, name)
493	f, err := c.fs.Open(name)
494	if err != nil {
495		return "", err
496	}
497	defer f.Close()
498
499	data, err := ioutil.ReadAll(f)
500	if err != nil {
501		return "", err
502	}
503	return string(data[:]), nil
504}
505
506func registerBp2buildConversionMutator(ctx RegisterMutatorsContext) {
507	ctx.TopDown("bp2build_conversion", convertWithBp2build).Parallel()
508}
509
510func convertWithBp2build(ctx TopDownMutatorContext) {
511	bModule, ok := ctx.Module().(Bazelable)
512	if !ok || !bModule.shouldConvertWithBp2build(ctx, ctx.Module()) {
513		return
514	}
515
516	bModule.ConvertWithBp2build(ctx)
517}
518
519// GetMainClassInManifest scans the manifest file specified in filepath and returns
520// the value of attribute Main-Class in the manifest file if it exists, or returns error.
521// WARNING: this is for bp2build converters of java_* modules only.
522func GetMainClassInManifest(c Config, filepath string) (string, error) {
523	file, err := c.fs.Open(filepath)
524	if err != nil {
525		return "", err
526	}
527	defer file.Close()
528	scanner := bufio.NewScanner(file)
529	for scanner.Scan() {
530		line := scanner.Text()
531		if strings.HasPrefix(line, "Main-Class:") {
532			return strings.TrimSpace(line[len("Main-Class:"):]), nil
533		}
534	}
535
536	return "", errors.New("Main-Class is not found.")
537}
538