diff --git a/bp2build/Android.bp b/bp2build/Android.bp index 8deb5a217..99d706cc5 100644 --- a/bp2build/Android.bp +++ b/bp2build/Android.bp @@ -19,6 +19,7 @@ bootstrap_go_package { "soong-bazel", "soong-cc", "soong-genrule", + "soong-python", "soong-sh", ], testSrcs: [ @@ -27,6 +28,7 @@ bootstrap_go_package { "cc_library_headers_conversion_test.go", "cc_object_conversion_test.go", "conversion_test.go", + "python_binary_conversion_test.go", "sh_conversion_test.go", "testing.go", ], diff --git a/bp2build/python_binary_conversion_test.go b/bp2build/python_binary_conversion_test.go new file mode 100644 index 000000000..7600e3651 --- /dev/null +++ b/bp2build/python_binary_conversion_test.go @@ -0,0 +1,170 @@ +package bp2build + +import ( + "android/soong/android" + "android/soong/python" + "fmt" + "strings" + "testing" +) + +func TestPythonBinaryHost(t *testing.T) { + testCases := []struct { + description string + moduleTypeUnderTest string + moduleTypeUnderTestFactory android.ModuleFactory + moduleTypeUnderTestBp2BuildMutator func(android.TopDownMutatorContext) + blueprint string + expectedBazelTargets []string + filesystem map[string]string + }{ + { + description: "simple python_binary_host converts to a native py_binary", + moduleTypeUnderTest: "python_binary_host", + moduleTypeUnderTestFactory: python.PythonBinaryHostFactory, + moduleTypeUnderTestBp2BuildMutator: python.PythonBinaryBp2Build, + filesystem: map[string]string{ + "a.py": "", + "b/c.py": "", + "b/d.py": "", + "b/e.py": "", + "files/data.txt": "", + }, + blueprint: `python_binary_host { + name: "foo", + main: "a.py", + srcs: [ + "**/*.py" + ], + exclude_srcs: [ + "b/e.py" + ], + data: [ + "files/data.txt", + ], + + bazel_module: { bp2build_available: true }, +} +`, + expectedBazelTargets: []string{`py_binary( + name = "foo", + data = [ + "files/data.txt", + ], + main = "a.py", + srcs = [ + "a.py", + "b/c.py", + "b/d.py", + ], +)`, + }, + }, + { + description: "py2 python_binary_host", + moduleTypeUnderTest: "python_binary_host", + moduleTypeUnderTestFactory: python.PythonBinaryHostFactory, + moduleTypeUnderTestBp2BuildMutator: python.PythonBinaryBp2Build, + blueprint: `python_binary_host { + name: "foo", + srcs: ["a.py"], + version: { + py2: { + enabled: true, + }, + py3: { + enabled: false, + }, + }, + + bazel_module: { bp2build_available: true }, +} +`, + expectedBazelTargets: []string{`py_binary( + name = "foo", + python_version = "PY2", + srcs = [ + "a.py", + ], +)`, + }, + }, + { + description: "py3 python_binary_host", + moduleTypeUnderTest: "python_binary_host", + moduleTypeUnderTestFactory: python.PythonBinaryHostFactory, + moduleTypeUnderTestBp2BuildMutator: python.PythonBinaryBp2Build, + blueprint: `python_binary_host { + name: "foo", + srcs: ["a.py"], + version: { + py2: { + enabled: false, + }, + py3: { + enabled: true, + }, + }, + + bazel_module: { bp2build_available: true }, +} +`, + expectedBazelTargets: []string{ + // python_version is PY3 by default. + `py_binary( + name = "foo", + srcs = [ + "a.py", + ], +)`, + }, + }, + } + + dir := "." + for _, testCase := range testCases { + filesystem := make(map[string][]byte) + toParse := []string{ + "Android.bp", + } + for f, content := range testCase.filesystem { + if strings.HasSuffix(f, "Android.bp") { + toParse = append(toParse, f) + } + filesystem[f] = []byte(content) + } + config := android.TestConfig(buildDir, nil, testCase.blueprint, filesystem) + ctx := android.NewTestContext(config) + + ctx.RegisterModuleType(testCase.moduleTypeUnderTest, testCase.moduleTypeUnderTestFactory) + ctx.RegisterBp2BuildMutator(testCase.moduleTypeUnderTest, testCase.moduleTypeUnderTestBp2BuildMutator) + ctx.RegisterForBazelConversion() + + _, errs := ctx.ParseFileList(dir, toParse) + if Errored(t, testCase.description, errs) { + continue + } + _, errs = ctx.ResolveDependencies(config) + if Errored(t, testCase.description, errs) { + continue + } + + codegenCtx := NewCodegenContext(config, *ctx.Context, Bp2Build) + bazelTargets := generateBazelTargetsForDir(codegenCtx, dir) + if actualCount, expectedCount := len(bazelTargets), len(testCase.expectedBazelTargets); actualCount != expectedCount { + fmt.Println(bazelTargets) + t.Errorf("%s: Expected %d bazel target, got %d", testCase.description, expectedCount, actualCount) + } else { + for i, target := range bazelTargets { + if w, g := testCase.expectedBazelTargets[i], target.content; w != g { + t.Errorf( + "%s: Expected generated Bazel target to be '%s', got '%s'", + testCase.description, + w, + g, + ) + } + } + } + } +} diff --git a/python/binary.go b/python/binary.go index 416a7eec0..372b8a8c1 100644 --- a/python/binary.go +++ b/python/binary.go @@ -20,10 +20,92 @@ import ( "fmt" "android/soong/android" + "android/soong/bazel" + + "github.com/google/blueprint/proptools" ) func init() { android.RegisterModuleType("python_binary_host", PythonBinaryHostFactory) + android.RegisterBp2BuildMutator("python_binary_host", PythonBinaryBp2Build) +} + +type bazelPythonBinaryAttributes struct { + Main string + Srcs bazel.LabelList + Data bazel.LabelList + Python_version string +} + +type bazelPythonBinary struct { + android.BazelTargetModuleBase + bazelPythonBinaryAttributes +} + +func BazelPythonBinaryFactory() android.Module { + module := &bazelPythonBinary{} + module.AddProperties(&module.bazelPythonBinaryAttributes) + android.InitBazelTargetModule(module) + return module +} + +func (m *bazelPythonBinary) Name() string { + return m.BaseModuleName() +} + +func (m *bazelPythonBinary) GenerateAndroidBuildActions(ctx android.ModuleContext) {} + +func PythonBinaryBp2Build(ctx android.TopDownMutatorContext) { + m, ok := ctx.Module().(*Module) + if !ok || !m.ConvertWithBp2build() { + return + } + + // a Module can be something other than a python_binary_host + if ctx.ModuleType() != "python_binary_host" { + return + } + + var main string + for _, propIntf := range m.GetProperties() { + if props, ok := propIntf.(*BinaryProperties); ok { + // main is optional. + if props.Main != nil { + main = *props.Main + break + } + } + } + // TODO(b/182306917): this doesn't fully handle all nested props versioned + // by the python version, which would have been handled by the version split + // mutator. This is sufficient for very simple python_binary_host modules + // under Bionic. + py3Enabled := proptools.BoolDefault(m.properties.Version.Py3.Enabled, false) + py2Enabled := proptools.BoolDefault(m.properties.Version.Py2.Enabled, false) + var python_version string + if py3Enabled && py2Enabled { + panic(fmt.Errorf( + "error for '%s' module: bp2build's python_binary_host converter does not support "+ + "converting a module that is enabled for both Python 2 and 3 at the same time.", m.Name())) + } else if py2Enabled { + python_version = "PY2" + } else { + // do nothing, since python_version defaults to PY3. + } + + attrs := &bazelPythonBinaryAttributes{ + Main: main, + Srcs: android.BazelLabelForModuleSrcExcludes(ctx, m.properties.Srcs, m.properties.Exclude_srcs), + Data: android.BazelLabelForModuleSrc(ctx, m.properties.Data), + Python_version: python_version, + } + + props := bazel.BazelTargetModuleProperties{ + // Use the native py_binary rule. + Rule_class: "py_binary", + } + + ctx.CreateBazelTargetModule(BazelPythonBinaryFactory, m.Name(), props, attrs) } type BinaryProperties struct { @@ -81,6 +163,8 @@ func NewBinary(hod android.HostOrDeviceSupported) (*Module, *binaryDecorator) { func PythonBinaryHostFactory() android.Module { module, _ := NewBinary(android.HostSupported) + android.InitBazelModule(module) + return module.init() } diff --git a/python/python.go b/python/python.go index b3e3d13b2..a078c0b58 100644 --- a/python/python.go +++ b/python/python.go @@ -125,6 +125,7 @@ type pathMapping struct { type Module struct { android.ModuleBase android.DefaultableModuleBase + android.BazelModuleBase properties BaseProperties protoProperties android.ProtoProperties