Refactor mixed builds to only take one pass
This large refactoring has both immense performance implications and
improves mixed builds complexity / usability. Summary:
1. Queueing calls to Bazel is done in a new mutator instead of a full
soong_build pass. Normal soong_build flow is interrupted (via a
functional hook in blueprint) to invoke bazel and parse its response.
2. Implementing mixed build support for additional modules is as simple
as implementing MixedBuildsBuildable. In this interface, define the
request that must be queued to Bazel, and then subsequently define
how to handle the returned bazel cquery metadata.
3. Mixed builds consists of only a single pass. This greatly
improves mixed build performance.
Result:
A 33% runtime improvement on soong analysis phase with mixed builds.
Caveats:
C++ BazelHandler handling still remains a bit of a mess; I did what
I could within this CL's scope, but this may require additional cleanup.
Test: Treehugger
Test: Verified that aosp_arm ninja file is bit-for-bit identical with or
without this change.
Change-Id: I412d9c94d429105f4ebfafc84100d546069e6621
diff --git a/cc/binary.go b/cc/binary.go
index c5017c1..7b5591a 100644
--- a/cc/binary.go
+++ b/cc/binary.go
@@ -17,6 +17,7 @@
import (
"path/filepath"
+ "android/soong/bazel/cquery"
"github.com/google/blueprint"
"github.com/google/blueprint/proptools"
@@ -562,25 +563,32 @@
}
type ccBinaryBazelHandler struct {
- android.BazelHandler
+ BazelHandler
module *Module
}
-func (handler *ccBinaryBazelHandler) GenerateBazelBuildActions(ctx android.ModuleContext, label string) bool {
+func (handler *ccBinaryBazelHandler) QueueBazelCall(ctx android.BaseModuleContext, label string) {
bazelCtx := ctx.Config().BazelContext
- filePaths, ok := bazelCtx.GetOutputFiles(label, android.GetConfigKey(ctx))
- if ok {
- if len(filePaths) != 1 {
- ctx.ModuleErrorf("expected exactly one output file for '%s', but got %s", label, filePaths)
- return false
- }
- outputFilePath := android.PathForBazelOut(ctx, filePaths[0])
- handler.module.outputFile = android.OptionalPathForPath(outputFilePath)
- // TODO(b/220164721): We need to decide if we should return the stripped as the unstripped.
- handler.module.linker.(*binaryDecorator).unstrippedOutputFile = outputFilePath
+ bazelCtx.QueueBazelRequest(label, cquery.GetOutputFiles, android.GetConfigKey(ctx))
+}
+
+func (handler *ccBinaryBazelHandler) ProcessBazelQueryResponse(ctx android.ModuleContext, label string) {
+ bazelCtx := ctx.Config().BazelContext
+ filePaths, err := bazelCtx.GetOutputFiles(label, android.GetConfigKey(ctx))
+ if err != nil {
+ ctx.ModuleErrorf(err.Error())
+ return
}
- return ok
+
+ if len(filePaths) != 1 {
+ ctx.ModuleErrorf("expected exactly one output file for '%s', but got %s", label, filePaths)
+ return
+ }
+ outputFilePath := android.PathForBazelOut(ctx, filePaths[0])
+ handler.module.outputFile = android.OptionalPathForPath(outputFilePath)
+ // TODO(b/220164721): We need to decide if we should return the stripped as the unstripped.
+ handler.module.linker.(*binaryDecorator).unstrippedOutputFile = outputFilePath
}
func binaryBp2build(ctx android.TopDownMutatorContext, m *Module, typ string) {
diff --git a/cc/cc.go b/cc/cc.go
index 8606920..3d21f63 100644
--- a/cc/cc.go
+++ b/cc/cc.go
@@ -772,6 +772,19 @@
return ok && ccDepTag == testPerSrcDepTag
}
+// bazelHandler is the interface for a helper object related to deferring to Bazel for
+// processing a cc module (during Bazel mixed builds). Individual module types should define
+// their own bazel handler if they support being handled by Bazel.
+type BazelHandler interface {
+ // QueueBazelCall invokes request-queueing functions on the BazelContext
+ //so that these requests are handled when Bazel's cquery is invoked.
+ QueueBazelCall(ctx android.BaseModuleContext, label string)
+
+ // ProcessBazelQueryResponse uses information retrieved from Bazel to set properties
+ // on the current module with given label.
+ ProcessBazelQueryResponse(ctx android.ModuleContext, label string)
+}
+
// Module contains the properties and members used by all C/C++ module types, and implements
// the blueprint.Module interface. It delegates to compiler, linker, and installer interfaces
// to construct the output file. Behavior can be customized with a Customizer, or "decorator",
@@ -811,7 +824,7 @@
compiler compiler
linker linker
installer installer
- bazelHandler android.BazelHandler
+ bazelHandler BazelHandler
features []feature
stl *stl
@@ -1773,31 +1786,51 @@
return subName
}
-// Returns true if Bazel was successfully used for the analysis of this module.
-func (c *Module) maybeGenerateBazelActions(actx android.ModuleContext) bool {
- var bazelModuleLabel string
+var _ android.MixedBuildBuildable = (*Module)(nil)
+
+func (c *Module) getBazelModuleLabel(ctx android.BaseModuleContext) string {
if c.typ() == fullLibrary && c.static() {
// cc_library is a special case in bp2build; two targets are generated -- one for each
// of the shared and static variants. The shared variant keeps the module name, but the
// static variant uses a different suffixed name.
- bazelModuleLabel = bazelLabelForStaticModule(actx, c)
- } else {
- bazelModuleLabel = c.GetBazelLabel(actx, c)
+ return bazelLabelForStaticModule(ctx, c)
+ }
+ return c.GetBazelLabel(ctx, c)
+}
+
+func (c *Module) QueueBazelCall(ctx android.BaseModuleContext) {
+ c.bazelHandler.QueueBazelCall(ctx, c.getBazelModuleLabel(ctx))
+}
+
+func (c *Module) IsMixedBuildSupported(ctx android.BaseModuleContext) bool {
+ return c.bazelHandler != nil
+}
+
+func (c *Module) ProcessBazelQueryResponse(ctx android.ModuleContext) {
+ bazelModuleLabel := c.getBazelModuleLabel(ctx)
+
+ c.bazelHandler.ProcessBazelQueryResponse(ctx, bazelModuleLabel)
+
+ c.Properties.SubName = GetSubnameProperty(ctx, c)
+ apexInfo := ctx.Provider(android.ApexInfoProvider).(android.ApexInfo)
+ if !apexInfo.IsForPlatform() {
+ c.hideApexVariantFromMake = true
}
- bazelActionsUsed := false
- // Mixed builds mode is disabled for modules outside of device OS.
- // TODO(b/200841190): Support non-device OS in mixed builds.
- if android.MixedBuildsEnabled(actx) && c.bazelHandler != nil {
- bazelActionsUsed = c.bazelHandler.GenerateBazelBuildActions(actx, bazelModuleLabel)
+ c.makeLinkType = GetMakeLinkType(ctx, c)
+
+ mctx := &moduleContext{
+ ModuleContext: ctx,
+ moduleContextImpl: moduleContextImpl{
+ mod: c,
+ },
}
- return bazelActionsUsed
+ mctx.ctx = mctx
+
+ c.maybeInstall(mctx, apexInfo)
}
func (c *Module) GenerateAndroidBuildActions(actx android.ModuleContext) {
- // TODO(cparsons): Any logic in this method occurring prior to querying Bazel should be
- // requested from Bazel instead.
-
// Handle the case of a test module split by `test_per_src` mutator.
//
// The `test_per_src` mutator adds an extra variation named "", depending on all the other
@@ -1824,11 +1857,6 @@
}
ctx.ctx = ctx
- if c.maybeGenerateBazelActions(actx) {
- c.maybeInstall(ctx, apexInfo)
- return
- }
-
deps := c.depsToPaths(ctx)
if ctx.Failed() {
return
diff --git a/cc/library.go b/cc/library.go
index fdbbccb..5746529 100644
--- a/cc/library.go
+++ b/cc/library.go
@@ -642,18 +642,18 @@
}
type ccLibraryBazelHandler struct {
- android.BazelHandler
+ BazelHandler
module *Module
}
// generateStaticBazelBuildActions constructs the StaticLibraryInfo Soong
// provider from a Bazel shared library's CcInfo provider.
-func (handler *ccLibraryBazelHandler) generateStaticBazelBuildActions(ctx android.ModuleContext, label string, ccInfo cquery.CcInfo) bool {
+func (handler *ccLibraryBazelHandler) generateStaticBazelBuildActions(ctx android.ModuleContext, label string, ccInfo cquery.CcInfo) {
rootStaticArchives := ccInfo.RootStaticArchives
if len(rootStaticArchives) != 1 {
ctx.ModuleErrorf("expected exactly one root archive file for '%s', but got %s", label, rootStaticArchives)
- return false
+ return
}
outputFilePath := android.PathForBazelOut(ctx, rootStaticArchives[0])
handler.module.outputFile = android.OptionalPathForPath(outputFilePath)
@@ -679,17 +679,17 @@
Build(),
})
- return true
+ return
}
// generateSharedBazelBuildActions constructs the SharedLibraryInfo Soong
// provider from a Bazel shared library's CcInfo provider.
-func (handler *ccLibraryBazelHandler) generateSharedBazelBuildActions(ctx android.ModuleContext, label string, ccInfo cquery.CcInfo) bool {
+func (handler *ccLibraryBazelHandler) generateSharedBazelBuildActions(ctx android.ModuleContext, label string, ccInfo cquery.CcInfo) {
rootDynamicLibraries := ccInfo.RootDynamicLibraries
if len(rootDynamicLibraries) != 1 {
ctx.ModuleErrorf("expected exactly one root dynamic library file for '%s', but got %s", label, rootDynamicLibraries)
- return false
+ return
}
outputFilePath := android.PathForBazelOut(ctx, rootDynamicLibraries[0])
handler.module.outputFile = android.OptionalPathForPath(outputFilePath)
@@ -709,30 +709,27 @@
// TODO(b/190524881): Include transitive static libraries in this provider to support
// static libraries with deps. The provider key for this is TransitiveStaticLibrariesForOrdering.
})
- return true
}
-func (handler *ccLibraryBazelHandler) GenerateBazelBuildActions(ctx android.ModuleContext, label string) bool {
+func (handler *ccLibraryBazelHandler) QueueBazelCall(ctx android.BaseModuleContext, label string) {
bazelCtx := ctx.Config().BazelContext
- ccInfo, ok, err := bazelCtx.GetCcInfo(label, android.GetConfigKey(ctx))
+ bazelCtx.QueueBazelRequest(label, cquery.GetCcInfo, android.GetConfigKey(ctx))
+}
+
+func (handler *ccLibraryBazelHandler) ProcessBazelQueryResponse(ctx android.ModuleContext, label string) {
+ bazelCtx := ctx.Config().BazelContext
+ ccInfo, err := bazelCtx.GetCcInfo(label, android.GetConfigKey(ctx))
if err != nil {
ctx.ModuleErrorf("Error getting Bazel CcInfo: %s", err)
- return false
- }
- if !ok {
- return ok
+ return
}
if handler.module.static() {
- if ok := handler.generateStaticBazelBuildActions(ctx, label, ccInfo); !ok {
- return false
- }
+ handler.generateStaticBazelBuildActions(ctx, label, ccInfo)
} else if handler.module.Shared() {
- if ok := handler.generateSharedBazelBuildActions(ctx, label, ccInfo); !ok {
- return false
- }
+ handler.generateSharedBazelBuildActions(ctx, label, ccInfo)
} else {
- return false
+ ctx.ModuleErrorf("Unhandled bazel case for %s (neither shared nor static!)", ctx.ModuleName())
}
handler.module.linker.(*libraryDecorator).setFlagExporterInfoFromCcInfo(ctx, ccInfo)
@@ -746,7 +743,6 @@
// implementation.
i.(*libraryDecorator).collectedSnapshotHeaders = android.Paths{}
}
- return ok
}
func (library *libraryDecorator) setFlagExporterInfoFromCcInfo(ctx android.ModuleContext, ccInfo cquery.CcInfo) {
diff --git a/cc/library_headers.go b/cc/library_headers.go
index 41ebcc7..6fd9568 100644
--- a/cc/library_headers.go
+++ b/cc/library_headers.go
@@ -17,6 +17,7 @@
import (
"android/soong/android"
"android/soong/bazel"
+ "android/soong/bazel/cquery"
)
func init() {
@@ -47,28 +48,30 @@
ctx.RegisterModuleType("cc_prebuilt_library_headers", prebuiltLibraryHeaderFactory)
}
-type libraryHeaderBazelHander struct {
- android.BazelHandler
+type libraryHeaderBazelHandler struct {
+ BazelHandler
module *Module
library *libraryDecorator
}
-func (h *libraryHeaderBazelHander) GenerateBazelBuildActions(ctx android.ModuleContext, label string) bool {
+func (handler *libraryHeaderBazelHandler) QueueBazelCall(ctx android.BaseModuleContext, label string) {
bazelCtx := ctx.Config().BazelContext
- ccInfo, ok, err := bazelCtx.GetCcInfo(label, android.GetConfigKey(ctx))
+ bazelCtx.QueueBazelRequest(label, cquery.GetCcInfo, android.GetConfigKey(ctx))
+}
+
+func (h *libraryHeaderBazelHandler) ProcessBazelQueryResponse(ctx android.ModuleContext, label string) {
+ bazelCtx := ctx.Config().BazelContext
+ ccInfo, err := bazelCtx.GetCcInfo(label, android.GetConfigKey(ctx))
if err != nil {
- ctx.ModuleErrorf("Error getting Bazel CcInfo: %s", err)
- return false
- }
- if !ok {
- return false
+ ctx.ModuleErrorf(err.Error())
+ return
}
outputPaths := ccInfo.OutputFiles
if len(outputPaths) != 1 {
ctx.ModuleErrorf("expected exactly one output file for %q, but got %q", label, outputPaths)
- return false
+ return
}
outputPath := android.PathForBazelOut(ctx, outputPaths[0])
@@ -83,8 +86,6 @@
// validation will fail. For now, set this to an empty list.
// TODO(cparsons): More closely mirror the collectHeadersForSnapshot implementation.
h.library.collectedSnapshotHeaders = android.Paths{}
-
- return true
}
// cc_library_headers contains a set of c/c++ headers which are imported by
@@ -96,7 +97,7 @@
library.HeaderOnly()
module.sdkMemberTypes = []android.SdkMemberType{headersLibrarySdkMemberType}
module.bazelable = true
- module.bazelHandler = &libraryHeaderBazelHander{module: module, library: library}
+ module.bazelHandler = &libraryHeaderBazelHandler{module: module, library: library}
return module.Init()
}
diff --git a/cc/object.go b/cc/object.go
index bd5bd45..a3be6b1 100644
--- a/cc/object.go
+++ b/cc/object.go
@@ -19,6 +19,7 @@
"android/soong/android"
"android/soong/bazel"
+ "android/soong/bazel/cquery"
)
//
@@ -46,23 +47,30 @@
}
type objectBazelHandler struct {
- android.BazelHandler
+ BazelHandler
module *Module
}
-func (handler *objectBazelHandler) GenerateBazelBuildActions(ctx android.ModuleContext, label string) bool {
+func (handler *objectBazelHandler) QueueBazelCall(ctx android.BaseModuleContext, label string) {
bazelCtx := ctx.Config().BazelContext
- objPaths, ok := bazelCtx.GetOutputFiles(label, android.GetConfigKey(ctx))
- if ok {
- if len(objPaths) != 1 {
- ctx.ModuleErrorf("expected exactly one object file for '%s', but got %s", label, objPaths)
- return false
- }
+ bazelCtx.QueueBazelRequest(label, cquery.GetOutputFiles, android.GetConfigKey(ctx))
+}
- handler.module.outputFile = android.OptionalPathForPath(android.PathForBazelOut(ctx, objPaths[0]))
+func (handler *objectBazelHandler) ProcessBazelQueryResponse(ctx android.ModuleContext, label string) {
+ bazelCtx := ctx.Config().BazelContext
+ objPaths, err := bazelCtx.GetOutputFiles(label, android.GetConfigKey(ctx))
+ if err != nil {
+ ctx.ModuleErrorf(err.Error())
+ return
}
- return ok
+
+ if len(objPaths) != 1 {
+ ctx.ModuleErrorf("expected exactly one object file for '%s', but got %s", label, objPaths)
+ return
+ }
+
+ handler.module.outputFile = android.OptionalPathForPath(android.PathForBazelOut(ctx, objPaths[0]))
}
type ObjectLinkerProperties struct {
diff --git a/cc/prebuilt.go b/cc/prebuilt.go
index f54c6f8..e8c60ba 100644
--- a/cc/prebuilt.go
+++ b/cc/prebuilt.go
@@ -20,6 +20,7 @@
"android/soong/android"
"android/soong/bazel"
+ "android/soong/bazel/cquery"
)
func init() {
@@ -406,25 +407,28 @@
}
type prebuiltStaticLibraryBazelHandler struct {
- android.BazelHandler
+ BazelHandler
module *Module
library *libraryDecorator
}
-func (h *prebuiltStaticLibraryBazelHandler) GenerateBazelBuildActions(ctx android.ModuleContext, label string) bool {
+func (h *prebuiltStaticLibraryBazelHandler) QueueBazelCall(ctx android.BaseModuleContext, label string) {
bazelCtx := ctx.Config().BazelContext
- ccInfo, ok, err := bazelCtx.GetCcInfo(label, android.GetConfigKey(ctx))
+ bazelCtx.QueueBazelRequest(label, cquery.GetCcInfo, android.GetConfigKey(ctx))
+}
+
+func (h *prebuiltStaticLibraryBazelHandler) ProcessBazelQueryResponse(ctx android.ModuleContext, label string) {
+ bazelCtx := ctx.Config().BazelContext
+ ccInfo, err := bazelCtx.GetCcInfo(label, android.GetConfigKey(ctx))
if err != nil {
- ctx.ModuleErrorf("Error getting Bazel CcInfo: %s", err)
- }
- if !ok {
- return false
+ ctx.ModuleErrorf(err.Error())
+ return
}
staticLibs := ccInfo.CcStaticLibraryFiles
if len(staticLibs) > 1 {
ctx.ModuleErrorf("expected 1 static library from bazel target %q, got %s", label, staticLibs)
- return false
+ return
}
// TODO(b/184543518): cc_prebuilt_library_static may have properties for re-exporting flags
@@ -439,7 +443,7 @@
if len(staticLibs) == 0 {
h.module.outputFile = android.OptionalPath{}
- return true
+ return
}
out := android.PathForBazelOut(ctx, staticLibs[0])
@@ -451,30 +455,31 @@
TransitiveStaticLibrariesForOrdering: depSet,
})
-
- return true
}
type prebuiltSharedLibraryBazelHandler struct {
- android.BazelHandler
+ BazelHandler
module *Module
library *libraryDecorator
}
-func (h *prebuiltSharedLibraryBazelHandler) GenerateBazelBuildActions(ctx android.ModuleContext, label string) bool {
+func (h *prebuiltSharedLibraryBazelHandler) QueueBazelCall(ctx android.BaseModuleContext, label string) {
bazelCtx := ctx.Config().BazelContext
- ccInfo, ok, err := bazelCtx.GetCcInfo(label, android.GetConfigKey(ctx))
+ bazelCtx.QueueBazelRequest(label, cquery.GetCcInfo, android.GetConfigKey(ctx))
+}
+
+func (h *prebuiltSharedLibraryBazelHandler) ProcessBazelQueryResponse(ctx android.ModuleContext, label string) {
+ bazelCtx := ctx.Config().BazelContext
+ ccInfo, err := bazelCtx.GetCcInfo(label, android.GetConfigKey(ctx))
if err != nil {
- ctx.ModuleErrorf("Error getting Bazel CcInfo for %s: %s", label, err)
- }
- if !ok {
- return false
+ ctx.ModuleErrorf(err.Error())
+ return
}
sharedLibs := ccInfo.CcSharedLibraryFiles
if len(sharedLibs) != 1 {
ctx.ModuleErrorf("expected 1 shared library from bazel target %s, got %q", label, sharedLibs)
- return false
+ return
}
// TODO(b/184543518): cc_prebuilt_library_shared may have properties for re-exporting flags
@@ -489,7 +494,7 @@
if len(sharedLibs) == 0 {
h.module.outputFile = android.OptionalPath{}
- return true
+ return
}
out := android.PathForBazelOut(ctx, sharedLibs[0])
@@ -514,8 +519,6 @@
h.library.setFlagExporterInfoFromCcInfo(ctx, ccInfo)
h.module.maybeUnhideFromMake()
-
- return true
}
func (p *prebuiltObjectLinker) prebuilt() *android.Prebuilt {