Remove infrastructure to run bp2build

Bug: 315353489
Test: m blueprint_tests
Change-Id: Idcf6377d389b94c39e4e6ff4b8efa8a9f9e78b17
This commit is contained in:
Colin Cross 2023-12-07 16:54:51 -08:00
parent 8ff105860d
commit b63d7b3af7
32 changed files with 62 additions and 9310 deletions

View file

@ -12,14 +12,11 @@ bootstrap_go_package {
"sbox_proto",
"soong",
"soong-android-soongconfig",
"soong-bazel",
"soong-cquery",
"soong-remoteexec",
"soong-response",
"soong-shared",
"soong-starlark",
"soong-starlark-format",
"soong-ui-bp2build_metrics_proto",
"soong-ui-metrics_proto",
"soong-android-allowlists",
@ -38,9 +35,6 @@ bootstrap_go_package {
"arch.go",
"arch_list.go",
"base_module_context.go",
"bazel.go",
"bazel_handler.go",
"bazel_paths.go",
"buildinfo_prop.go",
"config.go",
"test_config.go",
@ -106,9 +100,6 @@ bootstrap_go_package {
"androidmk_test.go",
"apex_test.go",
"arch_test.go",
"bazel_handler_test.go",
"bazel_paths_test.go",
"bazel_test.go",
"config_test.go",
"config_bp2build_test.go",
"configured_jars_test.go",

View file

@ -111,9 +111,6 @@ func (a *allApexContributions) SetPrebuiltSelectionInfoProvider(ctx BaseModuleCo
}
}
if ctx.Config().Bp2buildMode() { // Skip bp2build
return
}
p := PrebuiltSelectionInfoMap{}
ctx.VisitDirectDepsWithTag(acDepTag, func(child Module) {
if m, ok := child.(*apexContributions); ok {

View file

@ -425,7 +425,7 @@ func osMutator(bpctx blueprint.BottomUpMutatorContext) {
// blueprint.BottomUpMutatorContext because android.BottomUpMutatorContext
// filters out non-Soong modules. Now that we've handled them, create a
// normal android.BottomUpMutatorContext.
mctx := bottomUpMutatorContextFactory(bpctx, module, false, false)
mctx := bottomUpMutatorContextFactory(bpctx, module, false)
base := module.base()
@ -570,7 +570,7 @@ func archMutator(bpctx blueprint.BottomUpMutatorContext) {
// blueprint.BottomUpMutatorContext because android.BottomUpMutatorContext
// filters out non-Soong modules. Now that we've handled them, create a
// normal android.BottomUpMutatorContext.
mctx := bottomUpMutatorContextFactory(bpctx, module, false, false)
mctx := bottomUpMutatorContextFactory(bpctx, module, false)
base := module.base()

View file

@ -112,8 +112,6 @@ type BaseModuleContext interface {
// the first DependencyTag.
GetDirectDep(name string) (blueprint.Module, blueprint.DependencyTag)
ModuleFromName(name string) (blueprint.Module, bool)
// VisitDirectDepsBlueprint calls visit for each direct dependency. If there are multiple
// direct dependencies on the same module visit will be called multiple times on that module
// and OtherModuleDependencyTag will return a different tag for each.
@ -209,12 +207,6 @@ type BaseModuleContext interface {
// Calling this function prevents adding new dependencies.
getMissingDependencies() []string
// AddUnconvertedBp2buildDep stores module name of a direct dependency that was not converted via bp2build
AddUnconvertedBp2buildDep(dep string)
// AddMissingBp2buildDep stores the module name of a direct dependency that was not found.
AddMissingBp2buildDep(dep string)
Target() Target
TargetPrimary() bool
@ -243,12 +235,8 @@ type baseModuleContext struct {
strictVisitDeps bool // If true, enforce that all dependencies are enabled
bazelConversionMode bool
}
func (b *baseModuleContext) isBazelConversionMode() bool {
return b.bazelConversionMode
}
func (b *baseModuleContext) OtherModuleName(m blueprint.Module) string {
return b.bp.OtherModuleName(m)
}
@ -296,18 +284,6 @@ func (b *baseModuleContext) blueprintBaseModuleContext() blueprint.BaseModuleCon
return b.bp
}
// AddUnconvertedBp2buildDep stores module name of a dependency that was not converted to Bazel.
func (b *baseModuleContext) AddUnconvertedBp2buildDep(dep string) {
unconvertedDeps := &b.Module().base().commonProperties.BazelConversionStatus.UnconvertedDeps
*unconvertedDeps = append(*unconvertedDeps, dep)
}
// AddMissingBp2buildDep stores module name of a dependency that was not found in a Android.bp file.
func (b *baseModuleContext) AddMissingBp2buildDep(dep string) {
missingDeps := &b.Module().base().commonProperties.BazelConversionStatus.MissingDeps
*missingDeps = append(*missingDeps, dep)
}
func (b *baseModuleContext) AddMissingDependencies(deps []string) {
if deps != nil {
missingDeps := &b.Module().base().commonProperties.MissingDeps
@ -435,27 +411,6 @@ func (b *baseModuleContext) GetDirectDep(name string) (blueprint.Module, bluepri
return b.getDirectDepFirstTag(name)
}
func (b *baseModuleContext) ModuleFromName(name string) (blueprint.Module, bool) {
if !b.isBazelConversionMode() {
panic("cannot call ModuleFromName if not in bazel conversion mode")
}
var m blueprint.Module
var ok bool
if moduleName, _ := SrcIsModuleWithTag(name); moduleName != "" {
m, ok = b.bp.ModuleFromName(moduleName)
} else {
m, ok = b.bp.ModuleFromName(name)
}
if !ok {
return m, ok
}
// If this module is not preferred, tried to get the prebuilt version instead
if a, aOk := m.(Module); aOk && !IsModulePrebuilt(a) && !IsModulePreferred(a) {
return b.ModuleFromName("prebuilt_" + name)
}
return m, ok
}
func (b *baseModuleContext) VisitDirectDepsBlueprint(visit func(blueprint.Module)) {
b.bp.VisitDirectDeps(visit)
}

View file

@ -1,784 +0,0 @@
// Copyright 2021 Google Inc. All rights reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package android
import (
"bufio"
"errors"
"fmt"
"strings"
"android/soong/ui/metrics/bp2build_metrics_proto"
"github.com/google/blueprint"
"github.com/google/blueprint/bootstrap"
"github.com/google/blueprint/proptools"
"android/soong/android/allowlists"
)
const (
// A sentinel value to be used as a key in Bp2BuildConfig for modules with
// no package path. This is also the module dir for top level Android.bp
// modules.
Bp2BuildTopLevel = "."
)
type MixedBuildEnabledStatus int
const (
// This module can be mixed_built.
MixedBuildEnabled = iota
// There is a technical incompatibility preventing this module from being
// bazel-analyzed. Note: the module might also be incompatible.
TechnicalIncompatibility
// This module cannot be mixed_built due to some incompatibility with it
// that is not a platform incompatibility. Example: the module-type is not
// enabled, or is not bp2build-converted.
ModuleIncompatibility
// Missing dependencies. We can't query Bazel for modules if it has missing dependencies, there
// will be failures.
ModuleMissingDeps
)
// FileGroupAsLibrary describes a filegroup module that is converted to some library
// such as aidl_library or proto_library.
type FileGroupAsLibrary interface {
ShouldConvertToAidlLibrary(ctx BazelConversionPathContext) bool
ShouldConvertToProtoLibrary(ctx BazelConversionPathContext) bool
GetAidlLibraryLabel(ctx BazelConversionPathContext) string
GetProtoLibraryLabel(ctx BazelConversionPathContext) string
}
type BazelConversionStatus struct {
// Information about _all_ bp2build targets generated by this module. Multiple targets are
// supported as Soong handles some things within a single target that we may choose to split into
// multiple targets, e.g. renderscript, protos, yacc within a cc module.
Bp2buildInfo []bp2buildInfo `blueprint:"mutated"`
// UnconvertedBp2buildDep stores the module names of direct dependency that were not converted to
// Bazel
UnconvertedDeps []string `blueprint:"mutated"`
// MissingBp2buildDep stores the module names of direct dependency that were not found
MissingDeps []string `blueprint:"mutated"`
// If non-nil, indicates that the module could not be converted successfully
// with bp2build. This will describe the reason the module could not be converted.
UnconvertedReason *UnconvertedReason
// The Partition this module will be installed on.
// TODO(b/306200980) Investigate how to handle modules that are installed in multiple
// partitions.
Partition string `blueprint:"mutated"`
}
// The reason a module could not be converted to a BUILD target via bp2build.
// This should match bp2build_metrics_proto.UnconvertedReason, but omits private
// proto-related fields that prevent copying this struct.
type UnconvertedReason struct {
// Should correspond to a valid value in bp2build_metrics_proto.UnconvertedReasonType.
// A raw int is used here instead, because blueprint logic requires that all transitive
// fields of module definitions be primitives.
ReasonType int
Detail string
}
type BazelModuleProperties struct {
// The label of the Bazel target replacing this Soong module. When run in conversion mode, this
// will import the handcrafted build target into the autogenerated file. Note: this may result in
// a conflict due to duplicate targets if bp2build_available is also set.
Label *string
// If true, bp2build will generate the converted Bazel target for this module. Note: this may
// cause a conflict due to the duplicate targets if label is also set.
//
// This is a bool pointer to support tristates: true, false, not set.
//
// To opt in a module, set bazel_module: { bp2build_available: true }
// To opt out a module, set bazel_module: { bp2build_available: false }
// To defer the default setting for the directory, do not set the value.
Bp2build_available *bool
// CanConvertToBazel is set via InitBazelModule to indicate that a module type can be converted to
// Bazel with Bp2build.
CanConvertToBazel bool `blueprint:"mutated"`
}
// Properties contains common module properties for Bazel migration purposes.
type properties struct {
// In "Bazel mixed build" mode, this represents the Bazel target replacing
// this Soong module.
Bazel_module BazelModuleProperties
}
// namespacedVariableProperties is a map from a string representing a Soong
// config variable namespace, like "android" or "vendor_name" to a slice of
// pointer to a struct containing a single field called Soong_config_variables
// whose value mirrors the structure in the Blueprint file.
type namespacedVariableProperties map[string][]interface{}
// BazelModuleBase contains the property structs with metadata for modules which can be converted to
// Bazel.
type BazelModuleBase struct {
bazelProperties properties
// namespacedVariableProperties is used for soong_config_module_type support
// in bp2build. Soong config modules allow users to set module properties
// based on custom product variables defined in Android.bp files. These
// variables are namespaced to prevent clobbering, especially when set from
// Makefiles.
namespacedVariableProperties namespacedVariableProperties
// baseModuleType is set when this module was created from a module type
// defined by a soong_config_module_type. Every soong_config_module_type
// "wraps" another module type, e.g. a soong_config_module_type can wrap a
// cc_defaults to a custom_cc_defaults, or cc_binary to a custom_cc_binary.
// This baseModuleType is set to the wrapped module type.
baseModuleType string
}
// Bazelable is specifies the interface for modules that can be converted to Bazel.
type Bazelable interface {
bazelProps() *properties
HasHandcraftedLabel() bool
HandcraftedLabel() string
GetBazelLabel(ctx BazelConversionPathContext, module blueprint.Module) string
ShouldConvertWithBp2build(ctx ShouldConvertWithBazelContext) bool
shouldConvertWithBp2build(shouldConvertModuleContext, shouldConvertParams) bool
// ConvertWithBp2build either converts the module to a Bazel build target or
// declares the module as unconvertible (for logging and metrics).
// Modules must implement this function to be bp2build convertible. The function
// must either create at least one Bazel target module (using ctx.CreateBazelTargetModule or
// its related functions), or declare itself unconvertible using ctx.MarkBp2buildUnconvertible.
ConvertWithBp2build(ctx Bp2buildMutatorContext)
// namespacedVariableProps is a map from a soong config variable namespace
// (e.g. acme, android) to a map of interfaces{}, which are really
// reflect.Struct pointers, representing the value of the
// soong_config_variables property of a module. The struct pointer is the
// one with the single member called Soong_config_variables, which itself is
// a struct containing fields for each supported feature in that namespace.
//
// The reason for using a slice of interface{} is to support defaults
// propagation of the struct pointers.
namespacedVariableProps() namespacedVariableProperties
setNamespacedVariableProps(props namespacedVariableProperties)
BaseModuleType() string
SetBaseModuleType(baseModuleType string)
}
// ApiProvider is implemented by modules that contribute to an API surface
type ApiProvider interface {
ConvertWithApiBp2build(ctx TopDownMutatorContext)
}
// MixedBuildBuildable is an interface that module types should implement in order
// to be "handled by Bazel" in a mixed build.
type MixedBuildBuildable interface {
// IsMixedBuildSupported returns true if and only if this module should be
// "handled by Bazel" in a mixed build.
// This "escape hatch" allows modules with corner-case scenarios to opt out
// of being built with Bazel.
IsMixedBuildSupported(ctx BaseModuleContext) bool
// QueueBazelCall invokes request-queueing functions on the BazelContext
// so that these requests are handled when Bazel's cquery is invoked.
QueueBazelCall(ctx BaseModuleContext)
// ProcessBazelQueryResponse uses Bazel information (obtained from the BazelContext)
// to set module fields and providers to propagate this module's metadata upstream.
// This effectively "bridges the gap" between Bazel and Soong in a mixed build.
// Soong modules depending on this module should be oblivious to the fact that
// this module was handled by Bazel.
ProcessBazelQueryResponse(ctx ModuleContext)
}
// BazelModule is a lightweight wrapper interface around Module for Bazel-convertible modules.
type BazelModule interface {
Module
Bazelable
}
// InitBazelModule is a wrapper function that decorates a BazelModule with Bazel-conversion
// properties.
func InitBazelModule(module BazelModule) {
module.AddProperties(module.bazelProps())
module.bazelProps().Bazel_module.CanConvertToBazel = true
}
// BazelHandcraftedHook is a load hook to possibly register the current module as
// a "handcrafted" Bazel target of a given name. If the current module should be
// registered in this way, the hook function should return the target name. If
// it should not be registered in this way, this function should return the empty string.
type BazelHandcraftedHook func(ctx LoadHookContext) string
// AddBazelHandcraftedHook adds a load hook to (maybe) mark the given module so that
// it is treated by bp2build as if it has a handcrafted Bazel target.
func AddBazelHandcraftedHook(module BazelModule, hook BazelHandcraftedHook) {
AddLoadHook(module, func(ctx LoadHookContext) {
var targetName string = hook(ctx)
if len(targetName) > 0 {
moduleDir := ctx.ModuleDir()
if moduleDir == Bp2BuildTopLevel {
moduleDir = ""
}
label := fmt.Sprintf("//%s:%s", moduleDir, targetName)
module.bazelProps().Bazel_module.Label = &label
}
})
}
// bazelProps returns the Bazel properties for the given BazelModuleBase.
func (b *BazelModuleBase) bazelProps() *properties {
return &b.bazelProperties
}
func (b *BazelModuleBase) namespacedVariableProps() namespacedVariableProperties {
return b.namespacedVariableProperties
}
func (b *BazelModuleBase) setNamespacedVariableProps(props namespacedVariableProperties) {
b.namespacedVariableProperties = props
}
func (b *BazelModuleBase) BaseModuleType() string {
return b.baseModuleType
}
func (b *BazelModuleBase) SetBaseModuleType(baseModuleType string) {
b.baseModuleType = baseModuleType
}
// HasHandcraftedLabel returns whether this module has a handcrafted Bazel label.
func (b *BazelModuleBase) HasHandcraftedLabel() bool {
return b.bazelProperties.Bazel_module.Label != nil
}
// HandcraftedLabel returns the handcrafted label for this module, or empty string if there is none
func (b *BazelModuleBase) HandcraftedLabel() string {
return proptools.String(b.bazelProperties.Bazel_module.Label)
}
// GetBazelLabel returns the Bazel label for the given BazelModuleBase.
func (b *BazelModuleBase) GetBazelLabel(ctx BazelConversionPathContext, module blueprint.Module) string {
if b.HasHandcraftedLabel() {
return b.HandcraftedLabel()
}
if b.ShouldConvertWithBp2build(ctx) {
return bp2buildModuleLabel(ctx, module)
}
panic(fmt.Errorf("requested non-existent label for module %s", module.Name()))
}
type Bp2BuildConversionAllowlist struct {
// Configure modules in these directories to enable bp2build_available: true or false by default.
defaultConfig allowlists.Bp2BuildConfig
// Keep any existing BUILD files (and do not generate new BUILD files) for these directories
// in the synthetic Bazel workspace.
keepExistingBuildFile map[string]bool
// Per-module allowlist to always opt modules into both bp2build and Bazel Dev Mode mixed
// builds. These modules are usually in directories with many other modules that are not ready
// for conversion.
//
// A module can either be in this list or its directory allowlisted entirely
// in bp2buildDefaultConfig, but not both at the same time.
moduleAlwaysConvert map[string]bool
// Per-module-type allowlist to always opt modules in to both bp2build and
// Bazel Dev Mode mixed builds when they have the same type as one listed.
moduleTypeAlwaysConvert map[string]bool
// Per-module denylist to always opt modules out of bp2build conversion.
moduleDoNotConvert map[string]bool
}
// NewBp2BuildAllowlist creates a new, empty Bp2BuildConversionAllowlist
// which can be populated using builder pattern Set* methods
func NewBp2BuildAllowlist() Bp2BuildConversionAllowlist {
return Bp2BuildConversionAllowlist{
allowlists.Bp2BuildConfig{},
map[string]bool{},
map[string]bool{},
map[string]bool{},
map[string]bool{},
}
}
// SetDefaultConfig copies the entries from defaultConfig into the allowlist
func (a Bp2BuildConversionAllowlist) SetDefaultConfig(defaultConfig allowlists.Bp2BuildConfig) Bp2BuildConversionAllowlist {
if a.defaultConfig == nil {
a.defaultConfig = allowlists.Bp2BuildConfig{}
}
for k, v := range defaultConfig {
a.defaultConfig[k] = v
}
return a
}
// SetKeepExistingBuildFile copies the entries from keepExistingBuildFile into the allowlist
func (a Bp2BuildConversionAllowlist) SetKeepExistingBuildFile(keepExistingBuildFile map[string]bool) Bp2BuildConversionAllowlist {
if a.keepExistingBuildFile == nil {
a.keepExistingBuildFile = map[string]bool{}
}
for k, v := range keepExistingBuildFile {
a.keepExistingBuildFile[k] = v
}
return a
}
// SetModuleAlwaysConvertList copies the entries from moduleAlwaysConvert into the allowlist
func (a Bp2BuildConversionAllowlist) SetModuleAlwaysConvertList(moduleAlwaysConvert []string) Bp2BuildConversionAllowlist {
if a.moduleAlwaysConvert == nil {
a.moduleAlwaysConvert = map[string]bool{}
}
for _, m := range moduleAlwaysConvert {
a.moduleAlwaysConvert[m] = true
}
return a
}
// SetModuleTypeAlwaysConvertList copies the entries from moduleTypeAlwaysConvert into the allowlist
func (a Bp2BuildConversionAllowlist) SetModuleTypeAlwaysConvertList(moduleTypeAlwaysConvert []string) Bp2BuildConversionAllowlist {
if a.moduleTypeAlwaysConvert == nil {
a.moduleTypeAlwaysConvert = map[string]bool{}
}
for _, m := range moduleTypeAlwaysConvert {
a.moduleTypeAlwaysConvert[m] = true
}
return a
}
// SetModuleDoNotConvertList copies the entries from moduleDoNotConvert into the allowlist
func (a Bp2BuildConversionAllowlist) SetModuleDoNotConvertList(moduleDoNotConvert []string) Bp2BuildConversionAllowlist {
if a.moduleDoNotConvert == nil {
a.moduleDoNotConvert = map[string]bool{}
}
for _, m := range moduleDoNotConvert {
a.moduleDoNotConvert[m] = true
}
return a
}
// ShouldKeepExistingBuildFileForDir returns whether an existing BUILD file should be
// added to the build symlink forest based on the current global configuration.
func (a Bp2BuildConversionAllowlist) ShouldKeepExistingBuildFileForDir(dir string) bool {
if _, ok := a.keepExistingBuildFile[dir]; ok {
// Exact dir match
return true
}
var i int
// Check if subtree match
for {
j := strings.Index(dir[i:], "/")
if j == -1 {
return false //default
}
prefix := dir[0 : i+j]
i = i + j + 1 // skip the "/"
if recursive, ok := a.keepExistingBuildFile[prefix]; ok && recursive {
return true
}
}
}
var bp2BuildAllowListKey = NewOnceKey("Bp2BuildAllowlist")
var bp2buildAllowlist OncePer
func GetBp2BuildAllowList() Bp2BuildConversionAllowlist {
return bp2buildAllowlist.Once(bp2BuildAllowListKey, func() interface{} {
return NewBp2BuildAllowlist().SetDefaultConfig(allowlists.Bp2buildDefaultConfig).
SetKeepExistingBuildFile(allowlists.Bp2buildKeepExistingBuildFile).
SetModuleAlwaysConvertList(allowlists.Bp2buildModuleAlwaysConvertList).
SetModuleTypeAlwaysConvertList(allowlists.Bp2buildModuleTypeAlwaysConvertList).
SetModuleDoNotConvertList(allowlists.Bp2buildModuleDoNotConvertList)
}).(Bp2BuildConversionAllowlist)
}
// MixedBuildsEnabled returns a MixedBuildEnabledStatus regarding whether
// a module is ready to be replaced by a converted or handcrafted Bazel target.
// As a side effect, calling this method will also log whether this module is
// mixed build enabled for metrics reporting.
func MixedBuildsEnabled(ctx BaseModuleContext) MixedBuildEnabledStatus {
platformIncompatible := isPlatformIncompatible(ctx.Os(), ctx.Arch().ArchType)
if platformIncompatible {
ctx.Config().LogMixedBuild(ctx, false)
return TechnicalIncompatibility
}
if ctx.Config().AllowMissingDependencies() {
missingDeps := ctx.getMissingDependencies()
// If there are missing dependencies, querying Bazel will fail. Soong instead fails at execution
// time, not loading/analysis. disable mixed builds and fall back to Soong to maintain that
// behavior.
if len(missingDeps) > 0 {
ctx.Config().LogMixedBuild(ctx, false)
return ModuleMissingDeps
}
}
module := ctx.Module()
apexInfo := ctx.Provider(ApexInfoProvider).(ApexInfo)
withinApex := !apexInfo.IsForPlatform()
mixedBuildEnabled := ctx.Config().IsMixedBuildsEnabled() &&
module.Enabled() &&
convertedToBazel(ctx, module) &&
ctx.Config().BazelContext.IsModuleNameAllowed(module.Name(), withinApex)
ctx.Config().LogMixedBuild(ctx, mixedBuildEnabled)
if mixedBuildEnabled {
return MixedBuildEnabled
}
return ModuleIncompatibility
}
func isGoModule(module blueprint.Module) bool {
if _, ok := module.(*bootstrap.GoPackage); ok {
return true
}
if _, ok := module.(*bootstrap.GoBinary); ok {
return true
}
return false
}
// ConvertedToBazel returns whether this module has been converted (with bp2build or manually) to Bazel.
func convertedToBazel(ctx BazelConversionContext, module blueprint.Module) bool {
// Special-case bootstrap_go_package and bootstrap_go_binary
// These do not implement Bazelable, but have been converted
if isGoModule(module) {
return true
}
b, ok := module.(Bazelable)
if !ok {
return false
}
return b.HasHandcraftedLabel() || b.shouldConvertWithBp2build(ctx, shouldConvertParams{
module: module,
moduleDir: ctx.OtherModuleDir(module),
moduleName: ctx.OtherModuleName(module),
moduleType: ctx.OtherModuleType(module),
})
}
type ShouldConvertWithBazelContext interface {
ModuleErrorf(format string, args ...interface{})
Module() Module
Config() Config
ModuleType() string
ModuleName() string
ModuleDir() string
}
// ShouldConvertWithBp2build returns whether the given BazelModuleBase should be converted with bp2build
func (b *BazelModuleBase) ShouldConvertWithBp2build(ctx ShouldConvertWithBazelContext) bool {
return b.shouldConvertWithBp2build(ctx, shouldConvertParams{
module: ctx.Module(),
moduleDir: ctx.ModuleDir(),
moduleName: ctx.ModuleName(),
moduleType: ctx.ModuleType(),
})
}
type bazelOtherModuleContext interface {
ModuleErrorf(format string, args ...interface{})
Config() Config
OtherModuleType(m blueprint.Module) string
OtherModuleName(m blueprint.Module) string
OtherModuleDir(m blueprint.Module) string
}
func isPlatformIncompatible(osType OsType, arch ArchType) bool {
return osType == Windows || // Windows toolchains are not currently supported.
osType == LinuxBionic || // Linux Bionic toolchains are not currently supported.
osType == LinuxMusl || // Linux musl toolchains are not currently supported (b/259266326).
arch == Riscv64 // TODO(b/262192655) Riscv64 toolchains are not currently supported.
}
type shouldConvertModuleContext interface {
ModuleErrorf(format string, args ...interface{})
Config() Config
}
type shouldConvertParams struct {
module blueprint.Module
moduleType string
moduleDir string
moduleName string
}
func (b *BazelModuleBase) shouldConvertWithBp2build(ctx shouldConvertModuleContext, p shouldConvertParams) bool {
if !b.bazelProps().Bazel_module.CanConvertToBazel {
return false
}
module := p.module
propValue := b.bazelProperties.Bazel_module.Bp2build_available
packagePath := moduleDirWithPossibleOverride(ctx, module, p.moduleDir)
// Modules in unit tests which are enabled in the allowlist by type or name
// trigger this conditional because unit tests run under the "." package path
isTestModule := packagePath == Bp2BuildTopLevel && proptools.BoolDefault(propValue, false)
if isTestModule {
return true
}
moduleName := moduleNameWithPossibleOverride(ctx, module, p.moduleName)
// use "prebuilt_" + original module name as the java_import(_host) module name,
// to avoid the failure that a normal module and a prebuilt module with
// the same name are both allowlisted. This cannot be applied to all the *_import
// module types. For example, android_library_import has to use original module
// name here otherwise the *-nodeps targets cannot be handled correctly.
// TODO(b/304385140): remove this special casing
if p.moduleType == "java_import" || p.moduleType == "java_import_host" {
moduleName = module.Name()
}
allowlist := ctx.Config().Bp2buildPackageConfig
moduleNameAllowed := allowlist.moduleAlwaysConvert[moduleName]
moduleTypeAllowed := allowlist.moduleTypeAlwaysConvert[p.moduleType]
allowlistConvert := moduleNameAllowed || moduleTypeAllowed
if moduleNameAllowed && moduleTypeAllowed {
ctx.ModuleErrorf("A module %q of type %q cannot be in moduleAlwaysConvert and also be in moduleTypeAlwaysConvert", moduleName, p.moduleType)
return false
}
if allowlist.moduleDoNotConvert[moduleName] {
if moduleNameAllowed {
ctx.ModuleErrorf("a module %q cannot be in moduleDoNotConvert and also be in moduleAlwaysConvert", moduleName)
}
return false
}
// This is a tristate value: true, false, or unset.
if ok, directoryPath := bp2buildDefaultTrueRecursively(packagePath, allowlist.defaultConfig); ok {
if moduleNameAllowed {
ctx.ModuleErrorf("A module cannot be in a directory marked Bp2BuildDefaultTrue"+
" or Bp2BuildDefaultTrueRecursively and also be in moduleAlwaysConvert. Directory: '%s'"+
" Module: '%s'", directoryPath, moduleName)
return false
}
// Allow modules to explicitly opt-out.
return proptools.BoolDefault(propValue, true)
}
// Allow modules to explicitly opt-in.
return proptools.BoolDefault(propValue, allowlistConvert)
}
// bp2buildDefaultTrueRecursively checks that the package contains a prefix from the
// set of package prefixes where all modules must be converted. That is, if the
// package is x/y/z, and the list contains either x, x/y, or x/y/z, this function will
// return true.
//
// However, if the package is x/y, and it matches a Bp2BuildDefaultFalse "x/y" entry
// exactly, this module will return false early.
//
// This function will also return false if the package doesn't match anything in
// the config.
//
// This function will also return the allowlist entry which caused a particular
// package to be enabled. Since packages can be enabled via a recursive declaration,
// the path returned will not always be the same as the one provided.
func bp2buildDefaultTrueRecursively(packagePath string, config allowlists.Bp2BuildConfig) (bool, string) {
// Check if the package path has an exact match in the config.
if config[packagePath] == allowlists.Bp2BuildDefaultTrue || config[packagePath] == allowlists.Bp2BuildDefaultTrueRecursively {
return true, packagePath
} else if config[packagePath] == allowlists.Bp2BuildDefaultFalse || config[packagePath] == allowlists.Bp2BuildDefaultFalseRecursively {
return false, packagePath
}
// If not, check for the config recursively.
packagePrefix := packagePath
// e.g. for x/y/z, iterate over x/y, then x, taking the most-specific value from the allowlist.
for strings.Contains(packagePrefix, "/") {
dirIndex := strings.LastIndex(packagePrefix, "/")
packagePrefix = packagePrefix[:dirIndex]
switch value := config[packagePrefix]; value {
case allowlists.Bp2BuildDefaultTrueRecursively:
// package contains this prefix and this prefix should convert all modules
return true, packagePrefix
case allowlists.Bp2BuildDefaultFalseRecursively:
//package contains this prefix and this prefix should NOT convert any modules
return false, packagePrefix
}
// Continue to the next part of the package dir.
}
return false, packagePath
}
func registerBp2buildConversionMutator(ctx RegisterMutatorsContext) {
ctx.BottomUp("bp2build_conversion", bp2buildConversionMutator).Parallel()
ctx.BottomUp("bp2build_deps", bp2buildDepsMutator).Parallel()
}
func bp2buildConversionMutator(ctx BottomUpMutatorContext) {
// If an existing BUILD file in the module directory has a target defined
// with this same name as this module, assume that this is an existing
// definition for this target.
if ctx.Config().HasBazelBuildTargetInSource(ctx.ModuleDir(), ctx.ModuleName()) {
ctx.MarkBp2buildUnconvertible(bp2build_metrics_proto.UnconvertedReasonType_DEFINED_IN_BUILD_FILE, ctx.ModuleName())
return
}
bModule, ok := ctx.Module().(Bazelable)
if !ok {
ctx.MarkBp2buildUnconvertible(bp2build_metrics_proto.UnconvertedReasonType_TYPE_UNSUPPORTED, "")
return
}
// There may be cases where the target is created by a macro rather than in a BUILD file, those
// should be captured as well.
if bModule.HasHandcraftedLabel() {
// Defer to the BUILD target. Generating an additional target would
// cause a BUILD file conflict.
ctx.MarkBp2buildUnconvertible(bp2build_metrics_proto.UnconvertedReasonType_DEFINED_IN_BUILD_FILE, "")
return
}
// TODO: b/285631638 - Differentiate between denylisted modules and missing bp2build capabilities.
if !bModule.shouldConvertWithBp2build(ctx, shouldConvertParams{
module: ctx.Module(),
moduleDir: ctx.ModuleDir(),
moduleName: ctx.ModuleName(),
moduleType: ctx.ModuleType(),
}) {
ctx.MarkBp2buildUnconvertible(bp2build_metrics_proto.UnconvertedReasonType_UNSUPPORTED, "")
return
}
if ctx.Module().base().GetUnconvertedReason() != nil {
return
}
bModule.ConvertWithBp2build(ctx)
installCtx := &baseModuleContextToModuleInstallPathContext{ctx}
ctx.Module().base().setPartitionForBp2build(modulePartition(installCtx, true))
if len(ctx.Module().base().Bp2buildTargets()) == 0 && ctx.Module().base().GetUnconvertedReason() == nil {
panic(fmt.Errorf("illegal bp2build invariant: module '%s' was neither converted nor marked unconvertible", ctx.ModuleName()))
}
// If an existing BUILD file in the module directory has a target defined
// with the same name as any target generated by this module, assume that this
// is an existing definition for this target. (These generated target names
// may be different than the module name, as checked at the beginning of this function!)
for _, targetInfo := range ctx.Module().base().Bp2buildTargets() {
if ctx.Config().HasBazelBuildTargetInSource(targetInfo.TargetPackage(), targetInfo.TargetName()) {
// Defer to the BUILD target. Generating an additional target would
// cause a BUILD file conflict.
ctx.MarkBp2buildUnconvertible(bp2build_metrics_proto.UnconvertedReasonType_DEFINED_IN_BUILD_FILE, targetInfo.TargetName())
return
}
}
}
// TODO: b/285631638 - Add this as a new mutator to the bp2build conversion mutators.
// Currently, this only exists to prepare test coverage for the launch of this feature.
func bp2buildDepsMutator(ctx BottomUpMutatorContext) {
if ctx.Module().base().GetUnconvertedReason() != nil {
return
}
if len(ctx.Module().GetMissingBp2buildDeps()) > 0 {
exampleDep := ctx.Module().GetMissingBp2buildDeps()[0]
ctx.MarkBp2buildUnconvertible(
bp2build_metrics_proto.UnconvertedReasonType_UNCONVERTED_DEP, exampleDep)
}
// Transitively mark modules unconvertible with the following set of conditions.
ctx.VisitDirectDeps(func(dep Module) {
if dep.base().GetUnconvertedReason() == nil {
return
}
if dep.base().GetUnconvertedReason().ReasonType ==
int(bp2build_metrics_proto.UnconvertedReasonType_DEFINED_IN_BUILD_FILE) {
return
}
if ctx.OtherModuleDependencyTag(dep) != Bp2buildDepTag {
return
}
ctx.MarkBp2buildUnconvertible(
bp2build_metrics_proto.UnconvertedReasonType_UNCONVERTED_DEP, dep.Name())
})
}
// GetMainClassInManifest scans the manifest file specified in filepath and returns
// the value of attribute Main-Class in the manifest file if it exists, or returns error.
// WARNING: this is for bp2build converters of java_* modules only.
func GetMainClassInManifest(c Config, filepath string) (string, error) {
file, err := c.fs.Open(filepath)
if err != nil {
return "", err
}
defer file.Close()
scanner := bufio.NewScanner(file)
for scanner.Scan() {
line := scanner.Text()
if strings.HasPrefix(line, "Main-Class:") {
return strings.TrimSpace(line[len("Main-Class:"):]), nil
}
}
return "", errors.New("Main-Class is not found.")
}
func AttachValidationActions(ctx ModuleContext, outputFilePath Path, validations Paths) ModuleOutPath {
validatedOutputFilePath := PathForModuleOut(ctx, "validated", outputFilePath.Base())
ctx.Build(pctx, BuildParams{
Rule: CpNoPreserveSymlink,
Description: "run validations " + outputFilePath.Base(),
Output: validatedOutputFilePath,
Input: outputFilePath,
Validations: validations,
})
return validatedOutputFilePath
}
func RunsOn(hostSupported bool, deviceSupported bool, unitTest bool) []string {
var runsOn []string
if hostSupported && deviceSupported {
runsOn = []string{"host_without_device", "device"}
} else if hostSupported {
if unitTest {
runsOn = []string{"host_without_device"}
} else {
runsOn = []string{"host_with_device"}
}
} else if deviceSupported {
runsOn = []string{"device"}
}
return runsOn
}

File diff suppressed because it is too large Load diff

View file

@ -1,426 +0,0 @@
package android
import (
"encoding/json"
"os"
"path/filepath"
"reflect"
"strings"
"testing"
"android/soong/bazel"
"android/soong/bazel/cquery"
analysis_v2_proto "prebuilts/bazel/common/proto/analysis_v2"
"github.com/google/blueprint/metrics"
"google.golang.org/protobuf/proto"
)
var testConfig = TestConfig("out", nil, "", nil)
type testInvokeBazelContext struct{}
type mockBazelRunner struct {
testHelper *testing.T
// Stores mock behavior. If an issueBazelCommand request is made for command
// k, and {k:v} is present in this map, then the mock will return v.
bazelCommandResults map[bazelCommand]string
// Requests actually made of the mockBazelRunner with issueBazelCommand,
// keyed by the command they represent.
bazelCommandRequests map[bazelCommand]bazel.CmdRequest
}
func (r *mockBazelRunner) bazelCommandForRequest(cmdRequest bazel.CmdRequest) bazelCommand {
for _, arg := range cmdRequest.Argv {
for _, cmdType := range allBazelCommands {
if arg == cmdType.command {
return cmdType
}
}
}
r.testHelper.Fatalf("Unrecognized bazel request: %s", cmdRequest)
return cqueryCmd
}
func (r *mockBazelRunner) issueBazelCommand(cmdRequest bazel.CmdRequest, paths *bazelPaths, eventHandler *metrics.EventHandler) (string, string, error) {
command := r.bazelCommandForRequest(cmdRequest)
r.bazelCommandRequests[command] = cmdRequest
return r.bazelCommandResults[command], "", nil
}
func (t *testInvokeBazelContext) GetEventHandler() *metrics.EventHandler {
return &metrics.EventHandler{}
}
func TestRequestResultsAfterInvokeBazel(t *testing.T) {
label_foo := "@//foo:foo"
label_bar := "@//foo:bar"
apexKey := ApexConfigKey{
WithinApex: true,
ApexSdkVersion: "29",
ApiDomain: "myapex",
}
cfg_foo := configKey{"arm64_armv8-a", Android, apexKey}
cfg_bar := configKey{arch: "arm64_armv8-a", osType: Android}
cmd_results := []string{
`@//foo:foo|arm64_armv8-a|android|within_apex|29|myapex>>out/foo/foo.txt`,
`@//foo:bar|arm64_armv8-a|android>>out/foo/bar.txt`,
}
bazelContext, _ := testBazelContext(t, map[bazelCommand]string{cqueryCmd: strings.Join(cmd_results, "\n")})
bazelContext.QueueBazelRequest(label_foo, cquery.GetOutputFiles, cfg_foo)
bazelContext.QueueBazelRequest(label_bar, cquery.GetOutputFiles, cfg_bar)
err := bazelContext.InvokeBazel(testConfig, &testInvokeBazelContext{})
if err != nil {
t.Fatalf("Did not expect error invoking Bazel, but got %s", err)
}
verifyCqueryResult(t, bazelContext, label_foo, cfg_foo, "out/foo/foo.txt")
verifyCqueryResult(t, bazelContext, label_bar, cfg_bar, "out/foo/bar.txt")
}
func verifyCqueryResult(t *testing.T, ctx *mixedBuildBazelContext, label string, cfg configKey, result string) {
g, err := ctx.GetOutputFiles(label, cfg)
if err != nil {
t.Errorf("Expected cquery results after running InvokeBazel(), but got err %v", err)
} else if w := []string{result}; !reflect.DeepEqual(w, g) {
t.Errorf("Expected output %s, got %s", w, g)
}
}
func TestInvokeBazelWritesBazelFiles(t *testing.T) {
bazelContext, baseDir := testBazelContext(t, map[bazelCommand]string{})
err := bazelContext.InvokeBazel(testConfig, &testInvokeBazelContext{})
if err != nil {
t.Fatalf("Did not expect error invoking Bazel, but got %s", err)
}
if _, err := os.Stat(filepath.Join(baseDir, "soong_injection", "mixed_builds", "main.bzl")); os.IsNotExist(err) {
t.Errorf("Expected main.bzl to exist, but it does not")
} else if err != nil {
t.Errorf("Unexpected error stating main.bzl %s", err)
}
if _, err := os.Stat(filepath.Join(baseDir, "soong_injection", "mixed_builds", "BUILD.bazel")); os.IsNotExist(err) {
t.Errorf("Expected BUILD.bazel to exist, but it does not")
} else if err != nil {
t.Errorf("Unexpected error stating BUILD.bazel %s", err)
}
if _, err := os.Stat(filepath.Join(baseDir, "soong_injection", "WORKSPACE.bazel")); os.IsNotExist(err) {
t.Errorf("Expected WORKSPACE.bazel to exist, but it does not")
} else if err != nil {
t.Errorf("Unexpected error stating WORKSPACE.bazel %s", err)
}
}
func TestInvokeBazelPopulatesBuildStatements(t *testing.T) {
type testCase struct {
input string
command string
}
var testCases = []testCase{
{`
{
"artifacts": [
{ "id": 1, "path_fragment_id": 1 },
{ "id": 2, "path_fragment_id": 2 }],
"actions": [{
"target_Id": 1,
"action_Key": "x",
"mnemonic": "x",
"arguments": ["touch", "foo"],
"input_dep_set_ids": [1],
"output_Ids": [1],
"primary_output_id": 1
}],
"dep_set_of_files": [
{ "id": 1, "direct_artifact_ids": [1, 2] }],
"path_fragments": [
{ "id": 1, "label": "one" },
{ "id": 2, "label": "two" }]
}`,
"cd 'test/exec_root' && rm -rf 'one' && touch foo",
}, {`
{
"artifacts": [
{ "id": 1, "path_fragment_id": 10 },
{ "id": 2, "path_fragment_id": 20 }],
"actions": [{
"target_Id": 100,
"action_Key": "x",
"mnemonic": "x",
"arguments": ["bogus", "command"],
"output_Ids": [1, 2],
"primary_output_id": 1
}],
"path_fragments": [
{ "id": 10, "label": "one", "parent_id": 30 },
{ "id": 20, "label": "one.d", "parent_id": 30 },
{ "id": 30, "label": "parent" }]
}`,
`cd 'test/exec_root' && rm -rf 'parent/one' && bogus command && sed -i'' -E 's@(^|\s|")bazel-out/@\1test/bazel_out/@g' 'parent/one.d'`,
},
}
for i, testCase := range testCases {
data, err := JsonToActionGraphContainer(testCase.input)
if err != nil {
t.Error(err)
}
bazelContext, _ := testBazelContext(t, map[bazelCommand]string{aqueryCmd: string(data)})
err = bazelContext.InvokeBazel(testConfig, &testInvokeBazelContext{})
if err != nil {
t.Fatalf("testCase #%d: did not expect error invoking Bazel, but got %s", i+1, err)
}
got := bazelContext.BuildStatementsToRegister()
if want := 1; len(got) != want {
t.Fatalf("expected %d registered build statements, but got %#v", want, got)
}
cmd := RuleBuilderCommand{}
ctx := builderContextForTests{PathContextForTesting(TestConfig("out", nil, "", nil))}
createCommand(&cmd, got[0], "test/exec_root", "test/bazel_out", ctx, map[string]bazel.AqueryDepset{}, "")
if actual, expected := cmd.buf.String(), testCase.command; expected != actual {
t.Errorf("expected: [%s], actual: [%s]", expected, actual)
}
}
}
func TestMixedBuildSandboxedAction(t *testing.T) {
input := `{
"artifacts": [
{ "id": 1, "path_fragment_id": 1 },
{ "id": 2, "path_fragment_id": 2 }],
"actions": [{
"target_Id": 1,
"action_Key": "x",
"mnemonic": "x",
"arguments": ["touch", "foo"],
"input_dep_set_ids": [1],
"output_Ids": [1],
"primary_output_id": 1
}],
"dep_set_of_files": [
{ "id": 1, "direct_artifact_ids": [1, 2] }],
"path_fragments": [
{ "id": 1, "label": "one" },
{ "id": 2, "label": "two" }]
}`
data, err := JsonToActionGraphContainer(input)
if err != nil {
t.Error(err)
}
bazelContext, _ := testBazelContext(t, map[bazelCommand]string{aqueryCmd: string(data)})
err = bazelContext.InvokeBazel(testConfig, &testInvokeBazelContext{})
if err != nil {
t.Fatalf("TestMixedBuildSandboxedAction did not expect error invoking Bazel, but got %s", err)
}
statement := bazelContext.BuildStatementsToRegister()[0]
statement.ShouldRunInSbox = true
cmd := RuleBuilderCommand{}
ctx := builderContextForTests{PathContextForTesting(TestConfig("out", nil, "", nil))}
createCommand(&cmd, statement, "test/exec_root", "test/bazel_out", ctx, map[string]bazel.AqueryDepset{}, "")
// Assert that the output is generated in an intermediate directory
// fe05bcdcdc4928012781a5f1a2a77cbb5398e106 is the sha1 checksum of "one"
if actual, expected := cmd.outputs[0].String(), "out/soong/mixed_build_sbox_intermediates/fe05bcdcdc4928012781a5f1a2a77cbb5398e106/test/exec_root/one"; expected != actual {
t.Errorf("expected: [%s], actual: [%s]", expected, actual)
}
// Assert the actual command remains unchanged inside the sandbox
if actual, expected := cmd.buf.String(), "mkdir -p 'test/exec_root' && cd 'test/exec_root' && rm -rf 'one' && touch foo"; expected != actual {
t.Errorf("expected: [%s], actual: [%s]", expected, actual)
}
}
func TestCoverageFlagsAfterInvokeBazel(t *testing.T) {
testConfig.productVariables.ClangCoverage = boolPtr(true)
testConfig.productVariables.NativeCoveragePaths = []string{"foo1", "foo2"}
testConfig.productVariables.NativeCoverageExcludePaths = []string{"bar1", "bar2"}
verifyAqueryContainsFlags(t, testConfig, "--collect_code_coverage", "--instrumentation_filter=+foo1,+foo2,-bar1,-bar2")
testConfig.productVariables.NativeCoveragePaths = []string{"foo1"}
testConfig.productVariables.NativeCoverageExcludePaths = []string{"bar1"}
verifyAqueryContainsFlags(t, testConfig, "--collect_code_coverage", "--instrumentation_filter=+foo1,-bar1")
testConfig.productVariables.NativeCoveragePaths = []string{"foo1"}
testConfig.productVariables.NativeCoverageExcludePaths = nil
verifyAqueryContainsFlags(t, testConfig, "--collect_code_coverage", "--instrumentation_filter=+foo1")
testConfig.productVariables.NativeCoveragePaths = nil
testConfig.productVariables.NativeCoverageExcludePaths = []string{"bar1"}
verifyAqueryContainsFlags(t, testConfig, "--collect_code_coverage", "--instrumentation_filter=-bar1")
testConfig.productVariables.NativeCoveragePaths = []string{"*"}
testConfig.productVariables.NativeCoverageExcludePaths = nil
verifyAqueryContainsFlags(t, testConfig, "--collect_code_coverage", "--instrumentation_filter=+.*")
testConfig.productVariables.ClangCoverage = boolPtr(false)
verifyAqueryDoesNotContainSubstrings(t, testConfig, "collect_code_coverage", "instrumentation_filter")
}
func TestBazelRequestsSorted(t *testing.T) {
bazelContext, _ := testBazelContext(t, map[bazelCommand]string{})
cfgKeyArm64Android := configKey{arch: "arm64_armv8-a", osType: Android}
cfgKeyArm64Linux := configKey{arch: "arm64_armv8-a", osType: Linux}
cfgKeyOtherAndroid := configKey{arch: "otherarch", osType: Android}
bazelContext.QueueBazelRequest("zzz", cquery.GetOutputFiles, cfgKeyArm64Android)
bazelContext.QueueBazelRequest("ccc", cquery.GetApexInfo, cfgKeyArm64Android)
bazelContext.QueueBazelRequest("duplicate", cquery.GetOutputFiles, cfgKeyArm64Android)
bazelContext.QueueBazelRequest("duplicate", cquery.GetOutputFiles, cfgKeyArm64Android)
bazelContext.QueueBazelRequest("xxx", cquery.GetOutputFiles, cfgKeyArm64Linux)
bazelContext.QueueBazelRequest("aaa", cquery.GetOutputFiles, cfgKeyArm64Android)
bazelContext.QueueBazelRequest("aaa", cquery.GetOutputFiles, cfgKeyOtherAndroid)
bazelContext.QueueBazelRequest("bbb", cquery.GetOutputFiles, cfgKeyOtherAndroid)
if len(bazelContext.requests) != 7 {
t.Error("Expected 7 request elements, but got", len(bazelContext.requests))
}
lastString := ""
for _, val := range bazelContext.requests {
thisString := val.String()
if thisString <= lastString {
t.Errorf("Requests are not ordered correctly. '%s' came before '%s'", lastString, thisString)
}
lastString = thisString
}
}
func TestIsModuleNameAllowed(t *testing.T) {
libDisabled := "lib_disabled"
libEnabled := "lib_enabled"
libDclaWithinApex := "lib_dcla_within_apex"
libDclaNonApex := "lib_dcla_non_apex"
libNotConverted := "lib_not_converted"
disabledModules := map[string]bool{
libDisabled: true,
}
enabledModules := map[string]bool{
libEnabled: true,
}
dclaEnabledModules := map[string]bool{
libDclaWithinApex: true,
libDclaNonApex: true,
}
bazelContext := &mixedBuildBazelContext{
bazelEnabledModules: enabledModules,
bazelDisabledModules: disabledModules,
bazelDclaEnabledModules: dclaEnabledModules,
}
if bazelContext.IsModuleNameAllowed(libDisabled, true) {
t.Fatalf("%s shouldn't be allowed for mixed build", libDisabled)
}
if !bazelContext.IsModuleNameAllowed(libEnabled, true) {
t.Fatalf("%s should be allowed for mixed build", libEnabled)
}
if !bazelContext.IsModuleNameAllowed(libDclaWithinApex, true) {
t.Fatalf("%s should be allowed for mixed build", libDclaWithinApex)
}
if bazelContext.IsModuleNameAllowed(libDclaNonApex, false) {
t.Fatalf("%s shouldn't be allowed for mixed build", libDclaNonApex)
}
if bazelContext.IsModuleNameAllowed(libNotConverted, true) {
t.Fatalf("%s shouldn't be allowed for mixed build", libNotConverted)
}
}
func verifyAqueryContainsFlags(t *testing.T, config Config, expected ...string) {
t.Helper()
bazelContext, _ := testBazelContext(t, map[bazelCommand]string{})
err := bazelContext.InvokeBazel(config, &testInvokeBazelContext{})
if err != nil {
t.Fatalf("Did not expect error invoking Bazel, but got %s", err)
}
sliceContains := func(slice []string, x string) bool {
for _, s := range slice {
if s == x {
return true
}
}
return false
}
aqueryArgv := bazelContext.bazelRunner.(*mockBazelRunner).bazelCommandRequests[aqueryCmd].Argv
for _, expectedFlag := range expected {
if !sliceContains(aqueryArgv, expectedFlag) {
t.Errorf("aquery does not contain expected flag %#v. Argv was: %#v", expectedFlag, aqueryArgv)
}
}
}
func verifyAqueryDoesNotContainSubstrings(t *testing.T, config Config, substrings ...string) {
t.Helper()
bazelContext, _ := testBazelContext(t, map[bazelCommand]string{})
err := bazelContext.InvokeBazel(config, &testInvokeBazelContext{})
if err != nil {
t.Fatalf("Did not expect error invoking Bazel, but got %s", err)
}
sliceContainsSubstring := func(slice []string, substring string) bool {
for _, s := range slice {
if strings.Contains(s, substring) {
return true
}
}
return false
}
aqueryArgv := bazelContext.bazelRunner.(*mockBazelRunner).bazelCommandRequests[aqueryCmd].Argv
for _, substring := range substrings {
if sliceContainsSubstring(aqueryArgv, substring) {
t.Errorf("aquery contains unexpected substring %#v. Argv was: %#v", substring, aqueryArgv)
}
}
}
func testBazelContext(t *testing.T, bazelCommandResults map[bazelCommand]string) (*mixedBuildBazelContext, string) {
t.Helper()
p := bazelPaths{
soongOutDir: t.TempDir(),
outputBase: "outputbase",
workspaceDir: "workspace_dir",
}
if _, exists := bazelCommandResults[aqueryCmd]; !exists {
bazelCommandResults[aqueryCmd] = ""
}
runner := &mockBazelRunner{
testHelper: t,
bazelCommandResults: bazelCommandResults,
bazelCommandRequests: map[bazelCommand]bazel.CmdRequest{},
}
return &mixedBuildBazelContext{
bazelRunner: runner,
paths: &p,
}, p.soongOutDir
}
// Transform the json format to ActionGraphContainer
func JsonToActionGraphContainer(inputString string) ([]byte, error) {
var aqueryProtoResult analysis_v2_proto.ActionGraphContainer
err := json.Unmarshal([]byte(inputString), &aqueryProtoResult)
if err != nil {
return []byte(""), err
}
data, _ := proto.Marshal(&aqueryProtoResult)
return data, err
}

View file

@ -1,675 +0,0 @@
// Copyright 2015 Google Inc. All rights reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package android
import (
"fmt"
"os"
"path/filepath"
"strings"
"android/soong/bazel"
"github.com/google/blueprint"
"github.com/google/blueprint/pathtools"
)
// bazel_paths contains methods to:
// * resolve Soong path and module references into bazel.LabelList
// * resolve Bazel path references into Soong-compatible paths
//
// There is often a similar method for Bazel as there is for Soong path handling and should be used
// in similar circumstances
//
// Bazel Soong
// ==============================================================
// BazelLabelForModuleSrc PathForModuleSrc
// BazelLabelForModuleSrcExcludes PathForModuleSrcExcludes
// BazelLabelForModuleDeps n/a
// tbd PathForSource
// tbd ExistentPathsForSources
// PathForBazelOut PathForModuleOut
//
// Use cases:
// * Module contains a property (often tagged `android:"path"`) that expects paths *relative to the
// module directory*:
// * BazelLabelForModuleSrcExcludes, if the module also contains an excludes_<propname> property
// * BazelLabelForModuleSrc, otherwise
// * Converting references to other modules to Bazel Labels:
// BazelLabelForModuleDeps
// * Converting a path obtained from bazel_handler cquery results:
// PathForBazelOut
//
// NOTE: all Soong globs are expanded within Soong rather than being converted to a Bazel glob
// syntax. This occurs because Soong does not have a concept of crossing package boundaries,
// so the glob as computed by Soong may contain paths that cross package-boundaries. These
// would be unknowingly omitted if the glob were handled by Bazel. By expanding globs within
// Soong, we support identification and detection (within Bazel) use of paths that cross
// package boundaries.
//
// Path resolution:
// * filepath/globs: resolves as itself or is converted to an absolute Bazel label (e.g.
// //path/to/dir:<filepath>) if path exists in a separate package or subpackage.
// * references to other modules (using the ":name{.tag}" syntax). These resolve as a Bazel label
// for a target. If the Bazel target is in the local module directory, it will be returned
// relative to the current package (e.g. ":<target>"). Otherwise, it will be returned as an
// absolute Bazel label (e.g. "//path/to/dir:<target>"). If the reference to another module
// cannot be resolved,the function will panic. This is often due to the dependency not being added
// via an AddDependency* method.
// BazelConversionContext is a minimal context interface to check if a module should be converted by bp2build,
// with functions containing information to match against allowlists and denylists.
// If a module is deemed to be convertible by bp2build, then it should rely on a
// BazelConversionPathContext for more functions for dep/path features.
type BazelConversionContext interface {
Config() Config
Module() Module
OtherModuleType(m blueprint.Module) string
OtherModuleName(m blueprint.Module) string
OtherModuleDir(m blueprint.Module) string
ModuleErrorf(format string, args ...interface{})
}
// A subset of the ModuleContext methods which are sufficient to resolve references to paths/deps in
// order to form a Bazel-compatible label for conversion.
type BazelConversionPathContext interface {
EarlyModulePathContext
BazelConversionContext
ModuleName() string
ModuleType() string
ModuleErrorf(fmt string, args ...interface{})
PropertyErrorf(property, fmt string, args ...interface{})
GetDirectDep(name string) (blueprint.Module, blueprint.DependencyTag)
ModuleFromName(name string) (blueprint.Module, bool)
AddUnconvertedBp2buildDep(string)
AddMissingBp2buildDep(dep string)
}
// BazelLabelForModuleDeps expects a list of reference to other modules, ("<module>"
// or ":<module>") and returns a Bazel-compatible label which corresponds to dependencies on the
// module within the given ctx.
func BazelLabelForModuleDeps(ctx Bp2buildMutatorContext, modules []string) bazel.LabelList {
return BazelLabelForModuleDepsWithFn(ctx, modules, BazelModuleLabel, true)
}
// BazelLabelForModuleWholeDepsExcludes expects two lists: modules (containing modules to include in
// the list), and excludes (modules to exclude from the list). Both of these should contain
// references to other modules, ("<module>" or ":<module>"). It returns a Bazel-compatible label
// list which corresponds to dependencies on the module within the given ctx, and the excluded
// dependencies. Prebuilt dependencies will be appended with _alwayslink so they can be handled as
// whole static libraries.
func BazelLabelForModuleDepsExcludes(ctx Bp2buildMutatorContext, modules, excludes []string) bazel.LabelList {
return BazelLabelForModuleDepsExcludesWithFn(ctx, modules, excludes, BazelModuleLabel)
}
// BazelLabelForModuleDepsWithFn expects a list of reference to other modules, ("<module>"
// or ":<module>") and applies moduleToLabelFn to determine and return a Bazel-compatible label
// which corresponds to dependencies on the module within the given ctx.
func BazelLabelForModuleDepsWithFn(ctx Bp2buildMutatorContext, modules []string,
moduleToLabelFn func(BazelConversionPathContext, blueprint.Module) string,
markAsDeps bool) bazel.LabelList {
var labels bazel.LabelList
// In some cases, a nil string list is different than an explicitly empty list.
if len(modules) == 0 && modules != nil {
labels.Includes = []bazel.Label{}
return labels
}
modules = FirstUniqueStrings(modules)
for _, module := range modules {
bpText := module
if m := SrcIsModule(module); m == "" {
module = ":" + module
}
if m, t := SrcIsModuleWithTag(module); m != "" {
l := getOtherModuleLabel(ctx, m, t, moduleToLabelFn, markAsDeps)
if l != nil {
l.OriginalModuleName = bpText
labels.Includes = append(labels.Includes, *l)
}
} else {
ctx.ModuleErrorf("%q, is not a module reference", module)
}
}
return labels
}
// BazelLabelForModuleDepsExcludesWithFn expects two lists: modules (containing modules to include in the
// list), and excludes (modules to exclude from the list). Both of these should contain references
// to other modules, ("<module>" or ":<module>"). It applies moduleToLabelFn to determine and return a
// Bazel-compatible label list which corresponds to dependencies on the module within the given ctx, and
// the excluded dependencies.
func BazelLabelForModuleDepsExcludesWithFn(ctx Bp2buildMutatorContext, modules, excludes []string,
moduleToLabelFn func(BazelConversionPathContext, blueprint.Module) string) bazel.LabelList {
moduleLabels := BazelLabelForModuleDepsWithFn(ctx, RemoveListFromList(modules, excludes), moduleToLabelFn, true)
if len(excludes) == 0 {
return moduleLabels
}
excludeLabels := BazelLabelForModuleDepsWithFn(ctx, excludes, moduleToLabelFn, false)
return bazel.LabelList{
Includes: moduleLabels.Includes,
Excludes: excludeLabels.Includes,
}
}
func BazelLabelForModuleSrcSingle(ctx Bp2buildMutatorContext, path string) bazel.Label {
if srcs := BazelLabelForModuleSrcExcludes(ctx, []string{path}, []string(nil)).Includes; len(srcs) > 0 {
return srcs[0]
}
return bazel.Label{}
}
func BazelLabelForModuleDepSingle(ctx Bp2buildMutatorContext, path string) bazel.Label {
if srcs := BazelLabelForModuleDepsExcludes(ctx, []string{path}, []string(nil)).Includes; len(srcs) > 0 {
return srcs[0]
}
return bazel.Label{}
}
// BazelLabelForModuleSrc expects a list of path (relative to local module directory) and module
// references (":<module>") and returns a bazel.LabelList{} containing the resolved references in
// paths, relative to the local module, or Bazel-labels (absolute if in a different package or
// relative if within the same package).
// Properties must have been annotated with struct tag `android:"path"` so that dependencies modules
// will have already been handled by the pathdeps mutator.
func BazelLabelForModuleSrc(ctx Bp2buildMutatorContext, paths []string) bazel.LabelList {
return BazelLabelForModuleSrcExcludes(ctx, paths, []string(nil))
}
// BazelLabelForModuleSrc expects lists of path and excludes (relative to local module directory)
// and module references (":<module>") and returns a bazel.LabelList{} containing the resolved
// references in paths, minus those in excludes, relative to the local module, or Bazel-labels
// (absolute if in a different package or relative if within the same package).
// Properties must have been annotated with struct tag `android:"path"` so that dependencies modules
// will have already been handled by the pathdeps mutator.
func BazelLabelForModuleSrcExcludes(ctx Bp2buildMutatorContext, paths, excludes []string) bazel.LabelList {
excludeLabels := expandSrcsForBazel(ctx, excludes, []string(nil), false)
excluded := make([]string, 0, len(excludeLabels.Includes))
for _, e := range excludeLabels.Includes {
excluded = append(excluded, e.Label)
}
labels := expandSrcsForBazel(ctx, paths, excluded, true)
labels.Excludes = excludeLabels.Includes
labels = TransformSubpackagePaths(ctx.Config(), ctx.ModuleDir(), labels)
return labels
}
func BazelLabelForSrcPatternExcludes(ctx BazelConversionPathContext, dir, pattern string, excludes []string) bazel.LabelList {
topRelPaths, err := ctx.GlobWithDeps(filepath.Join(dir, pattern), excludes)
if err != nil {
ctx.ModuleErrorf("Could not search dir: %s for pattern %s due to %v\n", dir, pattern, err)
}
// An intermediate list of labels relative to `dir` that assumes that there no subpacakges beneath `dir`
dirRelLabels := []bazel.Label{}
for _, topRelPath := range topRelPaths {
dirRelPath := Rel(ctx, dir, topRelPath)
dirRelLabels = append(dirRelLabels, bazel.Label{Label: "./" + dirRelPath})
}
// Return the package boudary resolved labels
return TransformSubpackagePaths(ctx.Config(), dir, bazel.MakeLabelList(dirRelLabels))
}
// Returns true if a prefix + components[:i] is a package boundary.
//
// A package boundary is determined by a BUILD file in the directory. This can happen in 2 cases:
//
// 1. An Android.bp exists, which bp2build will always convert to a sibling BUILD file.
// 2. An Android.bp doesn't exist, but a checked-in BUILD/BUILD.bazel file exists, and that file
// is allowlisted by the bp2build configuration to be merged into the symlink forest workspace.
func isPackageBoundary(config Config, prefix string, components []string, componentIndex int) bool {
isSymlink := func(c Config, path string) bool {
f, err := c.fs.Lstat(path)
if err != nil {
// The file does not exist
return false
}
return f.Mode()&os.ModeSymlink == os.ModeSymlink
}
prefix = filepath.Join(prefix, filepath.Join(components[:componentIndex+1]...))
if exists, _, _ := config.fs.Exists(filepath.Join(prefix, "Android.bp")); exists {
return true
} else if config.Bp2buildPackageConfig.ShouldKeepExistingBuildFileForDir(prefix) || isSymlink(config, prefix) {
if exists, _, _ := config.fs.Exists(filepath.Join(prefix, "BUILD")); exists {
return true
} else if exists, _, _ := config.fs.Exists(filepath.Join(prefix, "BUILD.bazel")); exists {
return true
}
}
return false
}
// Transform a path (if necessary) to acknowledge package boundaries
//
// e.g. something like
//
// async_safe/include/async_safe/CHECK.h
//
// might become
//
// //bionic/libc/async_safe:include/async_safe/CHECK.h
//
// if the "async_safe" directory is actually a package and not just a directory.
//
// In particular, paths that extend into packages are transformed into absolute labels beginning with //.
func transformSubpackagePath(cfg Config, dir string, path bazel.Label) bazel.Label {
var newPath bazel.Label
// Don't transform OriginalModuleName
newPath.OriginalModuleName = path.OriginalModuleName
// if it wasn't a module, store the original path. We may need the original path to replace
// references if it is actually in another package
if path.OriginalModuleName == "" {
newPath.OriginalModuleName = path.Label
}
if strings.HasPrefix(path.Label, "//") {
// Assume absolute labels are already correct (e.g. //path/to/some/package:foo.h)
newPath.Label = path.Label
return newPath
}
if strings.HasPrefix(path.Label, "./") {
// Drop "./" for consistent handling of paths.
// Specifically, to not let "." be considered a package boundary.
// Say `inputPath` is `x/Android.bp` and that file has some module
// with `srcs=["y/a.c", "z/b.c"]`.
// And say the directory tree is:
// x
// ├── Android.bp
// ├── y
// │ ├── a.c
// │ └── Android.bp
// └── z
// └── b.c
// Then bazel equivalent labels in srcs should be:
// //x/y:a.c, x/z/b.c
// The above should still be the case if `x/Android.bp` had
// srcs=["./y/a.c", "./z/b.c"]
// However, if we didn't strip "./", we'd get
// //x/./y:a.c, //x/.:z/b.c
path.Label = strings.TrimPrefix(path.Label, "./")
}
pathComponents := strings.Split(path.Label, "/")
newLabel := ""
foundPackageBoundary := false
// Check the deepest subdirectory first and work upwards
for i := len(pathComponents) - 1; i >= 0; i-- {
pathComponent := pathComponents[i]
var sep string
if !foundPackageBoundary && isPackageBoundary(cfg, dir, pathComponents, i) {
sep = ":"
foundPackageBoundary = true
} else {
sep = "/"
}
if newLabel == "" {
newLabel = pathComponent
} else {
newLabel = pathComponent + sep + newLabel
}
}
if foundPackageBoundary {
// Ensure paths end up looking like //bionic/... instead of //./bionic/...
moduleDir := dir
if strings.HasPrefix(moduleDir, ".") {
moduleDir = moduleDir[1:]
}
// Make the path into an absolute label (e.g. //bionic/libc/foo:bar.h instead of just foo:bar.h)
if moduleDir == "" {
newLabel = "//" + newLabel
} else {
newLabel = "//" + moduleDir + "/" + newLabel
}
}
newPath.Label = newLabel
return newPath
}
// Transform paths to acknowledge package boundaries
// See transformSubpackagePath() for more information
func TransformSubpackagePaths(cfg Config, dir string, paths bazel.LabelList) bazel.LabelList {
var newPaths bazel.LabelList
for _, include := range paths.Includes {
newPaths.Includes = append(newPaths.Includes, transformSubpackagePath(cfg, dir, include))
}
for _, exclude := range paths.Excludes {
newPaths.Excludes = append(newPaths.Excludes, transformSubpackagePath(cfg, dir, exclude))
}
return newPaths
}
// Converts root-relative Paths to a list of bazel.Label relative to the module in ctx.
func RootToModuleRelativePaths(ctx BazelConversionPathContext, paths Paths) []bazel.Label {
var newPaths []bazel.Label
for _, path := range PathsWithModuleSrcSubDir(ctx, paths, "") {
s := path.Rel()
newPaths = append(newPaths, bazel.Label{Label: s})
}
return newPaths
}
var Bp2buildDepTag bp2buildDepTag
type bp2buildDepTag struct {
blueprint.BaseDependencyTag
}
// expandSrcsForBazel returns bazel.LabelList with paths rooted from the module's local source
// directory and Bazel target labels, excluding those included in the excludes argument (which
// should already be expanded to resolve references to Soong-modules). Valid elements of paths
// include:
// - filepath, relative to local module directory, resolves as a filepath relative to the local
// source directory
// - glob, relative to the local module directory, resolves as filepath(s), relative to the local
// module directory. Because Soong does not have a concept of crossing package boundaries, the
// glob as computed by Soong may contain paths that cross package-boundaries that would be
// unknowingly omitted if the glob were handled by Bazel. To allow identification and detect
// (within Bazel) use of paths that cross package boundaries, we expand globs within Soong rather
// than converting Soong glob syntax to Bazel glob syntax. **Invalid for excludes.**
// - other modules using the ":name{.tag}" syntax. These modules must implement SourceFileProducer
// or OutputFileProducer. These resolve as a Bazel label for a target. If the Bazel target is in
// the local module directory, it will be returned relative to the current package (e.g.
// ":<target>"). Otherwise, it will be returned as an absolute Bazel label (e.g.
// "//path/to/dir:<target>"). If the reference to another module cannot be resolved,the function
// will panic.
//
// Properties passed as the paths or excludes argument must have been annotated with struct tag
// `android:"path"` so that dependencies on other modules will have already been handled by the
// pathdeps mutator.
func expandSrcsForBazel(ctx Bp2buildMutatorContext, paths, expandedExcludes []string, markAsDeps bool) bazel.LabelList {
if paths == nil {
return bazel.LabelList{}
}
labels := bazel.LabelList{
Includes: []bazel.Label{},
}
// expandedExcludes contain module-dir relative paths, but root-relative paths
// are needed for GlobFiles later.
var rootRelativeExpandedExcludes []string
for _, e := range expandedExcludes {
rootRelativeExpandedExcludes = append(rootRelativeExpandedExcludes, filepath.Join(ctx.ModuleDir(), e))
}
for _, p := range paths {
if m, tag := SrcIsModuleWithTag(p); m != "" {
l := getOtherModuleLabel(ctx, m, tag, BazelModuleLabel, markAsDeps)
if l != nil && !InList(l.Label, expandedExcludes) {
if strings.HasPrefix(m, "//") {
// this is a module in a soong namespace
// It appears as //<namespace>:<module_name> in srcs, and not ://<namespace>:<module_name>
l.OriginalModuleName = m
} else {
l.OriginalModuleName = fmt.Sprintf(":%s", m)
}
labels.Includes = append(labels.Includes, *l)
}
} else {
var expandedPaths []bazel.Label
if pathtools.IsGlob(p) {
// e.g. turn "math/*.c" in
// external/arm-optimized-routines to external/arm-optimized-routines/math/*.c
rootRelativeGlobPath := pathForModuleSrc(ctx, p).String()
expandedPaths = RootToModuleRelativePaths(ctx, GlobFiles(ctx, rootRelativeGlobPath, rootRelativeExpandedExcludes))
} else {
if !InList(p, expandedExcludes) {
expandedPaths = append(expandedPaths, bazel.Label{Label: p})
}
}
labels.Includes = append(labels.Includes, expandedPaths...)
}
}
return labels
}
// getOtherModuleLabel returns a bazel.Label for the given dependency/tag combination for the
// module. The label will be relative to the current directory if appropriate. The dependency must
// already be resolved by either deps mutator or path deps mutator.
func getOtherModuleLabel(ctx Bp2buildMutatorContext, dep, tag string,
labelFromModule func(BazelConversionPathContext, blueprint.Module) string,
markAsDep bool) *bazel.Label {
m, _ := ctx.ModuleFromName(dep)
// The module was not found in an Android.bp file, this is often due to:
// * a limited manifest
// * a required module not being converted from Android.mk
if m == nil {
ctx.AddMissingBp2buildDep(dep)
return &bazel.Label{
Label: ":" + dep + "__BP2BUILD__MISSING__DEP",
}
}
// Returns true if a dependency from the current module to the target module
// should be skipped; doing so is a hack to circumvent certain problematic
// scenarios that will be addressed in the future.
shouldSkipDep := func(dep string) bool {
// Don't count dependencies of "libc". This is a hack to circumvent the
// fact that, in a variantless build graph, "libc" has a dependency on itself.
if ctx.ModuleName() == "libc" {
return true
}
// TODO: b/303307672: Dependencies on this module happen to "work" because
// there is a source file with the same name as this module in the
// same directory. We should remove this hack and enforce the underlying
// module of this name is the actual one used.
if dep == "mke2fs.conf" {
return true
}
// TODO: b/303310285: Remove this special-casing once all dependencies of
// crtbegin_dynamic are convertible
if ctx.ModuleName() == "crtbegin_dynamic" {
return true
}
return false
}
if markAsDep && !shouldSkipDep(dep) {
ctx.AddDependency(ctx.Module(), Bp2buildDepTag, dep)
}
if !convertedToBazel(ctx, m) {
ctx.AddUnconvertedBp2buildDep(dep)
}
label := BazelModuleLabel(ctx, ctx.Module())
otherLabel := labelFromModule(ctx, m)
// TODO(b/165114590): Convert tag (":name{.tag}") to corresponding Bazel implicit output targets.
if (tag != "" && m.Name() == "framework-res") ||
(tag == ".generated_srcjars" && ctx.OtherModuleType(m) == "java_aconfig_library") {
otherLabel += tag
}
if samePackage(label, otherLabel) {
otherLabel = bazelShortLabel(otherLabel)
}
return &bazel.Label{
Label: otherLabel,
}
}
func BazelModuleLabel(ctx BazelConversionPathContext, module blueprint.Module) string {
// TODO(b/165114590): Convert tag (":name{.tag}") to corresponding Bazel implicit output targets.
if !convertedToBazel(ctx, module) || isGoModule(module) {
return bp2buildModuleLabel(ctx, module)
}
b, _ := module.(Bazelable)
return b.GetBazelLabel(ctx, module)
}
func bazelShortLabel(label string) string {
i := strings.Index(label, ":")
if i == -1 {
panic(fmt.Errorf("Could not find the ':' character in '%s', expected a fully qualified label.", label))
}
return label[i:]
}
func bazelPackage(label string) string {
i := strings.Index(label, ":")
if i == -1 {
panic(fmt.Errorf("Could not find the ':' character in '%s', expected a fully qualified label.", label))
}
return label[0:i]
}
func samePackage(label1, label2 string) bool {
return bazelPackage(label1) == bazelPackage(label2)
}
func bp2buildModuleLabel(ctx BazelConversionContext, module blueprint.Module) string {
moduleName := moduleNameWithPossibleOverride(ctx, module, ctx.OtherModuleName(module))
moduleDir := moduleDirWithPossibleOverride(ctx, module, ctx.OtherModuleDir(module))
if moduleDir == Bp2BuildTopLevel {
moduleDir = ""
}
if a, ok := module.(Module); ok && IsModulePrebuilt(a) {
moduleName = RemoveOptionalPrebuiltPrefix(moduleName)
}
return fmt.Sprintf("//%s:%s", moduleDir, moduleName)
}
// BazelOutPath is a Bazel output path compatible to be used for mixed builds within Soong/Ninja.
type BazelOutPath struct {
OutputPath
}
// ensure BazelOutPath implements Path
var _ Path = BazelOutPath{}
// ensure BazelOutPath implements genPathProvider
var _ genPathProvider = BazelOutPath{}
// ensure BazelOutPath implements objPathProvider
var _ objPathProvider = BazelOutPath{}
func (p BazelOutPath) genPathWithExt(ctx ModuleOutPathContext, subdir, ext string) ModuleGenPath {
return PathForModuleGen(ctx, subdir, pathtools.ReplaceExtension(p.path, ext))
}
func (p BazelOutPath) objPathWithExt(ctx ModuleOutPathContext, subdir, ext string) ModuleObjPath {
return PathForModuleObj(ctx, subdir, pathtools.ReplaceExtension(p.path, ext))
}
// PathForBazelOutRelative returns a BazelOutPath representing the path under an output directory dedicated to
// bazel-owned outputs. Calling .Rel() on the result will give the input path as relative to the given
// relativeRoot.
func PathForBazelOutRelative(ctx PathContext, relativeRoot string, path string) BazelOutPath {
validatedPath, err := validatePath(filepath.Join("execroot", "__main__", path))
if err != nil {
reportPathError(ctx, err)
}
var relativeRootPath string
if pathComponents := strings.SplitN(path, "/", 4); len(pathComponents) >= 3 &&
pathComponents[0] == "bazel-out" && pathComponents[2] == "bin" {
// If the path starts with something like: bazel-out/linux_x86_64-fastbuild-ST-b4ef1c4402f9/bin/
// make it relative to that folder. bazel-out/volatile-status.txt is an example
// of something that starts with bazel-out but is not relative to the bin folder
relativeRootPath = filepath.Join("execroot", "__main__", pathComponents[0], pathComponents[1], pathComponents[2], relativeRoot)
} else {
relativeRootPath = filepath.Join("execroot", "__main__", relativeRoot)
}
var relPath string
if relPath, err = filepath.Rel(relativeRootPath, validatedPath); err != nil || strings.HasPrefix(relPath, "../") {
// We failed to make this path relative to execroot/__main__, fall back to a non-relative path
// One case where this happens is when path is ../bazel_tools/something
relativeRootPath = ""
relPath = validatedPath
}
outputPath := OutputPath{
basePath{"", ""},
ctx.Config().soongOutDir,
ctx.Config().BazelContext.OutputBase(),
}
return BazelOutPath{
// .withRel() appends its argument onto the current path, and only the most
// recently appended part is returned by outputPath.rel().
// So outputPath.rel() will return relPath.
OutputPath: outputPath.withRel(relativeRootPath).withRel(relPath),
}
}
// PathForBazelOut returns a BazelOutPath representing the path under an output directory dedicated to
// bazel-owned outputs.
func PathForBazelOut(ctx PathContext, path string) BazelOutPath {
return PathForBazelOutRelative(ctx, "", path)
}
// PathsForBazelOut returns a list of paths representing the paths under an output directory
// dedicated to Bazel-owned outputs.
func PathsForBazelOut(ctx PathContext, paths []string) Paths {
outs := make(Paths, 0, len(paths))
for _, p := range paths {
outs = append(outs, PathForBazelOut(ctx, p))
}
return outs
}
// BazelStringOrLabelFromProp splits a Soong module property that can be
// either a string literal, path (with android:path tag) or a module reference
// into separate bazel string or label attributes. Bazel treats string and label
// attributes as distinct types, so this function categorizes a string property
// into either one of them.
//
// e.g. apex.private_key = "foo.pem" can either refer to:
//
// 1. "foo.pem" in the current directory -> file target
// 2. "foo.pem" module -> rule target
// 3. "foo.pem" file in a different directory, prefixed by a product variable handled
// in a bazel macro. -> string literal
//
// For the first two cases, they are defined using the label attribute. For the third case,
// it's defined with the string attribute.
func BazelStringOrLabelFromProp(
ctx Bp2buildMutatorContext,
propToDistinguish *string) (bazel.LabelAttribute, bazel.StringAttribute) {
var labelAttr bazel.LabelAttribute
var strAttr bazel.StringAttribute
if propToDistinguish == nil {
// nil pointer
return labelAttr, strAttr
}
prop := String(propToDistinguish)
if SrcIsModule(prop) != "" {
// If it's a module (SrcIsModule will return the module name), set the
// resolved label to the label attribute.
labelAttr.SetValue(BazelLabelForModuleDepSingle(ctx, prop))
} else {
// Not a module name. This could be a string literal or a file target in
// the current dir. Check if the path exists:
path := ExistentPathForSource(ctx, ctx.ModuleDir(), prop)
if path.Valid() && parentDir(path.String()) == ctx.ModuleDir() {
// If it exists and the path is relative to the current dir, resolve the bazel label
// for the _file target_ and set it to the label attribute.
//
// Resolution is necessary because this could be a file in a subpackage.
labelAttr.SetValue(BazelLabelForModuleSrcSingle(ctx, prop))
} else {
// Otherwise, treat it as a string literal and assign to the string attribute.
strAttr.Value = propToDistinguish
}
}
return labelAttr, strAttr
}

View file

@ -1,240 +0,0 @@
// Copyright 2022 Google Inc. All rights reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package android
import (
"fmt"
"path/filepath"
"testing"
"android/soong/bazel"
"github.com/google/blueprint"
"github.com/google/blueprint/pathtools"
)
type TestBazelPathContext struct{}
func (*TestBazelPathContext) Config() Config {
cfg := NullConfig("out", "out/soong")
cfg.BazelContext = MockBazelContext{
OutputBaseDir: "out/bazel",
}
return cfg
}
func (*TestBazelPathContext) AddNinjaFileDeps(...string) {
panic("Unimplemented")
}
func TestPathForBazelOut(t *testing.T) {
ctx := &TestBazelPathContext{}
out := PathForBazelOut(ctx, "foo/bar/baz/boq.txt")
expectedPath := filepath.Join(ctx.Config().BazelContext.OutputBase(), "execroot/__main__/foo/bar/baz/boq.txt")
if out.String() != expectedPath {
t.Errorf("incorrect OutputPath: expected %q, got %q", expectedPath, out.String())
}
expectedRelPath := "foo/bar/baz/boq.txt"
if out.Rel() != expectedRelPath {
t.Errorf("incorrect OutputPath.Rel(): expected %q, got %q", expectedRelPath, out.Rel())
}
}
func TestPathForBazelOutRelative(t *testing.T) {
ctx := &TestBazelPathContext{}
out := PathForBazelOutRelative(ctx, "foo/bar", "foo/bar/baz/boq.txt")
expectedPath := filepath.Join(ctx.Config().BazelContext.OutputBase(), "execroot/__main__/foo/bar/baz/boq.txt")
if out.String() != expectedPath {
t.Errorf("incorrect OutputPath: expected %q, got %q", expectedPath, out.String())
}
expectedRelPath := "baz/boq.txt"
if out.Rel() != expectedRelPath {
t.Errorf("incorrect OutputPath.Rel(): expected %q, got %q", expectedRelPath, out.Rel())
}
}
func TestPathForBazelOutRelativeUnderBinFolder(t *testing.T) {
ctx := &TestBazelPathContext{}
out := PathForBazelOutRelative(ctx, "foo/bar", "bazel-out/linux_x86_64-fastbuild-ST-b4ef1c4402f9/bin/foo/bar/baz/boq.txt")
expectedPath := filepath.Join(ctx.Config().BazelContext.OutputBase(), "execroot/__main__/bazel-out/linux_x86_64-fastbuild-ST-b4ef1c4402f9/bin/foo/bar/baz/boq.txt")
if out.String() != expectedPath {
t.Errorf("incorrect OutputPath: expected %q, got %q", expectedPath, out.String())
}
expectedRelPath := "baz/boq.txt"
if out.Rel() != expectedRelPath {
t.Errorf("incorrect OutputPath.Rel(): expected %q, got %q", expectedRelPath, out.Rel())
}
}
func TestPathForBazelOutOutsideOfExecroot(t *testing.T) {
ctx := &TestBazelPathContext{}
out := PathForBazelOut(ctx, "../bazel_tools/linux_x86_64-fastbuild/bin/tools/android/java_base_extras.jar")
expectedPath := filepath.Join(ctx.Config().BazelContext.OutputBase(), "execroot/bazel_tools/linux_x86_64-fastbuild/bin/tools/android/java_base_extras.jar")
if out.String() != expectedPath {
t.Errorf("incorrect OutputPath: expected %q, got %q", expectedPath, out.String())
}
expectedRelPath := "execroot/bazel_tools/linux_x86_64-fastbuild/bin/tools/android/java_base_extras.jar"
if out.Rel() != expectedRelPath {
t.Errorf("incorrect OutputPath.Rel(): expected %q, got %q", expectedRelPath, out.Rel())
}
}
func TestPathForBazelOutRelativeWithParentDirectoryRoot(t *testing.T) {
ctx := &TestBazelPathContext{}
out := PathForBazelOutRelative(ctx, "../bazel_tools", "../bazel_tools/foo/bar/baz.sh")
expectedPath := filepath.Join(ctx.Config().BazelContext.OutputBase(), "execroot/bazel_tools/foo/bar/baz.sh")
if out.String() != expectedPath {
t.Errorf("incorrect OutputPath: expected %q, got %q", expectedPath, out.String())
}
expectedRelPath := "foo/bar/baz.sh"
if out.Rel() != expectedRelPath {
t.Errorf("incorrect OutputPath.Rel(): expected %q, got %q", expectedRelPath, out.Rel())
}
}
type TestBazelConversionPathContext struct {
TestBazelConversionContext
moduleDir string
cfg Config
mockGlobResults *[]string
}
func (ctx *TestBazelConversionPathContext) AddNinjaFileDeps(...string) {
panic("Unimplemented")
}
func (ctx *TestBazelConversionPathContext) GlobWithDeps(string, []string) ([]string, error) {
if ctx.mockGlobResults == nil {
return []string{}, fmt.Errorf("Set mock glob results first")
}
return *ctx.mockGlobResults, nil
}
func (ctx *TestBazelConversionPathContext) PropertyErrorf(string, string, ...interface{}) {
panic("Unimplemented")
}
func (ctx *TestBazelConversionPathContext) GetDirectDep(string) (blueprint.Module, blueprint.DependencyTag) {
panic("Unimplemented")
}
func (ctx *TestBazelConversionPathContext) ModuleFromName(string) (blueprint.Module, bool) {
panic("Unimplemented")
}
func (ctx *TestBazelConversionPathContext) AddUnconvertedBp2buildDep(string) {
panic("Unimplemented")
}
func (ctx *TestBazelConversionPathContext) AddMissingBp2buildDep(string) {
panic("Unimplemented")
}
func (ctx *TestBazelConversionPathContext) Module() Module {
panic("Unimplemented")
}
func (ctx *TestBazelConversionPathContext) Config() Config {
return ctx.cfg
}
func (ctx *TestBazelConversionPathContext) ModuleDir() string {
return ctx.moduleDir
}
func (ctx *TestBazelConversionPathContext) ModuleName() string {
panic("Unimplemented")
}
func (ctx *TestBazelConversionPathContext) ModuleType() string {
panic("Unimplemented")
}
func TestTransformSubpackagePath(t *testing.T) {
cfg := NullConfig("out", "out/soong")
cfg.fs = pathtools.MockFs(map[string][]byte{
"x/Android.bp": nil,
"x/y/Android.bp": nil,
})
var ctx BazelConversionPathContext = &TestBazelConversionPathContext{
moduleDir: "x",
cfg: cfg,
}
pairs := map[string]string{
"y/a.c": "//x/y:a.c",
"./y/a.c": "//x/y:a.c",
"z/b.c": "z/b.c",
"./z/b.c": "z/b.c",
}
for in, out := range pairs {
actual := transformSubpackagePath(ctx.Config(), ctx.ModuleDir(), bazel.Label{Label: in}).Label
if actual != out {
t.Errorf("expected:\n%v\nactual:\n%v", out, actual)
}
}
}
// Check that the files in a specific directory are returned with labels that respect package boundaries
// Since the test uses a mock for GlobWithDeps, the params passed to BazelLabelForSrcPatternExcludes are no-ops
func TestBazelLabelForSrcPatternExcludes(t *testing.T) {
cfg := NullConfig("out", "out/soong")
cfg.fs = pathtools.MockFs(map[string][]byte{
"x/Android.bp": nil,
"x/y/Android.bp": nil,
// .proto files
"foo.proto": nil,
"x/bar.proto": nil,
"x/baz.proto": nil,
"x/y/qux.proto": nil,
})
var ctx BazelConversionPathContext = &TestBazelConversionPathContext{
cfg: cfg,
}
// Root dir
ctx.(*TestBazelConversionPathContext).mockGlobResults = &[]string{"foo.proto", "x/bar.proto", "x/baz.proto", "x/y/qux.proto"}
actualLabelsFromRoot := BazelLabelForSrcPatternExcludes(ctx, ".", "**/*.proto", []string{})
expectedLabelsAsString := []string{"foo.proto", "//x:bar.proto", "//x:baz.proto", "//x/y:qux.proto"}
for i, actual := range actualLabelsFromRoot.Includes {
AssertStringEquals(t, "Error in finding src labels relative to root directory", expectedLabelsAsString[i], actual.Label)
}
// x dir
ctx.(*TestBazelConversionPathContext).mockGlobResults = &[]string{"x/bar.proto", "x/baz.proto", "x/y/qux.proto"}
actualLabelsFromRoot = BazelLabelForSrcPatternExcludes(ctx, "x", "**/*.proto", []string{})
expectedLabelsAsString = []string{"bar.proto", "baz.proto", "//x/y:qux.proto"}
for i, actual := range actualLabelsFromRoot.Includes {
AssertStringEquals(t, "Error in finding src labels relative to x directory", expectedLabelsAsString[i], actual.Label)
}
// y dir
ctx.(*TestBazelConversionPathContext).mockGlobResults = &[]string{"x/y/qux.proto"}
actualLabelsFromRoot = BazelLabelForSrcPatternExcludes(ctx, "x/y", "**/*.proto", []string{})
expectedLabelsAsString = []string{"qux.proto"}
for i, actual := range actualLabelsFromRoot.Includes {
AssertStringEquals(t, "Error in finding src labels relative to x/y directory", expectedLabelsAsString[i], actual.Label)
}
}

View file

@ -1,592 +0,0 @@
// Copyright 2021 Google Inc. All rights reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package android
import (
"fmt"
"testing"
"android/soong/android/allowlists"
"android/soong/bazel"
"github.com/google/blueprint"
"github.com/google/blueprint/proptools"
)
func TestConvertAllModulesInPackage(t *testing.T) {
testCases := []struct {
prefixes allowlists.Bp2BuildConfig
packageDir string
}{
{
prefixes: allowlists.Bp2BuildConfig{
"a": allowlists.Bp2BuildDefaultTrueRecursively,
},
packageDir: "a",
},
{
prefixes: allowlists.Bp2BuildConfig{
"a/b": allowlists.Bp2BuildDefaultTrueRecursively,
},
packageDir: "a/b",
},
{
prefixes: allowlists.Bp2BuildConfig{
"a/b": allowlists.Bp2BuildDefaultTrueRecursively,
"a/b/c": allowlists.Bp2BuildDefaultTrueRecursively,
},
packageDir: "a/b",
},
{
prefixes: allowlists.Bp2BuildConfig{
"a": allowlists.Bp2BuildDefaultTrueRecursively,
"d/e/f": allowlists.Bp2BuildDefaultTrueRecursively,
},
packageDir: "a/b",
},
{
prefixes: allowlists.Bp2BuildConfig{
"a": allowlists.Bp2BuildDefaultFalse,
"a/b": allowlists.Bp2BuildDefaultTrueRecursively,
"a/b/c": allowlists.Bp2BuildDefaultFalse,
},
packageDir: "a/b",
},
{
prefixes: allowlists.Bp2BuildConfig{
"a": allowlists.Bp2BuildDefaultTrueRecursively,
"a/b": allowlists.Bp2BuildDefaultFalse,
"a/b/c": allowlists.Bp2BuildDefaultTrueRecursively,
},
packageDir: "a",
},
{
prefixes: allowlists.Bp2BuildConfig{
"a": allowlists.Bp2BuildDefaultFalseRecursively,
"a/b": allowlists.Bp2BuildDefaultTrue,
},
packageDir: "a/b",
},
{
prefixes: allowlists.Bp2BuildConfig{
"a": allowlists.Bp2BuildDefaultFalseRecursively,
"a/b": allowlists.Bp2BuildDefaultTrueRecursively,
},
packageDir: "a/b/c",
},
}
for _, test := range testCases {
if ok, _ := bp2buildDefaultTrueRecursively(test.packageDir, test.prefixes); !ok {
t.Errorf("Expected to convert all modules in %s based on %v, but failed.", test.packageDir, test.prefixes)
}
}
}
func TestModuleOptIn(t *testing.T) {
testCases := []struct {
prefixes allowlists.Bp2BuildConfig
packageDir string
}{
{
prefixes: allowlists.Bp2BuildConfig{
"a/b": allowlists.Bp2BuildDefaultFalse,
},
packageDir: "a/b",
},
{
prefixes: allowlists.Bp2BuildConfig{
"a": allowlists.Bp2BuildDefaultFalse,
"a/b": allowlists.Bp2BuildDefaultTrueRecursively,
},
packageDir: "a",
},
{
prefixes: allowlists.Bp2BuildConfig{
"a/b": allowlists.Bp2BuildDefaultTrueRecursively,
},
packageDir: "a", // opt-in by default
},
{
prefixes: allowlists.Bp2BuildConfig{
"a/b/c": allowlists.Bp2BuildDefaultTrueRecursively,
},
packageDir: "a/b",
},
{
prefixes: allowlists.Bp2BuildConfig{
"a": allowlists.Bp2BuildDefaultTrueRecursively,
"d/e/f": allowlists.Bp2BuildDefaultTrueRecursively,
},
packageDir: "foo/bar",
},
{
prefixes: allowlists.Bp2BuildConfig{
"a": allowlists.Bp2BuildDefaultTrueRecursively,
"a/b": allowlists.Bp2BuildDefaultFalse,
"a/b/c": allowlists.Bp2BuildDefaultTrueRecursively,
},
packageDir: "a/b",
},
{
prefixes: allowlists.Bp2BuildConfig{
"a": allowlists.Bp2BuildDefaultFalse,
"a/b": allowlists.Bp2BuildDefaultTrueRecursively,
"a/b/c": allowlists.Bp2BuildDefaultFalse,
},
packageDir: "a",
},
{
prefixes: allowlists.Bp2BuildConfig{
"a": allowlists.Bp2BuildDefaultFalseRecursively,
"a/b": allowlists.Bp2BuildDefaultTrue,
},
packageDir: "a/b/c",
},
{
prefixes: allowlists.Bp2BuildConfig{
"a": allowlists.Bp2BuildDefaultTrueRecursively,
"a/b": allowlists.Bp2BuildDefaultFalseRecursively,
},
packageDir: "a/b/c",
},
}
for _, test := range testCases {
if ok, _ := bp2buildDefaultTrueRecursively(test.packageDir, test.prefixes); ok {
t.Errorf("Expected to allow module opt-in in %s based on %v, but failed.", test.packageDir, test.prefixes)
}
}
}
type TestBazelModule struct {
bazel.TestModuleInfo
BazelModuleBase
}
var _ blueprint.Module = TestBazelModule{}
func (m TestBazelModule) Name() string {
return m.TestModuleInfo.ModuleName
}
func (m TestBazelModule) GenerateBuildActions(blueprint.ModuleContext) {
}
type TestBazelConversionContext struct {
omc bazel.OtherModuleTestContext
allowlist Bp2BuildConversionAllowlist
errors []string
}
var _ bazelOtherModuleContext = &TestBazelConversionContext{}
func (bcc *TestBazelConversionContext) OtherModuleType(m blueprint.Module) string {
return bcc.omc.OtherModuleType(m)
}
func (bcc *TestBazelConversionContext) OtherModuleName(m blueprint.Module) string {
return bcc.omc.OtherModuleName(m)
}
func (bcc *TestBazelConversionContext) OtherModuleDir(m blueprint.Module) string {
return bcc.omc.OtherModuleDir(m)
}
func (bcc *TestBazelConversionContext) ModuleErrorf(format string, args ...interface{}) {
bcc.errors = append(bcc.errors, fmt.Sprintf(format, args...))
}
func (bcc *TestBazelConversionContext) Config() Config {
return Config{
&config{
Bp2buildPackageConfig: bcc.allowlist,
},
}
}
var bazelableBazelModuleBase = BazelModuleBase{
bazelProperties: properties{
Bazel_module: BazelModuleProperties{
CanConvertToBazel: true,
},
},
}
func TestBp2BuildAllowlist(t *testing.T) {
testCases := []struct {
description string
shouldConvert bool
expectedErrors []string
module TestBazelModule
allowlist Bp2BuildConversionAllowlist
}{
{
description: "allowlist enables module",
shouldConvert: true,
module: TestBazelModule{
TestModuleInfo: bazel.TestModuleInfo{
ModuleName: "foo",
Typ: "rule1",
Dir: "dir1",
},
BazelModuleBase: bazelableBazelModuleBase,
},
allowlist: Bp2BuildConversionAllowlist{
moduleAlwaysConvert: map[string]bool{
"foo": true,
},
},
},
{
description: "module in name allowlist and type allowlist fails",
shouldConvert: false,
expectedErrors: []string{"A module \"foo\" of type \"rule1\" cannot be in moduleAlwaysConvert and also be in moduleTypeAlwaysConvert"},
module: TestBazelModule{
TestModuleInfo: bazel.TestModuleInfo{
ModuleName: "foo",
Typ: "rule1",
Dir: "dir1",
},
BazelModuleBase: bazelableBazelModuleBase,
},
allowlist: Bp2BuildConversionAllowlist{
moduleAlwaysConvert: map[string]bool{
"foo": true,
},
moduleTypeAlwaysConvert: map[string]bool{
"rule1": true,
},
},
},
{
description: "module in allowlist and denylist fails",
shouldConvert: false,
expectedErrors: []string{"a module \"foo\" cannot be in moduleDoNotConvert and also be in moduleAlwaysConvert"},
module: TestBazelModule{
TestModuleInfo: bazel.TestModuleInfo{
ModuleName: "foo",
Typ: "rule1",
Dir: "dir1",
},
BazelModuleBase: bazelableBazelModuleBase,
},
allowlist: Bp2BuildConversionAllowlist{
moduleAlwaysConvert: map[string]bool{
"foo": true,
},
moduleDoNotConvert: map[string]bool{
"foo": true,
},
},
},
{
description: "module allowlist and enabled directory",
shouldConvert: false,
expectedErrors: []string{"A module cannot be in a directory marked Bp2BuildDefaultTrue or Bp2BuildDefaultTrueRecursively and also be in moduleAlwaysConvert. Directory: 'existing/build/dir' Module: 'foo'"},
module: TestBazelModule{
TestModuleInfo: bazel.TestModuleInfo{
ModuleName: "foo",
Typ: "rule1",
Dir: "existing/build/dir",
},
BazelModuleBase: bazelableBazelModuleBase,
},
allowlist: Bp2BuildConversionAllowlist{
moduleAlwaysConvert: map[string]bool{
"foo": true,
},
defaultConfig: allowlists.Bp2BuildConfig{
"existing/build/dir": allowlists.Bp2BuildDefaultTrue,
},
},
},
{
description: "module allowlist and enabled subdirectory",
shouldConvert: false,
expectedErrors: []string{"A module cannot be in a directory marked Bp2BuildDefaultTrue or Bp2BuildDefaultTrueRecursively and also be in moduleAlwaysConvert. Directory: 'existing/build/dir' Module: 'foo'"},
module: TestBazelModule{
TestModuleInfo: bazel.TestModuleInfo{
ModuleName: "foo",
Typ: "rule1",
Dir: "existing/build/dir/subdir",
},
BazelModuleBase: bazelableBazelModuleBase,
},
allowlist: Bp2BuildConversionAllowlist{
moduleAlwaysConvert: map[string]bool{
"foo": true,
},
defaultConfig: allowlists.Bp2BuildConfig{
"existing/build/dir": allowlists.Bp2BuildDefaultTrueRecursively,
},
},
},
{
description: "module enabled in unit test short-circuits other allowlists",
shouldConvert: true,
module: TestBazelModule{
TestModuleInfo: bazel.TestModuleInfo{
ModuleName: "foo",
Typ: "rule1",
Dir: ".",
},
BazelModuleBase: BazelModuleBase{
bazelProperties: properties{
Bazel_module: BazelModuleProperties{
CanConvertToBazel: true,
Bp2build_available: proptools.BoolPtr(true),
},
},
},
},
allowlist: Bp2BuildConversionAllowlist{
moduleAlwaysConvert: map[string]bool{
"foo": true,
},
moduleDoNotConvert: map[string]bool{
"foo": true,
},
},
},
}
for _, test := range testCases {
t.Run(test.description, func(t *testing.T) {
bcc := &TestBazelConversionContext{
omc: bazel.OtherModuleTestContext{
Modules: []bazel.TestModuleInfo{
test.module.TestModuleInfo,
},
},
allowlist: test.allowlist,
}
shouldConvert := test.module.shouldConvertWithBp2build(bcc,
shouldConvertParams{
module: test.module.TestModuleInfo,
moduleDir: test.module.TestModuleInfo.Dir,
moduleType: test.module.TestModuleInfo.Typ,
moduleName: test.module.TestModuleInfo.ModuleName,
},
)
if test.shouldConvert != shouldConvert {
t.Errorf("Module shouldConvert expected to be: %v, but was: %v", test.shouldConvert, shouldConvert)
}
errorsMatch := true
if len(test.expectedErrors) != len(bcc.errors) {
errorsMatch = false
} else {
for i, err := range test.expectedErrors {
if err != bcc.errors[i] {
errorsMatch = false
}
}
}
if !errorsMatch {
t.Errorf("Expected errors to be: %v, but were: %v", test.expectedErrors, bcc.errors)
}
})
}
}
func TestBp2buildAllowList(t *testing.T) {
allowlist := GetBp2BuildAllowList()
for k, v := range allowlists.Bp2buildDefaultConfig {
if allowlist.defaultConfig[k] != v {
t.Errorf("bp2build default config of %s: expected: %v, got: %v", k, v, allowlist.defaultConfig[k])
}
}
for k, v := range allowlists.Bp2buildKeepExistingBuildFile {
if allowlist.keepExistingBuildFile[k] != v {
t.Errorf("bp2build keep existing build file of %s: expected: %v, got: %v", k, v, allowlist.keepExistingBuildFile[k])
}
}
for _, k := range allowlists.Bp2buildModuleTypeAlwaysConvertList {
if !allowlist.moduleTypeAlwaysConvert[k] {
t.Errorf("bp2build module type always convert of %s: expected: true, got: %v", k, allowlist.moduleTypeAlwaysConvert[k])
}
}
for _, k := range allowlists.Bp2buildModuleDoNotConvertList {
if !allowlist.moduleDoNotConvert[k] {
t.Errorf("bp2build module do not convert of %s: expected: true, got: %v", k, allowlist.moduleDoNotConvert[k])
}
}
}
func TestShouldKeepExistingBuildFileForDir(t *testing.T) {
allowlist := NewBp2BuildAllowlist()
// entry "a/b2/c2" is moot because of its parent "a/b2"
allowlist.SetKeepExistingBuildFile(map[string]bool{"a": false, "a/b1": false, "a/b2": true, "a/b1/c1": true, "a/b2/c2": false})
truths := []string{"a", "a/b1", "a/b2", "a/b1/c1", "a/b2/c", "a/b2/c2", "a/b2/c2/d"}
falsities := []string{"a1", "a/b", "a/b1/c"}
for _, dir := range truths {
if !allowlist.ShouldKeepExistingBuildFileForDir(dir) {
t.Errorf("%s expected TRUE but was FALSE", dir)
}
}
for _, dir := range falsities {
if allowlist.ShouldKeepExistingBuildFileForDir(dir) {
t.Errorf("%s expected FALSE but was TRUE", dir)
}
}
}
type mixedBuildModule struct {
ModuleBase
BazelModuleBase
props struct {
Deps []string
Mixed_build_incompatible *bool
QueuedBazelCall bool `blueprint:"mutated"`
}
}
type mixedBuildModuleInfo struct {
QueuedBazelCall bool
}
var mixedBuildModuleProvider = blueprint.NewProvider(mixedBuildModuleInfo{})
func mixedBuildModuleFactory() Module {
m := &mixedBuildModule{}
m.AddProperties(&m.props)
InitAndroidArchModule(m, HostAndDeviceDefault, MultilibBoth)
InitBazelModule(m)
return m
}
func (m *mixedBuildModule) ConvertWithBp2build(ctx Bp2buildMutatorContext) {
}
func (m *mixedBuildModule) DepsMutator(ctx BottomUpMutatorContext) {
ctx.AddDependency(ctx.Module(), installDepTag{}, m.props.Deps...)
}
func (m *mixedBuildModule) GenerateAndroidBuildActions(ctx ModuleContext) {
}
func (m *mixedBuildModule) IsMixedBuildSupported(ctx BaseModuleContext) bool {
return !proptools.Bool(m.props.Mixed_build_incompatible)
}
func (m *mixedBuildModule) QueueBazelCall(ctx BaseModuleContext) {
m.props.QueuedBazelCall = true
}
func (m *mixedBuildModule) ProcessBazelQueryResponse(ctx ModuleContext) {
ctx.SetProvider(mixedBuildModuleProvider, mixedBuildModuleInfo{
QueuedBazelCall: m.props.QueuedBazelCall,
})
}
var prepareForMixedBuildTests = FixtureRegisterWithContext(func(ctx RegistrationContext) {
ctx.RegisterModuleType("deps", mixedBuildModuleFactory)
RegisterMixedBuildsMutator(ctx)
})
func TestMixedBuildsEnabledForType(t *testing.T) {
baseBp := `
deps {
name: "foo",
deps: ["bar"],
target: { windows: { enabled: true } },
%s
}
`
depBp := `
deps {
name: "bar",
target: {
windows: {
enabled: true,
},
},
}
`
testCases := []struct {
desc string
variant *string
missingDeps bool
extraBpInfo string
mixedBuildsEnabled bool
}{
{
desc: "mixed builds works",
mixedBuildsEnabled: true,
extraBpInfo: `bazel_module: { bp2build_available: true },`,
},
{
desc: "missing deps",
missingDeps: true,
mixedBuildsEnabled: false,
extraBpInfo: `bazel_module: { bp2build_available: true },`,
},
{
desc: "windows no mixed builds",
mixedBuildsEnabled: false,
variant: proptools.StringPtr("windows_x86"),
extraBpInfo: `bazel_module: { bp2build_available: true },`,
},
{
desc: "mixed builds disabled by type",
mixedBuildsEnabled: false,
extraBpInfo: `mixed_build_incompatible: true,
bazel_module: { bp2build_available: true },`,
},
{
desc: "mixed builds not bp2build available",
mixedBuildsEnabled: false,
extraBpInfo: `bazel_module: { bp2build_available: false },`,
},
}
for _, tc := range testCases {
t.Run(tc.desc, func(t *testing.T) {
handlers := GroupFixturePreparers(
prepareForMixedBuildTests,
PrepareForTestWithArchMutator,
FixtureModifyConfig(func(config Config) {
config.BazelContext = MockBazelContext{
OutputBaseDir: "base",
}
config.Targets[Windows] = []Target{
{Windows, Arch{ArchType: X86_64}, NativeBridgeDisabled, "", "", true},
{Windows, Arch{ArchType: X86}, NativeBridgeDisabled, "", "", true},
}
}),
)
bp := fmt.Sprintf(baseBp, tc.extraBpInfo)
if tc.missingDeps {
handlers = GroupFixturePreparers(
handlers,
PrepareForTestWithAllowMissingDependencies,
)
} else {
bp += depBp
}
result := handlers.RunTestWithBp(t, bp)
variant := proptools.StringDefault(tc.variant, "android_arm64_armv8-a")
m := result.ModuleForTests("foo", variant)
mixedBuildModuleInfo := result.TestContext.ModuleProvider(m.Module(), mixedBuildModuleProvider).(mixedBuildModuleInfo)
if w, g := tc.mixedBuildsEnabled, mixedBuildModuleInfo.QueuedBazelCall; w != g {
t.Errorf("Expected mixed builds enabled %t, got mixed builds enabled %t", w, g)
}
})
}
}

View file

@ -84,21 +84,13 @@ type CmdArgs struct {
SoongOutDir string
SoongVariables string
SymlinkForestMarker string
Bp2buildMarker string
BazelQueryViewDir string
ModuleGraphFile string
ModuleActionsFile string
DocFile string
BazelQueryViewDir string
ModuleGraphFile string
ModuleActionsFile string
DocFile string
MultitreeBuild bool
BazelMode bool
BazelModeStaging bool
BazelForceEnabledModules string
UseBazelProxy bool
BuildFromSourceStub bool
EnsureAllowlistIntegrity bool
@ -109,12 +101,6 @@ const (
// Don't use bazel at all during module analysis.
AnalysisNoBazel SoongBuildMode = iota
// Symlink fores mode: merge two directory trees into a symlink forest
SymlinkForest
// Bp2build mode: Generate BUILD files from blueprint files and exit.
Bp2build
// Generate BUILD files which faithfully represent the dependency graph of
// blueprint modules. Individual BUILD targets will not, however, faitfhully
// express build semantics.
@ -125,15 +111,6 @@ const (
// Generate a documentation file for module type definitions and exit.
GenerateDocFile
// Use bazel during analysis of a few allowlisted build modules. The allowlist
// is considered "staging, as these are modules being prepared to be released
// into prod mode shortly after.
BazelStagingMode
// Use bazel during analysis of build modules from an allowlist carefully
// curated by the build team to be proven stable.
BazelProdMode
)
// SoongOutDir returns the build output directory for the configuration.
@ -265,10 +242,6 @@ type config struct {
// Only available on configs created by TestConfig
TestProductVariables *ProductVariables
// A specialized context object for Bazel/Soong mixed builds and migration
// purposes.
BazelContext BazelContext
ProductVariablesFileName string
// BuildOS stores the OsType for the OS that the build is running on.
@ -310,9 +283,7 @@ type config struct {
fs pathtools.FileSystem
mockBpList string
BuildMode SoongBuildMode
Bp2buildPackageConfig Bp2BuildConversionAllowlist
Bp2buildSoongConfigDefinitions soongconfig.Bp2BuildSoongConfigDefinitions
BuildMode SoongBuildMode
// If MultitreeBuild is true then this is one inner tree of a multitree
// build directed by the multitree orchestrator.
@ -328,29 +299,6 @@ type config struct {
OncePer
// These fields are only used for metrics collection. A module should be added
// to these maps only if its implementation supports Bazel handling in mixed
// builds. A module being in the "enabled" list indicates that there is a
// variant of that module for which bazel-handling actually took place.
// A module being in the "disabled" list indicates that there is a variant of
// that module for which bazel-handling was denied.
mixedBuildsLock sync.Mutex
mixedBuildEnabledModules map[string]struct{}
mixedBuildDisabledModules map[string]struct{}
// These are modules to be built with Bazel beyond the allowlisted/build-mode
// specified modules. They are passed via the command-line flag
// "--bazel-force-enabled-modules"
bazelForceEnabledModules map[string]struct{}
// Names of Bazel targets as defined by BUILD files in the source tree,
// keyed by the directory in which they are defined.
bazelTargetsByDir map[string][]string
// If true, for any requests to Bazel, communicate with a Bazel proxy using
// unix sockets, instead of spawning Bazel as a subprocess.
UseBazelProxy bool
// If buildFromSourceStub is true then the Java API stubs are
// built from the source Java files, not the signature text files.
buildFromSourceStub bool
@ -561,14 +509,10 @@ func NewConfig(cmdArgs CmdArgs, availableEnv map[string]string) (Config, error)
runGoTests: cmdArgs.RunGoTests,
multilibConflicts: make(map[ArchType]bool),
moduleListFile: cmdArgs.ModuleListFile,
fs: pathtools.NewOsFs(absSrcDir),
mixedBuildDisabledModules: make(map[string]struct{}),
mixedBuildEnabledModules: make(map[string]struct{}),
bazelForceEnabledModules: make(map[string]struct{}),
moduleListFile: cmdArgs.ModuleListFile,
fs: pathtools.NewOsFs(absSrcDir),
MultitreeBuild: cmdArgs.MultitreeBuild,
UseBazelProxy: cmdArgs.UseBazelProxy,
buildFromSourceStub: cmdArgs.BuildFromSourceStub,
}
@ -661,28 +605,9 @@ func NewConfig(cmdArgs CmdArgs, availableEnv map[string]string) (Config, error)
config.BuildMode = mode
}
}
setBazelMode := func(arg bool, argName string, mode SoongBuildMode) {
if arg {
if config.BuildMode != AnalysisNoBazel {
fmt.Fprintf(os.Stderr, "buildMode is already set, illegal argument: %s", argName)
os.Exit(1)
}
config.BuildMode = mode
}
}
setBuildMode(cmdArgs.SymlinkForestMarker, SymlinkForest)
setBuildMode(cmdArgs.Bp2buildMarker, Bp2build)
setBuildMode(cmdArgs.BazelQueryViewDir, GenerateQueryView)
setBuildMode(cmdArgs.ModuleGraphFile, GenerateModuleGraph)
setBuildMode(cmdArgs.DocFile, GenerateDocFile)
setBazelMode(cmdArgs.BazelMode, "--bazel-mode", BazelProdMode)
setBazelMode(cmdArgs.BazelModeStaging, "--bazel-mode-staging", BazelStagingMode)
for _, module := range getForceEnabledModulesFromFlag(cmdArgs.BazelForceEnabledModules) {
config.bazelForceEnabledModules[module] = struct{}{}
}
config.BazelContext, err = NewBazelContext(config)
config.Bp2buildPackageConfig = GetBp2BuildAllowList()
// TODO(b/276958307): Replace the hardcoded list to a sdk_library local prop.
config.apiLibraries = map[string]struct{}{
@ -719,13 +644,6 @@ func NewConfig(cmdArgs CmdArgs, availableEnv map[string]string) (Config, error)
return Config{config}, err
}
func getForceEnabledModulesFromFlag(forceEnabledFlag string) []string {
if forceEnabledFlag == "" {
return []string{}
}
return strings.Split(forceEnabledFlag, ",")
}
// mockFileSystem replaces all reads with accesses to the provided map of
// filenames to contents stored as a byte slice.
func (c *config) mockFileSystem(bp string, fs map[string][]byte) {
@ -756,41 +674,6 @@ func (c *config) mockFileSystem(bp string, fs map[string][]byte) {
c.mockBpList = blueprint.MockModuleListFile
}
// TODO(b/265062549): Add a field to our collected (and uploaded) metrics which
// describes a reason that we fell back to non-mixed builds.
// Returns true if "Bazel builds" is enabled. In this mode, part of build
// analysis is handled by Bazel.
func (c *config) IsMixedBuildsEnabled() bool {
globalMixedBuildsSupport := c.Once(OnceKey{"globalMixedBuildsSupport"}, func() interface{} {
if c.productVariables.DeviceArch != nil && *c.productVariables.DeviceArch == "riscv64" {
return false
}
// Disable Bazel when Kythe is running
if c.EmitXrefRules() {
return false
}
if c.IsEnvTrue("GLOBAL_THINLTO") {
return false
}
if len(c.productVariables.SanitizeHost) > 0 {
return false
}
if len(c.productVariables.SanitizeDevice) > 0 {
return false
}
if len(c.productVariables.SanitizeDeviceDiag) > 0 {
return false
}
if len(c.productVariables.SanitizeDeviceArch) > 0 {
return false
}
return true
}).(bool)
bazelModeEnabled := c.BuildMode == BazelProdMode || c.BuildMode == BazelStagingMode
return globalMixedBuildsSupport && bazelModeEnabled
}
func (c *config) SetAllowMissingDependencies() {
c.productVariables.Allow_missing_dependencies = proptools.BoolPtr(true)
}
@ -1066,8 +949,6 @@ func (c *config) AllSupportedApiLevels() []ApiLevel {
// DefaultAppTargetSdk returns the API level that platform apps are targeting.
// This converts a codename to the exact ApiLevel it represents.
func (c *config) DefaultAppTargetSdk(ctx EarlyModuleContext) ApiLevel {
// This logic is replicated in starlark, if changing logic here update starlark code too
// https://cs.android.com/android/platform/superproject/+/master:build/bazel/rules/common/api.bzl;l=72;drc=231c7e8c8038fd478a79eb68aa5b9f5c64e0e061
if Bool(c.productVariables.Platform_sdk_final) {
return c.PlatformSdkVersion()
}
@ -1425,10 +1306,6 @@ func (c *config) PrebuiltHiddenApiDir(_ PathContext) string {
return String(c.productVariables.PrebuiltHiddenApiDir)
}
func (c *config) BazelModulesForceEnabledByFlag() map[string]struct{} {
return c.bazelForceEnabledModules
}
func (c *config) IsVndkDeprecated() bool {
return !Bool(c.productVariables.KeepVndk)
}
@ -2041,38 +1918,6 @@ func (c *config) UseHostMusl() bool {
return Bool(c.productVariables.HostMusl)
}
func (c *config) GetMixedBuildsEnabledModules() map[string]struct{} {
return c.mixedBuildEnabledModules
}
func (c *config) GetMixedBuildsDisabledModules() map[string]struct{} {
return c.mixedBuildDisabledModules
}
func (c *config) LogMixedBuild(ctx BaseModuleContext, useBazel bool) {
moduleName := ctx.Module().Name()
c.mixedBuildsLock.Lock()
defer c.mixedBuildsLock.Unlock()
if useBazel {
c.mixedBuildEnabledModules[moduleName] = struct{}{}
} else {
c.mixedBuildDisabledModules[moduleName] = struct{}{}
}
}
func (c *config) HasBazelBuildTargetInSource(dir string, target string) bool {
for _, existingTarget := range c.bazelTargetsByDir[dir] {
if target == existingTarget {
return true
}
}
return false
}
func (c *config) SetBazelBuildFileTargets(bazelTargetsByDir map[string][]string) {
c.bazelTargetsByDir = bazelTargetsByDir
}
// ApiSurfaces directory returns the source path inside the api_surfaces repo
// (relative to workspace root).
func (c *config) ApiSurfacesDir(s ApiSurface, version string) string {
@ -2106,12 +1951,6 @@ func (c *config) SetBuildFromTextStub(b bool) {
c.productVariables.Build_from_text_stub = boolPtr(b)
}
func (c *config) AddForceEnabledModules(forceEnabled []string) {
for _, forceEnabledModule := range forceEnabled {
c.bazelForceEnabledModules[forceEnabledModule] = struct{}{}
}
}
func (c *config) SetApiLibraries(libs []string) {
c.apiLibraries = make(map[string]struct{})
for _, lib := range libs {
@ -2123,11 +1962,6 @@ func (c *config) GetApiLibraries() map[string]struct{} {
return c.apiLibraries
}
// Bp2buildMode indicates whether the config is for bp2build mode of Soong
func (c *config) Bp2buildMode() bool {
return c.BuildMode == Bp2build
}
func (c *deviceConfig) CheckVendorSeappViolations() bool {
return Bool(c.config.productVariables.CheckVendorSeappViolations)
}

View file

@ -19,7 +19,6 @@ import (
"io/ioutil"
"os"
"runtime"
"sort"
"strconv"
"time"
@ -93,23 +92,6 @@ func collectMetrics(config Config, eventHandler *metrics.EventHandler) *soong_me
}
metrics.Events = append(metrics.Events, &perfInfo)
}
mixedBuildsInfo := soong_metrics_proto.MixedBuildsInfo{}
mixedBuildEnabledModules := make([]string, 0, len(config.mixedBuildEnabledModules))
for module, _ := range config.mixedBuildEnabledModules {
mixedBuildEnabledModules = append(mixedBuildEnabledModules, module)
}
mixedBuildDisabledModules := make([]string, 0, len(config.mixedBuildDisabledModules))
for module, _ := range config.mixedBuildDisabledModules {
mixedBuildDisabledModules = append(mixedBuildDisabledModules, module)
}
// Sorted for deterministic output.
sort.Strings(mixedBuildEnabledModules)
sort.Strings(mixedBuildDisabledModules)
mixedBuildsInfo.MixedBuildEnabledModules = mixedBuildEnabledModules
mixedBuildsInfo.MixedBuildDisabledModules = mixedBuildDisabledModules
metrics.MixedBuildsInfo = &mixedBuildsInfo
return metrics
}

View file

@ -16,7 +16,6 @@ package android
import (
"android/soong/bazel"
"android/soong/ui/metrics/bp2build_metrics_proto"
"crypto/md5"
"encoding/hex"
"encoding/json"
@ -96,16 +95,6 @@ type Module interface {
AddProperties(props ...interface{})
GetProperties() []interface{}
// If this module should not have bazel BUILD definitions generated by bp2build,
// GetUnconvertedReason returns a reason this is the case.
GetUnconvertedReason() *UnconvertedReason
// Bp2buildTargets returns the target(s) generated for Bazel via bp2build for this module
Bp2buildTargets() []bp2buildInfo
GetUnconvertedBp2buildDeps() []string
GetMissingBp2buildDeps() []string
GetPartitionForBp2build() string
BuildParamsForTests() []BuildParams
RuleParamsForTests() map[blueprint.Rule]blueprint.RuleParams
VariablesForTests() map[string]string
@ -520,9 +509,6 @@ type commonProperties struct {
// constants in image.go, but can also be set to a custom value by individual module types.
ImageVariation string `blueprint:"mutated"`
// Bazel conversion status
BazelConversionStatus BazelConversionStatus `blueprint:"mutated"`
// SoongConfigTrace records accesses to VendorVars (soong_config). The trace will be hashed
// and used as a subdir of PathForModuleOut. Note that we mainly focus on incremental
// builds among similar products (e.g. aosp_cf_x86_64_phone and aosp_cf_x86_64_foldable),
@ -532,41 +518,6 @@ type commonProperties struct {
SoongConfigTraceHash string `blueprint:"mutated"`
}
// CommonAttributes represents the common Bazel attributes from which properties
// in `commonProperties` are translated/mapped; such properties are annotated in
// a list their corresponding attribute. It is embedded within `bp2buildInfo`.
type CommonAttributes struct {
// Soong nameProperties -> Bazel name
Name string
// Data mapped from: Required
Data bazel.LabelListAttribute
// SkipData is neither a Soong nor Bazel target attribute
// If true, this will not fill the data attribute automatically
// This is useful for Soong modules that have 1:many Bazel targets
// Some of the generated Bazel targets might not have a data attribute
SkipData *bool
Tags bazel.StringListAttribute
Applicable_licenses bazel.LabelListAttribute
Testonly *bool
// Dir is neither a Soong nor Bazel target attribute
// If set, the bazel target will be created in this directory
// If unset, the bazel target will default to be created in the directory of the visited soong module
Dir *string
}
// constraintAttributes represents Bazel attributes pertaining to build constraints,
// which make restrict building a Bazel target for some set of platforms.
type constraintAttributes struct {
// Constraint values this target can be built for.
Target_compatible_with bazel.LabelListAttribute
}
type distProperties struct {
// configuration to distribute output files from this module to the distribution
// directory (default: $OUT/dist, configurable with $DIST_DIR)
@ -804,234 +755,6 @@ func InitCommonOSAndroidMultiTargetsArchModule(m Module, hod HostOrDeviceSupport
m.base().commonProperties.CreateCommonOSVariant = true
}
func (attrs *CommonAttributes) getRequiredWithoutCycles(ctx *bottomUpMutatorContext, props *commonProperties) []string {
// Treat `required` as if it's empty if data should be skipped for this target,
// as `required` is only used for the `data` attribute at this time, and we want
// to avoid lookups of labels that won't actually be dependencies of this target.
// TODO: b/202299295 - Refactor this to use `required` dependencies, once they
// are handled other than passing to `data`.
if proptools.Bool(attrs.SkipData) {
return []string{}
}
// The required property can contain the module itself. This causes a cycle
// when generated as the 'data' label list attribute in Bazel. Remove it if
// it exists. See b/247985196.
_, requiredWithoutCycles := RemoveFromList(ctx.ModuleName(), props.Required)
return FirstUniqueStrings(requiredWithoutCycles)
}
func (attrs *CommonAttributes) fillCommonBp2BuildModuleAttrs(ctx *bottomUpMutatorContext,
enabledPropertyOverrides bazel.BoolAttribute) constraintAttributes {
mod := ctx.Module().base()
// Assert passed-in attributes include Name
if len(attrs.Name) == 0 {
if ctx.ModuleType() != "package" {
ctx.ModuleErrorf("CommonAttributes in fillCommonBp2BuildModuleAttrs expects a `.Name`!")
}
}
depsToLabelList := func(deps []string) bazel.LabelListAttribute {
return bazel.MakeLabelListAttribute(BazelLabelForModuleDeps(ctx, deps))
}
var enabledProperty bazel.BoolAttribute
onlyAndroid := false
neitherHostNorDevice := false
osSupport := map[string]bool{}
// if the target is enabled and supports arch variance, determine the defaults based on the module
// type's host or device property and host_supported/device_supported properties
if mod.commonProperties.ArchSpecific {
moduleSupportsDevice := mod.DeviceSupported()
moduleSupportsHost := mod.HostSupported()
if moduleSupportsHost && !moduleSupportsDevice {
// for host only, we specify as unsupported on android rather than listing all host osSupport
// TODO(b/220874839): consider replacing this with a constraint that covers all host osSupport
// instead
enabledProperty.SetSelectValue(bazel.OsConfigurationAxis, Android.Name, proptools.BoolPtr(false))
} else if moduleSupportsDevice && !moduleSupportsHost {
enabledProperty.SetSelectValue(bazel.OsConfigurationAxis, Android.Name, proptools.BoolPtr(true))
// specify as a positive to ensure any target-specific enabled can be resolved
// also save that a target is only android, as if there is only the positive restriction on
// android, it'll be dropped, so we may need to add it back later
onlyAndroid = true
} else if !moduleSupportsHost && !moduleSupportsDevice {
neitherHostNorDevice = true
}
for _, osType := range OsTypeList() {
if osType.Class == Host {
osSupport[osType.Name] = moduleSupportsHost
} else if osType.Class == Device {
osSupport[osType.Name] = moduleSupportsDevice
}
}
}
if neitherHostNorDevice {
// we can't build this, disable
enabledProperty.Value = proptools.BoolPtr(false)
} else if mod.commonProperties.Enabled != nil {
enabledProperty.SetValue(mod.commonProperties.Enabled)
if !*mod.commonProperties.Enabled {
for oss, enabled := range osSupport {
if val := enabledProperty.SelectValue(bazel.OsConfigurationAxis, oss); enabled && val != nil && *val {
// if this should be disabled by default, clear out any enabling we've done
enabledProperty.SetSelectValue(bazel.OsConfigurationAxis, oss, nil)
}
}
}
}
attrs.Applicable_licenses = bazel.MakeLabelListAttribute(BazelLabelForModuleDeps(ctx, mod.commonProperties.Licenses))
requiredWithoutCycles := attrs.getRequiredWithoutCycles(ctx, &mod.commonProperties)
required := depsToLabelList(requiredWithoutCycles)
archVariantProps := mod.GetArchVariantProperties(ctx, &commonProperties{})
for axis, configToProps := range archVariantProps {
for config, _props := range configToProps {
if archProps, ok := _props.(*commonProperties); ok {
requiredWithoutCycles := attrs.getRequiredWithoutCycles(ctx, archProps)
required.SetSelectValue(axis, config, depsToLabelList(requiredWithoutCycles).Value)
if !neitherHostNorDevice {
if archProps.Enabled != nil {
if axis != bazel.OsConfigurationAxis || osSupport[config] {
enabledProperty.SetSelectValue(axis, config, archProps.Enabled)
}
}
}
}
}
}
if !neitherHostNorDevice {
if enabledPropertyOverrides.Value != nil {
enabledProperty.Value = enabledPropertyOverrides.Value
}
for _, axis := range enabledPropertyOverrides.SortedConfigurationAxes() {
configToBools := enabledPropertyOverrides.ConfigurableValues[axis]
for cfg, val := range configToBools {
if axis != bazel.OsConfigurationAxis || osSupport[cfg] || val /*If enabled is explicitly requested via overrides */ {
enabledProperty.SetSelectValue(axis, cfg, &val)
}
}
}
}
productConfigEnabledAttribute := bazel.LabelListAttribute{}
// TODO(b/234497586): Soong config variables and product variables have different overriding behavior, we
// should handle it correctly
if !proptools.BoolDefault(enabledProperty.Value, true) && !neitherHostNorDevice {
// If the module is not enabled by default, then we can check if a
// product variable enables it
productConfigEnabledAttribute = productVariableConfigEnableAttribute(ctx)
if len(productConfigEnabledAttribute.ConfigurableValues) > 0 {
// In this case, an existing product variable configuration overrides any
// module-level `enable: false` definition
newValue := true
enabledProperty.Value = &newValue
}
}
platformEnabledAttribute, err := enabledProperty.ToLabelListAttribute(
bazel.LabelList{[]bazel.Label{{Label: "@platforms//:incompatible"}}, nil},
bazel.LabelList{[]bazel.Label{}, nil})
if err != nil {
ctx.ModuleErrorf("Error processing platform enabled attribute: %s", err)
}
// if android is the only arch/os enabled, then add a restriction to only be compatible with android
if platformEnabledAttribute.IsNil() && onlyAndroid {
l := bazel.LabelAttribute{}
l.SetValue(bazel.Label{Label: bazel.OsConfigurationAxis.SelectKey(Android.Name)})
platformEnabledAttribute.Add(&l)
}
attrs.Data.Append(required)
// SkipData is not an attribute of any Bazel target
// Set this to nil so that it does not appear in the generated build file
attrs.SkipData = nil
moduleEnableConstraints := bazel.LabelListAttribute{}
moduleEnableConstraints.Append(platformEnabledAttribute)
moduleEnableConstraints.Append(productConfigEnabledAttribute)
addCompatibilityConstraintForCompileMultilib(ctx, &moduleEnableConstraints)
return constraintAttributes{Target_compatible_with: moduleEnableConstraints}
}
var (
incompatible = bazel.LabelList{[]bazel.Label{{Label: "@platforms//:incompatible"}}, nil}
)
// If compile_mulitilib is set to
// 1. 32: Add an incompatibility constraint for non-32 arches
// 1. 64: Add an incompatibility constraint for non-64 arches
func addCompatibilityConstraintForCompileMultilib(ctx *bottomUpMutatorContext, enabled *bazel.LabelListAttribute) {
mod := ctx.Module().base()
multilib, _ := decodeMultilib(mod, mod.commonProperties.CompileOS, ctx.Config().IgnorePrefer32OnDevice())
switch multilib {
case "32":
// Add an incompatibility constraint for all known 64-bit arches
enabled.SetSelectValue(bazel.ArchConfigurationAxis, "arm64", incompatible)
enabled.SetSelectValue(bazel.ArchConfigurationAxis, "x86_64", incompatible)
enabled.SetSelectValue(bazel.ArchConfigurationAxis, "riscv64", incompatible)
case "64":
// Add an incompatibility constraint for all known 32-bit arches
enabled.SetSelectValue(bazel.ArchConfigurationAxis, "arm", incompatible)
enabled.SetSelectValue(bazel.ArchConfigurationAxis, "x86", incompatible)
case "both":
// Do nothing: "both" is trivially compatible with 32-bit and 64-bit
// The top level rule (e.g. apex/partition) will be responsible for building this module in both variants via an
// outgoing_transition.
default: // e.g. first, common
// TODO - b/299135307: Add bp2build support for these properties.
}
}
// Check product variables for `enabled: true` flag override.
// Returns a list of the constraint_value targets who enable this override.
func productVariableConfigEnableAttribute(ctx *bottomUpMutatorContext) bazel.LabelListAttribute {
result := bazel.LabelListAttribute{}
productVariableProps, errs := ProductVariableProperties(ctx, ctx.Module())
for _, err := range errs {
ctx.ModuleErrorf("ProductVariableProperties error: %s", err)
}
if productConfigProps, exists := productVariableProps["Enabled"]; exists {
for productConfigProp, prop := range productConfigProps {
flag, ok := prop.(*bool)
if !ok {
ctx.ModuleErrorf("Could not convert product variable enabled property")
}
if flag == nil {
// soong config var is not used to set `enabled`. nothing to do.
continue
} else if *flag {
axis := productConfigProp.ConfigurationAxis()
result.SetSelectValue(axis, bazel.ConditionsDefaultConfigKey, bazel.MakeLabelList([]bazel.Label{{Label: "@platforms//:incompatible"}}))
result.SetSelectValue(axis, productConfigProp.SelectKey(), bazel.LabelList{Includes: []bazel.Label{}})
} else if scp, isSoongConfigProperty := productConfigProp.(SoongConfigProperty); isSoongConfigProperty && scp.value == bazel.ConditionsDefaultConfigKey {
// productVariableConfigEnableAttribute runs only if `enabled: false` is set at the top-level outside soong_config_variables
// conditions_default { enabled: false} is a no-op in this case
continue
} else {
// TODO(b/210546943): handle negative case where `enabled: false`
ctx.ModuleErrorf("`enabled: false` is not currently supported for configuration variables. See b/210546943")
}
}
}
return result
}
// A ModuleBase object contains the properties that are common to all Android
// modules. It should be included as an anonymous field in every module
// struct definition. InitAndroidModule should then be called from the module's
@ -1146,81 +869,6 @@ type ModuleBase struct {
licenseMetadataFile WritablePath
}
// A struct containing all relevant information about a Bazel target converted via bp2build.
type bp2buildInfo struct {
Dir string
BazelProps bazel.BazelTargetModuleProperties
CommonAttrs CommonAttributes
ConstraintAttrs constraintAttributes
Attrs interface{}
}
// TargetName returns the Bazel target name of a bp2build converted target.
func (b bp2buildInfo) TargetName() string {
return b.CommonAttrs.Name
}
// TargetPackage returns the Bazel package of a bp2build converted target.
func (b bp2buildInfo) TargetPackage() string {
return b.Dir
}
// BazelRuleClass returns the Bazel rule class of a bp2build converted target.
func (b bp2buildInfo) BazelRuleClass() string {
return b.BazelProps.Rule_class
}
// BazelRuleLoadLocation returns the location of the Bazel rule of a bp2build converted target.
// This may be empty as native Bazel rules do not need to be loaded.
func (b bp2buildInfo) BazelRuleLoadLocation() string {
return b.BazelProps.Bzl_load_location
}
// BazelAttributes returns the Bazel attributes of a bp2build converted target.
func (b bp2buildInfo) BazelAttributes() []interface{} {
return []interface{}{&b.CommonAttrs, &b.ConstraintAttrs, b.Attrs}
}
func (m *ModuleBase) addBp2buildInfo(info bp2buildInfo) {
m.commonProperties.BazelConversionStatus.Bp2buildInfo = append(m.commonProperties.BazelConversionStatus.Bp2buildInfo, info)
}
func (m *ModuleBase) setPartitionForBp2build(partition string) {
m.commonProperties.BazelConversionStatus.Partition = partition
}
func (m *ModuleBase) setBp2buildUnconvertible(reasonType bp2build_metrics_proto.UnconvertedReasonType, detail string) {
m.commonProperties.BazelConversionStatus.UnconvertedReason = &UnconvertedReason{
ReasonType: int(reasonType),
Detail: detail,
}
}
func (m *ModuleBase) GetUnconvertedReason() *UnconvertedReason {
return m.commonProperties.BazelConversionStatus.UnconvertedReason
}
// Bp2buildTargets returns the Bazel targets bp2build generated for this module.
func (m *ModuleBase) Bp2buildTargets() []bp2buildInfo {
return m.commonProperties.BazelConversionStatus.Bp2buildInfo
}
// Bp2buildTargets returns the Bazel targets bp2build generated for this module.
func (m *ModuleBase) GetPartitionForBp2build() string {
return m.commonProperties.BazelConversionStatus.Partition
}
// GetUnconvertedBp2buildDeps returns the list of module names of this module's direct dependencies that
// were not converted to Bazel.
func (m *ModuleBase) GetUnconvertedBp2buildDeps() []string {
return FirstUniqueStrings(m.commonProperties.BazelConversionStatus.UnconvertedDeps)
}
// GetMissingBp2buildDeps returns the list of module names that were not found in Android.bp files.
func (m *ModuleBase) GetMissingBp2buildDeps() []string {
return FirstUniqueStrings(m.commonProperties.BazelConversionStatus.MissingDeps)
}
func (m *ModuleBase) AddJSONData(d *map[string]interface{}) {
(*d)["Android"] = map[string]interface{}{
// Properties set in Blueprint or in blueprint of a defaults modules
@ -2031,11 +1679,7 @@ func (m *ModuleBase) GenerateBuildActions(blueprintCtx blueprint.ModuleContext)
return
}
if mixedBuildMod, handled := m.isHandledByBazel(ctx); handled {
mixedBuildMod.ProcessBazelQueryResponse(ctx)
} else {
m.module.GenerateAndroidBuildActions(ctx)
}
m.module.GenerateAndroidBuildActions(ctx)
if ctx.Failed() {
return
}
@ -2092,15 +1736,6 @@ func (m *ModuleBase) GenerateBuildActions(blueprintCtx blueprint.ModuleContext)
m.variables = ctx.variables
}
func (m *ModuleBase) isHandledByBazel(ctx ModuleContext) (MixedBuildBuildable, bool) {
if mixedBuildMod, ok := m.module.(MixedBuildBuildable); ok {
if mixedBuildMod.IsMixedBuildSupported(ctx) && (MixedBuildsEnabled(ctx) == MixedBuildEnabled) {
return mixedBuildMod, true
}
}
return nil, false
}
// Check the supplied dist structure to make sure that it is valid.
//
// property - the base property, e.g. dist or dists[1], which is combined with the
@ -2193,6 +1828,18 @@ func (m *ModuleBase) IsNativeBridgeSupported() bool {
return proptools.Bool(m.commonProperties.Native_bridge_supported)
}
// ModuleNameWithPossibleOverride returns the name of the OverrideModule that overrides the current
// variant of this OverridableModule, or ctx.ModuleName() if this module is not an OverridableModule
// or if this variant is not overridden.
func ModuleNameWithPossibleOverride(ctx BaseModuleContext) string {
if overridable, ok := ctx.Module().(OverridableModule); ok {
if o := overridable.GetOverriddenBy(); o != "" {
return o
}
}
return ctx.ModuleName()
}
// SrcIsModule decodes module references in the format ":unqualified-name" or "//namespace:name"
// into the module name, or empty string if the input was not a module reference.
func SrcIsModule(s string) (module string) {
@ -2615,36 +2262,3 @@ func (s *soongConfigTraceSingleton) GenerateBuildActions(ctx SingletonContext) {
WriteFileRule(ctx, outFile, string(j))
ctx.Phony("soong_config_trace", outFile)
}
// Interface implemented by xsd_config which has 1:many mappings in bp2build workspace
// This interface exists because we want to
// 1. Determine the name of the additional targets generated by the primary soong module
// 2. Enable distinguishing an xsd_config module from other Soong modules using type assertion
type XsdConfigBp2buildTargets interface {
CppBp2buildTargetName() string
JavaBp2buildTargetName() string
}
// XsdModuleToTargetName is a function that takes an XsdConfigBp2buildTarget
type XsdModuleToTargetName func(xsd XsdConfigBp2buildTargets) string
// XsdLabelMapper returns a bazel.LabelMapper for partitioning XSD sources/headers given an
// XsdModuleToTargetName function.
func XsdLabelMapper(targetName XsdModuleToTargetName) bazel.LabelMapper {
return func(ctx bazel.OtherModuleContext, label bazel.Label) (string, bool) {
mod, exists := ctx.ModuleFromName(label.OriginalModuleName)
if !exists {
return label.Label, false
}
xsdMod, isXsd := mod.(XsdConfigBp2buildTargets)
if !isXsd {
return label.Label, false
}
// Remove the base module name
ret := strings.TrimSuffix(label.Label, mod.Name())
// Append the language specific target name
ret += targetName(xsdMod)
return ret, true
}
}

View file

@ -15,11 +15,6 @@
package android
import (
"path/filepath"
"android/soong/bazel"
"android/soong/ui/metrics/bp2build_metrics_proto"
"github.com/google/blueprint"
)
@ -32,40 +27,9 @@ import (
// run FinalDeps mutators (CreateVariations disallowed in this phase)
// continue on to GenerateAndroidBuildActions
// RegisterMutatorsForBazelConversion is a alternate registration pipeline for bp2build. Exported for testing.
func RegisterMutatorsForBazelConversion(ctx *Context, preArchMutators []RegisterMutatorFunc) {
bp2buildMutators := append(preArchMutators, registerBp2buildConversionMutator)
registerMutatorsForBazelConversion(ctx, bp2buildMutators)
}
func registerMutatorsForBazelConversion(ctx *Context, bp2buildMutators []RegisterMutatorFunc) {
mctx := &registerMutatorsContext{
bazelConversionMode: true,
}
allMutators := append([]RegisterMutatorFunc{
RegisterNamespaceMutator,
RegisterDefaultsPreArchMutators,
// TODO(b/165114590): this is required to resolve deps that are only prebuilts, but we should
// evaluate the impact on conversion.
RegisterPrebuiltsPreArchMutators,
RegisterPrebuiltsPostDepsMutators,
},
bp2buildMutators...)
// Register bp2build mutators
for _, f := range allMutators {
f(mctx)
}
mctx.mutators.registerAll(ctx)
}
// collateGloballyRegisteredMutators constructs the list of mutators that have been registered
// with the InitRegistrationContext and will be used at runtime.
func collateGloballyRegisteredMutators() sortableComponents {
// ensure mixed builds mutator is the last mutator
finalDeps = append(finalDeps, registerMixedBuildsMutator)
return collateRegisteredMutators(preArch, preDeps, postDeps, finalDeps)
}
@ -94,9 +58,8 @@ func collateRegisteredMutators(preArch, preDeps, postDeps, finalDeps []RegisterM
}
type registerMutatorsContext struct {
mutators sortableComponents
finalPhase bool
bazelConversionMode bool
mutators sortableComponents
finalPhase bool
}
type RegisterMutatorsContext interface {
@ -219,58 +182,6 @@ func FinalDepsMutators(f RegisterMutatorFunc) {
finalDeps = append(finalDeps, f)
}
var bp2buildPreArchMutators = []RegisterMutatorFunc{}
// A minimal context for Bp2build conversion
type Bp2buildMutatorContext interface {
BazelConversionPathContext
BaseMutatorContext
// AddDependency adds a dependency to the given module. It returns a slice of modules for each
// dependency (some entries may be nil).
//
// If the mutator is parallel (see MutatorHandle.Parallel), this method will pause until the
// new dependencies have had the current mutator called on them. If the mutator is not
// parallel this method does not affect the ordering of the current mutator pass, but will
// be ordered correctly for all future mutator passes.
AddDependency(module blueprint.Module, tag blueprint.DependencyTag, name ...string) []blueprint.Module
// CreateBazelTargetModule creates a BazelTargetModule by calling the
// factory method, just like in CreateModule, but also requires
// BazelTargetModuleProperties containing additional metadata for the
// bp2build codegenerator.
CreateBazelTargetModule(bazel.BazelTargetModuleProperties, CommonAttributes, interface{})
// CreateBazelTargetModuleWithRestrictions creates a BazelTargetModule by calling the
// factory method, just like in CreateModule, but also requires
// BazelTargetModuleProperties containing additional metadata for the
// bp2build codegenerator. The generated target is restricted to only be buildable for certain
// platforms, as dictated by a given bool attribute: the target will not be buildable in
// any platform for which this bool attribute is false.
CreateBazelTargetModuleWithRestrictions(bazel.BazelTargetModuleProperties, CommonAttributes, interface{}, bazel.BoolAttribute)
// MarkBp2buildUnconvertible registers the current module as "unconvertible to bp2build" for the
// given reason.
MarkBp2buildUnconvertible(reasonType bp2build_metrics_proto.UnconvertedReasonType, detail string)
// CreateBazelTargetAliasInDir creates an alias definition in `dir` directory.
// This function can be used to create alias definitions in a directory that is different
// from the directory of the visited Soong module.
CreateBazelTargetAliasInDir(dir string, name string, actual bazel.Label)
// CreateBazelConfigSetting creates a config_setting in <dir>/BUILD.bazel
// build/bazel has several static config_setting(s) that are used in Bazel builds.
// This function can be used to createa additional config_setting(s) based on the build graph
// (e.g. a config_setting specific to an apex variant)
CreateBazelConfigSetting(csa bazel.ConfigSettingAttributes, ca CommonAttributes, dir string)
}
// PreArchBp2BuildMutators adds mutators to be register for converting Android Blueprint modules
// into Bazel BUILD targets that should run prior to deps and conversion.
func PreArchBp2BuildMutators(f RegisterMutatorFunc) {
bp2buildPreArchMutators = append(bp2buildPreArchMutators, f)
}
type BaseMutatorContext interface {
BaseModuleContext
@ -301,7 +212,15 @@ type BottomUpMutator func(BottomUpMutatorContext)
type BottomUpMutatorContext interface {
BaseMutatorContext
Bp2buildMutatorContext
// AddDependency adds a dependency to the given module. It returns a slice of modules for each
// dependency (some entries may be nil).
//
// If the mutator is parallel (see MutatorHandle.Parallel), this method will pause until the
// new dependencies have had the current mutator called on them. If the mutator is not
// parallel this method does not affect the ordering of the current mutator pass, but will
// be ordered correctly for all future mutator passes.
AddDependency(module blueprint.Module, tag blueprint.DependencyTag, name ...string) []blueprint.Module
// AddReverseDependency adds a dependency from the destination to the given module.
// Does not affect the ordering of the current mutator pass, but will be ordered
@ -416,10 +335,9 @@ type bottomUpMutatorContext struct {
}
func bottomUpMutatorContextFactory(ctx blueprint.BottomUpMutatorContext, a Module,
finalPhase, bazelConversionMode bool) BottomUpMutatorContext {
finalPhase bool) BottomUpMutatorContext {
moduleContext := a.base().baseModuleContextFactory(ctx)
moduleContext.bazelConversionMode = bazelConversionMode
return &bottomUpMutatorContext{
bp: ctx,
@ -430,10 +348,9 @@ func bottomUpMutatorContextFactory(ctx blueprint.BottomUpMutatorContext, a Modul
func (x *registerMutatorsContext) BottomUp(name string, m BottomUpMutator) MutatorHandle {
finalPhase := x.finalPhase
bazelConversionMode := x.bazelConversionMode
f := func(ctx blueprint.BottomUpMutatorContext) {
if a, ok := ctx.Module().(Module); ok {
m(bottomUpMutatorContextFactory(ctx, a, finalPhase, bazelConversionMode))
m(bottomUpMutatorContextFactory(ctx, a, finalPhase))
}
}
mutator := &mutator{name: x.mutatorName(name), bottomUpMutator: f}
@ -550,15 +467,13 @@ type TransitionMutator interface {
}
type androidTransitionMutator struct {
finalPhase bool
bazelConversionMode bool
mutator TransitionMutator
finalPhase bool
mutator TransitionMutator
}
func (a *androidTransitionMutator) Split(ctx blueprint.BaseModuleContext) []string {
if m, ok := ctx.Module().(Module); ok {
moduleContext := m.base().baseModuleContextFactory(ctx)
moduleContext.bazelConversionMode = a.bazelConversionMode
return a.mutator.Split(&moduleContext)
} else {
return []string{""}
@ -607,15 +522,14 @@ func (a *androidTransitionMutator) IncomingTransition(ctx blueprint.IncomingTran
func (a *androidTransitionMutator) Mutate(ctx blueprint.BottomUpMutatorContext, variation string) {
if am, ok := ctx.Module().(Module); ok {
a.mutator.Mutate(bottomUpMutatorContextFactory(ctx, am, a.finalPhase, a.bazelConversionMode), variation)
a.mutator.Mutate(bottomUpMutatorContextFactory(ctx, am, a.finalPhase), variation)
}
}
func (x *registerMutatorsContext) Transition(name string, m TransitionMutator) {
atm := &androidTransitionMutator{
finalPhase: x.finalPhase,
bazelConversionMode: x.bazelConversionMode,
mutator: m,
finalPhase: x.finalPhase,
mutator: m,
}
mutator := &mutator{
name: name,
@ -624,9 +538,6 @@ func (x *registerMutatorsContext) Transition(name string, m TransitionMutator) {
}
func (x *registerMutatorsContext) mutatorName(name string) string {
if x.bazelConversionMode {
return name + "_bp2build"
}
return name
}
@ -634,7 +545,6 @@ func (x *registerMutatorsContext) TopDown(name string, m TopDownMutator) Mutator
f := func(ctx blueprint.TopDownMutatorContext) {
if a, ok := ctx.Module().(Module); ok {
moduleContext := a.base().baseModuleContextFactory(ctx)
moduleContext.bazelConversionMode = x.bazelConversionMode
actx := &topDownMutatorContext{
bp: ctx,
baseModuleContext: moduleContext,
@ -698,179 +608,6 @@ func registerDepsMutator(ctx RegisterMutatorsContext) {
ctx.BottomUp("deps", depsMutator).Parallel()
}
func (t *bottomUpMutatorContext) CreateBazelTargetModule(
bazelProps bazel.BazelTargetModuleProperties,
commonAttrs CommonAttributes,
attrs interface{}) {
t.createBazelTargetModule(bazelProps, commonAttrs, attrs, bazel.BoolAttribute{})
}
func (t *bottomUpMutatorContext) CreateBazelTargetModuleWithRestrictions(
bazelProps bazel.BazelTargetModuleProperties,
commonAttrs CommonAttributes,
attrs interface{},
enabledProperty bazel.BoolAttribute) {
t.createBazelTargetModule(bazelProps, commonAttrs, attrs, enabledProperty)
}
func (t *bottomUpMutatorContext) MarkBp2buildUnconvertible(
reasonType bp2build_metrics_proto.UnconvertedReasonType, detail string) {
mod := t.Module()
mod.base().setBp2buildUnconvertible(reasonType, detail)
}
var (
bazelAliasModuleProperties = bazel.BazelTargetModuleProperties{
Rule_class: "alias",
}
)
type bazelAliasAttributes struct {
Actual *bazel.LabelAttribute
}
func (t *bottomUpMutatorContext) CreateBazelTargetAliasInDir(
dir string,
name string,
actual bazel.Label) {
mod := t.Module()
attrs := &bazelAliasAttributes{
Actual: bazel.MakeLabelAttribute(actual.Label),
}
info := bp2buildInfo{
Dir: dir,
BazelProps: bazelAliasModuleProperties,
CommonAttrs: CommonAttributes{Name: name},
ConstraintAttrs: constraintAttributes{},
Attrs: attrs,
}
mod.base().addBp2buildInfo(info)
}
// Returns the directory in which the bazel target will be generated
// If ca.Dir is not nil, use that
// Otherwise default to the directory of the soong module
func dirForBazelTargetGeneration(t *bottomUpMutatorContext, ca *CommonAttributes) string {
dir := t.OtherModuleDir(t.Module())
if ca.Dir != nil {
dir = *ca.Dir
// Restrict its use to dirs that contain an Android.bp file.
// There are several places in bp2build where we use the existence of Android.bp/BUILD on the filesystem
// to curate a compatible label for src files (e.g. headers for cc).
// If we arbritrarily create BUILD files, then it might render those curated labels incompatible.
if exists, _, _ := t.Config().fs.Exists(filepath.Join(dir, "Android.bp")); !exists {
t.ModuleErrorf("Cannot use ca.Dir to create a BazelTarget in dir: %v since it does not contain an Android.bp file", dir)
}
// Set ca.Dir to nil so that it does not get emitted to the BUILD files
ca.Dir = nil
}
return dir
}
func (t *bottomUpMutatorContext) CreateBazelConfigSetting(
csa bazel.ConfigSettingAttributes,
ca CommonAttributes,
dir string) {
mod := t.Module()
info := bp2buildInfo{
Dir: dir,
BazelProps: bazel.BazelTargetModuleProperties{
Rule_class: "config_setting",
},
CommonAttrs: ca,
ConstraintAttrs: constraintAttributes{},
Attrs: &csa,
}
mod.base().addBp2buildInfo(info)
}
// ApexAvailableTags converts the apex_available property value of an ApexModule
// module and returns it as a list of keyed tags.
func ApexAvailableTags(mod Module) bazel.StringListAttribute {
attr := bazel.StringListAttribute{}
// Transform specific attributes into tags.
if am, ok := mod.(ApexModule); ok {
// TODO(b/218841706): hidl_interface has the apex_available prop, but it's
// defined directly as a prop and not via ApexModule, so this doesn't
// pick those props up.
apexAvailable := am.apexModuleBase().ApexAvailable()
// If a user does not specify apex_available in Android.bp, then soong provides a default.
// To avoid verbosity of BUILD files, remove this default from user-facing BUILD files.
if len(am.apexModuleBase().ApexProperties.Apex_available) == 0 {
apexAvailable = []string{}
}
attr.Value = ConvertApexAvailableToTags(apexAvailable)
}
return attr
}
func ApexAvailableTagsWithoutTestApexes(ctx BaseModuleContext, mod Module) bazel.StringListAttribute {
attr := bazel.StringListAttribute{}
if am, ok := mod.(ApexModule); ok {
apexAvailableWithoutTestApexes := removeTestApexes(ctx, am.apexModuleBase().ApexAvailable())
// If a user does not specify apex_available in Android.bp, then soong provides a default.
// To avoid verbosity of BUILD files, remove this default from user-facing BUILD files.
if len(am.apexModuleBase().ApexProperties.Apex_available) == 0 {
apexAvailableWithoutTestApexes = []string{}
}
attr.Value = ConvertApexAvailableToTags(apexAvailableWithoutTestApexes)
}
return attr
}
func removeTestApexes(ctx BaseModuleContext, apex_available []string) []string {
testApexes := []string{}
for _, aa := range apex_available {
// ignore the wildcards
if InList(aa, AvailableToRecognziedWildcards) {
continue
}
mod, _ := ctx.ModuleFromName(aa)
if apex, ok := mod.(ApexTestInterface); ok && apex.IsTestApex() {
testApexes = append(testApexes, aa)
}
}
return RemoveListFromList(CopyOf(apex_available), testApexes)
}
func ConvertApexAvailableToTags(apexAvailable []string) []string {
if len(apexAvailable) == 0 {
// We need nil specifically to make bp2build not add the tags property at all,
// instead of adding it with an empty list
return nil
}
result := make([]string, 0, len(apexAvailable))
for _, a := range apexAvailable {
result = append(result, "apex_available="+a)
}
return result
}
// ConvertApexAvailableToTagsWithoutTestApexes converts a list of apex names to a list of bazel tags
// This function drops any test apexes from the input.
func ConvertApexAvailableToTagsWithoutTestApexes(ctx BaseModuleContext, apexAvailable []string) []string {
noTestApexes := removeTestApexes(ctx, apexAvailable)
return ConvertApexAvailableToTags(noTestApexes)
}
func (t *bottomUpMutatorContext) createBazelTargetModule(
bazelProps bazel.BazelTargetModuleProperties,
commonAttrs CommonAttributes,
attrs interface{},
enabledProperty bazel.BoolAttribute) {
constraintAttributes := commonAttrs.fillCommonBp2BuildModuleAttrs(t, enabledProperty)
mod := t.Module()
info := bp2buildInfo{
Dir: dirForBazelTargetGeneration(t, &commonAttrs),
BazelProps: bazelProps,
CommonAttrs: commonAttrs,
ConstraintAttrs: constraintAttributes,
Attrs: attrs,
}
mod.base().addBp2buildInfo(info)
}
// android.topDownMutatorContext either has to embed blueprint.TopDownMutatorContext, in which case every method that
// has an overridden version in android.BaseModuleContext has to be manually forwarded to BaseModuleContext to avoid
// ambiguous method errors, or it has to store a blueprint.TopDownMutatorContext non-embedded, in which case every

View file

@ -16,7 +16,6 @@ package android
import (
"fmt"
"reflect"
"strings"
"testing"
@ -268,22 +267,3 @@ func TestNoCreateVariationsInFinalDeps(t *testing.T) {
FixtureWithRootAndroidBp(`test {name: "foo"}`),
).RunTest(t)
}
func TestConvertApexAvailableToTags(t *testing.T) {
input := []string{
"com.android.adbd",
"//apex_available:platform",
}
actual := ConvertApexAvailableToTags(input)
expected := []string{
"apex_available=com.android.adbd",
"apex_available=//apex_available:platform",
}
if !reflect.DeepEqual(actual, expected) {
t.Errorf("Expected: %v, actual: %v", expected, actual)
}
if ConvertApexAvailableToTags(nil) != nil {
t.Errorf("Expected providing nil to return nil")
}
}

View file

@ -214,17 +214,6 @@ func (b *OverridableModuleBase) override(ctx BaseModuleContext, m Module, o Over
}
b.overridableModuleProperties.OverriddenBy = o.Name()
b.overridableModuleProperties.OverriddenByModuleDir = o.ModuleDir()
if oBazelable, ok := o.base().module.(Bazelable); ok {
if bBazelable, ok := m.(Bazelable); ok {
oProps := oBazelable.bazelProps()
bProps := bBazelable.bazelProps()
bProps.Bazel_module.Bp2build_available = oProps.Bazel_module.Bp2build_available
bProps.Bazel_module.Label = oProps.Bazel_module.Label
} else {
ctx.ModuleErrorf("Override type cannot be Bazelable if original module type is not Bazelable %v %v.", o.Name(), m.Name())
}
}
}
// GetOverriddenBy returns the name of the override module that has overridden this module.
@ -348,31 +337,3 @@ func replaceDepsOnOverridingModuleMutator(ctx BottomUpMutatorContext) {
}
}
}
// ModuleNameWithPossibleOverride returns the name of the OverrideModule that overrides the current
// variant of this OverridableModule, or ctx.ModuleName() if this module is not an OverridableModule
// or if this variant is not overridden.
func ModuleNameWithPossibleOverride(ctx BazelConversionContext) string {
return moduleNameWithPossibleOverride(ctx, ctx.Module(), ctx.OtherModuleName(ctx.Module()))
}
func moduleNameWithPossibleOverride(ctx shouldConvertModuleContext, module blueprint.Module, name string) string {
if overridable, ok := module.(OverridableModule); ok {
if o := overridable.GetOverriddenBy(); o != "" {
return o
}
}
return name
}
// moduleDirWithPossibleOverride returns the dir of the OverrideModule that overrides the current
// variant of the given OverridableModule, or ctx.OtherModuleName() if the module is not an
// OverridableModule or if the variant is not overridden.
func moduleDirWithPossibleOverride(ctx shouldConvertModuleContext, module blueprint.Module, dir string) string {
if overridable, ok := module.(OverridableModule); ok {
if o := overridable.GetOverriddenByModuleDir(); o != "" {
return o
}
}
return dir
}

View file

@ -461,10 +461,6 @@ func PrebuiltSelectModuleMutator(ctx BottomUpMutatorContext) {
// Propagate the provider received from `all_apex_contributions`
// to the source module
ctx.VisitDirectDepsWithTag(acDepTag, func(am Module) {
if ctx.Config().Bp2buildMode() {
// This provider key is not applicable in bp2build
return
}
psi := ctx.OtherModuleProvider(am, PrebuiltSelectionInfoProvider).(PrebuiltSelectionInfoMap)
ctx.SetProvider(PrebuiltSelectionInfoProvider, psi)
})
@ -570,55 +566,20 @@ func (p *Prebuilt) usePrebuilt(ctx BaseMutatorContext, source Module, prebuilt M
// fall back to the existing source vs prebuilt selection.
// TODO: Drop the fallback mechanisms
if !ctx.Config().Bp2buildMode() {
if p.srcsSupplier != nil && len(p.srcsSupplier(ctx, prebuilt)) == 0 {
return false
}
if p.srcsSupplier != nil && len(p.srcsSupplier(ctx, prebuilt)) == 0 {
return false
}
// Skip prebuilt modules under unexported namespaces so that we won't
// end up shadowing non-prebuilt module when prebuilt module under same
// name happens to have a `Prefer` property set to true.
if ctx.Config().KatiEnabled() && !prebuilt.ExportedToMake() {
return false
}
// Skip prebuilt modules under unexported namespaces so that we won't
// end up shadowing non-prebuilt module when prebuilt module under same
// name happens to have a `Prefer` property set to true.
if ctx.Config().KatiEnabled() && !prebuilt.ExportedToMake() {
return false
}
// If source is not available or is disabled then always use the prebuilt.
if source == nil || !source.Enabled() {
// If in bp2build mode, we need to check product variables & Soong config variables, which may
// have overridden the "enabled" property but have not been merged into the property value as
// they would in a non-bp2build mode invocation
if ctx.Config().Bp2buildMode() && source != nil {
productVariableProps, errs := ProductVariableProperties(ctx, source)
if productConfigProps, exists := productVariableProps["Enabled"]; len(errs) == 0 && exists && len(productConfigProps) == 1 {
var prop ProductConfigOrSoongConfigProperty
var value bool
for p, v := range productConfigProps {
prop = p
actual, ok := v.(*bool)
if ok {
value = proptools.Bool(actual)
}
}
if scv, ok := prop.(SoongConfigProperty); ok {
// If the product config var is enabled but the value of enabled is false still, the
// prebuilt is preferred. Otherwise, check if the prebulit is explicitly preferred
if ctx.Config().VendorConfig(scv.namespace).Bool(strings.ToLower(scv.name)) && !value {
return true
}
} else {
// TODO: b/300998219 - handle product vars
// We don't handle product variables yet, so return based on the non-product specific
// value of enabled
return true
}
} else {
// No "enabled" property override, return true since this module isn't enabled
return true
}
} else {
return true
}
return true
}
// If the use_source_config_var property is set then it overrides the prefer property setting.

View file

@ -15,15 +15,9 @@
package android
import (
"bufio"
"fmt"
"path/filepath"
"reflect"
"regexp"
"android/soong/shared"
"github.com/google/blueprint"
"reflect"
)
// A sortable component is one whose registration order affects the order in which it is executed
@ -166,75 +160,6 @@ func NewContext(config Config) *Context {
return ctx
}
// Helper function to register the module types used in bp2build.
func registerModuleTypes(ctx *Context) {
for _, t := range moduleTypes {
t.register(ctx)
}
// Required for SingletonModule types, even though we are not using them.
for _, t := range singletons {
t.register(ctx)
}
}
// RegisterForBazelConversion registers an alternate shadow pipeline of
// singletons, module types and mutators to register for converting Blueprint
// files to semantically equivalent BUILD files.
func (ctx *Context) RegisterForBazelConversion() {
registerModuleTypes(ctx)
RegisterMutatorsForBazelConversion(ctx, bp2buildPreArchMutators)
}
// RegisterExistingBazelTargets reads Bazel BUILD.bazel and BUILD files under
// the workspace, and returns a map containing names of Bazel targets defined in
// these BUILD files.
// For example, maps "//foo/bar" to ["baz", "qux"] if `//foo/bar:{baz,qux}` exist.
func (c *Context) RegisterExistingBazelTargets(topDir string, existingBazelFiles []string) error {
result := map[string][]string{}
// Search for instances of `name = "$NAME"` (with arbitrary spacing).
targetNameRegex := regexp.MustCompile(`(?m)^\s*name\s*=\s*\"([^\"]+)\"`)
parseBuildFile := func(path string) error {
fullPath := shared.JoinPath(topDir, path)
sourceDir := filepath.Dir(path)
fileInfo, err := c.Config().fs.Stat(fullPath)
if err != nil {
return fmt.Errorf("Error accessing Bazel file '%s': %s", path, err)
}
if !fileInfo.IsDir() &&
(fileInfo.Name() == "BUILD" || fileInfo.Name() == "BUILD.bazel") {
f, err := c.Config().fs.Open(fullPath)
if err != nil {
return fmt.Errorf("Error reading Bazel file '%s': %s", path, err)
}
defer f.Close()
scanner := bufio.NewScanner(f)
for scanner.Scan() {
line := scanner.Text()
matches := targetNameRegex.FindAllStringSubmatch(line, -1)
for _, match := range matches {
result[sourceDir] = append(result[sourceDir], match[1])
}
}
}
return nil
}
for _, path := range existingBazelFiles {
if !c.Config().Bp2buildPackageConfig.ShouldKeepExistingBuildFileForDir(filepath.Dir(path)) {
continue
}
err := parseBuildFile(path)
if err != nil {
return err
}
}
c.Config().SetBazelBuildFileTargets(result)
return nil
}
// Register the pipeline of singletons, module types, and mutators for
// generating build.ninja and other files for Kati, from Android.bp files.
func (ctx *Context) Register() {
@ -260,8 +185,6 @@ func (ctx *Context) registerSingletonMakeVarsProvider(makevars SingletonMakeVars
func collateGloballyRegisteredSingletons() sortableComponents {
allSingletons := append(sortableComponents(nil), singletons...)
allSingletons = append(allSingletons,
singleton{parallel: true, name: "bazeldeps", factory: BazelSingleton},
// Register phony just before makevars so it can write out its phony rules as Make rules
singleton{parallel: false, name: "phony", factory: phonySingletonFactory},

View file

@ -61,11 +61,7 @@ func TestConfig(buildDir string, env map[string]string, bp string, fs map[string
// passed to PathForSource or PathForModuleSrc.
TestAllowNonExistentPaths: true,
BazelContext: noopBazelContext{},
BuildMode: BazelProdMode,
mixedBuildDisabledModules: make(map[string]struct{}),
mixedBuildEnabledModules: make(map[string]struct{}),
bazelForceEnabledModules: make(map[string]struct{}),
BuildMode: AnalysisNoBazel,
}
config.deviceConfig = &deviceConfig{
config: config,

View file

@ -219,10 +219,6 @@ func (ctx *TestContext) FinalDepsMutators(f RegisterMutatorFunc) {
ctx.finalDeps = append(ctx.finalDeps, f)
}
func (ctx *TestContext) RegisterBp2BuildConfig(config Bp2BuildConversionAllowlist) {
ctx.config.Bp2buildPackageConfig = config
}
// PreArchBp2BuildMutators adds mutators to be register for converting Android Blueprint modules
// into Bazel BUILD targets that should run prior to deps and conversion.
func (ctx *TestContext) PreArchBp2BuildMutators(f RegisterMutatorFunc) {
@ -449,12 +445,6 @@ func (ctx *TestContext) Register() {
ctx.singletonOrder = componentsToNames(singletons)
}
// RegisterForBazelConversion prepares a test context for bp2build conversion.
func (ctx *TestContext) RegisterForBazelConversion() {
ctx.config.BuildMode = Bp2build
RegisterMutatorsForBazelConversion(ctx.Context, ctx.bp2buildPreArch)
}
func (ctx *TestContext) ParseFileList(rootDir string, filePaths []string) (deps []string, errs []error) {
// This function adapts the old style ParseFileList calls that are spread throughout the tests
// to the new style that takes a config.

View file

@ -591,3 +591,9 @@ func CheckDuplicate(values []string) (duplicate string, found bool) {
}
return "", false
}
func AddToStringSet(set map[string]bool, items []string) {
for _, item := range items {
set[item] = true
}
}

View file

@ -20,7 +20,6 @@ import (
"runtime"
"strings"
"android/soong/android/soongconfig"
"android/soong/bazel"
"github.com/google/blueprint/proptools"
@ -746,44 +745,6 @@ func (p SoongConfigProperty) SelectKey() string {
// property, like ["-DDEFINES"] for cflags.
type ProductConfigProperties map[string]map[ProductConfigOrSoongConfigProperty]interface{}
// ProductVariableProperties returns a ProductConfigProperties containing only the properties which
// have been set for the given module.
func ProductVariableProperties(ctx ArchVariantContext, module Module) (ProductConfigProperties, []error) {
var errs []error
moduleBase := module.base()
productConfigProperties := ProductConfigProperties{}
if moduleBase.variableProperties != nil {
productVariablesProperty := proptools.FieldNameForProperty("product_variables")
if moduleBase.ArchSpecific() {
for /* axis */ _, configToProps := range moduleBase.GetArchVariantProperties(ctx, moduleBase.variableProperties) {
for config, props := range configToProps {
variableValues := reflect.ValueOf(props).Elem().FieldByName(productVariablesProperty)
productConfigProperties.AddProductConfigProperties(variableValues, config)
}
}
} else {
variableValues := reflect.ValueOf(moduleBase.variableProperties).Elem().FieldByName(productVariablesProperty)
productConfigProperties.AddProductConfigProperties(variableValues, "")
}
}
if m, ok := module.(Bazelable); ok && m.namespacedVariableProps() != nil {
for namespace, namespacedVariableProps := range m.namespacedVariableProps() {
for _, namespacedVariableProp := range namespacedVariableProps {
variableValues := reflect.ValueOf(namespacedVariableProp).Elem().FieldByName(soongconfig.SoongConfigProperty)
err := productConfigProperties.AddSoongConfigProperties(namespace, variableValues)
if err != nil {
errs = append(errs, err)
}
}
}
}
return productConfigProperties, errs
}
func (p *ProductConfigProperties) AddProductConfigProperty(
propertyName, productVariableName, arch string, propertyValue interface{}) {
@ -833,10 +794,6 @@ func (p *ProductConfigProperties) AddEitherProperty(
}
}
var (
conditionsDefaultField string = proptools.FieldNameForProperty(bazel.ConditionsDefaultConfigKey)
)
// maybeExtractConfigVarProp attempts to read this value as a config var struct
// wrapped by interfaces and ptrs. If it's not the right type, the second return
// value is false.

View file

@ -7,16 +7,11 @@ bootstrap_go_package {
pkgPath: "android/soong/bp2build",
srcs: [
"androidbp_to_build_templates.go",
"bp2build.go",
"bp2build_product_config.go",
"build_conversion.go",
"bzl_conversion.go",
"configurability.go",
"constants.go",
"conversion.go",
"metrics.go",
"symlink_forest.go",
"testing.go",
],
deps: [
"blueprint-bootstrap",
@ -41,7 +36,6 @@ bootstrap_go_package {
],
testSrcs: [
"conversion_test.go",
"performance_test.go",
],
pluginFor: [
"soong_build",

View file

@ -1,145 +0,0 @@
// Copyright 2020 Google Inc. All rights reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package bp2build
import (
"fmt"
"os"
"path/filepath"
"strings"
"android/soong/android"
"android/soong/bazel"
"android/soong/shared"
"android/soong/starlark_import"
)
func deleteFilesExcept(ctx *CodegenContext, rootOutputPath android.OutputPath, except []BazelFile) {
// Delete files that should no longer be present.
bp2buildDirAbs := shared.JoinPath(ctx.topDir, rootOutputPath.String())
filesToDelete := make(map[string]struct{})
err := filepath.Walk(bp2buildDirAbs,
func(path string, info os.FileInfo, err error) error {
if err != nil {
return err
}
if !info.IsDir() {
relPath, err := filepath.Rel(bp2buildDirAbs, path)
if err != nil {
return err
}
filesToDelete[relPath] = struct{}{}
}
return nil
})
if err != nil {
fmt.Printf("ERROR reading %s: %s", bp2buildDirAbs, err)
os.Exit(1)
}
for _, bazelFile := range except {
filePath := filepath.Join(bazelFile.Dir, bazelFile.Basename)
delete(filesToDelete, filePath)
}
for f, _ := range filesToDelete {
absPath := shared.JoinPath(bp2buildDirAbs, f)
if err := os.RemoveAll(absPath); err != nil {
fmt.Printf("ERROR deleting %s: %s", absPath, err)
os.Exit(1)
}
}
}
// Codegen is the backend of bp2build. The code generator is responsible for
// writing .bzl files that are equivalent to Android.bp files that are capable
// of being built with Bazel.
func Codegen(ctx *CodegenContext) *CodegenMetrics {
ctx.Context().BeginEvent("Codegen")
defer ctx.Context().EndEvent("Codegen")
// This directory stores BUILD files that could be eventually checked-in.
bp2buildDir := android.PathForOutput(ctx, "bp2build")
res, errs := GenerateBazelTargets(ctx, true)
if len(errs) > 0 {
errMsgs := make([]string, len(errs))
for i, err := range errs {
errMsgs[i] = fmt.Sprintf("%q", err)
}
fmt.Printf("ERROR: Encountered %d error(s): \nERROR: %s", len(errs), strings.Join(errMsgs, "\n"))
os.Exit(1)
}
var bp2buildFiles []BazelFile
productConfig, err := createProductConfigFiles(ctx, res.moduleNameToPartition, res.metrics.convertedModulePathMap)
ctx.Context().EventHandler.Do("CreateBazelFile", func() {
allTargets := make(map[string]BazelTargets)
for k, v := range res.buildFileToTargets {
allTargets[k] = append(allTargets[k], v...)
}
for k, v := range productConfig.bp2buildTargets {
allTargets[k] = append(allTargets[k], v...)
}
bp2buildFiles = CreateBazelFiles(nil, allTargets, ctx.mode)
})
bp2buildFiles = append(bp2buildFiles, productConfig.bp2buildFiles...)
injectionFiles, err := createSoongInjectionDirFiles(ctx, res.metrics)
if err != nil {
fmt.Printf("%s\n", err.Error())
os.Exit(1)
}
injectionFiles = append(injectionFiles, productConfig.injectionFiles...)
writeFiles(ctx, bp2buildDir, bp2buildFiles)
// Delete files under the bp2build root which weren't just written. An
// alternative would have been to delete the whole directory and write these
// files. However, this would regenerate files which were otherwise unchanged
// since the last bp2build run, which would have negative incremental
// performance implications.
deleteFilesExcept(ctx, bp2buildDir, bp2buildFiles)
writeFiles(ctx, android.PathForOutput(ctx, bazel.SoongInjectionDirName), injectionFiles)
starlarkDeps, err := starlark_import.GetNinjaDeps()
if err != nil {
fmt.Fprintf(os.Stderr, "%s\n", err)
os.Exit(1)
}
ctx.AddNinjaFileDeps(starlarkDeps...)
return &res.metrics
}
// Get the output directory and create it if it doesn't exist.
func getOrCreateOutputDir(outputDir android.OutputPath, ctx android.PathContext, dir string) android.OutputPath {
dirPath := outputDir.Join(ctx, dir)
if err := android.CreateOutputDirIfNonexistent(dirPath, os.ModePerm); err != nil {
fmt.Printf("ERROR: path %s: %s", dirPath, err.Error())
}
return dirPath
}
// writeFiles materializes a list of BazelFile rooted at outputDir.
func writeFiles(ctx android.PathContext, outputDir android.OutputPath, files []BazelFile) {
for _, f := range files {
p := getOrCreateOutputDir(outputDir, ctx, f.Dir).Join(ctx, f.Basename)
if err := writeFile(p, f.Contents); err != nil {
panic(fmt.Errorf("Failed to write %q (dir %q) due to %q", f.Basename, f.Dir, err))
}
}
}
func writeFile(pathToFile android.OutputPath, content string) error {
// These files are made editable to allow users to modify and iterate on them
// in the source tree.
return android.WriteFileToOutputDir(pathToFile, []byte(content), 0644)
}

View file

@ -1,885 +0,0 @@
package bp2build
import (
"encoding/json"
"fmt"
"path/filepath"
"reflect"
"sort"
"strings"
"android/soong/android"
"android/soong/android/soongconfig"
"android/soong/starlark_import"
"github.com/google/blueprint/proptools"
"go.starlark.net/starlark"
)
type createProductConfigFilesResult struct {
injectionFiles []BazelFile
bp2buildFiles []BazelFile
bp2buildTargets map[string]BazelTargets
}
type bazelLabel struct {
repo string
pkg string
target string
}
const releaseAconfigValueSetsName = "release_aconfig_value_sets"
func (l *bazelLabel) Less(other *bazelLabel) bool {
if l.repo < other.repo {
return true
}
if l.repo > other.repo {
return false
}
if l.pkg < other.pkg {
return true
}
if l.pkg > other.pkg {
return false
}
return l.target < other.target
}
func (l *bazelLabel) String() string {
return fmt.Sprintf("@%s//%s:%s", l.repo, l.pkg, l.target)
}
func createProductConfigFiles(
ctx *CodegenContext,
moduleNameToPartition map[string]string,
convertedModulePathMap map[string]string) (createProductConfigFilesResult, error) {
cfg := &ctx.config
targetProduct := "unknown"
if cfg.HasDeviceProduct() {
targetProduct = cfg.DeviceProduct()
}
targetBuildVariant := "user"
if cfg.Eng() {
targetBuildVariant = "eng"
} else if cfg.Debuggable() {
targetBuildVariant = "userdebug"
}
var res createProductConfigFilesResult
productVariables := ctx.Config().ProductVariables()
// TODO(b/306243251): For some reason, using the real value of native_coverage makes some select
// statements ambiguous
productVariables.Native_coverage = nil
productVariablesBytes, err := json.Marshal(productVariables)
if err != nil {
return res, err
}
currentProductFolder := fmt.Sprintf("build/bazel/products/%s", targetProduct)
if len(productVariables.PartitionVarsForBazelMigrationOnlyDoNotUse.ProductDirectory) > 0 {
currentProductFolder = fmt.Sprintf("%s%s", productVariables.PartitionVarsForBazelMigrationOnlyDoNotUse.ProductDirectory, targetProduct)
}
productReplacer := strings.NewReplacer(
"{PRODUCT}", targetProduct,
"{VARIANT}", targetBuildVariant,
"{PRODUCT_FOLDER}", currentProductFolder)
productsForTestingMap, err := starlark_import.GetStarlarkValue[map[string]map[string]starlark.Value]("products_for_testing")
if err != nil {
return res, err
}
productsForTesting := android.SortedKeys(productsForTestingMap)
for i := range productsForTesting {
productsForTesting[i] = fmt.Sprintf(" \"@//build/bazel/tests/products:%s\",", productsForTesting[i])
}
productLabelsToVariables := make(map[bazelLabel]*android.ProductVariables)
productLabelsToVariables[bazelLabel{
repo: "",
pkg: currentProductFolder,
target: targetProduct,
}] = &productVariables
for product, productVariablesStarlark := range productsForTestingMap {
productVariables, err := starlarkMapToProductVariables(productVariablesStarlark)
if err != nil {
return res, err
}
productLabelsToVariables[bazelLabel{
repo: "",
pkg: "build/bazel/tests/products",
target: product,
}] = &productVariables
}
res.bp2buildTargets = make(map[string]BazelTargets)
res.bp2buildTargets[currentProductFolder] = append(res.bp2buildTargets[currentProductFolder], BazelTarget{
name: productReplacer.Replace("{PRODUCT}"),
packageName: currentProductFolder,
content: productReplacer.Replace(`android_product(
name = "{PRODUCT}",
soong_variables = _soong_variables,
)`),
ruleClass: "android_product",
loads: []BazelLoad{
{
file: ":soong.variables.bzl",
symbols: []BazelLoadSymbol{{
symbol: "variables",
alias: "_soong_variables",
}},
},
{
file: "//build/bazel/product_config:android_product.bzl",
symbols: []BazelLoadSymbol{{symbol: "android_product"}},
},
},
})
createTargets(ctx, productLabelsToVariables, moduleNameToPartition, convertedModulePathMap, res.bp2buildTargets)
platformMappingContent, err := platformMappingContent(
productLabelsToVariables,
ctx.Config().Bp2buildSoongConfigDefinitions,
convertedModulePathMap)
if err != nil {
return res, err
}
res.injectionFiles = []BazelFile{
newFile(
"product_config_platforms",
"BUILD.bazel",
productReplacer.Replace(`
package(default_visibility = [
"@//build/bazel/product_config:__subpackages__",
"@soong_injection//product_config_platforms:__subpackages__",
])
load("@//{PRODUCT_FOLDER}:soong.variables.bzl", _soong_variables = "variables")
load("@//build/bazel/product_config:android_product.bzl", "android_product")
# Bazel will qualify its outputs by the platform name. When switching between products, this
# means that soong-built files that depend on bazel-built files will suddenly get different
# dependency files, because the path changes, and they will be rebuilt. In order to avoid this
# extra rebuilding, make mixed builds always use a single platform so that the bazel artifacts
# are always under the same path.
android_product(
name = "mixed_builds_product",
soong_variables = _soong_variables,
extra_constraints = ["@//build/bazel/platforms:mixed_builds"],
)
`)),
newFile(
"product_config_platforms",
"product_labels.bzl",
productReplacer.Replace(`
# This file keeps a list of all the products in the android source tree, because they're
# discovered as part of a preprocessing step before bazel runs.
# TODO: When we start generating the platforms for more than just the
# currently lunched product, they should all be listed here
product_labels = [
"@soong_injection//product_config_platforms:mixed_builds_product",
"@//{PRODUCT_FOLDER}:{PRODUCT}",
`)+strings.Join(productsForTesting, "\n")+"\n]\n"),
newFile(
"product_config_platforms",
"common.bazelrc",
productReplacer.Replace(`
build --platform_mappings=platform_mappings
build --platforms @//{PRODUCT_FOLDER}:{PRODUCT}_linux_x86_64
build --//build/bazel/product_config:target_build_variant={VARIANT}
build:android --platforms=@//{PRODUCT_FOLDER}:{PRODUCT}
build:linux_x86 --platforms=@//{PRODUCT_FOLDER}:{PRODUCT}_linux_x86
build:linux_x86_64 --platforms=@//{PRODUCT_FOLDER}:{PRODUCT}_linux_x86_64
build:linux_bionic_x86_64 --platforms=@//{PRODUCT_FOLDER}:{PRODUCT}_linux_bionic_x86_64
build:linux_musl_x86 --platforms=@//{PRODUCT_FOLDER}:{PRODUCT}_linux_musl_x86
build:linux_musl_x86_64 --platforms=@//{PRODUCT_FOLDER}:{PRODUCT}_linux_musl_x86_64
`)),
newFile(
"product_config_platforms",
"linux.bazelrc",
productReplacer.Replace(`
build --host_platform @//{PRODUCT_FOLDER}:{PRODUCT}_linux_x86_64
`)),
newFile(
"product_config_platforms",
"darwin.bazelrc",
productReplacer.Replace(`
build --host_platform @//{PRODUCT_FOLDER}:{PRODUCT}_darwin_x86_64
`)),
}
res.bp2buildFiles = []BazelFile{
newFile(
"",
"platform_mappings",
platformMappingContent),
newFile(
currentProductFolder,
"soong.variables.bzl",
`variables = json.decode("""`+strings.ReplaceAll(string(productVariablesBytes), "\\", "\\\\")+`""")`),
}
return res, nil
}
func platformMappingContent(
productLabelToVariables map[bazelLabel]*android.ProductVariables,
soongConfigDefinitions soongconfig.Bp2BuildSoongConfigDefinitions,
convertedModulePathMap map[string]string) (string, error) {
var result strings.Builder
mergedConvertedModulePathMap := make(map[string]string)
for k, v := range convertedModulePathMap {
mergedConvertedModulePathMap[k] = v
}
additionalModuleNamesToPackages, err := starlark_import.GetStarlarkValue[map[string]string]("additional_module_names_to_packages")
if err != nil {
return "", err
}
for k, v := range additionalModuleNamesToPackages {
mergedConvertedModulePathMap[k] = v
}
productLabels := make([]bazelLabel, 0, len(productLabelToVariables))
for k := range productLabelToVariables {
productLabels = append(productLabels, k)
}
sort.Slice(productLabels, func(i, j int) bool {
return productLabels[i].Less(&productLabels[j])
})
result.WriteString("platforms:\n")
for _, productLabel := range productLabels {
platformMappingSingleProduct(productLabel, productLabelToVariables[productLabel], soongConfigDefinitions, mergedConvertedModulePathMap, &result)
}
return result.String(), nil
}
var bazelPlatformSuffixes = []string{
"",
"_darwin_arm64",
"_darwin_x86_64",
"_linux_bionic_arm64",
"_linux_bionic_x86_64",
"_linux_musl_x86",
"_linux_musl_x86_64",
"_linux_x86",
"_linux_x86_64",
"_windows_x86",
"_windows_x86_64",
}
func platformMappingSingleProduct(
label bazelLabel,
productVariables *android.ProductVariables,
soongConfigDefinitions soongconfig.Bp2BuildSoongConfigDefinitions,
convertedModulePathMap map[string]string,
result *strings.Builder) {
platform_sdk_version := -1
if productVariables.Platform_sdk_version != nil {
platform_sdk_version = *productVariables.Platform_sdk_version
}
defaultAppCertificateFilegroup := "//build/bazel/utils:empty_filegroup"
if proptools.String(productVariables.DefaultAppCertificate) != "" {
defaultAppCertificateFilegroup = "@//" + filepath.Dir(proptools.String(productVariables.DefaultAppCertificate)) + ":generated_android_certificate_directory"
}
// TODO: b/301598690 - commas can't be escaped in a string-list passed in a platform mapping,
// so commas are switched for ":" here, and must be back-substituted into commas
// wherever the AAPTCharacteristics product config variable is used.
AAPTConfig := []string{}
for _, conf := range productVariables.AAPTConfig {
AAPTConfig = append(AAPTConfig, strings.Replace(conf, ",", ":", -1))
}
for _, suffix := range bazelPlatformSuffixes {
result.WriteString(" ")
result.WriteString(label.String())
result.WriteString(suffix)
result.WriteString("\n")
result.WriteString(fmt.Sprintf(" --//build/bazel/product_config:aapt_characteristics=%s\n", proptools.String(productVariables.AAPTCharacteristics)))
result.WriteString(fmt.Sprintf(" --//build/bazel/product_config:aapt_config=%s\n", strings.Join(AAPTConfig, ",")))
result.WriteString(fmt.Sprintf(" --//build/bazel/product_config:aapt_preferred_config=%s\n", proptools.String(productVariables.AAPTPreferredConfig)))
result.WriteString(fmt.Sprintf(" --//build/bazel/product_config:always_use_prebuilt_sdks=%t\n", proptools.Bool(productVariables.Always_use_prebuilt_sdks)))
result.WriteString(fmt.Sprintf(" --//build/bazel/product_config:arc=%t\n", proptools.Bool(productVariables.Arc)))
result.WriteString(fmt.Sprintf(" --//build/bazel/product_config:apex_global_min_sdk_version_override=%s\n", proptools.String(productVariables.ApexGlobalMinSdkVersionOverride)))
result.WriteString(fmt.Sprintf(" --//build/bazel/product_config:binder32bit=%t\n", proptools.Bool(productVariables.Binder32bit)))
result.WriteString(fmt.Sprintf(" --//build/bazel/product_config:build_from_text_stub=%t\n", proptools.Bool(productVariables.Build_from_text_stub)))
result.WriteString(fmt.Sprintf(" --//build/bazel/product_config:build_broken_incorrect_partition_images=%t\n", productVariables.BuildBrokenIncorrectPartitionImages))
result.WriteString(fmt.Sprintf(" --//build/bazel/product_config:build_id=%s\n", proptools.String(productVariables.BuildId)))
result.WriteString(fmt.Sprintf(" --//build/bazel/product_config:build_version_tags=%s\n", strings.Join(productVariables.BuildVersionTags, ",")))
result.WriteString(fmt.Sprintf(" --//build/bazel/product_config:cfi_exclude_paths=%s\n", strings.Join(productVariables.CFIExcludePaths, ",")))
result.WriteString(fmt.Sprintf(" --//build/bazel/product_config:cfi_include_paths=%s\n", strings.Join(productVariables.CFIIncludePaths, ",")))
result.WriteString(fmt.Sprintf(" --//build/bazel/product_config:compressed_apex=%t\n", proptools.Bool(productVariables.CompressedApex)))
result.WriteString(fmt.Sprintf(" --//build/bazel/product_config:default_app_certificate=%s\n", proptools.String(productVariables.DefaultAppCertificate)))
result.WriteString(fmt.Sprintf(" --//build/bazel/product_config:default_app_certificate_filegroup=%s\n", defaultAppCertificateFilegroup))
result.WriteString(fmt.Sprintf(" --//build/bazel/product_config:device_abi=%s\n", strings.Join(productVariables.DeviceAbi, ",")))
result.WriteString(fmt.Sprintf(" --//build/bazel/product_config:device_max_page_size_supported=%s\n", proptools.String(productVariables.DeviceMaxPageSizeSupported)))
result.WriteString(fmt.Sprintf(" --//build/bazel/product_config:device_name=%s\n", proptools.String(productVariables.DeviceName)))
result.WriteString(fmt.Sprintf(" --//build/bazel/product_config:device_no_bionic_page_size_macro=%t\n", proptools.Bool(productVariables.DeviceNoBionicPageSizeMacro)))
result.WriteString(fmt.Sprintf(" --//build/bazel/product_config:device_product=%s\n", proptools.String(productVariables.DeviceProduct)))
result.WriteString(fmt.Sprintf(" --//build/bazel/product_config:device_platform=%s\n", label.String()))
result.WriteString(fmt.Sprintf(" --//build/bazel/product_config:enable_cfi=%t\n", proptools.BoolDefault(productVariables.EnableCFI, true)))
result.WriteString(fmt.Sprintf(" --//build/bazel/product_config:enforce_vintf_manifest=%t\n", proptools.Bool(productVariables.Enforce_vintf_manifest)))
result.WriteString(fmt.Sprintf(" --//build/bazel/product_config:malloc_not_svelte=%t\n", proptools.Bool(productVariables.Malloc_not_svelte)))
result.WriteString(fmt.Sprintf(" --//build/bazel/product_config:malloc_pattern_fill_contents=%t\n", proptools.Bool(productVariables.Malloc_pattern_fill_contents)))
result.WriteString(fmt.Sprintf(" --//build/bazel/product_config:malloc_zero_contents=%t\n", proptools.Bool(productVariables.Malloc_zero_contents)))
result.WriteString(fmt.Sprintf(" --//build/bazel/product_config:memtag_heap_exclude_paths=%s\n", strings.Join(productVariables.MemtagHeapExcludePaths, ",")))
result.WriteString(fmt.Sprintf(" --//build/bazel/product_config:memtag_heap_async_include_paths=%s\n", strings.Join(productVariables.MemtagHeapAsyncIncludePaths, ",")))
result.WriteString(fmt.Sprintf(" --//build/bazel/product_config:memtag_heap_sync_include_paths=%s\n", strings.Join(productVariables.MemtagHeapSyncIncludePaths, ",")))
result.WriteString(fmt.Sprintf(" --//build/bazel/product_config:manifest_package_name_overrides=%s\n", strings.Join(productVariables.ManifestPackageNameOverrides, ",")))
result.WriteString(fmt.Sprintf(" --//build/bazel/product_config:native_coverage=%t\n", proptools.Bool(productVariables.Native_coverage)))
result.WriteString(fmt.Sprintf(" --//build/bazel/product_config:platform_sdk_final=%t\n", proptools.Bool(productVariables.Platform_sdk_final)))
result.WriteString(fmt.Sprintf(" --//build/bazel/product_config:platform_security_patch=%s\n", proptools.String(productVariables.Platform_security_patch)))
result.WriteString(fmt.Sprintf(" --//build/bazel/product_config:platform_version_last_stable=%s\n", proptools.String(productVariables.Platform_version_last_stable)))
result.WriteString(fmt.Sprintf(" --//build/bazel/product_config:platform_version_name=%s\n", proptools.String(productVariables.Platform_version_name)))
result.WriteString(fmt.Sprintf(" --//build/bazel/product_config:product_brand=%s\n", productVariables.ProductBrand))
result.WriteString(fmt.Sprintf(" --//build/bazel/product_config:product_manufacturer=%s\n", productVariables.ProductManufacturer))
result.WriteString(fmt.Sprintf(" --//build/bazel/product_config:release_aconfig_flag_default_permission=%s\n", productVariables.ReleaseAconfigFlagDefaultPermission))
releaseAconfigValueSets := "//build/bazel/product_config:empty_aconfig_value_sets"
if len(productVariables.ReleaseAconfigValueSets) > 0 {
releaseAconfigValueSets = "@//" + label.pkg + ":" + releaseAconfigValueSetsName + "_" + label.target
}
result.WriteString(fmt.Sprintf(" --//build/bazel/product_config:release_aconfig_value_sets=%s\n", releaseAconfigValueSets))
result.WriteString(fmt.Sprintf(" --//build/bazel/product_config:release_version=%s\n", productVariables.ReleaseVersion))
result.WriteString(fmt.Sprintf(" --//build/bazel/product_config:platform_sdk_version=%d\n", platform_sdk_version))
result.WriteString(fmt.Sprintf(" --//build/bazel/product_config:safestack=%t\n", proptools.Bool(productVariables.Safestack)))
result.WriteString(fmt.Sprintf(" --//build/bazel/product_config:treble_linker_namespaces=%t\n", proptools.Bool(productVariables.Treble_linker_namespaces)))
result.WriteString(fmt.Sprintf(" --//build/bazel/product_config:tidy_checks=%s\n", proptools.String(productVariables.TidyChecks)))
result.WriteString(fmt.Sprintf(" --//build/bazel/product_config:uml=%t\n", proptools.Bool(productVariables.Uml)))
result.WriteString(fmt.Sprintf(" --//build/bazel/product_config:unbundled_build=%t\n", proptools.Bool(productVariables.Unbundled_build)))
result.WriteString(fmt.Sprintf(" --//build/bazel/product_config:unbundled_build_apps=%s\n", strings.Join(productVariables.Unbundled_build_apps, ",")))
for _, override := range productVariables.CertificateOverrides {
parts := strings.SplitN(override, ":", 2)
if apexPath, ok := convertedModulePathMap[parts[0]]; ok {
if overrideCertPath, ok := convertedModulePathMap[parts[1]]; ok {
result.WriteString(fmt.Sprintf(" --%s:%s_certificate_override=%s:%s\n", apexPath, parts[0], overrideCertPath, parts[1]))
}
}
}
for _, namespace := range android.SortedKeys(productVariables.VendorVars) {
for _, variable := range android.SortedKeys(productVariables.VendorVars[namespace]) {
value := productVariables.VendorVars[namespace][variable]
key := namespace + "__" + variable
_, hasBool := soongConfigDefinitions.BoolVars[key]
_, hasString := soongConfigDefinitions.StringVars[key]
_, hasValue := soongConfigDefinitions.ValueVars[key]
if !hasBool && !hasString && !hasValue {
// Not all soong config variables are defined in Android.bp files. For example,
// prebuilt_bootclasspath_fragment uses soong config variables in a nonstandard
// way, that causes them to be present in the soong.variables file but not
// defined in an Android.bp file. There's also nothing stopping you from setting
// a variable in make that doesn't exist in soong. We only generate build
// settings for the ones that exist in soong, so skip all others.
continue
}
if hasBool && hasString || hasBool && hasValue || hasString && hasValue {
panic(fmt.Sprintf("Soong config variable %s:%s appears to be of multiple types. bool? %t, string? %t, value? %t", namespace, variable, hasBool, hasString, hasValue))
}
if hasBool {
// Logic copied from soongConfig.Bool()
value = strings.ToLower(value)
if value == "1" || value == "y" || value == "yes" || value == "on" || value == "true" {
value = "true"
} else {
value = "false"
}
}
result.WriteString(fmt.Sprintf(" --//build/bazel/product_config/soong_config_variables:%s=%s\n", strings.ToLower(key), value))
}
}
}
}
func starlarkMapToProductVariables(in map[string]starlark.Value) (android.ProductVariables, error) {
result := android.ProductVariables{}
productVarsReflect := reflect.ValueOf(&result).Elem()
for i := 0; i < productVarsReflect.NumField(); i++ {
field := productVarsReflect.Field(i)
fieldType := productVarsReflect.Type().Field(i)
name := fieldType.Name
if name == "BootJars" || name == "ApexBootJars" || name == "VendorSnapshotModules" ||
name == "RecoverySnapshotModules" {
// These variables have more complicated types, and we don't need them right now
continue
}
if _, ok := in[name]; ok {
if name == "VendorVars" {
vendorVars, err := starlark_import.Unmarshal[map[string]map[string]string](in[name])
if err != nil {
return result, err
}
field.Set(reflect.ValueOf(vendorVars))
continue
}
switch field.Type().Kind() {
case reflect.Bool:
val, err := starlark_import.Unmarshal[bool](in[name])
if err != nil {
return result, err
}
field.SetBool(val)
case reflect.String:
val, err := starlark_import.Unmarshal[string](in[name])
if err != nil {
return result, err
}
field.SetString(val)
case reflect.Slice:
if field.Type().Elem().Kind() != reflect.String {
return result, fmt.Errorf("slices of types other than strings are unimplemented")
}
val, err := starlark_import.UnmarshalReflect(in[name], field.Type())
if err != nil {
return result, err
}
field.Set(val)
case reflect.Pointer:
switch field.Type().Elem().Kind() {
case reflect.Bool:
val, err := starlark_import.UnmarshalNoneable[bool](in[name])
if err != nil {
return result, err
}
field.Set(reflect.ValueOf(val))
case reflect.String:
val, err := starlark_import.UnmarshalNoneable[string](in[name])
if err != nil {
return result, err
}
field.Set(reflect.ValueOf(val))
case reflect.Int:
val, err := starlark_import.UnmarshalNoneable[int](in[name])
if err != nil {
return result, err
}
field.Set(reflect.ValueOf(val))
default:
return result, fmt.Errorf("pointers of types other than strings/bools are unimplemented: %s", field.Type().Elem().Kind().String())
}
default:
return result, fmt.Errorf("unimplemented type: %s", field.Type().String())
}
}
}
result.Native_coverage = proptools.BoolPtr(
proptools.Bool(result.GcovCoverage) ||
proptools.Bool(result.ClangCoverage))
return result, nil
}
func createTargets(
ctx *CodegenContext,
productLabelsToVariables map[bazelLabel]*android.ProductVariables,
moduleNameToPartition map[string]string,
convertedModulePathMap map[string]string,
res map[string]BazelTargets) {
createGeneratedAndroidCertificateDirectories(productLabelsToVariables, res)
createAvbKeyFilegroups(productLabelsToVariables, res)
createReleaseAconfigValueSetsFilegroup(productLabelsToVariables, res)
for label, variables := range productLabelsToVariables {
createSystemPartition(ctx, label, &variables.PartitionVarsForBazelMigrationOnlyDoNotUse, moduleNameToPartition, convertedModulePathMap, res)
}
}
func createGeneratedAndroidCertificateDirectories(productLabelsToVariables map[bazelLabel]*android.ProductVariables, targets map[string]BazelTargets) {
var allDefaultAppCertificateDirs []string
for _, productVariables := range productLabelsToVariables {
if proptools.String(productVariables.DefaultAppCertificate) != "" {
d := filepath.Dir(proptools.String(productVariables.DefaultAppCertificate))
if !android.InList(d, allDefaultAppCertificateDirs) {
allDefaultAppCertificateDirs = append(allDefaultAppCertificateDirs, d)
}
}
}
for _, dir := range allDefaultAppCertificateDirs {
content := `filegroup(
name = "generated_android_certificate_directory",
srcs = glob([
"*.pk8",
"*.pem",
"*.avbpubkey",
]),
visibility = ["//visibility:public"],
)`
targets[dir] = append(targets[dir], BazelTarget{
name: "generated_android_certificate_directory",
packageName: dir,
content: content,
ruleClass: "filegroup",
})
}
}
func createReleaseAconfigValueSetsFilegroup(productLabelsToVariables map[bazelLabel]*android.ProductVariables, targets map[string]BazelTargets) {
for label, productVariables := range productLabelsToVariables {
if len(productVariables.ReleaseAconfigValueSets) > 0 {
key := label.target
dir := label.pkg
var value_sets strings.Builder
for _, value_set := range productVariables.ReleaseAconfigValueSets {
value_sets.WriteString(" \"" + value_set + "\",\n")
}
name := releaseAconfigValueSetsName + "_" + key
content := "aconfig_value_sets(\n" +
" name = \"" + name + "\",\n" +
" value_sets = [\n" +
value_sets.String() +
" ],\n" +
" visibility = [\"//visibility:public\"],\n" +
")"
targets[dir] = append(targets[dir], BazelTarget{
name: name,
packageName: dir,
content: content,
ruleClass: "aconfig_value_sets",
loads: []BazelLoad{{
file: "//build/bazel/rules/aconfig:aconfig_value_sets.bzl",
symbols: []BazelLoadSymbol{{
symbol: "aconfig_value_sets",
}},
}},
})
}
}
}
func createAvbKeyFilegroups(productLabelsToVariables map[bazelLabel]*android.ProductVariables, targets map[string]BazelTargets) {
var allAvbKeys []string
for _, productVariables := range productLabelsToVariables {
for _, partitionVariables := range productVariables.PartitionVarsForBazelMigrationOnlyDoNotUse.PartitionQualifiedVariables {
if partitionVariables.BoardAvbKeyPath != "" {
if !android.InList(partitionVariables.BoardAvbKeyPath, allAvbKeys) {
allAvbKeys = append(allAvbKeys, partitionVariables.BoardAvbKeyPath)
}
}
}
}
for _, key := range allAvbKeys {
dir := filepath.Dir(key)
name := filepath.Base(key)
content := fmt.Sprintf(`filegroup(
name = "%s_filegroup",
srcs = ["%s"],
visibility = ["//visibility:public"],
)`, name, name)
targets[dir] = append(targets[dir], BazelTarget{
name: name + "_filegroup",
packageName: dir,
content: content,
ruleClass: "filegroup",
})
}
}
func createSystemPartition(
ctx *CodegenContext,
platformLabel bazelLabel,
variables *android.PartitionVariables,
moduleNameToPartition map[string]string,
convertedModulePathMap map[string]string,
targets map[string]BazelTargets) {
if !variables.PartitionQualifiedVariables["system"].BuildingImage {
return
}
qualifiedVariables := variables.PartitionQualifiedVariables["system"]
imageProps := generateImagePropDictionary(variables, "system")
imageProps["skip_fsck"] = "true"
var properties strings.Builder
for _, prop := range android.SortedKeys(imageProps) {
properties.WriteString(prop)
properties.WriteRune('=')
properties.WriteString(imageProps[prop])
properties.WriteRune('\n')
}
var extraProperties strings.Builder
if variables.BoardAvbEnable {
extraProperties.WriteString(" avb_enable = True,\n")
extraProperties.WriteString(fmt.Sprintf(" avb_add_hashtree_footer_args = %q,\n", qualifiedVariables.BoardAvbAddHashtreeFooterArgs))
keypath := qualifiedVariables.BoardAvbKeyPath
if keypath != "" {
extraProperties.WriteString(fmt.Sprintf(" avb_key = \"//%s:%s\",\n", filepath.Dir(keypath), filepath.Base(keypath)+"_filegroup"))
extraProperties.WriteString(fmt.Sprintf(" avb_algorithm = %q,\n", qualifiedVariables.BoardAvbAlgorithm))
extraProperties.WriteString(fmt.Sprintf(" avb_rollback_index = %s,\n", qualifiedVariables.BoardAvbRollbackIndex))
extraProperties.WriteString(fmt.Sprintf(" avb_rollback_index_location = %s,\n", qualifiedVariables.BoardAvbRollbackIndexLocation))
}
}
var deps []string
for _, mod := range variables.ProductPackages {
if path, ok := convertedModulePathMap[mod]; ok && ctx.Config().BazelContext.IsModuleNameAllowed(mod, false) {
if partition, ok := moduleNameToPartition[mod]; ok && partition == "system" {
if path == "//." {
path = "//"
}
deps = append(deps, fmt.Sprintf(" \"%s:%s\",\n", path, mod))
}
}
}
if len(deps) > 0 {
sort.Strings(deps)
extraProperties.WriteString(" deps = [\n")
for _, dep := range deps {
extraProperties.WriteString(dep)
}
extraProperties.WriteString(" ],\n")
}
targets[platformLabel.pkg] = append(targets[platformLabel.pkg], BazelTarget{
name: "system_image",
packageName: platformLabel.pkg,
content: fmt.Sprintf(`partition(
name = "system_image",
base_staging_dir = "//build/bazel/bazel_sandwich:system_staging_dir",
base_staging_dir_file_list = "//build/bazel/bazel_sandwich:system_staging_dir_file_list",
root_dir = "//build/bazel/bazel_sandwich:root_staging_dir",
selinux_file_contexts = "//build/bazel/bazel_sandwich:selinux_file_contexts",
image_properties = """
%s
""",
%s
type = "system",
)`, properties.String(), extraProperties.String()),
ruleClass: "partition",
loads: []BazelLoad{{
file: "//build/bazel/rules/partitions:partition.bzl",
symbols: []BazelLoadSymbol{{
symbol: "partition",
}},
}},
}, BazelTarget{
name: "system_image_test",
packageName: platformLabel.pkg,
content: `partition_diff_test(
name = "system_image_test",
partition1 = "//build/bazel/bazel_sandwich:make_system_image",
partition2 = ":system_image",
)`,
ruleClass: "partition_diff_test",
loads: []BazelLoad{{
file: "//build/bazel/rules/partitions/diff:partition_diff.bzl",
symbols: []BazelLoadSymbol{{
symbol: "partition_diff_test",
}},
}},
}, BazelTarget{
name: "run_system_image_test",
packageName: platformLabel.pkg,
content: `run_test_in_build(
name = "run_system_image_test",
test = ":system_image_test",
)`,
ruleClass: "run_test_in_build",
loads: []BazelLoad{{
file: "//build/bazel/bazel_sandwich:run_test_in_build.bzl",
symbols: []BazelLoadSymbol{{
symbol: "run_test_in_build",
}},
}},
})
}
var allPartitionTypes = []string{
"system",
"vendor",
"cache",
"userdata",
"product",
"system_ext",
"oem",
"odm",
"vendor_dlkm",
"odm_dlkm",
"system_dlkm",
}
// An equivalent of make's generate-image-prop-dictionary function
func generateImagePropDictionary(variables *android.PartitionVariables, partitionType string) map[string]string {
partitionQualifiedVariables, ok := variables.PartitionQualifiedVariables[partitionType]
if !ok {
panic("Unknown partitionType: " + partitionType)
}
ret := map[string]string{}
if partitionType == "system" {
if len(variables.PartitionQualifiedVariables["system_other"].BoardPartitionSize) > 0 {
ret["system_other_size"] = variables.PartitionQualifiedVariables["system_other"].BoardPartitionSize
}
if len(partitionQualifiedVariables.ProductHeadroom) > 0 {
ret["system_headroom"] = partitionQualifiedVariables.ProductHeadroom
}
addCommonRoFlagsToImageProps(variables, partitionType, ret)
}
// TODO: other partition-specific logic
if variables.TargetUserimagesUseExt2 {
ret["fs_type"] = "ext2"
} else if variables.TargetUserimagesUseExt3 {
ret["fs_type"] = "ext3"
} else if variables.TargetUserimagesUseExt4 {
ret["fs_type"] = "ext4"
}
if !variables.TargetUserimagesSparseExtDisabled {
ret["extfs_sparse_flag"] = "-s"
}
if !variables.TargetUserimagesSparseErofsDisabled {
ret["erofs_sparse_flag"] = "-s"
}
if !variables.TargetUserimagesSparseSquashfsDisabled {
ret["squashfs_sparse_flag"] = "-s"
}
if !variables.TargetUserimagesSparseF2fsDisabled {
ret["f2fs_sparse_flag"] = "-S"
}
erofsCompressor := variables.BoardErofsCompressor
if len(erofsCompressor) == 0 && hasErofsPartition(variables) {
if len(variables.BoardErofsUseLegacyCompression) > 0 {
erofsCompressor = "lz4"
} else {
erofsCompressor = "lz4hc,9"
}
}
if len(erofsCompressor) > 0 {
ret["erofs_default_compressor"] = erofsCompressor
}
if len(variables.BoardErofsCompressorHints) > 0 {
ret["erofs_default_compress_hints"] = variables.BoardErofsCompressorHints
}
if len(variables.BoardErofsCompressorHints) > 0 {
ret["erofs_default_compress_hints"] = variables.BoardErofsCompressorHints
}
if len(variables.BoardErofsPclusterSize) > 0 {
ret["erofs_pcluster_size"] = variables.BoardErofsPclusterSize
}
if len(variables.BoardErofsShareDupBlocks) > 0 {
ret["erofs_share_dup_blocks"] = variables.BoardErofsShareDupBlocks
}
if len(variables.BoardErofsUseLegacyCompression) > 0 {
ret["erofs_use_legacy_compression"] = variables.BoardErofsUseLegacyCompression
}
if len(variables.BoardExt4ShareDupBlocks) > 0 {
ret["ext4_share_dup_blocks"] = variables.BoardExt4ShareDupBlocks
}
if len(variables.BoardFlashLogicalBlockSize) > 0 {
ret["flash_logical_block_size"] = variables.BoardFlashLogicalBlockSize
}
if len(variables.BoardFlashEraseBlockSize) > 0 {
ret["flash_erase_block_size"] = variables.BoardFlashEraseBlockSize
}
if len(variables.BoardExt4ShareDupBlocks) > 0 {
ret["ext4_share_dup_blocks"] = variables.BoardExt4ShareDupBlocks
}
if len(variables.BoardExt4ShareDupBlocks) > 0 {
ret["ext4_share_dup_blocks"] = variables.BoardExt4ShareDupBlocks
}
for _, partitionType := range allPartitionTypes {
if qualifiedVariables, ok := variables.PartitionQualifiedVariables[partitionType]; ok && len(qualifiedVariables.ProductVerityPartition) > 0 {
ret[partitionType+"_verity_block_device"] = qualifiedVariables.ProductVerityPartition
}
}
// TODO: Vboot
// TODO: AVB
if variables.BoardUsesRecoveryAsBoot {
ret["recovery_as_boot"] = "true"
}
if variables.ProductUseDynamicPartitionSize {
ret["use_dynamic_partition_size"] = "true"
}
if variables.CopyImagesForTargetFilesZip {
ret["use_fixed_timestamp"] = "true"
}
return ret
}
// Soong equivalent of make's add-common-ro-flags-to-image-props
func addCommonRoFlagsToImageProps(variables *android.PartitionVariables, partitionType string, ret map[string]string) {
partitionQualifiedVariables, ok := variables.PartitionQualifiedVariables[partitionType]
if !ok {
panic("Unknown partitionType: " + partitionType)
}
if len(partitionQualifiedVariables.BoardErofsCompressor) > 0 {
ret[partitionType+"_erofs_compressor"] = partitionQualifiedVariables.BoardErofsCompressor
}
if len(partitionQualifiedVariables.BoardErofsCompressHints) > 0 {
ret[partitionType+"_erofs_compress_hints"] = partitionQualifiedVariables.BoardErofsCompressHints
}
if len(partitionQualifiedVariables.BoardErofsPclusterSize) > 0 {
ret[partitionType+"_erofs_pcluster_size"] = partitionQualifiedVariables.BoardErofsPclusterSize
}
if len(partitionQualifiedVariables.BoardExtfsRsvPct) > 0 {
ret[partitionType+"_extfs_rsv_pct"] = partitionQualifiedVariables.BoardExtfsRsvPct
}
if len(partitionQualifiedVariables.BoardF2fsSloadCompressFlags) > 0 {
ret[partitionType+"_f2fs_sldc_flags"] = partitionQualifiedVariables.BoardF2fsSloadCompressFlags
}
if len(partitionQualifiedVariables.BoardFileSystemCompress) > 0 {
ret[partitionType+"_f2fs_compress"] = partitionQualifiedVariables.BoardFileSystemCompress
}
if len(partitionQualifiedVariables.BoardFileSystemType) > 0 {
ret[partitionType+"_fs_type"] = partitionQualifiedVariables.BoardFileSystemType
}
if len(partitionQualifiedVariables.BoardJournalSize) > 0 {
ret[partitionType+"_journal_size"] = partitionQualifiedVariables.BoardJournalSize
}
if len(partitionQualifiedVariables.BoardPartitionReservedSize) > 0 {
ret[partitionType+"_reserved_size"] = partitionQualifiedVariables.BoardPartitionReservedSize
}
if len(partitionQualifiedVariables.BoardPartitionSize) > 0 {
ret[partitionType+"_size"] = partitionQualifiedVariables.BoardPartitionSize
}
if len(partitionQualifiedVariables.BoardSquashfsBlockSize) > 0 {
ret[partitionType+"_squashfs_block_size"] = partitionQualifiedVariables.BoardSquashfsBlockSize
}
if len(partitionQualifiedVariables.BoardSquashfsCompressor) > 0 {
ret[partitionType+"_squashfs_compressor"] = partitionQualifiedVariables.BoardSquashfsCompressor
}
if len(partitionQualifiedVariables.BoardSquashfsCompressorOpt) > 0 {
ret[partitionType+"_squashfs_compressor_opt"] = partitionQualifiedVariables.BoardSquashfsCompressorOpt
}
if len(partitionQualifiedVariables.BoardSquashfsDisable4kAlign) > 0 {
ret[partitionType+"_squashfs_disable_4k_align"] = partitionQualifiedVariables.BoardSquashfsDisable4kAlign
}
if len(partitionQualifiedVariables.BoardPartitionSize) == 0 && len(partitionQualifiedVariables.BoardPartitionReservedSize) == 0 && len(partitionQualifiedVariables.ProductHeadroom) == 0 {
ret[partitionType+"_disable_sparse"] = "true"
}
addCommonFlagsToImageProps(variables, partitionType, ret)
}
func hasErofsPartition(variables *android.PartitionVariables) bool {
return variables.PartitionQualifiedVariables["product"].BoardFileSystemType == "erofs" ||
variables.PartitionQualifiedVariables["system_ext"].BoardFileSystemType == "erofs" ||
variables.PartitionQualifiedVariables["odm"].BoardFileSystemType == "erofs" ||
variables.PartitionQualifiedVariables["vendor"].BoardFileSystemType == "erofs" ||
variables.PartitionQualifiedVariables["system"].BoardFileSystemType == "erofs" ||
variables.PartitionQualifiedVariables["vendor_dlkm"].BoardFileSystemType == "erofs" ||
variables.PartitionQualifiedVariables["odm_dlkm"].BoardFileSystemType == "erofs" ||
variables.PartitionQualifiedVariables["system_dlkm"].BoardFileSystemType == "erofs"
}
// Soong equivalent of make's add-common-flags-to-image-props
func addCommonFlagsToImageProps(variables *android.PartitionVariables, partitionType string, ret map[string]string) {
// The selinux_fc will be handled separately
partitionQualifiedVariables, ok := variables.PartitionQualifiedVariables[partitionType]
if !ok {
panic("Unknown partitionType: " + partitionType)
}
ret["building_"+partitionType+"_image"] = boolToMakeString(partitionQualifiedVariables.BuildingImage)
}
func boolToMakeString(b bool) string {
if b {
return "true"
}
return ""
}

View file

@ -28,10 +28,7 @@ import (
"android/soong/android"
"android/soong/bazel"
"android/soong/starlark_fmt"
"android/soong/ui/metrics/bp2build_metrics_proto"
"github.com/google/blueprint"
"github.com/google/blueprint/bootstrap"
"github.com/google/blueprint/proptools"
)
@ -201,18 +198,13 @@ func (ctx *CodegenContext) Mode() CodegenMode {
type CodegenMode int
const (
// Bp2Build - generate BUILD files with targets buildable by Bazel directly.
//
// This mode is used for the Soong->Bazel build definition conversion.
Bp2Build CodegenMode = iota
// QueryView - generate BUILD files with targets representing fully mutated
// Soong modules, representing the fully configured Soong module graph with
// variants and dependency edges.
//
// This mode is used for discovering and introspecting the existing Soong
// module graph.
QueryView
QueryView CodegenMode = iota
)
type unconvertedDepsMode int
@ -227,8 +219,6 @@ const (
func (mode CodegenMode) String() string {
switch mode {
case Bp2Build:
return "Bp2Build"
case QueryView:
return "QueryView"
default:
@ -256,9 +246,6 @@ func (ctx *CodegenContext) Context() *android.Context { return ctx.context }
// writing BUILD files in the output directory.
func NewCodegenContext(config android.Config, context *android.Context, mode CodegenMode, topDir string) *CodegenContext {
var unconvertedDeps unconvertedDepsMode
if config.IsEnvTrue("BP2BUILD_ERROR_UNCONVERTED") {
unconvertedDeps = errorModulesUnconvertedDeps
}
return &CodegenContext{
context: context,
config: config,
@ -281,526 +268,30 @@ func propsToAttributes(props map[string]string) string {
type conversionResults struct {
buildFileToTargets map[string]BazelTargets
moduleNameToPartition map[string]string
metrics CodegenMetrics
}
func (r conversionResults) BuildDirToTargets() map[string]BazelTargets {
return r.buildFileToTargets
}
// struct to store state of b bazel targets (e.g. go targets which do not implement android.Module)
// this implements bp2buildModule interface and is passed to generateBazelTargets
type bTarget struct {
targetName string
targetPackage string
bazelRuleClass string
bazelRuleLoadLocation string
bazelAttributes []interface{}
}
var _ bp2buildModule = (*bTarget)(nil)
func (b bTarget) TargetName() string {
return b.targetName
}
func (b bTarget) TargetPackage() string {
return b.targetPackage
}
func (b bTarget) BazelRuleClass() string {
return b.bazelRuleClass
}
func (b bTarget) BazelRuleLoadLocation() string {
return b.bazelRuleLoadLocation
}
func (b bTarget) BazelAttributes() []interface{} {
return b.bazelAttributes
}
// Creates a target_compatible_with entry that is *not* compatible with android
func targetNotCompatibleWithAndroid() bazel.LabelListAttribute {
ret := bazel.LabelListAttribute{}
ret.SetSelectValue(bazel.OsConfigurationAxis, bazel.OsAndroid,
bazel.MakeLabelList(
[]bazel.Label{
bazel.Label{
Label: "@platforms//:incompatible",
},
},
),
)
return ret
}
// helper function to return labels for srcs used in bootstrap_go_package and bootstrap_go_binary
// this function has the following limitations which make it unsuitable for widespread use
// - wildcard patterns in srcs
// This is ok for go since build/blueprint does not support it.
//
// Prefer to use `BazelLabelForModuleSrc` instead
func goSrcLabels(cfg android.Config, moduleDir string, srcs []string, linuxSrcs, darwinSrcs []string) bazel.LabelListAttribute {
labels := func(srcs []string) bazel.LabelList {
ret := []bazel.Label{}
for _, src := range srcs {
srcLabel := bazel.Label{
Label: src,
}
ret = append(ret, srcLabel)
}
// Respect package boundaries
return android.TransformSubpackagePaths(
cfg,
moduleDir,
bazel.MakeLabelList(ret),
)
}
ret := bazel.LabelListAttribute{}
// common
ret.SetSelectValue(bazel.NoConfigAxis, "", labels(srcs))
// linux
ret.SetSelectValue(bazel.OsConfigurationAxis, bazel.OsLinux, labels(linuxSrcs))
// darwin
ret.SetSelectValue(bazel.OsConfigurationAxis, bazel.OsDarwin, labels(darwinSrcs))
return ret
}
func goDepLabels(deps []string, goModulesMap nameToGoLibraryModule) bazel.LabelListAttribute {
labels := []bazel.Label{}
for _, dep := range deps {
moduleDir := goModulesMap[dep].Dir
if moduleDir == "." {
moduleDir = ""
}
label := bazel.Label{
Label: fmt.Sprintf("//%s:%s", moduleDir, dep),
}
labels = append(labels, label)
}
return bazel.MakeLabelListAttribute(bazel.MakeLabelList(labels))
}
// attributes common to blueprint_go_binary and bootstap_go_package
type goAttributes struct {
Importpath bazel.StringAttribute
Srcs bazel.LabelListAttribute
Deps bazel.LabelListAttribute
Data bazel.LabelListAttribute
Target_compatible_with bazel.LabelListAttribute
// attributes for the dynamically generated go_test target
Embed bazel.LabelListAttribute
}
type goTestProperties struct {
name string
dir string
testSrcs []string
linuxTestSrcs []string
darwinTestSrcs []string
testData []string
// Name of the target that should be compiled together with the test
embedName string
}
// Creates a go_test target for bootstrap_go_package / blueprint_go_binary
func generateBazelTargetsGoTest(ctx *android.Context, goModulesMap nameToGoLibraryModule, gp goTestProperties) (BazelTarget, error) {
ca := android.CommonAttributes{
Name: gp.name,
}
ga := goAttributes{
Srcs: goSrcLabels(ctx.Config(), gp.dir, gp.testSrcs, gp.linuxTestSrcs, gp.darwinTestSrcs),
Data: goSrcLabels(ctx.Config(), gp.dir, gp.testData, []string{}, []string{}),
Embed: bazel.MakeLabelListAttribute(
bazel.MakeLabelList(
[]bazel.Label{bazel.Label{Label: ":" + gp.embedName}},
),
),
Target_compatible_with: targetNotCompatibleWithAndroid(),
}
libTest := bTarget{
targetName: gp.name,
targetPackage: gp.dir,
bazelRuleClass: "go_test",
bazelRuleLoadLocation: "@io_bazel_rules_go//go:def.bzl",
bazelAttributes: []interface{}{&ca, &ga},
}
return generateBazelTarget(ctx, libTest)
}
// TODO - b/288491147: testSrcs of certain bootstrap_go_package/blueprint_go_binary are not hermetic and depend on
// testdata checked into the filesystem.
// Denylist the generation of go_test targets for these Soong modules.
// The go_library/go_binary will still be generated, since those are hermitic.
var (
goTestsDenylist = []string{
"android-archive-zip",
"bazel_notice_gen",
"blueprint-bootstrap-bpdoc",
"blueprint-microfactory",
"blueprint-pathtools",
"bssl_ar",
"compliance_checkmetadata",
"compliance_checkshare",
"compliance_dumpgraph",
"compliance_dumpresolutions",
"compliance_listshare",
"compliance-module",
"compliancenotice_bom",
"compliancenotice_shippedlibs",
"compliance_rtrace",
"compliance_sbom",
"golang-protobuf-internal-fuzz-jsonfuzz",
"golang-protobuf-internal-fuzz-textfuzz",
"golang-protobuf-internal-fuzz-wirefuzz",
"htmlnotice",
"protoc-gen-go",
"rbcrun-module",
"spdx-tools-builder",
"spdx-tools-builder2v1",
"spdx-tools-builder2v2",
"spdx-tools-builder2v3",
"spdx-tools-idsearcher",
"spdx-tools-spdx-json",
"spdx-tools-utils",
"soong-ui-build",
"textnotice",
"xmlnotice",
}
)
func testOfGoPackageIsIncompatible(g *bootstrap.GoPackage) bool {
return android.InList(g.Name(), goTestsDenylist) ||
// Denylist tests of soong_build
// Theses tests have a guard that prevent usage outside a test environment
// The guard (`ensureTestOnly`) looks for a `-test` in os.Args, which is present in soong's gotestrunner, but missing in `b test`
g.IsPluginFor("soong_build") ||
// soong-android is a dep of soong_build
// This dependency is created by soong_build by listing it in its deps explicitly in Android.bp, and not via `plugin_for` in `soong-android`
g.Name() == "soong-android"
}
func testOfGoBinaryIsIncompatible(g *bootstrap.GoBinary) bool {
return android.InList(g.Name(), goTestsDenylist)
}
func generateBazelTargetsGoPackage(ctx *android.Context, g *bootstrap.GoPackage, goModulesMap nameToGoLibraryModule) ([]BazelTarget, []error) {
ca := android.CommonAttributes{
Name: g.Name(),
}
// For this bootstrap_go_package dep chain,
// A --> B --> C ( ---> depends on)
// Soong provides the convenience of only listing B as deps of A even if a src file of A imports C
// Bazel OTOH
// 1. requires C to be listed in `deps` expllicity.
// 2. does not require C to be listed if src of A does not import C
//
// bp2build does not have sufficient info on whether C is a direct dep of A or not, so for now collect all transitive deps and add them to deps
transitiveDeps := transitiveGoDeps(g.Deps(), goModulesMap)
ga := goAttributes{
Importpath: bazel.StringAttribute{
Value: proptools.StringPtr(g.GoPkgPath()),
},
Srcs: goSrcLabels(ctx.Config(), ctx.ModuleDir(g), g.Srcs(), g.LinuxSrcs(), g.DarwinSrcs()),
Deps: goDepLabels(
android.FirstUniqueStrings(transitiveDeps),
goModulesMap,
),
Target_compatible_with: targetNotCompatibleWithAndroid(),
}
lib := bTarget{
targetName: g.Name(),
targetPackage: ctx.ModuleDir(g),
bazelRuleClass: "go_library",
bazelRuleLoadLocation: "@io_bazel_rules_go//go:def.bzl",
bazelAttributes: []interface{}{&ca, &ga},
}
retTargets := []BazelTarget{}
var retErrs []error
if libTarget, err := generateBazelTarget(ctx, lib); err == nil {
retTargets = append(retTargets, libTarget)
} else {
retErrs = []error{err}
}
// If the library contains test srcs, create an additional go_test target
if !testOfGoPackageIsIncompatible(g) && (len(g.TestSrcs()) > 0 || len(g.LinuxTestSrcs()) > 0 || len(g.DarwinTestSrcs()) > 0) {
gp := goTestProperties{
name: g.Name() + "-test",
dir: ctx.ModuleDir(g),
testSrcs: g.TestSrcs(),
linuxTestSrcs: g.LinuxTestSrcs(),
darwinTestSrcs: g.DarwinTestSrcs(),
testData: g.TestData(),
embedName: g.Name(), // embed the source go_library in the test so that its .go files are included in the compilation unit
}
if libTestTarget, err := generateBazelTargetsGoTest(ctx, goModulesMap, gp); err == nil {
retTargets = append(retTargets, libTestTarget)
} else {
retErrs = append(retErrs, err)
}
}
return retTargets, retErrs
}
type goLibraryModule struct {
Dir string
Deps []string
}
type buildConversionMetadata struct {
nameToGoLibraryModule nameToGoLibraryModule
ndkHeaders []blueprint.Module
}
type nameToGoLibraryModule map[string]goLibraryModule
// Visit each module in the graph, and collect metadata about the build graph
// If a module is of type `bootstrap_go_package`, return a map containing metadata like its dir and deps
// If a module is of type `ndk_headers`, add it to a list and return the list
func createBuildConversionMetadata(ctx *android.Context) buildConversionMetadata {
goMap := nameToGoLibraryModule{}
ndkHeaders := []blueprint.Module{}
ctx.VisitAllModules(func(m blueprint.Module) {
moduleType := ctx.ModuleType(m)
// We do not need to store information about blueprint_go_binary since it does not have any rdeps
if moduleType == "bootstrap_go_package" {
goMap[m.Name()] = goLibraryModule{
Dir: ctx.ModuleDir(m),
Deps: m.(*bootstrap.GoPackage).Deps(),
}
} else if moduleType == "ndk_headers" || moduleType == "versioned_ndk_headers" {
ndkHeaders = append(ndkHeaders, m)
}
})
return buildConversionMetadata{
nameToGoLibraryModule: goMap,
ndkHeaders: ndkHeaders,
}
}
// Returns the deps in the transitive closure of a go target
func transitiveGoDeps(directDeps []string, goModulesMap nameToGoLibraryModule) []string {
allDeps := directDeps
i := 0
for i < len(allDeps) {
curr := allDeps[i]
allDeps = append(allDeps, goModulesMap[curr].Deps...)
i += 1
}
allDeps = android.SortedUniqueStrings(allDeps)
return allDeps
}
func generateBazelTargetsGoBinary(ctx *android.Context, g *bootstrap.GoBinary, goModulesMap nameToGoLibraryModule) ([]BazelTarget, []error) {
ca := android.CommonAttributes{
Name: g.Name(),
}
retTargets := []BazelTarget{}
var retErrs []error
// For this bootstrap_go_package dep chain,
// A --> B --> C ( ---> depends on)
// Soong provides the convenience of only listing B as deps of A even if a src file of A imports C
// Bazel OTOH
// 1. requires C to be listed in `deps` expllicity.
// 2. does not require C to be listed if src of A does not import C
//
// bp2build does not have sufficient info on whether C is a direct dep of A or not, so for now collect all transitive deps and add them to deps
transitiveDeps := transitiveGoDeps(g.Deps(), goModulesMap)
goSource := ""
// If the library contains test srcs, create an additional go_test target
// The go_test target will embed a go_source containining the source .go files it tests
if !testOfGoBinaryIsIncompatible(g) && (len(g.TestSrcs()) > 0 || len(g.LinuxTestSrcs()) > 0 || len(g.DarwinTestSrcs()) > 0) {
// Create a go_source containing the source .go files of go_library
// This target will be an `embed` of the go_binary and go_test
goSource = g.Name() + "-source"
ca := android.CommonAttributes{
Name: goSource,
}
ga := goAttributes{
Srcs: goSrcLabels(ctx.Config(), ctx.ModuleDir(g), g.Srcs(), g.LinuxSrcs(), g.DarwinSrcs()),
Deps: goDepLabels(transitiveDeps, goModulesMap),
Target_compatible_with: targetNotCompatibleWithAndroid(),
}
libTestSource := bTarget{
targetName: goSource,
targetPackage: ctx.ModuleDir(g),
bazelRuleClass: "go_source",
bazelRuleLoadLocation: "@io_bazel_rules_go//go:def.bzl",
bazelAttributes: []interface{}{&ca, &ga},
}
if libSourceTarget, err := generateBazelTarget(ctx, libTestSource); err == nil {
retTargets = append(retTargets, libSourceTarget)
} else {
retErrs = append(retErrs, err)
}
// Create a go_test target
gp := goTestProperties{
name: g.Name() + "-test",
dir: ctx.ModuleDir(g),
testSrcs: g.TestSrcs(),
linuxTestSrcs: g.LinuxTestSrcs(),
darwinTestSrcs: g.DarwinTestSrcs(),
testData: g.TestData(),
// embed the go_source in the test
embedName: g.Name() + "-source",
}
if libTestTarget, err := generateBazelTargetsGoTest(ctx, goModulesMap, gp); err == nil {
retTargets = append(retTargets, libTestTarget)
} else {
retErrs = append(retErrs, err)
}
}
// Create a go_binary target
ga := goAttributes{
Deps: goDepLabels(transitiveDeps, goModulesMap),
Target_compatible_with: targetNotCompatibleWithAndroid(),
}
// If the binary has testSrcs, embed the common `go_source`
if goSource != "" {
ga.Embed = bazel.MakeLabelListAttribute(
bazel.MakeLabelList(
[]bazel.Label{bazel.Label{Label: ":" + goSource}},
),
)
} else {
ga.Srcs = goSrcLabels(ctx.Config(), ctx.ModuleDir(g), g.Srcs(), g.LinuxSrcs(), g.DarwinSrcs())
}
bin := bTarget{
targetName: g.Name(),
targetPackage: ctx.ModuleDir(g),
bazelRuleClass: "go_binary",
bazelRuleLoadLocation: "@io_bazel_rules_go//go:def.bzl",
bazelAttributes: []interface{}{&ca, &ga},
}
if binTarget, err := generateBazelTarget(ctx, bin); err == nil {
retTargets = append(retTargets, binTarget)
} else {
retErrs = []error{err}
}
return retTargets, retErrs
}
func GenerateBazelTargets(ctx *CodegenContext, generateFilegroups bool) (conversionResults, []error) {
ctx.Context().BeginEvent("GenerateBazelTargets")
defer ctx.Context().EndEvent("GenerateBazelTargets")
buildFileToTargets := make(map[string]BazelTargets)
// Simple metrics tracking for bp2build
metrics := CreateCodegenMetrics()
dirs := make(map[string]bool)
moduleNameToPartition := make(map[string]string)
var errs []error
// Visit go libraries in a pre-run and store its state in a map
// The time complexity remains O(N), and this does not add significant wall time.
meta := createBuildConversionMetadata(ctx.Context())
nameToGoLibMap := meta.nameToGoLibraryModule
ndkHeaders := meta.ndkHeaders
bpCtx := ctx.Context()
bpCtx.VisitAllModules(func(m blueprint.Module) {
dir := bpCtx.ModuleDir(m)
moduleType := bpCtx.ModuleType(m)
dirs[dir] = true
var targets []BazelTarget
var targetErrs []error
switch ctx.Mode() {
case Bp2Build:
if aModule, ok := m.(android.Module); ok {
reason := aModule.GetUnconvertedReason()
if reason != nil {
// If this module was force-enabled, cause an error.
if _, ok := ctx.Config().BazelModulesForceEnabledByFlag()[m.Name()]; ok && m.Name() != "" {
err := fmt.Errorf("Force Enabled Module %s not converted", m.Name())
errs = append(errs, err)
}
// Log the module isn't to be converted by bp2build.
// TODO: b/291598248 - Log handcrafted modules differently than other unconverted modules.
metrics.AddUnconvertedModule(m, moduleType, dir, *reason)
return
}
if len(aModule.Bp2buildTargets()) == 0 {
panic(fmt.Errorf("illegal bp2build invariant: module '%s' was neither converted nor marked unconvertible", aModule.Name()))
}
// Handle modules converted to generated targets.
targets, targetErrs = generateBazelTargets(bpCtx, aModule)
errs = append(errs, targetErrs...)
for _, t := range targets {
// A module can potentially generate more than 1 Bazel
// target, each of a different rule class.
metrics.IncrementRuleClassCount(t.ruleClass)
}
// record the partition
moduleNameToPartition[android.RemoveOptionalPrebuiltPrefix(aModule.Name())] = aModule.GetPartitionForBp2build()
// Log the module.
metrics.AddConvertedModule(aModule, moduleType, dir)
// Handle modules with unconverted deps. By default, emit a warning.
if unconvertedDeps := aModule.GetUnconvertedBp2buildDeps(); len(unconvertedDeps) > 0 {
msg := fmt.Sprintf("%s %s:%s depends on unconverted modules: %s",
moduleType, bpCtx.ModuleDir(m), m.Name(), strings.Join(unconvertedDeps, ", "))
switch ctx.unconvertedDepMode {
case warnUnconvertedDeps:
metrics.moduleWithUnconvertedDepsMsgs = append(metrics.moduleWithUnconvertedDepsMsgs, msg)
case errorModulesUnconvertedDeps:
errs = append(errs, fmt.Errorf(msg))
return
}
}
if unconvertedDeps := aModule.GetMissingBp2buildDeps(); len(unconvertedDeps) > 0 {
msg := fmt.Sprintf("%s %s:%s depends on missing modules: %s",
moduleType, bpCtx.ModuleDir(m), m.Name(), strings.Join(unconvertedDeps, ", "))
switch ctx.unconvertedDepMode {
case warnUnconvertedDeps:
metrics.moduleWithMissingDepsMsgs = append(metrics.moduleWithMissingDepsMsgs, msg)
case errorModulesUnconvertedDeps:
errs = append(errs, fmt.Errorf(msg))
return
}
}
} else if glib, ok := m.(*bootstrap.GoPackage); ok {
targets, targetErrs = generateBazelTargetsGoPackage(bpCtx, glib, nameToGoLibMap)
errs = append(errs, targetErrs...)
metrics.IncrementRuleClassCount("bootstrap_go_package")
metrics.AddConvertedModule(glib, "bootstrap_go_package", dir)
} else if gbin, ok := m.(*bootstrap.GoBinary); ok {
targets, targetErrs = generateBazelTargetsGoBinary(bpCtx, gbin, nameToGoLibMap)
errs = append(errs, targetErrs...)
metrics.IncrementRuleClassCount("blueprint_go_binary")
metrics.AddConvertedModule(gbin, "blueprint_go_binary", dir)
} else {
metrics.AddUnconvertedModule(m, moduleType, dir, android.UnconvertedReason{
ReasonType: int(bp2build_metrics_proto.UnconvertedReasonType_TYPE_UNSUPPORTED),
})
return
}
case QueryView:
// Blocklist certain module types from being generated.
if canonicalizeModuleType(bpCtx.ModuleType(m)) == "package" {
@ -824,37 +315,6 @@ func GenerateBazelTargets(ctx *CodegenContext, generateFilegroups bool) (convers
}
})
// Create an ndk_sysroot target that has a dependency edge on every target corresponding to Soong's ndk_headers
// This root target will provide headers to sdk variants of jni libraries
if ctx.Mode() == Bp2Build {
var depLabels bazel.LabelList
for _, ndkHeader := range ndkHeaders {
depLabel := bazel.Label{
Label: "//" + bpCtx.ModuleDir(ndkHeader) + ":" + ndkHeader.Name(),
}
depLabels.Add(&depLabel)
}
a := struct {
Deps bazel.LabelListAttribute
}{
Deps: bazel.MakeLabelListAttribute(bazel.UniqueSortedBazelLabelList(depLabels)),
}
ndkSysroot := bTarget{
targetName: "ndk_sysroot",
targetPackage: "build/bazel/rules/cc", // The location is subject to change, use build/bazel for now
bazelRuleClass: "cc_library_headers",
bazelRuleLoadLocation: "//build/bazel/rules/cc:cc_library_headers.bzl",
bazelAttributes: []interface{}{&a},
}
if t, err := generateBazelTarget(bpCtx, ndkSysroot); err == nil {
dir := ndkSysroot.targetPackage
buildFileToTargets[dir] = append(buildFileToTargets[dir], t)
} else {
errs = append(errs, err)
}
}
if len(errs) > 0 {
return conversionResults{}, errs
}
@ -883,72 +343,9 @@ func GenerateBazelTargets(ctx *CodegenContext, generateFilegroups bool) (convers
return conversionResults{
buildFileToTargets: buildFileToTargets,
moduleNameToPartition: moduleNameToPartition,
metrics: metrics,
}, errs
}
func generateBazelTargets(ctx bpToBuildContext, m android.Module) ([]BazelTarget, []error) {
var targets []BazelTarget
var errs []error
for _, m := range m.Bp2buildTargets() {
target, err := generateBazelTarget(ctx, m)
if err != nil {
errs = append(errs, err)
return targets, errs
}
targets = append(targets, target)
}
return targets, errs
}
type bp2buildModule interface {
TargetName() string
TargetPackage() string
BazelRuleClass() string
BazelRuleLoadLocation() string
BazelAttributes() []interface{}
}
func generateBazelTarget(ctx bpToBuildContext, m bp2buildModule) (BazelTarget, error) {
ruleClass := m.BazelRuleClass()
bzlLoadLocation := m.BazelRuleLoadLocation()
// extract the bazel attributes from the module.
attrs := m.BazelAttributes()
props, err := extractModuleProperties(attrs, true)
if err != nil {
return BazelTarget{}, err
}
// name is handled in a special manner
delete(props.Attrs, "name")
// Return the Bazel target with rule class and attributes, ready to be
// code-generated.
attributes := propsToAttributes(props.Attrs)
var content string
targetName := m.TargetName()
if targetName != "" {
content = fmt.Sprintf(ruleTargetTemplate, ruleClass, targetName, attributes)
} else {
content = fmt.Sprintf(unnamedRuleTargetTemplate, ruleClass, attributes)
}
var loads []BazelLoad
if bzlLoadLocation != "" {
loads = append(loads, BazelLoad{
file: bzlLoadLocation,
symbols: []BazelLoadSymbol{{symbol: ruleClass}},
})
}
return BazelTarget{
name: targetName,
packageName: m.TargetPackage(),
ruleClass: ruleClass,
loads: loads,
content: content,
}, nil
}
// Convert a module and its deps and props into a Bazel macro/rule
// representation in the BUILD file.
func generateSoongModuleTarget(ctx bpToBuildContext, m blueprint.Module) (BazelTarget, error) {

View file

@ -1,15 +1,10 @@
package bp2build
import (
"encoding/json"
"fmt"
"reflect"
"strconv"
"strings"
"android/soong/android"
"android/soong/starlark_fmt"
"github.com/google/blueprint/proptools"
)
@ -19,79 +14,6 @@ type BazelFile struct {
Contents string
}
// createSoongInjectionDirFiles returns most of the files to write to the soong_injection directory.
// Some other files also come from CreateProductConfigFiles
func createSoongInjectionDirFiles(ctx *CodegenContext, metrics CodegenMetrics) ([]BazelFile, error) {
cfg := ctx.Config()
var files []BazelFile
files = append(files, newFile("android", GeneratedBuildFileName, "")) // Creates a //cc_toolchain package.
files = append(files, newFile("android", "constants.bzl", android.BazelCcToolchainVars(cfg)))
if buf, err := json.MarshalIndent(metrics.convertedModuleWithType, "", " "); err != nil {
return []BazelFile{}, err
} else {
files = append(files, newFile("metrics", "converted_modules.json", string(buf)))
}
convertedModulePathMap, err := json.MarshalIndent(metrics.convertedModulePathMap, "", "\t")
if err != nil {
panic(err)
}
files = append(files, newFile("metrics", GeneratedBuildFileName, "")) // Creates a //metrics package.
files = append(files, newFile("metrics", "converted_modules_path_map.json", string(convertedModulePathMap)))
files = append(files, newFile("metrics", "converted_modules_path_map.bzl", "modules = "+strings.ReplaceAll(string(convertedModulePathMap), "\\", "\\\\")))
files = append(files, newFile("product_config", "soong_config_variables.bzl", cfg.Bp2buildSoongConfigDefinitions.String()))
files = append(files, newFile("product_config", "arch_configuration.bzl", android.StarlarkArchConfigurations()))
apiLevelsMap, err := android.GetApiLevelsMap(cfg)
if err != nil {
return nil, err
}
apiLevelsContent, err := json.Marshal(apiLevelsMap)
if err != nil {
return nil, err
}
files = append(files, newFile("api_levels", GeneratedBuildFileName, `exports_files(["api_levels.json"])`))
// TODO(b/269691302) value of apiLevelsContent is product variable dependent and should be avoided for soong injection
files = append(files, newFile("api_levels", "api_levels.json", string(apiLevelsContent)))
files = append(files, newFile("api_levels", "platform_versions.bzl", platformVersionContents(cfg)))
files = append(files, newFile("allowlists", GeneratedBuildFileName, ""))
// TODO(b/262781701): Create an alternate soong_build entrypoint for writing out these files only when requested
files = append(files, newFile("allowlists", "mixed_build_prod_allowlist.txt", strings.Join(android.GetBazelEnabledModules(android.BazelProdMode), "\n")+"\n"))
files = append(files, newFile("allowlists", "mixed_build_staging_allowlist.txt", strings.Join(android.GetBazelEnabledModules(android.BazelStagingMode), "\n")+"\n"))
return files, nil
}
func platformVersionContents(cfg android.Config) string {
// Despite these coming from cfg.productVariables, they are actually hardcoded in global
// makefiles, not set in individual product config makesfiles, so they're safe to just export
// and load() directly.
platformVersionActiveCodenames := make([]string, 0, len(cfg.PlatformVersionActiveCodenames()))
for _, codename := range cfg.PlatformVersionActiveCodenames() {
platformVersionActiveCodenames = append(platformVersionActiveCodenames, fmt.Sprintf("%q", codename))
}
platformSdkVersion := "None"
if cfg.RawPlatformSdkVersion() != nil {
platformSdkVersion = strconv.Itoa(*cfg.RawPlatformSdkVersion())
}
return fmt.Sprintf(`
platform_versions = struct(
platform_sdk_final = %s,
platform_sdk_version = %s,
platform_sdk_codename = %q,
platform_version_active_codenames = [%s],
)
`, starlark_fmt.PrintBool(cfg.PlatformSdkFinal()), platformSdkVersion, cfg.PlatformSdkCodename(), strings.Join(platformVersionActiveCodenames, ", "))
}
func CreateBazelFiles(ruleShims map[string]RuleShim, buildToTargets map[string]BazelTargets, mode CodegenMode) []BazelFile {
var files []BazelFile
@ -125,20 +47,7 @@ func createBuildFiles(buildToTargets map[string]BazelTargets, mode CodegenMode)
targets.sort()
var content string
if mode == Bp2Build {
content = `# READ THIS FIRST:
# This file was automatically generated by bp2build for the Bazel migration project.
# Feel free to edit or test it, but do *not* check it into your version control system.
`
content += targets.LoadStatements()
content += "\n\n"
// Get package rule from the handcrafted BUILD file, otherwise emit the default one.
prText := "package(default_visibility = [\"//visibility:public\"])\n"
if pr := targets.packageRule(); pr != nil {
prText = pr.content
}
content += prText
} else if mode == QueryView {
if mode == QueryView {
content = soongModuleLoad
}
if content != "" {
@ -161,14 +70,6 @@ func newFile(dir, basename, content string) BazelFile {
const (
bazelRulesSubDir = "build/bazel/queryview_rules"
// additional files:
// * workspace file
// * base BUILD file
// * rules BUILD file
// * rules providers.bzl file
// * rules soong_module.bzl file
numAdditionalFiles = 5
)
var (

View file

@ -1,231 +0,0 @@
package bp2build
import (
"fmt"
"os"
"path/filepath"
"strings"
"android/soong/android"
"android/soong/shared"
"android/soong/ui/metrics/bp2build_metrics_proto"
"google.golang.org/protobuf/proto"
"github.com/google/blueprint"
)
type moduleInfo struct {
Name string `json:"name"`
Type string `json:"type"`
}
// CodegenMetrics represents information about the Blueprint-to-BUILD
// conversion process.
// Use CreateCodegenMetrics() to get a properly initialized instance
type CodegenMetrics struct {
serialized *bp2build_metrics_proto.Bp2BuildMetrics
// List of modules with unconverted deps
// NOTE: NOT in the .proto
moduleWithUnconvertedDepsMsgs []string
// List of modules with missing deps
// NOTE: NOT in the .proto
moduleWithMissingDepsMsgs []string
// Map of converted modules and paths to call
// NOTE: NOT in the .proto
convertedModulePathMap map[string]string
// Name and type of converted modules
convertedModuleWithType []moduleInfo
}
func CreateCodegenMetrics() CodegenMetrics {
return CodegenMetrics{
serialized: &bp2build_metrics_proto.Bp2BuildMetrics{
RuleClassCount: make(map[string]uint64),
ConvertedModuleTypeCount: make(map[string]uint64),
TotalModuleTypeCount: make(map[string]uint64),
UnconvertedModules: make(map[string]*bp2build_metrics_proto.UnconvertedReason),
},
convertedModulePathMap: make(map[string]string),
}
}
// Serialize returns the protoized version of CodegenMetrics: bp2build_metrics_proto.Bp2BuildMetrics
func (metrics *CodegenMetrics) Serialize() *bp2build_metrics_proto.Bp2BuildMetrics {
return metrics.serialized
}
// Print the codegen metrics to stdout.
func (metrics *CodegenMetrics) Print() {
generatedTargetCount := uint64(0)
for _, ruleClass := range android.SortedKeys(metrics.serialized.RuleClassCount) {
count := metrics.serialized.RuleClassCount[ruleClass]
fmt.Printf("[bp2build] %s: %d targets\n", ruleClass, count)
generatedTargetCount += count
}
fmt.Printf(
`[bp2build] Converted %d Android.bp modules to %d total generated BUILD targets. Included %d handcrafted BUILD targets. There are %d total Android.bp modules.
%d converted modules have unconverted deps:
%s
%d converted modules have missing deps:
%s
`,
metrics.serialized.GeneratedModuleCount,
generatedTargetCount,
metrics.serialized.HandCraftedModuleCount,
metrics.TotalModuleCount(),
len(metrics.moduleWithUnconvertedDepsMsgs),
strings.Join(metrics.moduleWithUnconvertedDepsMsgs, "\n\t"),
len(metrics.moduleWithMissingDepsMsgs),
strings.Join(metrics.moduleWithMissingDepsMsgs, "\n\t"),
)
}
const bp2buildMetricsFilename = "bp2build_metrics.pb"
// fail prints $PWD to stderr, followed by the given printf string and args (vals),
// then the given alert, and then exits with 1 for failure
func fail(err error, alertFmt string, vals ...interface{}) {
cwd, wderr := os.Getwd()
if wderr != nil {
cwd = "FAILED TO GET $PWD: " + wderr.Error()
}
fmt.Fprintf(os.Stderr, "\nIn "+cwd+":\n"+alertFmt+"\n"+err.Error()+"\n", vals...)
os.Exit(1)
}
// Write the bp2build-protoized codegen metrics into the given directory
func (metrics *CodegenMetrics) Write(dir string) {
if _, err := os.Stat(dir); os.IsNotExist(err) {
// The metrics dir doesn't already exist, so create it (and parents)
if err := os.MkdirAll(dir, 0755); err != nil { // rx for all; w for user
fail(err, "Failed to `mkdir -p` %s", dir)
}
} else if err != nil {
fail(err, "Failed to `stat` %s", dir)
}
metricsFile := filepath.Join(dir, bp2buildMetricsFilename)
if err := metrics.dump(metricsFile); err != nil {
fail(err, "Error outputting %s", metricsFile)
}
if _, err := os.Stat(metricsFile); err != nil {
if os.IsNotExist(err) {
fail(err, "MISSING BP2BUILD METRICS OUTPUT: %s", metricsFile)
} else {
fail(err, "FAILED TO `stat` BP2BUILD METRICS OUTPUT: %s", metricsFile)
}
}
}
// ReadCodegenMetrics loads CodegenMetrics from `dir`
// returns a nil pointer if the file doesn't exist
func ReadCodegenMetrics(dir string) *CodegenMetrics {
metricsFile := filepath.Join(dir, bp2buildMetricsFilename)
if _, err := os.Stat(metricsFile); err != nil {
if os.IsNotExist(err) {
return nil
} else {
fail(err, "FAILED TO `stat` BP2BUILD METRICS OUTPUT: %s", metricsFile)
panic("unreachable after fail")
}
}
if buf, err := os.ReadFile(metricsFile); err != nil {
fail(err, "FAILED TO READ BP2BUILD METRICS OUTPUT: %s", metricsFile)
panic("unreachable after fail")
} else {
bp2BuildMetrics := bp2build_metrics_proto.Bp2BuildMetrics{
RuleClassCount: make(map[string]uint64),
ConvertedModuleTypeCount: make(map[string]uint64),
TotalModuleTypeCount: make(map[string]uint64),
}
if err := proto.Unmarshal(buf, &bp2BuildMetrics); err != nil {
fail(err, "FAILED TO PARSE BP2BUILD METRICS OUTPUT: %s", metricsFile)
}
return &CodegenMetrics{
serialized: &bp2BuildMetrics,
convertedModulePathMap: make(map[string]string),
}
}
}
func (metrics *CodegenMetrics) IncrementRuleClassCount(ruleClass string) {
metrics.serialized.RuleClassCount[ruleClass] += 1
}
func (metrics *CodegenMetrics) AddEvent(event *bp2build_metrics_proto.Event) {
metrics.serialized.Events = append(metrics.serialized.Events, event)
}
func (metrics *CodegenMetrics) SetSymlinkCount(n uint64) {
if m := metrics.serialized.WorkspaceSymlinkCount; m != 0 {
fmt.Fprintf(os.Stderr, "unexpected non-zero workspaceSymlinkCount of %d", m)
}
metrics.serialized.WorkspaceSymlinkCount = n
}
func (metrics *CodegenMetrics) SetMkDirCount(n uint64) {
if m := metrics.serialized.WorkspaceMkDirCount; m != 0 {
fmt.Fprintf(os.Stderr, "unexpected non-zero workspaceDirCount of %d", m)
}
metrics.serialized.WorkspaceMkDirCount = n
}
func (metrics *CodegenMetrics) TotalModuleCount() uint64 {
return metrics.serialized.HandCraftedModuleCount +
metrics.serialized.GeneratedModuleCount +
metrics.serialized.UnconvertedModuleCount
}
// Dump serializes the metrics to the given filename
func (metrics *CodegenMetrics) dump(filename string) (err error) {
ser := metrics.Serialize()
return shared.Save(ser, filename)
}
type ConversionType int
const (
Generated ConversionType = iota
Handcrafted
)
func (metrics *CodegenMetrics) AddConvertedModule(m blueprint.Module, moduleType string, dir string) {
//a package module has empty name
if moduleType == "package" {
return
}
// Undo prebuilt_ module name prefix modifications
moduleName := android.RemoveOptionalPrebuiltPrefix(m.Name())
metrics.serialized.ConvertedModules = append(metrics.serialized.ConvertedModules, moduleName)
metrics.convertedModuleWithType = append(metrics.convertedModuleWithType, moduleInfo{
moduleName,
moduleType,
})
metrics.convertedModulePathMap[moduleName] = "//" + dir
metrics.serialized.ConvertedModuleTypeCount[moduleType] += 1
metrics.serialized.TotalModuleTypeCount[moduleType] += 1
metrics.serialized.GeneratedModuleCount += 1
}
func (metrics *CodegenMetrics) AddUnconvertedModule(m blueprint.Module, moduleType string, dir string,
reason android.UnconvertedReason) {
//a package module has empty name
if moduleType == "package" {
return
}
// Undo prebuilt_ module name prefix modifications
moduleName := android.RemoveOptionalPrebuiltPrefix(m.Name())
metrics.serialized.UnconvertedModules[moduleName] = &bp2build_metrics_proto.UnconvertedReason{
Type: bp2build_metrics_proto.UnconvertedReasonType(reason.ReasonType),
Detail: reason.Detail,
}
metrics.serialized.UnconvertedModuleCount += 1
metrics.serialized.TotalModuleTypeCount[moduleType] += 1
if reason.ReasonType == int(bp2build_metrics_proto.UnconvertedReasonType_DEFINED_IN_BUILD_FILE) {
metrics.serialized.HandCraftedModuleCount += 1
}
}

View file

@ -1,219 +0,0 @@
// Copyright 2021 Google Inc. All rights reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package bp2build
// to run the benchmarks in this file, you must run go test with the -bench.
// The benchmarked portion will run for the specified time (can be set via -benchtime)
// This can mean if you are benchmarking a faster portion of a larger operation, it will take
// longer.
// If you are seeing a small number of iterations for a specific run, the data is less reliable, to
// run for longer, set -benchtime to a larger value.
import (
"fmt"
"math"
"strings"
"testing"
"android/soong/android"
)
const (
performance_test_dir = "."
)
func genCustomModule(i int, convert bool) string {
var conversionString string
if convert {
conversionString = `bazel_module: { bp2build_available: true },`
}
return fmt.Sprintf(`
custom {
name: "arch_paths_%[1]d",
string_list_prop: ["\t", "\n"],
string_prop: "a\t\n\r",
arch_paths: ["outer", ":outer_dep_%[1]d"],
arch: {
x86: {
arch_paths: ["abc", ":x86_dep_%[1]d"],
},
x86_64: {
arch_paths: ["64bit"],
arch_paths_exclude: ["outer"],
},
},
%[2]s
}
custom {
name: "outer_dep_%[1]d",
%[2]s
}
custom {
name: "x86_dep_%[1]d",
%[2]s
}
`, i, conversionString)
}
func genCustomModuleBp(pctConverted float64) string {
modules := 100
bp := make([]string, 0, modules)
toConvert := int(math.Round(float64(modules) * pctConverted))
for i := 0; i < modules; i++ {
bp = append(bp, genCustomModule(i, i < toConvert))
}
return strings.Join(bp, "\n\n")
}
type testConfig struct {
config android.Config
ctx *android.TestContext
codegenCtx *CodegenContext
}
func (tc testConfig) parse() []error {
_, errs := tc.ctx.ParseFileList(performance_test_dir, []string{"Android.bp"})
return errs
}
func (tc testConfig) resolveDependencies() []error {
_, errs := tc.ctx.ResolveDependencies(tc.config)
return errs
}
func (tc testConfig) convert() {
generateBazelTargetsForDir(tc.codegenCtx, performance_test_dir)
}
func setup(builddir string, tcSize float64) testConfig {
config := android.TestConfig(buildDir, nil, genCustomModuleBp(tcSize), nil)
ctx := android.NewTestContext(config)
registerCustomModuleForBp2buildConversion(ctx)
codegenCtx := NewCodegenContext(config, ctx.Context, Bp2Build, "")
return testConfig{
config,
ctx,
codegenCtx,
}
}
var pctToConvert = []float64{0.0, 0.01, 0.05, 0.10, 0.25, 0.5, 0.75, 1.0}
// This is not intended to test performance, but to verify performance infra continues to work
func TestConvertManyModulesFull(t *testing.T) {
for _, tcSize := range pctToConvert {
t.Run(fmt.Sprintf("pctConverted %f", tcSize), func(t *testing.T) {
testConfig := setup(buildDir, tcSize)
errs := testConfig.parse()
if len(errs) > 0 {
t.Fatalf("Unexpected errors: %s", errs)
}
errs = testConfig.resolveDependencies()
if len(errs) > 0 {
t.Fatalf("Unexpected errors: %s", errs)
}
testConfig.convert()
})
}
}
func BenchmarkManyModulesFull(b *testing.B) {
for _, tcSize := range pctToConvert {
b.Run(fmt.Sprintf("pctConverted %f", tcSize), func(b *testing.B) {
for n := 0; n < b.N; n++ {
b.StopTimer()
testConfig := setup(buildDir, tcSize)
b.StartTimer()
errs := testConfig.parse()
if len(errs) > 0 {
b.Fatalf("Unexpected errors: %s", errs)
}
errs = testConfig.resolveDependencies()
if len(errs) > 0 {
b.Fatalf("Unexpected errors: %s", errs)
}
testConfig.convert()
b.StopTimer()
}
})
}
}
func BenchmarkManyModulesResolveDependencies(b *testing.B) {
for _, tcSize := range pctToConvert {
b.Run(fmt.Sprintf("pctConverted %f", tcSize), func(b *testing.B) {
for n := 0; n < b.N; n++ {
b.StopTimer()
// setup we don't want to measure
testConfig := setup(buildDir, tcSize)
errs := testConfig.parse()
if len(errs) > 0 {
b.Fatalf("Unexpected errors: %s", errs)
}
b.StartTimer()
errs = testConfig.resolveDependencies()
b.StopTimer()
if len(errs) > 0 {
b.Fatalf("Unexpected errors: %s", errs)
}
testConfig.convert()
}
})
}
}
func BenchmarkManyModulesGenerateBazelTargetsForDir(b *testing.B) {
for _, tcSize := range pctToConvert {
b.Run(fmt.Sprintf("pctConverted %f", tcSize), func(b *testing.B) {
for n := 0; n < b.N; n++ {
b.StopTimer()
// setup we don't want to measure
testConfig := setup(buildDir, tcSize)
errs := testConfig.parse()
if len(errs) > 0 {
b.Fatalf("Unexpected errors: %s", errs)
}
errs = testConfig.resolveDependencies()
if len(errs) > 0 {
b.Fatalf("Unexpected errors: %s", errs)
}
b.StartTimer()
testConfig.convert()
b.StopTimer()
}
})
}
}

View file

@ -1,511 +0,0 @@
// Copyright 2022 Google Inc. All rights reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package bp2build
import (
"fmt"
"io/ioutil"
"os"
"path/filepath"
"regexp"
"sort"
"strconv"
"sync"
"sync/atomic"
"android/soong/shared"
"github.com/google/blueprint/pathtools"
)
// A tree structure that describes what to do at each directory in the created
// symlink tree. Currently, it is used to enumerate which files/directories
// should be excluded from symlinking. Each instance of "node" represents a file
// or a directory. If excluded is true, then that file/directory should be
// excluded from symlinking. Otherwise, the node is not excluded, but one of its
// descendants is (otherwise the node in question would not exist)
type instructionsNode struct {
name string
excluded bool // If false, this is just an intermediate node
children map[string]*instructionsNode
}
type symlinkForestContext struct {
verbose bool
topdir string // $TOPDIR
// State
wg sync.WaitGroup
depCh chan string
mkdirCount atomic.Uint64
symlinkCount atomic.Uint64
}
// Ensures that the node for the given path exists in the tree and returns it.
func ensureNodeExists(root *instructionsNode, path string) *instructionsNode {
if path == "" {
return root
}
if path[len(path)-1] == '/' {
path = path[:len(path)-1] // filepath.Split() leaves a trailing slash
}
dir, base := filepath.Split(path)
// First compute the parent node...
dn := ensureNodeExists(root, dir)
// then create the requested node as its direct child, if needed.
if child, ok := dn.children[base]; ok {
return child
} else {
dn.children[base] = &instructionsNode{base, false, make(map[string]*instructionsNode)}
return dn.children[base]
}
}
// Turns a list of paths to be excluded into a tree
func instructionsFromExcludePathList(paths []string) *instructionsNode {
result := &instructionsNode{"", false, make(map[string]*instructionsNode)}
for _, p := range paths {
ensureNodeExists(result, p).excluded = true
}
return result
}
func mergeBuildFiles(output string, srcBuildFile string, generatedBuildFile string, verbose bool) error {
srcBuildFileContent, err := os.ReadFile(srcBuildFile)
if err != nil {
return err
}
generatedBuildFileContent, err := os.ReadFile(generatedBuildFile)
if err != nil {
return err
}
// There can't be a package() call in both the source and generated BUILD files.
// bp2build will generate a package() call for licensing information, but if
// there's no licensing information, it will still generate a package() call
// that just sets default_visibility=public. If the handcrafted build file
// also has a package() call, we'll allow it to override the bp2build
// generated one if it doesn't have any licensing information. If the bp2build
// one has licensing information and the handcrafted one exists, we'll leave
// them both in for bazel to throw an error.
packageRegex := regexp.MustCompile(`(?m)^package\s*\(`)
packageDefaultVisibilityRegex := regexp.MustCompile(`(?m)^package\s*\(\s*default_visibility\s*=\s*\[\s*"//visibility:public",?\s*]\s*\)`)
if packageRegex.Find(srcBuildFileContent) != nil {
if verbose && packageDefaultVisibilityRegex.Find(generatedBuildFileContent) != nil {
fmt.Fprintf(os.Stderr, "Both '%s' and '%s' have a package() target, removing the first one\n",
generatedBuildFile, srcBuildFile)
}
generatedBuildFileContent = packageDefaultVisibilityRegex.ReplaceAll(generatedBuildFileContent, []byte{})
}
newContents := generatedBuildFileContent
if newContents[len(newContents)-1] != '\n' {
newContents = append(newContents, '\n')
}
newContents = append(newContents, srcBuildFileContent...)
// Say you run bp2build 4 times:
// - The first time there's only an Android.bp file. bp2build will convert it to a build file
// under out/soong/bp2build, then symlink from the forest to that generated file
// - Then you add a handcrafted BUILD file in the same directory. bp2build will merge this with
// the generated one, and write the result to the output file in the forest. But the output
// file was a symlink to out/soong/bp2build from the previous step! So we erroneously update
// the file in out/soong/bp2build instead. So far this doesn't cause any problems...
// - You run a 3rd bp2build with no relevant changes. Everything continues to work.
// - You then add a comment to the handcrafted BUILD file. This causes a merge with the
// generated file again. But since we wrote to the generated file in step 2, the generated
// file has an old copy of the handcrafted file in it! This probably causes duplicate bazel
// targets.
// To solve this, if we see that the output file is a symlink from a previous build, remove it.
stat, err := os.Lstat(output)
if err != nil && !os.IsNotExist(err) {
return err
} else if err == nil {
if stat.Mode()&os.ModeSymlink == os.ModeSymlink {
if verbose {
fmt.Fprintf(os.Stderr, "Removing symlink so that we can replace it with a merged file: %s\n", output)
}
err = os.Remove(output)
if err != nil {
return err
}
}
}
return pathtools.WriteFileIfChanged(output, newContents, 0666)
}
// Calls readdir() and returns it as a map from the basename of the files in dir
// to os.FileInfo.
func readdirToMap(dir string) map[string]os.FileInfo {
entryList, err := ioutil.ReadDir(dir)
result := make(map[string]os.FileInfo)
if err != nil {
if os.IsNotExist(err) {
// It's okay if a directory doesn't exist; it just means that one of the
// trees to be merged contains parts the other doesn't
return result
} else {
fmt.Fprintf(os.Stderr, "Cannot readdir '%s': %s\n", dir, err)
os.Exit(1)
}
}
for _, fi := range entryList {
result[fi.Name()] = fi
}
return result
}
// Creates a symbolic link at dst pointing to src
func symlinkIntoForest(topdir, dst, src string) uint64 {
srcPath := shared.JoinPath(topdir, src)
dstPath := shared.JoinPath(topdir, dst)
// Check whether a symlink already exists.
if dstInfo, err := os.Lstat(dstPath); err != nil {
if !os.IsNotExist(err) {
fmt.Fprintf(os.Stderr, "Failed to lstat '%s': %s", dst, err)
os.Exit(1)
}
} else {
if dstInfo.Mode()&os.ModeSymlink != 0 {
// Assume that the link's target is correct, i.e. no manual tampering.
// E.g. OUT_DIR could have been previously used with a different source tree check-out!
return 0
} else {
if err := os.RemoveAll(dstPath); err != nil {
fmt.Fprintf(os.Stderr, "Failed to remove '%s': %s", dst, err)
os.Exit(1)
}
}
}
// Create symlink.
if err := os.Symlink(srcPath, dstPath); err != nil {
fmt.Fprintf(os.Stderr, "Cannot create symlink at '%s' pointing to '%s': %s", dst, src, err)
os.Exit(1)
}
return 1
}
func isDir(path string, fi os.FileInfo) bool {
if (fi.Mode() & os.ModeSymlink) != os.ModeSymlink {
return fi.IsDir()
}
fi2, statErr := os.Stat(path)
if statErr == nil {
return fi2.IsDir()
}
// Check if this is a dangling symlink. If so, treat it like a file, not a dir.
_, lstatErr := os.Lstat(path)
if lstatErr != nil {
fmt.Fprintf(os.Stderr, "Cannot stat or lstat '%s': %s\n%s\n", path, statErr, lstatErr)
os.Exit(1)
}
return false
}
// Returns the mtime of the soong_build binary to determine whether we should
// force symlink_forest to re-execute
func getSoongBuildMTime() (int64, error) {
binaryPath, err := os.Executable()
if err != nil {
return 0, err
}
info, err := os.Stat(binaryPath)
if err != nil {
return 0, err
}
return info.ModTime().UnixMilli(), nil
}
// cleanSymlinkForest will remove the whole symlink forest directory
func cleanSymlinkForest(topdir, forest string) error {
return os.RemoveAll(shared.JoinPath(topdir, forest))
}
// This returns whether symlink forest should clean and replant symlinks.
// It compares the mtime of this executable with the mtime of the last-run
// soong_build binary. If they differ, then we should clean and replant.
func shouldCleanSymlinkForest(topdir string, forest string, soongBuildMTime int64) (bool, error) {
mtimeFilePath := shared.JoinPath(topdir, forest, "soong_build_mtime")
mtimeFileContents, err := os.ReadFile(mtimeFilePath)
if err != nil {
if os.IsNotExist(err) {
// This is likely the first time this has run with this functionality - clean away!
return true, nil
} else {
return false, err
}
}
return strconv.FormatInt(soongBuildMTime, 10) != string(mtimeFileContents), nil
}
func writeSoongBuildMTimeFile(topdir, forest string, mtime int64) error {
mtimeFilePath := shared.JoinPath(topdir, forest, "soong_build_mtime")
contents := []byte(strconv.FormatInt(mtime, 10))
return os.WriteFile(mtimeFilePath, contents, 0666)
}
// Recursively plants a symlink forest at forestDir. The symlink tree will
// contain every file in buildFilesDir and srcDir excluding the files in
// instructions. Collects every directory encountered during the traversal of
// srcDir .
func plantSymlinkForestRecursive(context *symlinkForestContext, instructions *instructionsNode, forestDir string, buildFilesDir string, srcDir string) {
defer context.wg.Done()
if instructions != nil && instructions.excluded {
// Excluded paths are skipped at the level of the non-excluded parent.
fmt.Fprintf(os.Stderr, "may not specify a root-level exclude directory '%s'", srcDir)
os.Exit(1)
}
// We don't add buildFilesDir here because the bp2build files marker files is
// already a dependency which covers it. If we ever wanted to turn this into
// a generic symlink forest creation tool, we'd need to add it, too.
context.depCh <- srcDir
srcDirMap := readdirToMap(shared.JoinPath(context.topdir, srcDir))
buildFilesMap := readdirToMap(shared.JoinPath(context.topdir, buildFilesDir))
renamingBuildFile := false
if _, ok := srcDirMap["BUILD"]; ok {
if _, ok := srcDirMap["BUILD.bazel"]; !ok {
if _, ok := buildFilesMap["BUILD.bazel"]; ok {
renamingBuildFile = true
srcDirMap["BUILD.bazel"] = srcDirMap["BUILD"]
delete(srcDirMap, "BUILD")
if instructions != nil {
if _, ok := instructions.children["BUILD"]; ok {
instructions.children["BUILD.bazel"] = instructions.children["BUILD"]
delete(instructions.children, "BUILD")
}
}
}
}
}
allEntries := make([]string, 0, len(srcDirMap)+len(buildFilesMap))
for n := range srcDirMap {
allEntries = append(allEntries, n)
}
for n := range buildFilesMap {
if _, ok := srcDirMap[n]; !ok {
allEntries = append(allEntries, n)
}
}
// Tests read the error messages generated, so ensure their order is deterministic
sort.Strings(allEntries)
fullForestPath := shared.JoinPath(context.topdir, forestDir)
createForestDir := false
if fi, err := os.Lstat(fullForestPath); err != nil {
if os.IsNotExist(err) {
createForestDir = true
} else {
fmt.Fprintf(os.Stderr, "Could not read info for '%s': %s\n", forestDir, err)
}
} else if fi.Mode()&os.ModeDir == 0 {
if err := os.RemoveAll(fullForestPath); err != nil {
fmt.Fprintf(os.Stderr, "Failed to remove '%s': %s", forestDir, err)
os.Exit(1)
}
createForestDir = true
}
if createForestDir {
if err := os.MkdirAll(fullForestPath, 0777); err != nil {
fmt.Fprintf(os.Stderr, "Could not mkdir '%s': %s\n", forestDir, err)
os.Exit(1)
}
context.mkdirCount.Add(1)
}
// Start with a list of items that already exist in the forest, and remove
// each element as it is processed in allEntries. Any remaining items in
// forestMapForDeletion must be removed. (This handles files which were
// removed since the previous forest generation).
forestMapForDeletion := readdirToMap(shared.JoinPath(context.topdir, forestDir))
for _, f := range allEntries {
if f[0] == '.' {
continue // Ignore dotfiles
}
delete(forestMapForDeletion, f)
// todo add deletionCount metric
// The full paths of children in the input trees and in the output tree
forestChild := shared.JoinPath(forestDir, f)
srcChild := shared.JoinPath(srcDir, f)
if f == "BUILD.bazel" && renamingBuildFile {
srcChild = shared.JoinPath(srcDir, "BUILD")
}
buildFilesChild := shared.JoinPath(buildFilesDir, f)
// Descend in the instruction tree if it exists
var instructionsChild *instructionsNode
if instructions != nil {
instructionsChild = instructions.children[f]
}
srcChildEntry, sExists := srcDirMap[f]
buildFilesChildEntry, bExists := buildFilesMap[f]
if instructionsChild != nil && instructionsChild.excluded {
if bExists {
context.symlinkCount.Add(symlinkIntoForest(context.topdir, forestChild, buildFilesChild))
}
continue
}
sDir := sExists && isDir(shared.JoinPath(context.topdir, srcChild), srcChildEntry)
bDir := bExists && isDir(shared.JoinPath(context.topdir, buildFilesChild), buildFilesChildEntry)
if !sExists {
if bDir && instructionsChild != nil {
// Not in the source tree, but we have to exclude something from under
// this subtree, so descend
context.wg.Add(1)
go plantSymlinkForestRecursive(context, instructionsChild, forestChild, buildFilesChild, srcChild)
} else {
// Not in the source tree, symlink BUILD file
context.symlinkCount.Add(symlinkIntoForest(context.topdir, forestChild, buildFilesChild))
}
} else if !bExists {
if sDir && instructionsChild != nil {
// Not in the build file tree, but we have to exclude something from
// under this subtree, so descend
context.wg.Add(1)
go plantSymlinkForestRecursive(context, instructionsChild, forestChild, buildFilesChild, srcChild)
} else {
// Not in the build file tree, symlink source tree, carry on
context.symlinkCount.Add(symlinkIntoForest(context.topdir, forestChild, srcChild))
}
} else if sDir && bDir {
// Both are directories. Descend.
context.wg.Add(1)
go plantSymlinkForestRecursive(context, instructionsChild, forestChild, buildFilesChild, srcChild)
} else if !sDir && !bDir {
// Neither is a directory. Merge them.
srcBuildFile := shared.JoinPath(context.topdir, srcChild)
generatedBuildFile := shared.JoinPath(context.topdir, buildFilesChild)
// The Android.bp file that codegen used to produce `buildFilesChild` is
// already a dependency, we can ignore `buildFilesChild`.
context.depCh <- srcChild
if err := mergeBuildFiles(shared.JoinPath(context.topdir, forestChild), srcBuildFile, generatedBuildFile, context.verbose); err != nil {
fmt.Fprintf(os.Stderr, "Error merging %s and %s: %s",
srcBuildFile, generatedBuildFile, err)
os.Exit(1)
}
} else {
// Both exist and one is a file. This is an error.
fmt.Fprintf(os.Stderr,
"Conflict in workspace symlink tree creation: both '%s' and '%s' exist and exactly one is a directory\n",
srcChild, buildFilesChild)
os.Exit(1)
}
}
// Remove all files in the forest that exist in neither the source
// tree nor the build files tree. (This handles files which were removed
// since the previous forest generation).
for f := range forestMapForDeletion {
var instructionsChild *instructionsNode
if instructions != nil {
instructionsChild = instructions.children[f]
}
if instructionsChild != nil && instructionsChild.excluded {
// This directory may be excluded because bazel writes to it under the
// forest root. Thus this path is intentionally left alone.
continue
}
forestChild := shared.JoinPath(context.topdir, forestDir, f)
if err := os.RemoveAll(forestChild); err != nil {
fmt.Fprintf(os.Stderr, "Failed to remove '%s/%s': %s", forestDir, f, err)
os.Exit(1)
}
}
}
// PlantSymlinkForest Creates a symlink forest by merging the directory tree at "buildFiles" and
// "srcDir" while excluding paths listed in "exclude". Returns the set of paths
// under srcDir on which readdir() had to be called to produce the symlink
// forest.
func PlantSymlinkForest(verbose bool, topdir string, forest string, buildFiles string, exclude []string) (deps []string, mkdirCount, symlinkCount uint64) {
context := &symlinkForestContext{
verbose: verbose,
topdir: topdir,
depCh: make(chan string),
mkdirCount: atomic.Uint64{},
symlinkCount: atomic.Uint64{},
}
// Check whether soong_build has been modified since the last run
soongBuildMTime, err := getSoongBuildMTime()
if err != nil {
fmt.Fprintln(os.Stderr, err)
os.Exit(1)
}
shouldClean, err := shouldCleanSymlinkForest(topdir, forest, soongBuildMTime)
if err != nil {
fmt.Fprintln(os.Stderr, err)
os.Exit(1)
} else if shouldClean {
err = cleanSymlinkForest(topdir, forest)
if err != nil {
fmt.Fprintln(os.Stderr, err)
os.Exit(1)
}
}
instructions := instructionsFromExcludePathList(exclude)
go func() {
context.wg.Add(1)
plantSymlinkForestRecursive(context, instructions, forest, buildFiles, ".")
context.wg.Wait()
close(context.depCh)
}()
for dep := range context.depCh {
deps = append(deps, dep)
}
err = writeSoongBuildMTimeFile(topdir, forest, soongBuildMTime)
if err != nil {
fmt.Fprintln(os.Stderr, err)
os.Exit(1)
}
return deps, context.mkdirCount.Load(), context.symlinkCount.Load()
}

View file

@ -1,785 +0,0 @@
// Copyright 2021 Google Inc. All rights reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package bp2build
/*
For shareable/common bp2build testing functionality and dumping ground for
specific-but-shared functionality among tests in package
*/
import (
"fmt"
"path/filepath"
"regexp"
"sort"
"strings"
"testing"
"android/soong/ui/metrics/bp2build_metrics_proto"
"github.com/google/blueprint/proptools"
"android/soong/android"
"android/soong/android/allowlists"
"android/soong/bazel"
)
var (
buildDir string
)
var labelRegex = regexp.MustCompile(`^//([^: ]+):([^ ]+)$`)
var simpleModuleNameRegex = regexp.MustCompile(`^[^: /]+$`)
func checkError(t *testing.T, errs []error, expectedErr error) bool {
t.Helper()
if len(errs) != 1 {
return false
}
if strings.Contains(errs[0].Error(), expectedErr.Error()) {
return true
}
return false
}
func errored(t *testing.T, tc Bp2buildTestCase, errs []error) bool {
t.Helper()
if tc.ExpectedErr != nil {
// Rely on checkErrors, as this test case is expected to have an error.
return false
}
if len(errs) > 0 {
for _, err := range errs {
t.Errorf("%s: %s", tc.Description, err)
}
return true
}
// All good, continue execution.
return false
}
func RunBp2BuildTestCaseSimple(t *testing.T, tc Bp2buildTestCase) {
t.Helper()
RunBp2BuildTestCase(t, func(ctx android.RegistrationContext) {}, tc)
}
type Bp2buildTestCase struct {
Description string
ModuleTypeUnderTest string
ModuleTypeUnderTestFactory android.ModuleFactory
// Text to add to the toplevel, root Android.bp file. If Dir is not set, all
// ExpectedBazelTargets are assumed to be generated by this file.
Blueprint string
// ExpectedBazelTargets compares the BazelTargets generated in `Dir` (if not empty).
// Otherwise, it checks the BazelTargets generated by `Blueprint` in the root directory.
ExpectedBazelTargets []string
// ExpectedConvertedModules asserts that modules in this list are labeled as "converted
// by bp2build" in the metrics reported by bp2build.
ExpectedConvertedModules []string
// ExpectedHandcraftedModules asserts that modules in this list are labeled as "handcrafted
// in build files" in the metrics reported by bp2build. Such modules are either explicitly
// defined in a BUILD file (by name), or registered as "otherwise implicitly handled"
// by bp2build (for example, by macros owned by other modules).
ExpectedHandcraftedModules []string
// AlreadyExistingBuildContents, if non-empty, simulates an already-present source BUILD file
// in the directory under test. The BUILD file has the given contents. This BUILD file
// will also be treated as "BUILD file to keep" by the simulated bp2build environment.
AlreadyExistingBuildContents string
// StubbedBuildDefinitions, if non-empty, adds stub definitions to already-present source
// BUILD files for each bazel label given. The BUILD files with these stub definitions
// are added to the BUILD file given in AlreadyExistingBuildContents.
// Labels may be of the form //pkg/to:target_name (which would be defined in pkg/to/BUILD.bazel)
// or `target_name` (which would be defined in ./BUILD.bazel).
StubbedBuildDefinitions []string
Filesystem map[string]string
// Dir sets the directory which will be compared against the targets in ExpectedBazelTargets.
// This should used in conjunction with the Filesystem property to check for targets
// generated from a directory that is not the root.
// If not set, all ExpectedBazelTargets are assumed to be generated by the text in the
// Blueprint property.
Dir string
// An error with a string contained within the string of the expected error
ExpectedErr error
UnconvertedDepsMode unconvertedDepsMode
// For every directory listed here, the BUILD file for that directory will
// be merged with the generated BUILD file. This allows custom BUILD targets
// to be used in tests, or use BUILD files to draw package boundaries.
KeepBuildFileForDirs []string
// An extra FixturePreparer to use when running the test. If you need multiple extra
// FixturePreparers, use android.GroupFixturePreparers()
ExtraFixturePreparer android.FixturePreparer
// If bp2build_product_config.go should run as part of the test.
RunBp2buildProductConfig bool
}
func RunBp2BuildTestCase(t *testing.T, registerModuleTypes func(ctx android.RegistrationContext), tc Bp2buildTestCase) {
t.Helper()
preparers := []android.FixturePreparer{
android.FixtureRegisterWithContext(registerModuleTypes),
}
if tc.ExtraFixturePreparer != nil {
preparers = append(preparers, tc.ExtraFixturePreparer)
}
preparers = append(preparers, android.FixtureSetTestRunner(&bazelTestRunner{generateProductConfigTargets: tc.RunBp2buildProductConfig}))
bp2buildSetup := android.GroupFixturePreparers(
preparers...,
)
runBp2BuildTestCaseWithSetup(t, bp2buildSetup, tc)
}
func runBp2BuildTestCaseWithSetup(t *testing.T, extraPreparer android.FixturePreparer, tc Bp2buildTestCase) {
t.Helper()
if tc.Filesystem == nil {
tc.Filesystem = map[string]string{}
}
checkDir := "."
if tc.Dir != "" {
checkDir = tc.Dir
}
keepExistingBuildDirs := tc.KeepBuildFileForDirs
buildFilesToParse := []string{}
if len(tc.StubbedBuildDefinitions) > 0 {
for _, buildDef := range tc.StubbedBuildDefinitions {
globalLabelMatch := labelRegex.FindStringSubmatch(buildDef)
var dir, targetName string
if len(globalLabelMatch) > 0 {
dir = globalLabelMatch[1]
targetName = globalLabelMatch[2]
} else {
if !simpleModuleNameRegex.MatchString(buildDef) {
t.Errorf("Stubbed build definition '%s' must be either a simple module name or of global target syntax (//foo/bar:baz).", buildDef)
return
}
dir = "."
targetName = buildDef
}
buildFilePath := filepath.Join(dir, "BUILD")
tc.Filesystem[buildFilePath] +=
MakeBazelTarget(
"bp2build_test_stub",
targetName,
AttrNameToString{})
keepExistingBuildDirs = append(keepExistingBuildDirs, dir)
buildFilesToParse = append(buildFilesToParse, buildFilePath)
}
}
if len(tc.AlreadyExistingBuildContents) > 0 {
buildFilePath := filepath.Join(checkDir, "BUILD")
tc.Filesystem[buildFilePath] += tc.AlreadyExistingBuildContents
keepExistingBuildDirs = append(keepExistingBuildDirs, checkDir)
buildFilesToParse = append(buildFilesToParse, buildFilePath)
}
filesystem := make(map[string][]byte)
for f, content := range tc.Filesystem {
filesystem[f] = []byte(content)
}
preparers := []android.FixturePreparer{
extraPreparer,
android.FixtureMergeMockFs(filesystem),
android.FixtureWithRootAndroidBp(tc.Blueprint),
android.FixtureRegisterWithContext(func(ctx android.RegistrationContext) {
ctx.RegisterModuleType(tc.ModuleTypeUnderTest, tc.ModuleTypeUnderTestFactory)
}),
android.FixtureModifyContextWithMockFs(func(ctx *android.TestContext) {
// A default configuration for tests to not have to specify bp2build_available on top level
// targets.
bp2buildConfig := android.NewBp2BuildAllowlist().SetDefaultConfig(
allowlists.Bp2BuildConfig{
android.Bp2BuildTopLevel: allowlists.Bp2BuildDefaultTrueRecursively,
},
)
for _, f := range keepExistingBuildDirs {
bp2buildConfig.SetKeepExistingBuildFile(map[string]bool{
f: /*recursive=*/ false,
})
}
ctx.RegisterBp2BuildConfig(bp2buildConfig)
// This setting is added to bp2build invocations. It prevents bp2build
// from cloning modules to their original state after mutators run. This
// would lose some data intentionally set by these mutators.
ctx.SkipCloneModulesAfterMutators = true
err := ctx.RegisterExistingBazelTargets(".", buildFilesToParse)
if err != nil {
t.Errorf("error parsing build files in test setup: %s", err)
}
}),
android.FixtureModifyEnv(func(env map[string]string) {
if tc.UnconvertedDepsMode == errorModulesUnconvertedDeps {
env["BP2BUILD_ERROR_UNCONVERTED"] = "true"
}
}),
}
preparer := android.GroupFixturePreparers(preparers...)
if tc.ExpectedErr != nil {
pattern := "\\Q" + tc.ExpectedErr.Error() + "\\E"
preparer = preparer.ExtendWithErrorHandler(android.FixtureExpectsOneErrorPattern(pattern))
}
result := preparer.RunTestWithCustomResult(t).(*BazelTestResult)
if len(result.Errs) > 0 {
return
}
expectedTargets := map[string][]string{
checkDir: tc.ExpectedBazelTargets,
}
result.CompareAllBazelTargets(t, tc, expectedTargets, true)
}
// bazelTestRunner customizes the test fixture mechanism to run tests of the bp2build build mode.
type bazelTestRunner struct {
generateProductConfigTargets bool
}
func (b *bazelTestRunner) FinalPreparer(result *android.TestResult) android.CustomTestResult {
ctx := result.TestContext
ctx.RegisterForBazelConversion()
return &BazelTestResult{TestResult: result}
}
func (b *bazelTestRunner) PostParseProcessor(result android.CustomTestResult) {
bazelResult := result.(*BazelTestResult)
ctx := bazelResult.TestContext
config := bazelResult.Config
_, errs := ctx.ResolveDependencies(config)
if bazelResult.CollateErrs(errs) {
return
}
codegenCtx := NewCodegenContext(config, ctx.Context, Bp2Build, "")
res, errs := GenerateBazelTargets(codegenCtx, false)
if bazelResult.CollateErrs(errs) {
return
}
if b.generateProductConfigTargets {
productConfig, err := createProductConfigFiles(codegenCtx, res.moduleNameToPartition, res.metrics.convertedModulePathMap)
if err != nil {
bazelResult.CollateErrs([]error{err})
return
}
for k, v := range productConfig.bp2buildTargets {
res.buildFileToTargets[k] = append(res.buildFileToTargets[k], v...)
}
}
// Store additional data for access by tests.
bazelResult.conversionResults = res
}
// BazelTestResult is a wrapper around android.TestResult to provide type safe access to the bazel
// specific data stored by the bazelTestRunner.
type BazelTestResult struct {
*android.TestResult
// The result returned by the GenerateBazelTargets function.
conversionResults
}
// CompareAllBazelTargets compares the BazelTargets produced by the test for all the directories
// with the supplied set of expected targets.
//
// If ignoreUnexpected=false then this enforces an exact match where every BazelTarget produced must
// have a corresponding expected BazelTarget.
//
// If ignoreUnexpected=true then it will ignore directories for which there are no expected targets.
func (b BazelTestResult) CompareAllBazelTargets(t *testing.T, tc Bp2buildTestCase, expectedTargets map[string][]string, ignoreUnexpected bool) {
t.Helper()
actualTargets := b.buildFileToTargets
// Generate the sorted set of directories to check.
dirsToCheck := android.SortedKeys(expectedTargets)
if !ignoreUnexpected {
// This needs to perform an exact match so add the directories in which targets were
// produced to the list of directories to check.
dirsToCheck = append(dirsToCheck, android.SortedKeys(actualTargets)...)
dirsToCheck = android.SortedUniqueStrings(dirsToCheck)
}
for _, dir := range dirsToCheck {
expected := expectedTargets[dir]
actual := actualTargets[dir]
if expected == nil {
if actual != nil {
t.Errorf("did not expect any bazel modules in %q but found %d", dir, len(actual))
}
} else if actual == nil {
expectedCount := len(expected)
if expectedCount > 0 {
t.Errorf("expected %d bazel modules in %q but did not find any", expectedCount, dir)
}
} else {
b.CompareBazelTargets(t, tc.Description, expected, actual)
}
}
for _, module := range tc.ExpectedConvertedModules {
if _, found := b.metrics.convertedModulePathMap[module]; !found {
t.Errorf("expected %s to be generated by bp2build, but was not. Map of converted modules: %s", module, b.metrics.convertedModulePathMap)
}
}
for _, module := range tc.ExpectedHandcraftedModules {
if reason, found := b.metrics.serialized.UnconvertedModules[module]; !found {
t.Errorf("expected %s to be marked 'unconverted' by bp2build, but was not found. Full list: %s",
module, b.metrics.serialized.UnconvertedModules)
} else {
if reason.Type != bp2build_metrics_proto.UnconvertedReasonType_DEFINED_IN_BUILD_FILE {
t.Errorf("expected %s to be marked 'handcrafted' by bp2build, but was disabled for another reason: %s", module, reason)
}
}
}
}
func (b BazelTestResult) CompareBazelTargets(t *testing.T, description string, expectedContents []string, actualTargets BazelTargets) {
t.Helper()
if actualCount, expectedCount := len(actualTargets), len(expectedContents); actualCount != expectedCount {
t.Errorf("%s: Expected %d bazel target (%s), got %d (%s)",
description, expectedCount, expectedContents, actualCount, actualTargets)
} else {
sort.SliceStable(actualTargets, func(i, j int) bool {
return actualTargets[i].name < actualTargets[j].name
})
sort.SliceStable(expectedContents, func(i, j int) bool {
return getTargetName(expectedContents[i]) < getTargetName(expectedContents[j])
})
for i, actualTarget := range actualTargets {
if w, g := expectedContents[i], actualTarget.content; w != g {
t.Errorf(
"%s[%d]: Expected generated Bazel target to be `%s`, got `%s`",
description, i, w, g)
}
}
}
}
type nestedProps struct {
Nested_prop *string
}
type EmbeddedProps struct {
Embedded_prop *string
}
type OtherEmbeddedProps struct {
Other_embedded_prop *string
}
type customProps struct {
EmbeddedProps
*OtherEmbeddedProps
Bool_prop bool
Bool_ptr_prop *bool
// Ensure that properties tagged `blueprint:mutated` are omitted
Int_prop int `blueprint:"mutated"`
Int64_ptr_prop *int64
String_prop string
String_literal_prop *string `android:"arch_variant"`
String_ptr_prop *string
String_list_prop []string
Nested_props nestedProps
Nested_props_ptr *nestedProps
Arch_paths []string `android:"path,arch_variant"`
Arch_paths_exclude []string `android:"path,arch_variant"`
// Prop used to indicate this conversion should be 1 module -> multiple targets
One_to_many_prop *bool
// Prop used to simulate an unsupported property in bp2build conversion. If this
// is true, this module should be treated as "unconvertible" via bp2build.
Does_not_convert_to_bazel *bool
Api *string // File describing the APIs of this module
Test_config_setting *bool // Used to test generation of config_setting targets
Dir *string // Dir in which the Bazel Target will be created
}
type customModule struct {
android.ModuleBase
android.BazelModuleBase
props customProps
}
// OutputFiles is needed because some instances of this module use dist with a
// tag property which requires the module implements OutputFileProducer.
func (m *customModule) OutputFiles(tag string) (android.Paths, error) {
return android.PathsForTesting("path" + tag), nil
}
func (m *customModule) GenerateAndroidBuildActions(ctx android.ModuleContext) {
// nothing for now.
}
func customModuleFactoryBase() android.Module {
module := &customModule{}
module.AddProperties(&module.props)
android.InitBazelModule(module)
return module
}
func customModuleFactoryHostAndDevice() android.Module {
m := customModuleFactoryBase()
android.InitAndroidArchModule(m, android.HostAndDeviceSupported, android.MultilibBoth)
return m
}
func customModuleFactoryDeviceSupported() android.Module {
m := customModuleFactoryBase()
android.InitAndroidArchModule(m, android.DeviceSupported, android.MultilibBoth)
return m
}
func customModuleFactoryHostSupported() android.Module {
m := customModuleFactoryBase()
android.InitAndroidArchModule(m, android.HostSupported, android.MultilibBoth)
return m
}
func customModuleFactoryHostAndDeviceDefault() android.Module {
m := customModuleFactoryBase()
android.InitAndroidArchModule(m, android.HostAndDeviceDefault, android.MultilibBoth)
return m
}
func customModuleFactoryNeitherHostNorDeviceSupported() android.Module {
m := customModuleFactoryBase()
android.InitAndroidArchModule(m, android.NeitherHostNorDeviceSupported, android.MultilibBoth)
return m
}
type testProps struct {
Test_prop struct {
Test_string_prop string
}
}
type customTestModule struct {
android.ModuleBase
props customProps
test_props testProps
}
func (m *customTestModule) GenerateAndroidBuildActions(ctx android.ModuleContext) {
// nothing for now.
}
func customTestModuleFactoryBase() android.Module {
m := &customTestModule{}
m.AddProperties(&m.props)
m.AddProperties(&m.test_props)
return m
}
func customTestModuleFactory() android.Module {
m := customTestModuleFactoryBase()
android.InitAndroidModule(m)
return m
}
type customDefaultsModule struct {
android.ModuleBase
android.DefaultsModuleBase
}
func customDefaultsModuleFactoryBase() android.DefaultsModule {
module := &customDefaultsModule{}
module.AddProperties(&customProps{})
return module
}
func customDefaultsModuleFactoryBasic() android.Module {
return customDefaultsModuleFactoryBase()
}
func customDefaultsModuleFactory() android.Module {
m := customDefaultsModuleFactoryBase()
android.InitDefaultsModule(m)
return m
}
type EmbeddedAttr struct {
Embedded_attr *string
}
type OtherEmbeddedAttr struct {
Other_embedded_attr *string
}
type customBazelModuleAttributes struct {
EmbeddedAttr
*OtherEmbeddedAttr
String_literal_prop bazel.StringAttribute
String_ptr_prop *string
String_list_prop []string
Arch_paths bazel.LabelListAttribute
Api bazel.LabelAttribute
}
func (m *customModule) dir() *string {
return m.props.Dir
}
func (m *customModule) ConvertWithBp2build(ctx android.Bp2buildMutatorContext) {
if p := m.props.Does_not_convert_to_bazel; p != nil && *p {
ctx.MarkBp2buildUnconvertible(bp2build_metrics_proto.UnconvertedReasonType_PROPERTY_UNSUPPORTED, "")
return
}
if p := m.props.One_to_many_prop; p != nil && *p {
customBp2buildOneToMany(ctx, m)
return
}
paths := bazel.LabelListAttribute{}
strAttr := bazel.StringAttribute{}
for axis, configToProps := range m.GetArchVariantProperties(ctx, &customProps{}) {
for config, props := range configToProps {
if custProps, ok := props.(*customProps); ok {
if custProps.Arch_paths != nil {
paths.SetSelectValue(axis, config, android.BazelLabelForModuleSrcExcludes(ctx, custProps.Arch_paths, custProps.Arch_paths_exclude))
}
if custProps.String_literal_prop != nil {
strAttr.SetSelectValue(axis, config, custProps.String_literal_prop)
}
}
}
}
productVariableProps, errs := android.ProductVariableProperties(ctx, ctx.Module())
for _, err := range errs {
ctx.ModuleErrorf("ProductVariableProperties error: %s", err)
}
if props, ok := productVariableProps["String_literal_prop"]; ok {
for c, p := range props {
if val, ok := p.(*string); ok {
strAttr.SetSelectValue(c.ConfigurationAxis(), c.SelectKey(), val)
}
}
}
paths.ResolveExcludes()
attrs := &customBazelModuleAttributes{
String_literal_prop: strAttr,
String_ptr_prop: m.props.String_ptr_prop,
String_list_prop: m.props.String_list_prop,
Arch_paths: paths,
}
attrs.Embedded_attr = m.props.Embedded_prop
if m.props.OtherEmbeddedProps != nil {
attrs.OtherEmbeddedAttr = &OtherEmbeddedAttr{Other_embedded_attr: m.props.OtherEmbeddedProps.Other_embedded_prop}
}
props := bazel.BazelTargetModuleProperties{
Rule_class: "custom",
}
ctx.CreateBazelTargetModule(props, android.CommonAttributes{Name: m.Name(), Dir: m.dir()}, attrs)
if proptools.Bool(m.props.Test_config_setting) {
m.createConfigSetting(ctx)
}
}
func (m *customModule) createConfigSetting(ctx android.Bp2buildMutatorContext) {
csa := bazel.ConfigSettingAttributes{
Flag_values: bazel.StringMapAttribute{
"//build/bazel/rules/my_string_setting": m.Name(),
},
}
ca := android.CommonAttributes{
Name: m.Name() + "_config_setting",
}
ctx.CreateBazelConfigSetting(
csa,
ca,
ctx.ModuleDir(),
)
}
// A bp2build mutator that uses load statements and creates a 1:M mapping from
// module to target.
func customBp2buildOneToMany(ctx android.Bp2buildMutatorContext, m *customModule) {
baseName := m.Name()
attrs := &customBazelModuleAttributes{}
myLibraryProps := bazel.BazelTargetModuleProperties{
Rule_class: "my_library",
Bzl_load_location: "//build/bazel/rules:rules.bzl",
}
ctx.CreateBazelTargetModule(myLibraryProps, android.CommonAttributes{Name: baseName}, attrs)
protoLibraryProps := bazel.BazelTargetModuleProperties{
Rule_class: "proto_library",
Bzl_load_location: "//build/bazel/rules:proto.bzl",
}
ctx.CreateBazelTargetModule(protoLibraryProps, android.CommonAttributes{Name: baseName + "_proto_library_deps"}, attrs)
myProtoLibraryProps := bazel.BazelTargetModuleProperties{
Rule_class: "my_proto_library",
Bzl_load_location: "//build/bazel/rules:proto.bzl",
}
ctx.CreateBazelTargetModule(myProtoLibraryProps, android.CommonAttributes{Name: baseName + "_my_proto_library_deps"}, attrs)
}
// Helper method for tests to easily access the targets in a dir.
func generateBazelTargetsForDir(codegenCtx *CodegenContext, dir string) (BazelTargets, []error) {
// TODO: Set generateFilegroups to true and/or remove the generateFilegroups argument completely
res, err := GenerateBazelTargets(codegenCtx, false)
if err != nil {
return BazelTargets{}, err
}
return res.buildFileToTargets[dir], err
}
func registerCustomModuleForBp2buildConversion(ctx *android.TestContext) {
ctx.RegisterModuleType("custom", customModuleFactoryHostAndDevice)
ctx.RegisterForBazelConversion()
}
func simpleModule(typ, name string) string {
return fmt.Sprintf(`
%s {
name: "%s",
}`, typ, name)
}
type AttrNameToString map[string]string
func (a AttrNameToString) clone() AttrNameToString {
newAttrs := make(AttrNameToString, len(a))
for k, v := range a {
newAttrs[k] = v
}
return newAttrs
}
// makeBazelTargetNoRestrictions returns bazel target build file definition that can be host or
// device specific, or independent of host/device.
func makeBazelTargetHostOrDevice(typ, name string, attrs AttrNameToString, hod android.HostOrDeviceSupported) string {
if _, ok := attrs["target_compatible_with"]; !ok {
switch hod {
case android.HostSupported:
attrs["target_compatible_with"] = `select({
"//build/bazel_common_rules/platforms/os:android": ["@platforms//:incompatible"],
"//conditions:default": [],
})`
case android.DeviceSupported:
attrs["target_compatible_with"] = `["//build/bazel_common_rules/platforms/os:android"]`
}
}
attrStrings := make([]string, 0, len(attrs)+1)
if name != "" {
attrStrings = append(attrStrings, fmt.Sprintf(` name = "%s",`, name))
}
for _, k := range android.SortedKeys(attrs) {
attrStrings = append(attrStrings, fmt.Sprintf(" %s = %s,", k, attrs[k]))
}
return fmt.Sprintf(`%s(
%s
)`, typ, strings.Join(attrStrings, "\n"))
}
// MakeBazelTargetNoRestrictions returns bazel target build file definition that does not add a
// target_compatible_with. This is useful for module types like filegroup and genrule that arch not
// arch variant
func MakeBazelTargetNoRestrictions(typ, name string, attrs AttrNameToString) string {
return makeBazelTargetHostOrDevice(typ, name, attrs, android.HostAndDeviceDefault)
}
// makeBazelTargetNoRestrictions returns bazel target build file definition that is device specific
// as this is the most common default in Soong.
func MakeBazelTarget(typ, name string, attrs AttrNameToString) string {
return makeBazelTargetHostOrDevice(typ, name, attrs, android.DeviceSupported)
}
type ExpectedRuleTarget struct {
Rule string
Name string
Attrs AttrNameToString
Hod android.HostOrDeviceSupported
}
func (ebr ExpectedRuleTarget) String() string {
return makeBazelTargetHostOrDevice(ebr.Rule, ebr.Name, ebr.Attrs, ebr.Hod)
}
func makeCcStubSuiteTargets(name string, attrs AttrNameToString) string {
if _, hasStubs := attrs["stubs_symbol_file"]; !hasStubs {
return ""
}
STUB_SUITE_ATTRS := map[string]string{
"api_surface": "api_surface",
"stubs_symbol_file": "symbol_file",
"stubs_versions": "versions",
"soname": "soname",
"source_library_label": "source_library_label",
}
stubSuiteAttrs := AttrNameToString{}
for key, _ := range attrs {
if _, stubSuiteAttr := STUB_SUITE_ATTRS[key]; stubSuiteAttr {
stubSuiteAttrs[STUB_SUITE_ATTRS[key]] = attrs[key]
} else {
panic(fmt.Sprintf("unused cc_stub_suite attr %q\n", key))
}
}
return MakeBazelTarget("cc_stub_suite", name+"_stub_libs", stubSuiteAttrs)
}
func MakeNeverlinkDuplicateTarget(moduleType string, name string) string {
return MakeNeverlinkDuplicateTargetWithAttrs(moduleType, name, AttrNameToString{
"sdk_version": `"current"`, // use as default
})
}
func MakeNeverlinkDuplicateTargetWithAttrs(moduleType string, name string, extraAttrs AttrNameToString) string {
attrs := extraAttrs
attrs["neverlink"] = `True`
attrs["exports"] = `[":` + name + `"]`
return MakeBazelTarget(moduleType, name+"-neverlink", attrs)
}
func getTargetName(targetContent string) string {
data := strings.Split(targetContent, "name = \"")
if len(data) < 2 {
return ""
} else {
endIndex := strings.Index(data[1], "\"")
return data[1][:endIndex]
}
}

View file

@ -28,8 +28,6 @@ import (
"android/soong/android/allowlists"
"android/soong/bp2build"
"android/soong/shared"
"android/soong/ui/metrics/bp2build_metrics_proto"
"github.com/google/blueprint"
"github.com/google/blueprint/bootstrap"
"github.com/google/blueprint/deptools"
@ -74,16 +72,10 @@ func init() {
flag.StringVar(&cmdlineArgs.ModuleActionsFile, "module_actions_file", "", "JSON file to output inputs/outputs of actions of modules")
flag.StringVar(&cmdlineArgs.DocFile, "soong_docs", "", "build documentation file to output")
flag.StringVar(&cmdlineArgs.BazelQueryViewDir, "bazel_queryview_dir", "", "path to the bazel queryview directory relative to --top")
flag.StringVar(&cmdlineArgs.Bp2buildMarker, "bp2build_marker", "", "If set, run bp2build, touch the specified marker file then exit")
flag.StringVar(&cmdlineArgs.SymlinkForestMarker, "symlink_forest_marker", "", "If set, create the bp2build symlink forest, touch the specified marker file, then exit")
flag.StringVar(&cmdlineArgs.OutFile, "o", "build.ninja", "the Ninja file to output")
flag.StringVar(&cmdlineArgs.SoongVariables, "soong_variables", "soong.variables", "the file contains all build variables")
flag.StringVar(&cmdlineArgs.BazelForceEnabledModules, "bazel-force-enabled-modules", "", "additional modules to build with Bazel. Comma-delimited")
flag.BoolVar(&cmdlineArgs.EmptyNinjaFile, "empty-ninja-file", false, "write out a 0-byte ninja file")
flag.BoolVar(&cmdlineArgs.MultitreeBuild, "multitree-build", false, "this is a multitree build")
flag.BoolVar(&cmdlineArgs.BazelMode, "bazel-mode", false, "use bazel for analysis of certain modules")
flag.BoolVar(&cmdlineArgs.BazelModeStaging, "bazel-mode-staging", false, "use bazel for analysis of certain near-ready modules")
flag.BoolVar(&cmdlineArgs.UseBazelProxy, "use-bazel-proxy", false, "communicate with bazel using unix socket proxy instead of spawning subprocesses")
flag.BoolVar(&cmdlineArgs.BuildFromSourceStub, "build-from-source-stub", false, "build Java stubs from source files instead of API text files")
flag.BoolVar(&cmdlineArgs.EnsureAllowlistIntegrity, "ensure-allowlist-integrity", false, "verify that allowlisted modules are mixed-built")
// Flags that probably shouldn't be flags of soong_build, but we haven't found
@ -110,40 +102,6 @@ func newContext(configuration android.Config) *android.Context {
return ctx
}
// Bazel-enabled mode. Attaches a mutator to queue Bazel requests, adds a
// BeforePrepareBuildActionsHook to invoke Bazel, and then uses Bazel metadata
// for modules that should be handled by Bazel.
func runMixedModeBuild(ctx *android.Context, extraNinjaDeps []string) string {
ctx.EventHandler.Begin("mixed_build")
defer ctx.EventHandler.End("mixed_build")
bazelHook := func() error {
err := ctx.Config().BazelContext.QueueBazelSandwichCqueryRequests(ctx.Config())
if err != nil {
return err
}
return ctx.Config().BazelContext.InvokeBazel(ctx.Config(), ctx)
}
ctx.SetBeforePrepareBuildActionsHook(bazelHook)
ninjaDeps, err := bootstrap.RunBlueprint(cmdlineArgs.Args, bootstrap.DoEverything, ctx.Context, ctx.Config())
maybeQuit(err, "")
ninjaDeps = append(ninjaDeps, extraNinjaDeps...)
bazelPaths, err := readFileLines(ctx.Config().Getenv("BAZEL_DEPS_FILE"))
if err != nil {
panic("Bazel deps file not found: " + err.Error())
}
ninjaDeps = append(ninjaDeps, bazelPaths...)
ninjaDeps = append(ninjaDeps, writeBuildGlobsNinjaFile(ctx)...)
writeDepFile(cmdlineArgs.OutFile, ctx.EventHandler, ninjaDeps)
if needToWriteNinjaHint(ctx) {
writeNinjaHint(ctx)
}
return cmdlineArgs.OutFile
}
func needToWriteNinjaHint(ctx *android.Context) bool {
switch ctx.Config().GetenvWithDefault("SOONG_GENERATES_NINJA_HINT", "") {
case "always":
@ -228,67 +186,6 @@ func writeMetrics(configuration android.Config, eventHandler *metrics.EventHandl
maybeQuit(err, "error writing soong_build metrics %s", metricsFile)
}
// Errors out if any modules expected to be mixed_built were not, unless
// the modules did not exist.
func checkForAllowlistIntegrityError(configuration android.Config, isStagingMode bool) error {
modules := findMisconfiguredModules(configuration, isStagingMode)
if len(modules) == 0 {
return nil
}
return fmt.Errorf("Error: expected the following modules to be mixed_built: %s", modules)
}
// Returns true if the given module has all of the following true:
// 1. Is allowlisted to be built with Bazel.
// 2. Has a variant which is *not* built with Bazel.
// 3. Has no variant which is built with Bazel.
//
// This indicates the allowlisting of this variant had no effect.
// TODO(b/280457637): Return true for nonexistent modules.
func isAllowlistMisconfiguredForModule(module string, mixedBuildsEnabled map[string]struct{}, mixedBuildsDisabled map[string]struct{}) bool {
_, enabled := mixedBuildsEnabled[module]
if enabled {
return false
}
_, disabled := mixedBuildsDisabled[module]
return disabled
}
// Returns the list of modules that should have been mixed_built (per the
// allowlists and cmdline flags) but were not.
// Note: nonexistent modules are excluded from the list. See b/280457637
func findMisconfiguredModules(configuration android.Config, isStagingMode bool) []string {
retval := []string{}
forceEnabledModules := configuration.BazelModulesForceEnabledByFlag()
mixedBuildsEnabled := configuration.GetMixedBuildsEnabledModules()
mixedBuildsDisabled := configuration.GetMixedBuildsDisabledModules()
for _, module := range allowlists.ProdMixedBuildsEnabledList {
if isAllowlistMisconfiguredForModule(module, mixedBuildsEnabled, mixedBuildsDisabled) {
retval = append(retval, module)
}
}
if isStagingMode {
for _, module := range allowlists.StagingMixedBuildsEnabledList {
if isAllowlistMisconfiguredForModule(module, mixedBuildsEnabled, mixedBuildsDisabled) {
retval = append(retval, module)
}
}
}
for module, _ := range forceEnabledModules {
if isAllowlistMisconfiguredForModule(module, mixedBuildsEnabled, mixedBuildsDisabled) {
retval = append(retval, module)
}
}
return retval
}
func writeJsonModuleGraphAndActions(ctx *android.Context, cmdArgs android.CmdArgs) {
graphFile, graphErr := os.Create(shared.JoinPath(topDir, cmdArgs.ModuleGraphFile))
maybeQuit(graphErr, "graph err")
@ -424,37 +321,9 @@ func main() {
ctx := newContext(configuration)
android.StartBackgroundMetrics(configuration)
var finalOutputFile string
// Run Soong for a specific activity, like bp2build, queryview
// or the actual Soong build for the build.ninja file.
switch configuration.BuildMode {
case android.SymlinkForest:
finalOutputFile = runSymlinkForestCreation(ctx, extraNinjaDeps, metricsDir)
case android.Bp2build:
// Run the alternate pipeline of bp2build mutators and singleton to convert
// Blueprint to BUILD files before everything else.
finalOutputFile = runBp2Build(ctx, extraNinjaDeps, metricsDir)
default:
ctx.Register()
isMixedBuildsEnabled := configuration.IsMixedBuildsEnabled()
if isMixedBuildsEnabled {
finalOutputFile = runMixedModeBuild(ctx, extraNinjaDeps)
if cmdlineArgs.EnsureAllowlistIntegrity {
if err := checkForAllowlistIntegrityError(configuration, cmdlineArgs.BazelModeStaging); err != nil {
maybeQuit(err, "")
}
}
} else {
finalOutputFile = runSoongOnlyBuild(ctx, extraNinjaDeps)
}
writeMetrics(configuration, ctx.EventHandler, metricsDir)
}
// Register this environment variablesas being an implicit dependencies of
// soong_build. Changes to this environment variable will result in
// retriggering soong_build.
configuration.Getenv("USE_BAZEL_VERSION")
ctx.Register()
finalOutputFile := runSoongOnlyBuild(ctx, extraNinjaDeps)
writeMetrics(configuration, ctx.EventHandler, metricsDir)
writeUsedEnvironmentFile(configuration)
@ -497,213 +366,6 @@ func touch(path string) {
maybeQuit(err, "error touching '%s'", path)
}
// Read the bazel.list file that the Soong Finder already dumped earlier (hopefully)
// It contains the locations of BUILD files, BUILD.bazel files, etc. in the source dir
func getExistingBazelRelatedFiles(topDir string) ([]string, error) {
bazelFinderFile := filepath.Join(filepath.Dir(cmdlineArgs.ModuleListFile), "bazel.list")
if !filepath.IsAbs(bazelFinderFile) {
// Assume this was a relative path under topDir
bazelFinderFile = filepath.Join(topDir, bazelFinderFile)
}
return readFileLines(bazelFinderFile)
}
func bazelArtifacts() []string {
return []string{
"bazel-bin",
"bazel-genfiles",
"bazel-out",
"bazel-testlogs",
"bazel-workspace",
"bazel-" + filepath.Base(topDir),
}
}
// This could in theory easily be separated into a binary that generically
// merges two directories into a symlink tree. The main obstacle is that this
// function currently depends on both Bazel-specific knowledge (the existence
// of bazel-* symlinks) and configuration (the set of BUILD.bazel files that
// should and should not be kept)
//
// Ideally, bp2build would write a file that contains instructions to the
// symlink tree creation binary. Then the latter would not need to depend on
// the very heavy-weight machinery of soong_build .
func runSymlinkForestCreation(ctx *android.Context, extraNinjaDeps []string, metricsDir string) string {
var ninjaDeps []string
var mkdirCount, symlinkCount uint64
ctx.EventHandler.Do("symlink_forest", func() {
ninjaDeps = append(ninjaDeps, extraNinjaDeps...)
verbose := ctx.Config().IsEnvTrue("BP2BUILD_VERBOSE")
// PlantSymlinkForest() returns all the directories that were readdir()'ed.
// Such a directory SHOULD be added to `ninjaDeps` so that a child directory
// or file created/deleted under it would trigger an update of the symlink forest.
generatedRoot := shared.JoinPath(ctx.Config().SoongOutDir(), "bp2build")
workspaceRoot := shared.JoinPath(ctx.Config().SoongOutDir(), "workspace")
var symlinkForestDeps []string
ctx.EventHandler.Do("plant", func() {
symlinkForestDeps, mkdirCount, symlinkCount = bp2build.PlantSymlinkForest(
verbose, topDir, workspaceRoot, generatedRoot, excludedFromSymlinkForest(ctx, verbose))
})
ninjaDeps = append(ninjaDeps, symlinkForestDeps...)
})
writeDepFile(cmdlineArgs.SymlinkForestMarker, ctx.EventHandler, ninjaDeps)
touch(shared.JoinPath(topDir, cmdlineArgs.SymlinkForestMarker))
codegenMetrics := bp2build.ReadCodegenMetrics(metricsDir)
if codegenMetrics == nil {
m := bp2build.CreateCodegenMetrics()
codegenMetrics = &m
} else {
//TODO (usta) we cannot determine if we loaded a stale file, i.e. from an unrelated prior
//invocation of codegen. We should simply use a separate .pb file
}
codegenMetrics.SetSymlinkCount(symlinkCount)
codegenMetrics.SetMkDirCount(mkdirCount)
writeBp2BuildMetrics(codegenMetrics, ctx.EventHandler, metricsDir)
return cmdlineArgs.SymlinkForestMarker
}
func excludedFromSymlinkForest(ctx *android.Context, verbose bool) []string {
excluded := bazelArtifacts()
if cmdlineArgs.OutDir[0] != '/' {
excluded = append(excluded, cmdlineArgs.OutDir)
}
// Find BUILD files in the srcDir which are not in the allowlist
// (android.Bp2BuildConversionAllowlist#ShouldKeepExistingBuildFileForDir)
// and return their paths so they can be left out of the Bazel workspace dir (i.e. ignored)
existingBazelFiles, err := getExistingBazelRelatedFiles(topDir)
maybeQuit(err, "Error determining existing Bazel-related files")
for _, path := range existingBazelFiles {
fullPath := shared.JoinPath(topDir, path)
fileInfo, err2 := os.Stat(fullPath)
if err2 != nil {
// Warn about error, but continue trying to check files
fmt.Fprintf(os.Stderr, "WARNING: Error accessing path '%s', err: %s\n", fullPath, err2)
continue
}
// Exclude only files named 'BUILD' or 'BUILD.bazel' and unless forcibly kept
if fileInfo.IsDir() ||
(fileInfo.Name() != "BUILD" && fileInfo.Name() != "BUILD.bazel") ||
ctx.Config().Bp2buildPackageConfig.ShouldKeepExistingBuildFileForDir(filepath.Dir(path)) {
// Don't ignore this existing build file
continue
}
if verbose {
fmt.Fprintf(os.Stderr, "Ignoring existing BUILD file: %s\n", path)
}
excluded = append(excluded, path)
}
// Temporarily exclude stuff to make `bazel build //external/...` (and `bazel build //frameworks/...`) work
excluded = append(excluded,
// FIXME: 'autotest_lib' is a symlink back to external/autotest, and this causes an infinite
// symlink expansion error for Bazel
"external/autotest/venv/autotest_lib",
"external/autotest/autotest_lib",
"external/autotest/client/autotest_lib/client",
// FIXME: The external/google-fruit/extras/bazel_root/third_party/fruit dir is poison
// It contains several symlinks back to real source dirs, and those source dirs contain
// BUILD files we want to ignore
"external/google-fruit/extras/bazel_root/third_party/fruit",
// FIXME: 'frameworks/compile/slang' has a filegroup error due to an escaping issue
"frameworks/compile/slang",
)
return excluded
}
// Run Soong in the bp2build mode. This creates a standalone context that registers
// an alternate pipeline of mutators and singletons specifically for generating
// Bazel BUILD files instead of Ninja files.
func runBp2Build(ctx *android.Context, extraNinjaDeps []string, metricsDir string) string {
var codegenMetrics *bp2build.CodegenMetrics
ctx.EventHandler.Do("bp2build", func() {
ctx.EventHandler.Do("read_build", func() {
existingBazelFiles, err := getExistingBazelRelatedFiles(topDir)
maybeQuit(err, "Error determining existing Bazel-related files")
err = ctx.RegisterExistingBazelTargets(topDir, existingBazelFiles)
maybeQuit(err, "Error parsing existing Bazel-related files")
})
// Propagate "allow misssing dependencies" bit. This is normally set in
// newContext(), but we create ctx without calling that method.
ctx.SetAllowMissingDependencies(ctx.Config().AllowMissingDependencies())
ctx.SetNameInterface(newNameResolver(ctx.Config()))
ctx.RegisterForBazelConversion()
ctx.SetModuleListFile(cmdlineArgs.ModuleListFile)
// Skip cloning modules during bp2build's blueprint run. Some mutators set
// bp2build-related module values which should be preserved during codegen.
ctx.SkipCloneModulesAfterMutators = true
var ninjaDeps []string
ninjaDeps = append(ninjaDeps, extraNinjaDeps...)
// Run the loading and analysis pipeline to prepare the graph of regular
// Modules parsed from Android.bp files, and the BazelTargetModules mapped
// from the regular Modules.
ctx.EventHandler.Do("bootstrap", func() {
blueprintArgs := cmdlineArgs
bootstrapDeps, err := bootstrap.RunBlueprint(blueprintArgs.Args,
bootstrap.StopBeforePrepareBuildActions, ctx.Context, ctx.Config())
maybeQuit(err, "")
ninjaDeps = append(ninjaDeps, bootstrapDeps...)
})
globListFiles := writeBuildGlobsNinjaFile(ctx)
ninjaDeps = append(ninjaDeps, globListFiles...)
// Run the code-generation phase to convert BazelTargetModules to BUILD files
// and print conversion codegenMetrics to the user.
codegenContext := bp2build.NewCodegenContext(ctx.Config(), ctx, bp2build.Bp2Build, topDir)
codegenMetrics = bp2build.Codegen(codegenContext)
ninjaDeps = append(ninjaDeps, codegenContext.AdditionalNinjaDeps()...)
writeDepFile(cmdlineArgs.Bp2buildMarker, ctx.EventHandler, ninjaDeps)
touch(shared.JoinPath(topDir, cmdlineArgs.Bp2buildMarker))
})
// Only report metrics when in bp2build mode. The metrics aren't relevant
// for queryview, since that's a total repo-wide conversion and there's a
// 1:1 mapping for each module.
if ctx.Config().IsEnvTrue("BP2BUILD_VERBOSE") {
codegenMetrics.Print()
}
writeBp2BuildMetrics(codegenMetrics, ctx.EventHandler, metricsDir)
return cmdlineArgs.Bp2buildMarker
}
// Write Bp2Build metrics into $LOG_DIR
func writeBp2BuildMetrics(codegenMetrics *bp2build.CodegenMetrics, eventHandler *metrics.EventHandler, metricsDir string) {
for _, event := range eventHandler.CompletedEvents() {
codegenMetrics.AddEvent(&bp2build_metrics_proto.Event{
Name: event.Id,
StartTime: uint64(event.Start.UnixNano()),
RealTime: event.RuntimeNanoseconds(),
})
}
if len(metricsDir) < 1 {
fmt.Fprintf(os.Stderr, "\nMissing required env var for generating bp2build metrics: LOG_DIR\n")
os.Exit(1)
}
codegenMetrics.Write(metricsDir)
}
func readFileLines(path string) ([]string, error) {
data, err := os.ReadFile(path)
if err == nil {
return strings.Split(strings.TrimSpace(string(data)), "\n"), nil
}
return nil, err
}
func maybeQuit(err error, format string, args ...interface{}) {
if err == nil {
return