Implement code-generation step for bp2build.

Implement bp2build codegen as a discrete step that runs after an
alternatively registered pipeline of mutators, instead of a
presingleton.

bp2build codegen requires a Context that supports VisitAllModules and
PathContext, so this CL also makes a BpToBuildWrapperContext that
conforms to PathContext by adding two method implementations.

Test: GENERATE_BAZEL_FILES=true m nothing && bazel query //... --config=bp2build | wc -l # 31433
Test: m queryview && bazel query //... --config=queryview # 63638

Change-Id: I0dd359746584b228046d2d0ff00895f28f9bdfc3
This commit is contained in:
Jingwen Chen
2020-12-14 02:58:54 -05:00
parent 4d31a041c7
commit daa54bcbba
7 changed files with 72 additions and 139 deletions

View File

@@ -37,9 +37,6 @@ type singleton struct {
var singletons []singleton var singletons []singleton
var preSingletons []singleton var preSingletons []singleton
var bazelConverterSingletons []singleton
var bazelConverterPreSingletons []singleton
type mutator struct { type mutator struct {
name string name string
bottomUpMutator blueprint.BottomUpMutator bottomUpMutator blueprint.BottomUpMutator
@@ -94,14 +91,6 @@ func RegisterPreSingletonType(name string, factory SingletonFactory) {
preSingletons = append(preSingletons, singleton{name, factory}) preSingletons = append(preSingletons, singleton{name, factory})
} }
func RegisterBazelConverterSingletonType(name string, factory SingletonFactory) {
bazelConverterSingletons = append(bazelConverterSingletons, singleton{name, factory})
}
func RegisterBazelConverterPreSingletonType(name string, factory SingletonFactory) {
bazelConverterPreSingletons = append(bazelConverterPreSingletons, singleton{name, factory})
}
type Context struct { type Context struct {
*blueprint.Context *blueprint.Context
config Config config Config
@@ -117,21 +106,20 @@ func NewContext(config Config) *Context {
// singletons, module types and mutators to register for converting Blueprint // singletons, module types and mutators to register for converting Blueprint
// files to semantically equivalent BUILD files. // files to semantically equivalent BUILD files.
func (ctx *Context) RegisterForBazelConversion() { func (ctx *Context) RegisterForBazelConversion() {
for _, t := range bazelConverterPreSingletons {
ctx.RegisterPreSingletonType(t.name, SingletonFactoryAdaptor(ctx, t.factory))
}
for _, t := range moduleTypes { for _, t := range moduleTypes {
ctx.RegisterModuleType(t.name, ModuleFactoryAdaptor(t.factory)) ctx.RegisterModuleType(t.name, ModuleFactoryAdaptor(t.factory))
} }
for _, t := range bazelConverterSingletons { // Required for SingletonModule types, even though we are not using them.
for _, t := range singletons {
ctx.RegisterSingletonType(t.name, SingletonFactoryAdaptor(ctx, t.factory)) ctx.RegisterSingletonType(t.name, SingletonFactoryAdaptor(ctx, t.factory))
} }
registerMutatorsForBazelConversion(ctx.Context) registerMutatorsForBazelConversion(ctx.Context)
} }
// 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() { func (ctx *Context) Register() {
for _, t := range preSingletons { for _, t := range preSingletons {
ctx.RegisterPreSingletonType(t.name, SingletonFactoryAdaptor(ctx, t.factory)) ctx.RegisterPreSingletonType(t.name, SingletonFactoryAdaptor(ctx, t.factory))

View File

@@ -16,52 +16,34 @@ package bp2build
import ( import (
"android/soong/android" "android/soong/android"
"fmt"
"os" "os"
) )
// The Bazel bp2build singleton is responsible for writing .bzl files that are equivalent to // The Bazel bp2build code generator is responsible for writing .bzl files that are equivalent to
// Android.bp files that are capable of being built with Bazel. // Android.bp files that are capable of being built with Bazel.
func init() { func Codegen(ctx CodegenContext) {
android.RegisterBazelConverterPreSingletonType("androidbp_to_build", AndroidBpToBuildSingleton) outputDir := android.PathForOutput(ctx, "bp2build")
} android.RemoveAllOutputDir(outputDir)
func AndroidBpToBuildSingleton() android.Singleton {
return &androidBpToBuildSingleton{
name: "bp2build",
}
}
type androidBpToBuildSingleton struct {
name string
outputDir android.OutputPath
}
func (s *androidBpToBuildSingleton) GenerateBuildActions(ctx android.SingletonContext) {
s.outputDir = android.PathForOutput(ctx, s.name)
android.RemoveAllOutputDir(s.outputDir)
if !ctx.Config().IsEnvTrue("CONVERT_TO_BAZEL") {
return
}
ruleShims := CreateRuleShims(android.ModuleTypeFactories()) ruleShims := CreateRuleShims(android.ModuleTypeFactories())
buildToTargets := GenerateSoongModuleTargets(ctx) buildToTargets := GenerateSoongModuleTargets(ctx.Context())
filesToWrite := CreateBazelFiles(ruleShims, buildToTargets) filesToWrite := CreateBazelFiles(ruleShims, buildToTargets)
for _, f := range filesToWrite { for _, f := range filesToWrite {
if err := s.writeFile(ctx, f); err != nil { if err := writeFile(outputDir, ctx, f); err != nil {
ctx.Errorf("Failed to write %q (dir %q) due to %q", f.Basename, f.Dir, err) fmt.Errorf("Failed to write %q (dir %q) due to %q", f.Basename, f.Dir, err)
} }
} }
} }
func (s *androidBpToBuildSingleton) getOutputPath(ctx android.PathContext, dir string) android.OutputPath { func writeFile(outputDir android.OutputPath, ctx android.PathContext, f BazelFile) error {
return s.outputDir.Join(ctx, dir) return writeReadOnlyFile(ctx, getOutputPath(outputDir, ctx, f.Dir), f.Basename, f.Contents)
} }
func (s *androidBpToBuildSingleton) writeFile(ctx android.PathContext, f BazelFile) error { func getOutputPath(outputDir android.OutputPath, ctx android.PathContext, dir string) android.OutputPath {
return writeReadOnlyFile(ctx, s.getOutputPath(ctx, f.Dir), f.Basename, f.Contents) return outputDir.Join(ctx, dir)
} }
// The auto-conversion directory should be read-only, sufficient for bazel query. The files // The auto-conversion directory should be read-only, sufficient for bazel query. The files

View File

@@ -39,8 +39,26 @@ type bpToBuildContext interface {
ModuleSubDir(module blueprint.Module) string ModuleSubDir(module blueprint.Module) string
ModuleType(module blueprint.Module) string ModuleType(module blueprint.Module) string
VisitAllModulesBlueprint(visit func(blueprint.Module)) VisitAllModules(visit func(blueprint.Module))
VisitDirectDeps(module android.Module, visit func(android.Module)) VisitDirectDeps(module blueprint.Module, visit func(blueprint.Module))
}
type CodegenContext struct {
config android.Config
context android.Context
}
func (ctx CodegenContext) AddNinjaFileDeps(...string) {}
func (ctx CodegenContext) Config() android.Config { return ctx.config }
func (ctx CodegenContext) Context() android.Context { return ctx.context }
// NewCodegenContext creates a wrapper context that conforms to PathContext for
// writing BUILD files in the output directory.
func NewCodegenContext(config android.Config, context android.Context) CodegenContext {
return CodegenContext{
context: context,
config: config,
}
} }
// props is an unsorted map. This function ensures that // props is an unsorted map. This function ensures that
@@ -57,7 +75,7 @@ func propsToAttributes(props map[string]string) string {
func GenerateSoongModuleTargets(ctx bpToBuildContext) map[string][]BazelTarget { func GenerateSoongModuleTargets(ctx bpToBuildContext) map[string][]BazelTarget {
buildFileToTargets := make(map[string][]BazelTarget) buildFileToTargets := make(map[string][]BazelTarget)
ctx.VisitAllModulesBlueprint(func(m blueprint.Module) { ctx.VisitAllModules(func(m blueprint.Module) {
dir := ctx.ModuleDir(m) dir := ctx.ModuleDir(m)
t := generateSoongModuleTarget(ctx, m) t := generateSoongModuleTarget(ctx, m)
buildFileToTargets[ctx.ModuleDir(m)] = append(buildFileToTargets[dir], t) buildFileToTargets[ctx.ModuleDir(m)] = append(buildFileToTargets[dir], t)
@@ -75,7 +93,7 @@ func generateSoongModuleTarget(ctx bpToBuildContext, m blueprint.Module) BazelTa
// out the implications of that. // out the implications of that.
depLabels := map[string]bool{} depLabels := map[string]bool{}
if aModule, ok := m.(android.Module); ok { if aModule, ok := m.(android.Module); ok {
ctx.VisitDirectDeps(aModule, func(depModule android.Module) { ctx.VisitDirectDeps(aModule, func(depModule blueprint.Module) {
depLabels[qualifiedTargetLabel(ctx, depModule)] = true depLabels[qualifiedTargetLabel(ctx, depModule)] = true
}) })
} }

View File

@@ -200,11 +200,7 @@ func TestGenerateSoongModuleTargets(t *testing.T) {
_, errs = ctx.PrepareBuildActions(config) _, errs = ctx.PrepareBuildActions(config)
android.FailIfErrored(t, errs) android.FailIfErrored(t, errs)
bp2BuildCtx := bp2buildBlueprintWrapContext{ bazelTargets := GenerateSoongModuleTargets(ctx.Context.Context)[dir]
bpCtx: ctx.Context.Context,
}
bazelTargets := GenerateSoongModuleTargets(&bp2BuildCtx)[dir]
if g, w := len(bazelTargets), 1; g != w { if g, w := len(bazelTargets), 1; g != w {
t.Fatalf("Expected %d bazel target, got %d", w, g) t.Fatalf("Expected %d bazel target, got %d", w, g)
} }

View File

@@ -2,8 +2,6 @@ package bp2build
import ( import (
"android/soong/android" "android/soong/android"
"github.com/google/blueprint"
) )
type nestedProps struct { type nestedProps struct {
@@ -102,35 +100,3 @@ func customDefaultsModuleFactory() android.Module {
android.InitDefaultsModule(m) android.InitDefaultsModule(m)
return m return m
} }
type bp2buildBlueprintWrapContext struct {
bpCtx *blueprint.Context
}
func (ctx *bp2buildBlueprintWrapContext) ModuleName(module blueprint.Module) string {
return ctx.bpCtx.ModuleName(module)
}
func (ctx *bp2buildBlueprintWrapContext) ModuleDir(module blueprint.Module) string {
return ctx.bpCtx.ModuleDir(module)
}
func (ctx *bp2buildBlueprintWrapContext) ModuleSubDir(module blueprint.Module) string {
return ctx.bpCtx.ModuleSubDir(module)
}
func (ctx *bp2buildBlueprintWrapContext) ModuleType(module blueprint.Module) string {
return ctx.bpCtx.ModuleType(module)
}
func (ctx *bp2buildBlueprintWrapContext) VisitAllModulesBlueprint(visit func(blueprint.Module)) {
ctx.bpCtx.VisitAllModules(visit)
}
func (ctx *bp2buildBlueprintWrapContext) VisitDirectDeps(module android.Module, visit func(android.Module)) {
ctx.bpCtx.VisitDirectDeps(module, func(m blueprint.Module) {
if aModule, ok := m.(android.Module); ok {
visit(aModule)
}
})
}

View File

@@ -23,6 +23,7 @@ import (
"github.com/google/blueprint/bootstrap" "github.com/google/blueprint/bootstrap"
"android/soong/android" "android/soong/android"
"android/soong/bp2build"
) )
var ( var (
@@ -54,18 +55,12 @@ func newNameResolver(config android.Config) *android.NameResolver {
// bazelConversionRequested checks that the user is intending to convert // bazelConversionRequested checks that the user is intending to convert
// Blueprint to Bazel BUILD files. // Blueprint to Bazel BUILD files.
func bazelConversionRequested(configuration android.Config) bool { func bazelConversionRequested(configuration android.Config) bool {
return configuration.IsEnvTrue("CONVERT_TO_BAZEL") return configuration.IsEnvTrue("GENERATE_BAZEL_FILES")
} }
func newContext(srcDir string, configuration android.Config) *android.Context { func newContext(configuration android.Config) *android.Context {
ctx := android.NewContext(configuration) ctx := android.NewContext(configuration)
if bazelConversionRequested(configuration) { ctx.Register()
// Register an alternate set of singletons and mutators for bazel
// conversion for Bazel conversion.
ctx.RegisterForBazelConversion()
} else {
ctx.Register()
}
if !shouldPrepareBuildActions(configuration) { if !shouldPrepareBuildActions(configuration) {
configuration.SetStopBefore(bootstrap.StopBeforePrepareBuildActions) configuration.SetStopBefore(bootstrap.StopBeforePrepareBuildActions)
} }
@@ -100,13 +95,22 @@ func main() {
// enabled even if it completed successfully. // enabled even if it completed successfully.
extraNinjaDeps = append(extraNinjaDeps, filepath.Join(configuration.BuildDir(), "always_rerun_for_delve")) extraNinjaDeps = append(extraNinjaDeps, filepath.Join(configuration.BuildDir(), "always_rerun_for_delve"))
} }
if bazelConversionRequested(configuration) {
// Run the alternate pipeline of bp2build mutators and singleton to convert Blueprint to BUILD files
// before everything else.
runBp2Build(configuration, extraNinjaDeps)
// Short-circuit and return.
return
}
if configuration.BazelContext.BazelEnabled() { if configuration.BazelContext.BazelEnabled() {
// Bazel-enabled mode. Soong runs in two passes. // Bazel-enabled mode. Soong runs in two passes.
// First pass: Analyze the build tree, but only store all bazel commands // First pass: Analyze the build tree, but only store all bazel commands
// needed to correctly evaluate the tree in the second pass. // needed to correctly evaluate the tree in the second pass.
// TODO(cparsons): Don't output any ninja file, as the second pass will overwrite // TODO(cparsons): Don't output any ninja file, as the second pass will overwrite
// the incorrect results from the first pass, and file I/O is expensive. // the incorrect results from the first pass, and file I/O is expensive.
firstCtx := newContext(srcDir, configuration) firstCtx := newContext(configuration)
configuration.SetStopBefore(bootstrap.StopBeforeWriteNinja) configuration.SetStopBefore(bootstrap.StopBeforeWriteNinja)
bootstrap.Main(firstCtx.Context, configuration, extraNinjaDeps...) bootstrap.Main(firstCtx.Context, configuration, extraNinjaDeps...)
// Invoke bazel commands and save results for second pass. // Invoke bazel commands and save results for second pass.
@@ -120,10 +124,10 @@ func main() {
fmt.Fprintf(os.Stderr, "%s", err) fmt.Fprintf(os.Stderr, "%s", err)
os.Exit(1) os.Exit(1)
} }
ctx = newContext(srcDir, secondPassConfig) ctx = newContext(secondPassConfig)
bootstrap.Main(ctx.Context, secondPassConfig, extraNinjaDeps...) bootstrap.Main(ctx.Context, secondPassConfig, extraNinjaDeps...)
} else { } else {
ctx = newContext(srcDir, configuration) ctx = newContext(configuration)
bootstrap.Main(ctx.Context, configuration, extraNinjaDeps...) bootstrap.Main(ctx.Context, configuration, extraNinjaDeps...)
} }
@@ -154,6 +158,22 @@ func main() {
} }
} }
// 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(configuration android.Config, extraNinjaDeps []string) {
// Register an alternate set of singletons and mutators for bazel
// conversion for Bazel conversion.
bp2buildCtx := android.NewContext(configuration)
bp2buildCtx.RegisterForBazelConversion()
configuration.SetStopBefore(bootstrap.StopBeforePrepareBuildActions)
bp2buildCtx.SetNameInterface(newNameResolver(configuration))
bootstrap.Main(bp2buildCtx.Context, configuration, extraNinjaDeps...)
codegenContext := bp2build.NewCodegenContext(configuration, *bp2buildCtx)
bp2build.Codegen(codegenContext)
}
// shouldPrepareBuildActions reads configuration and flags if build actions // shouldPrepareBuildActions reads configuration and flags if build actions
// should be generated. // should be generated.
func shouldPrepareBuildActions(configuration android.Config) bool { func shouldPrepareBuildActions(configuration android.Config) bool {

View File

@@ -20,48 +20,11 @@ import (
"io/ioutil" "io/ioutil"
"os" "os"
"path/filepath" "path/filepath"
"github.com/google/blueprint"
) )
type queryviewContext struct {
bpCtx *blueprint.Context
}
func (ctx *queryviewContext) ModuleName(module blueprint.Module) string {
return ctx.bpCtx.ModuleName(module)
}
func (ctx *queryviewContext) ModuleDir(module blueprint.Module) string {
return ctx.bpCtx.ModuleDir(module)
}
func (ctx *queryviewContext) ModuleSubDir(module blueprint.Module) string {
return ctx.bpCtx.ModuleSubDir(module)
}
func (ctx *queryviewContext) ModuleType(module blueprint.Module) string {
return ctx.bpCtx.ModuleType(module)
}
func (ctx *queryviewContext) VisitAllModulesBlueprint(visit func(blueprint.Module)) {
ctx.bpCtx.VisitAllModules(visit)
}
func (ctx *queryviewContext) VisitDirectDeps(module android.Module, visit func(android.Module)) {
ctx.bpCtx.VisitDirectDeps(module, func(m blueprint.Module) {
if aModule, ok := m.(android.Module); ok {
visit(aModule)
}
})
}
func createBazelQueryView(ctx *android.Context, bazelQueryViewDir string) error { func createBazelQueryView(ctx *android.Context, bazelQueryViewDir string) error {
qvCtx := queryviewContext{
bpCtx: ctx.Context,
}
ruleShims := bp2build.CreateRuleShims(android.ModuleTypeFactories()) ruleShims := bp2build.CreateRuleShims(android.ModuleTypeFactories())
buildToTargets := bp2build.GenerateSoongModuleTargets(&qvCtx) buildToTargets := bp2build.GenerateSoongModuleTargets(*ctx)
filesToWrite := bp2build.CreateBazelFiles(ruleShims, buildToTargets) filesToWrite := bp2build.CreateBazelFiles(ruleShims, buildToTargets)
for _, f := range filesToWrite { for _, f := range filesToWrite {