bp2build: framework for generating BazelTargetModules.
This CL creates the framework necessary for generating
BazelTargetModules from regular Soong Android modules.
BazelTargetModules are code-generated into Bazel targets in BUILD files.
See the follow-up CL for examples of creating filegroup/genrule
BazelTargetModules.
Test: GENERATE_BAZEL_FILES=true m nothing # creates out/soong/bp2build
with no BUILD files, because there are no BazelTargetModules in the
module graph.
Change-Id: I33a96365bd439043b13af6db9e439592e9983188
diff --git a/bp2build/Android.bp b/bp2build/Android.bp
index 49587f4..8007574 100644
--- a/bp2build/Android.bp
+++ b/bp2build/Android.bp
@@ -10,6 +10,7 @@
],
deps: [
"soong-android",
+ "soong-bazel",
],
testSrcs: [
"build_conversion_test.go",
diff --git a/bp2build/androidbp_to_build_templates.go b/bp2build/androidbp_to_build_templates.go
index 75c3ccb..9bac86b 100644
--- a/bp2build/androidbp_to_build_templates.go
+++ b/bp2build/androidbp_to_build_templates.go
@@ -15,7 +15,7 @@
package bp2build
const (
- // The default `load` preamble for every generated BUILD file.
+ // The default `load` preamble for every generated queryview BUILD file.
soongModuleLoad = `package(default_visibility = ["//visibility:public"])
load("//build/bazel/queryview_rules:soong_module.bzl", "soong_module")
@@ -31,6 +31,10 @@
module_deps = %s,
%s)`
+ bazelTarget = `%s(
+ name = "%s",
+%s)`
+
// A simple provider to mark and differentiate Soong module rule shims from
// regular Bazel rules. Every Soong module rule shim returns a
// SoongModuleInfo provider, and can only depend on rules returning
diff --git a/bp2build/bp2build.go b/bp2build/bp2build.go
index 49729e0..75b6097 100644
--- a/bp2build/bp2build.go
+++ b/bp2build/bp2build.go
@@ -28,9 +28,9 @@
ruleShims := CreateRuleShims(android.ModuleTypeFactories())
- buildToTargets := GenerateSoongModuleTargets(ctx.Context())
+ buildToTargets := GenerateSoongModuleTargets(ctx.Context(), true)
- filesToWrite := CreateBazelFiles(ruleShims, buildToTargets)
+ filesToWrite := CreateBazelFiles(ruleShims, buildToTargets, true)
for _, f := range filesToWrite {
if err := writeFile(outputDir, ctx, f); err != nil {
fmt.Errorf("Failed to write %q (dir %q) due to %q", f.Basename, f.Dir, err)
diff --git a/bp2build/build_conversion.go b/bp2build/build_conversion.go
index bece8f6..da2fb7f 100644
--- a/bp2build/build_conversion.go
+++ b/bp2build/build_conversion.go
@@ -73,16 +73,51 @@
return attributes
}
-func GenerateSoongModuleTargets(ctx bpToBuildContext) map[string][]BazelTarget {
+func GenerateSoongModuleTargets(ctx bpToBuildContext, bp2buildEnabled bool) map[string][]BazelTarget {
buildFileToTargets := make(map[string][]BazelTarget)
ctx.VisitAllModules(func(m blueprint.Module) {
dir := ctx.ModuleDir(m)
- t := generateSoongModuleTarget(ctx, m)
+ var t BazelTarget
+
+ if bp2buildEnabled {
+ if _, ok := m.(android.BazelTargetModule); !ok {
+ return
+ }
+ t = generateBazelTarget(ctx, m)
+ } else {
+ t = generateSoongModuleTarget(ctx, m)
+ }
+
buildFileToTargets[ctx.ModuleDir(m)] = append(buildFileToTargets[dir], t)
})
return buildFileToTargets
}
+func generateBazelTarget(ctx bpToBuildContext, m blueprint.Module) BazelTarget {
+ // extract the bazel attributes from the module.
+ props := getBuildProperties(ctx, m)
+
+ // extract the rule class name from the attributes. Since the string value
+ // will be string-quoted, remove the quotes here.
+ ruleClass := strings.Replace(props.Attrs["rule_class"], "\"", "", 2)
+ // Delete it from being generated in the BUILD file.
+ delete(props.Attrs, "rule_class")
+
+ // Return the Bazel target with rule class and attributes, ready to be
+ // code-generated.
+ attributes := propsToAttributes(props.Attrs)
+ targetName := targetNameForBp2Build(ctx, m)
+ return BazelTarget{
+ name: targetName,
+ content: fmt.Sprintf(
+ bazelTarget,
+ ruleClass,
+ targetName,
+ attributes,
+ ),
+ }
+}
+
// 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 {
@@ -306,6 +341,10 @@
return strings.Repeat(" ", indent)
}
+func targetNameForBp2Build(c bpToBuildContext, logicModule blueprint.Module) string {
+ return strings.Replace(c.ModuleName(logicModule), "__bp2build__", "", 1)
+}
+
func targetNameWithVariant(c bpToBuildContext, logicModule blueprint.Module) string {
name := ""
if c.ModuleSubDir(logicModule) != "" {
diff --git a/bp2build/build_conversion_test.go b/bp2build/build_conversion_test.go
index 4e31aa7..0217580 100644
--- a/bp2build/build_conversion_test.go
+++ b/bp2build/build_conversion_test.go
@@ -200,7 +200,7 @@
_, errs = ctx.PrepareBuildActions(config)
android.FailIfErrored(t, errs)
- bazelTargets := GenerateSoongModuleTargets(ctx.Context.Context)[dir]
+ bazelTargets := GenerateSoongModuleTargets(ctx.Context.Context, false)[dir]
if g, w := len(bazelTargets), 1; g != w {
t.Fatalf("Expected %d bazel target, got %d", w, g)
}
@@ -210,7 +210,58 @@
t.Errorf(
"Expected generated Bazel target to be '%s', got '%s'",
testCase.expectedBazelTarget,
- actualBazelTarget,
+ actualBazelTarget.content,
+ )
+ }
+ }
+}
+
+func TestGenerateBazelTargetModules(t *testing.T) {
+ testCases := []struct {
+ bp string
+ expectedBazelTarget string
+ }{
+ {
+ bp: `custom {
+ name: "foo",
+ string_list_prop: ["a", "b"],
+ string_prop: "a",
+}`,
+ expectedBazelTarget: `custom(
+ name = "foo",
+ string_list_prop = [
+ "a",
+ "b",
+ ],
+ string_prop = "a",
+)`,
+ },
+ }
+
+ dir := "."
+ for _, testCase := range testCases {
+ config := android.TestConfig(buildDir, nil, testCase.bp, nil)
+ ctx := android.NewTestContext(config)
+ ctx.RegisterModuleType("custom", customModuleFactory)
+ ctx.RegisterBp2BuildMutator("custom", customBp2BuildMutator)
+ ctx.RegisterForBazelConversion()
+
+ _, errs := ctx.ParseFileList(dir, []string{"Android.bp"})
+ android.FailIfErrored(t, errs)
+ _, errs = ctx.ResolveDependencies(config)
+ android.FailIfErrored(t, errs)
+
+ bazelTargets := GenerateSoongModuleTargets(ctx.Context.Context, true)[dir]
+ if g, w := len(bazelTargets), 1; g != w {
+ t.Fatalf("Expected %d bazel target, got %d", w, g)
+ }
+
+ actualBazelTarget := bazelTargets[0]
+ if actualBazelTarget.content != testCase.expectedBazelTarget {
+ t.Errorf(
+ "Expected generated Bazel target to be '%s', got '%s'",
+ testCase.expectedBazelTarget,
+ actualBazelTarget.content,
)
}
}
diff --git a/bp2build/bzl_conversion_test.go b/bp2build/bzl_conversion_test.go
index 8bea3f6..01c7271 100644
--- a/bp2build/bzl_conversion_test.go
+++ b/bp2build/bzl_conversion_test.go
@@ -172,7 +172,7 @@
content: "irrelevant",
},
}
- files := CreateBazelFiles(ruleShims, make(map[string][]BazelTarget))
+ files := CreateBazelFiles(ruleShims, make(map[string][]BazelTarget), false)
var actualSoongModuleBzl BazelFile
for _, f := range files {
diff --git a/bp2build/conversion.go b/bp2build/conversion.go
index cdfb38b..cccc474 100644
--- a/bp2build/conversion.go
+++ b/bp2build/conversion.go
@@ -17,7 +17,8 @@
func CreateBazelFiles(
ruleShims map[string]RuleShim,
- buildToTargets map[string][]BazelTarget) []BazelFile {
+ buildToTargets map[string][]BazelTarget,
+ bp2buildEnabled bool) []BazelFile {
files := make([]BazelFile, 0, len(ruleShims)+len(buildToTargets)+numAdditionalFiles)
// Write top level files: WORKSPACE and BUILD. These files are empty.
@@ -26,22 +27,30 @@
files = append(files, newFile("", "BUILD", ""))
files = append(files, newFile(bazelRulesSubDir, "BUILD", ""))
- files = append(files, newFile(bazelRulesSubDir, "providers.bzl", providersBzl))
- for bzlFileName, ruleShim := range ruleShims {
- files = append(files, newFile(bazelRulesSubDir, bzlFileName+".bzl", ruleShim.content))
+ if !bp2buildEnabled {
+ // These files are only used for queryview.
+ files = append(files, newFile(bazelRulesSubDir, "providers.bzl", providersBzl))
+
+ for bzlFileName, ruleShim := range ruleShims {
+ files = append(files, newFile(bazelRulesSubDir, bzlFileName+".bzl", ruleShim.content))
+ }
+ files = append(files, newFile(bazelRulesSubDir, "soong_module.bzl", generateSoongModuleBzl(ruleShims)))
}
- files = append(files, newFile(bazelRulesSubDir, "soong_module.bzl", generateSoongModuleBzl(ruleShims)))
- files = append(files, createBuildFiles(buildToTargets)...)
+ files = append(files, createBuildFiles(buildToTargets, bp2buildEnabled)...)
return files
}
-func createBuildFiles(buildToTargets map[string][]BazelTarget) []BazelFile {
+func createBuildFiles(buildToTargets map[string][]BazelTarget, bp2buildEnabled bool) []BazelFile {
files := make([]BazelFile, 0, len(buildToTargets))
for _, dir := range android.SortedStringKeys(buildToTargets) {
content := soongModuleLoad
+ if bp2buildEnabled {
+ // No need to load soong_module for bp2build BUILD files.
+ content = ""
+ }
targets := buildToTargets[dir]
sort.Slice(targets, func(i, j int) bool { return targets[i].name < targets[j].name })
for _, t := range targets {
diff --git a/bp2build/conversion_test.go b/bp2build/conversion_test.go
index a38fa6a..b40aa1b 100644
--- a/bp2build/conversion_test.go
+++ b/bp2build/conversion_test.go
@@ -19,12 +19,44 @@
"testing"
)
-func TestCreateBazelFiles_AddsTopLevelFiles(t *testing.T) {
- files := CreateBazelFiles(map[string]RuleShim{}, map[string][]BazelTarget{})
- expectedFilePaths := []struct {
- dir string
- basename string
- }{
+type filepath struct {
+ dir string
+ basename string
+}
+
+func assertFilecountsAreEqual(t *testing.T, actual []BazelFile, expected []filepath) {
+ if a, e := len(actual), len(expected); a != e {
+ t.Errorf("Expected %d files, got %d", e, a)
+ }
+}
+
+func assertFileContent(t *testing.T, actual []BazelFile, expected []filepath) {
+ for i := range actual {
+ if g, w := actual[i], expected[i]; g.Dir != w.dir || g.Basename != w.basename {
+ t.Errorf("Did not find expected file %s/%s", g.Dir, g.Basename)
+ } else if g.Basename == "BUILD" || g.Basename == "WORKSPACE" {
+ if g.Contents != "" {
+ t.Errorf("Expected %s to have no content.", g)
+ }
+ } else if g.Contents == "" {
+ t.Errorf("Contents of %s unexpected empty.", g)
+ }
+ }
+}
+
+func sortFiles(files []BazelFile) {
+ sort.Slice(files, func(i, j int) bool {
+ if dir1, dir2 := files[i].Dir, files[j].Dir; dir1 == dir2 {
+ return files[i].Basename < files[j].Basename
+ } else {
+ return dir1 < dir2
+ }
+ })
+}
+
+func TestCreateBazelFiles_QueryView_AddsTopLevelFiles(t *testing.T) {
+ files := CreateBazelFiles(map[string]RuleShim{}, map[string][]BazelTarget{}, false)
+ expectedFilePaths := []filepath{
{
dir: "",
basename: "BUILD",
@@ -47,27 +79,29 @@
},
}
- if g, w := len(files), len(expectedFilePaths); g != w {
- t.Errorf("Expected %d files, got %d", w, g)
+ assertFilecountsAreEqual(t, files, expectedFilePaths)
+ sortFiles(files)
+ assertFileContent(t, files, expectedFilePaths)
+}
+
+func TestCreateBazelFiles_Bp2Build_AddsTopLevelFiles(t *testing.T) {
+ files := CreateBazelFiles(map[string]RuleShim{}, map[string][]BazelTarget{}, true)
+ expectedFilePaths := []filepath{
+ {
+ dir: "",
+ basename: "BUILD",
+ },
+ {
+ dir: "",
+ basename: "WORKSPACE",
+ },
+ {
+ dir: bazelRulesSubDir,
+ basename: "BUILD",
+ },
}
- sort.Slice(files, func(i, j int) bool {
- if dir1, dir2 := files[i].Dir, files[j].Dir; dir1 == dir2 {
- return files[i].Basename < files[j].Basename
- } else {
- return dir1 < dir2
- }
- })
-
- for i := range files {
- if g, w := files[i], expectedFilePaths[i]; g.Dir != w.dir || g.Basename != w.basename {
- t.Errorf("Did not find expected file %s/%s", g.Dir, g.Basename)
- } else if g.Basename == "BUILD" || g.Basename == "WORKSPACE" {
- if g.Contents != "" {
- t.Errorf("Expected %s to have no content.", g)
- }
- } else if g.Contents == "" {
- t.Errorf("Contents of %s unexpected empty.", g)
- }
- }
+ assertFilecountsAreEqual(t, files, expectedFilePaths)
+ sortFiles(files)
+ assertFileContent(t, files, expectedFilePaths)
}
diff --git a/bp2build/testing.go b/bp2build/testing.go
index 2da32c6..4c31d2d 100644
--- a/bp2build/testing.go
+++ b/bp2build/testing.go
@@ -2,6 +2,9 @@
import (
"android/soong/android"
+ "android/soong/bazel"
+
+ "github.com/google/blueprint/proptools"
)
type nestedProps struct {
@@ -100,3 +103,37 @@
android.InitDefaultsModule(m)
return m
}
+
+type customBazelModuleAttributes struct {
+ Name *string
+ String_prop string
+ String_list_prop []string
+}
+
+type customBazelModule struct {
+ android.BazelTargetModuleBase
+ customBazelModuleAttributes
+}
+
+func customBazelModuleFactory() android.Module {
+ module := &customBazelModule{}
+ module.AddProperties(&module.customBazelModuleAttributes)
+ android.InitBazelTargetModule(module)
+ return module
+}
+
+func (m *customBazelModule) Name() string { return m.BaseModuleName() }
+func (m *customBazelModule) GenerateAndroidBuildActions(ctx android.ModuleContext) {}
+
+func customBp2BuildMutator(ctx android.TopDownMutatorContext) {
+ if m, ok := ctx.Module().(*customModule); ok {
+ name := "__bp2build__" + m.Name()
+ ctx.CreateModule(customBazelModuleFactory, &customBazelModuleAttributes{
+ Name: proptools.StringPtr(name),
+ String_prop: m.props.String_prop,
+ String_list_prop: m.props.String_list_prop,
+ }, &bazel.BazelTargetModuleProperties{
+ Rule_class: "custom",
+ })
+ }
+}