Files
build_soong/bp2build/build_conversion_test.go
Jingwen Chen 5146ac02a1 bp2build: don't generate filegroups (or error) if it contains a file
with the same name.

Also add capability to test for errors raised in bp2build mutators /
contexts.

This CL does two things to filegroups:

1) If the filegroup has only 1 source file with the same name as itself,
don't generate a filegroup target. Instead, dependents will depend
directly on the Bazel file target instead.

2) If the filegroup has more than 1 source file and 1 of them has the
same name as itself, the bp2build mutator will error out. If bp2build
on CI passes, it means that the source tree / product we're testing
against does not have such a case (which seems to be true for most
source trees).

Either way, this will allow us to be unblocked for most of the errant
filegroups (case 1) in the tree.

Test: CI
Test: New test cases in filegroup_conversion_test.go

Fixes: 194762573
Change-Id: I830c53efc8808569afe3c5f9f08436855bcdafed
2021-09-09 07:12:13 +00:00

1608 lines
44 KiB
Go

// 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 (
"android/soong/android"
"android/soong/genrule"
"strings"
"testing"
)
func TestGenerateSoongModuleTargets(t *testing.T) {
testCases := []struct {
description string
bp string
expectedBazelTarget string
}{
{
description: "only name",
bp: `custom { name: "foo" }
`,
expectedBazelTarget: `soong_module(
name = "foo",
soong_module_name = "foo",
soong_module_type = "custom",
soong_module_variant = "",
soong_module_deps = [
],
bool_prop = False,
)`,
},
{
description: "handles bool",
bp: `custom {
name: "foo",
bool_prop: true,
}
`,
expectedBazelTarget: `soong_module(
name = "foo",
soong_module_name = "foo",
soong_module_type = "custom",
soong_module_variant = "",
soong_module_deps = [
],
bool_prop = True,
)`,
},
{
description: "string escaping",
bp: `custom {
name: "foo",
owner: "a_string_with\"quotes\"_and_\\backslashes\\\\",
}
`,
expectedBazelTarget: `soong_module(
name = "foo",
soong_module_name = "foo",
soong_module_type = "custom",
soong_module_variant = "",
soong_module_deps = [
],
bool_prop = False,
owner = "a_string_with\"quotes\"_and_\\backslashes\\\\",
)`,
},
{
description: "single item string list",
bp: `custom {
name: "foo",
required: ["bar"],
}
`,
expectedBazelTarget: `soong_module(
name = "foo",
soong_module_name = "foo",
soong_module_type = "custom",
soong_module_variant = "",
soong_module_deps = [
],
bool_prop = False,
required = ["bar"],
)`,
},
{
description: "list of strings",
bp: `custom {
name: "foo",
target_required: ["qux", "bazqux"],
}
`,
expectedBazelTarget: `soong_module(
name = "foo",
soong_module_name = "foo",
soong_module_type = "custom",
soong_module_variant = "",
soong_module_deps = [
],
bool_prop = False,
target_required = [
"qux",
"bazqux",
],
)`,
},
{
description: "dist/dists",
bp: `custom {
name: "foo",
dist: {
targets: ["goal_foo"],
tag: ".foo",
},
dists: [{
targets: ["goal_bar"],
tag: ".bar",
}],
}
`,
expectedBazelTarget: `soong_module(
name = "foo",
soong_module_name = "foo",
soong_module_type = "custom",
soong_module_variant = "",
soong_module_deps = [
],
bool_prop = False,
dist = {
"tag": ".foo",
"targets": ["goal_foo"],
},
dists = [{
"tag": ".bar",
"targets": ["goal_bar"],
}],
)`,
},
{
description: "put it together",
bp: `custom {
name: "foo",
required: ["bar"],
target_required: ["qux", "bazqux"],
bool_prop: true,
owner: "custom_owner",
dists: [
{
tag: ".tag",
targets: ["my_goal"],
},
],
}
`,
expectedBazelTarget: `soong_module(
name = "foo",
soong_module_name = "foo",
soong_module_type = "custom",
soong_module_variant = "",
soong_module_deps = [
],
bool_prop = True,
dists = [{
"tag": ".tag",
"targets": ["my_goal"],
}],
owner = "custom_owner",
required = ["bar"],
target_required = [
"qux",
"bazqux",
],
)`,
},
}
dir := "."
for _, testCase := range testCases {
t.Run(testCase.description, func(t *testing.T) {
config := android.TestConfig(buildDir, nil, testCase.bp, nil)
ctx := android.NewTestContext(config)
ctx.RegisterModuleType("custom", customModuleFactory)
ctx.Register()
_, errs := ctx.ParseFileList(dir, []string{"Android.bp"})
android.FailIfErrored(t, errs)
_, errs = ctx.PrepareBuildActions(config)
android.FailIfErrored(t, errs)
codegenCtx := NewCodegenContext(config, *ctx.Context, QueryView)
bazelTargets := generateBazelTargetsForDir(codegenCtx, dir)
if actualCount, expectedCount := len(bazelTargets), 1; actualCount != expectedCount {
t.Fatalf("Expected %d bazel target, got %d", expectedCount, actualCount)
}
actualBazelTarget := bazelTargets[0]
if actualBazelTarget.content != testCase.expectedBazelTarget {
t.Errorf(
"Expected generated Bazel target to be '%s', got '%s'",
testCase.expectedBazelTarget,
actualBazelTarget.content,
)
}
})
}
}
func TestGenerateBazelTargetModules(t *testing.T) {
testCases := []bp2buildTestCase{
{
blueprint: `custom {
name: "foo",
string_list_prop: ["a", "b"],
string_prop: "a",
bazel_module: { bp2build_available: true },
}`,
expectedBazelTargets: []string{`custom(
name = "foo",
string_list_prop = [
"a",
"b",
],
string_prop = "a",
)`,
},
},
{
blueprint: `custom {
name: "control_characters",
string_list_prop: ["\t", "\n"],
string_prop: "a\t\n\r",
bazel_module: { bp2build_available: true },
}`,
expectedBazelTargets: []string{`custom(
name = "control_characters",
string_list_prop = [
"\t",
"\n",
],
string_prop = "a\t\n\r",
)`,
},
},
{
blueprint: `custom {
name: "has_dep",
arch_paths: [":dep"],
bazel_module: { bp2build_available: true },
}
custom {
name: "dep",
arch_paths: ["abc"],
bazel_module: { bp2build_available: true },
}`,
expectedBazelTargets: []string{`custom(
name = "dep",
arch_paths = ["abc"],
)`,
`custom(
name = "has_dep",
arch_paths = [":dep"],
)`,
},
},
{
blueprint: `custom {
name: "arch_paths",
arch: {
x86: {
arch_paths: ["abc"],
},
},
bazel_module: { bp2build_available: true },
}`,
expectedBazelTargets: []string{`custom(
name = "arch_paths",
arch_paths = select({
"//build/bazel/platforms/arch:x86": ["abc"],
"//conditions:default": [],
}),
)`,
},
},
{
blueprint: `custom {
name: "has_dep",
arch: {
x86: {
arch_paths: [":dep"],
},
},
bazel_module: { bp2build_available: true },
}
custom {
name: "dep",
arch_paths: ["abc"],
bazel_module: { bp2build_available: true },
}`,
expectedBazelTargets: []string{`custom(
name = "dep",
arch_paths = ["abc"],
)`,
`custom(
name = "has_dep",
arch_paths = select({
"//build/bazel/platforms/arch:x86": [":dep"],
"//conditions:default": [],
}),
)`,
},
},
}
dir := "."
for _, testCase := range testCases {
config := android.TestConfig(buildDir, nil, testCase.blueprint, nil)
ctx := android.NewTestContext(config)
registerCustomModuleForBp2buildConversion(ctx)
_, errs := ctx.ParseFileList(dir, []string{"Android.bp"})
if errored(t, testCase, errs) {
continue
}
_, errs = ctx.ResolveDependencies(config)
if errored(t, testCase, errs) {
continue
}
codegenCtx := NewCodegenContext(config, *ctx.Context, Bp2Build)
bazelTargets := generateBazelTargetsForDir(codegenCtx, dir)
if actualCount, expectedCount := len(bazelTargets), len(testCase.expectedBazelTargets); actualCount != expectedCount {
t.Errorf("Expected %d bazel target, got %d", expectedCount, actualCount)
} else {
for i, expectedBazelTarget := range testCase.expectedBazelTargets {
actualBazelTarget := bazelTargets[i]
if actualBazelTarget.content != expectedBazelTarget {
t.Errorf(
"Expected generated Bazel target to be '%s', got '%s'",
expectedBazelTarget,
actualBazelTarget.content,
)
}
}
}
}
}
func TestLoadStatements(t *testing.T) {
testCases := []struct {
bazelTargets BazelTargets
expectedLoadStatements string
}{
{
bazelTargets: BazelTargets{
BazelTarget{
name: "foo",
ruleClass: "cc_library",
bzlLoadLocation: "//build/bazel/rules:cc.bzl",
},
},
expectedLoadStatements: `load("//build/bazel/rules:cc.bzl", "cc_library")`,
},
{
bazelTargets: BazelTargets{
BazelTarget{
name: "foo",
ruleClass: "cc_library",
bzlLoadLocation: "//build/bazel/rules:cc.bzl",
},
BazelTarget{
name: "bar",
ruleClass: "cc_library",
bzlLoadLocation: "//build/bazel/rules:cc.bzl",
},
},
expectedLoadStatements: `load("//build/bazel/rules:cc.bzl", "cc_library")`,
},
{
bazelTargets: BazelTargets{
BazelTarget{
name: "foo",
ruleClass: "cc_library",
bzlLoadLocation: "//build/bazel/rules:cc.bzl",
},
BazelTarget{
name: "bar",
ruleClass: "cc_binary",
bzlLoadLocation: "//build/bazel/rules:cc.bzl",
},
},
expectedLoadStatements: `load("//build/bazel/rules:cc.bzl", "cc_binary", "cc_library")`,
},
{
bazelTargets: BazelTargets{
BazelTarget{
name: "foo",
ruleClass: "cc_library",
bzlLoadLocation: "//build/bazel/rules:cc.bzl",
},
BazelTarget{
name: "bar",
ruleClass: "cc_binary",
bzlLoadLocation: "//build/bazel/rules:cc.bzl",
},
BazelTarget{
name: "baz",
ruleClass: "java_binary",
bzlLoadLocation: "//build/bazel/rules:java.bzl",
},
},
expectedLoadStatements: `load("//build/bazel/rules:cc.bzl", "cc_binary", "cc_library")
load("//build/bazel/rules:java.bzl", "java_binary")`,
},
{
bazelTargets: BazelTargets{
BazelTarget{
name: "foo",
ruleClass: "cc_binary",
bzlLoadLocation: "//build/bazel/rules:cc.bzl",
},
BazelTarget{
name: "bar",
ruleClass: "java_binary",
bzlLoadLocation: "//build/bazel/rules:java.bzl",
},
BazelTarget{
name: "baz",
ruleClass: "genrule",
// Note: no bzlLoadLocation for native rules
},
},
expectedLoadStatements: `load("//build/bazel/rules:cc.bzl", "cc_binary")
load("//build/bazel/rules:java.bzl", "java_binary")`,
},
}
for _, testCase := range testCases {
actual := testCase.bazelTargets.LoadStatements()
expected := testCase.expectedLoadStatements
if actual != expected {
t.Fatalf("Expected load statements to be %s, got %s", expected, actual)
}
}
}
func TestGenerateBazelTargetModules_OneToMany_LoadedFromStarlark(t *testing.T) {
testCases := []struct {
bp string
expectedBazelTarget string
expectedBazelTargetCount int
expectedLoadStatements string
}{
{
bp: `custom {
name: "bar",
bazel_module: { bp2build_available: true },
}`,
expectedBazelTarget: `my_library(
name = "bar",
)
proto_library(
name = "bar_proto_library_deps",
)
my_proto_library(
name = "bar_my_proto_library_deps",
)`,
expectedBazelTargetCount: 3,
expectedLoadStatements: `load("//build/bazel/rules:proto.bzl", "my_proto_library", "proto_library")
load("//build/bazel/rules:rules.bzl", "my_library")`,
},
}
dir := "."
for _, testCase := range testCases {
config := android.TestConfig(buildDir, nil, testCase.bp, nil)
ctx := android.NewTestContext(config)
ctx.RegisterModuleType("custom", customModuleFactory)
ctx.RegisterBp2BuildMutator("custom", customBp2BuildMutatorFromStarlark)
ctx.RegisterForBazelConversion()
_, errs := ctx.ParseFileList(dir, []string{"Android.bp"})
android.FailIfErrored(t, errs)
_, errs = ctx.ResolveDependencies(config)
android.FailIfErrored(t, errs)
codegenCtx := NewCodegenContext(config, *ctx.Context, Bp2Build)
bazelTargets := generateBazelTargetsForDir(codegenCtx, dir)
if actualCount := len(bazelTargets); actualCount != testCase.expectedBazelTargetCount {
t.Fatalf("Expected %d bazel target, got %d", testCase.expectedBazelTargetCount, actualCount)
}
actualBazelTargets := bazelTargets.String()
if actualBazelTargets != testCase.expectedBazelTarget {
t.Errorf(
"Expected generated Bazel target to be '%s', got '%s'",
testCase.expectedBazelTarget,
actualBazelTargets,
)
}
actualLoadStatements := bazelTargets.LoadStatements()
if actualLoadStatements != testCase.expectedLoadStatements {
t.Errorf(
"Expected generated load statements to be '%s', got '%s'",
testCase.expectedLoadStatements,
actualLoadStatements,
)
}
}
}
func TestModuleTypeBp2Build(t *testing.T) {
otherGenruleBp := map[string]string{
"other/Android.bp": `genrule {
name: "foo.tool",
out: ["foo_tool.out"],
srcs: ["foo_tool.in"],
cmd: "cp $(in) $(out)",
}
genrule {
name: "other.tool",
out: ["other_tool.out"],
srcs: ["other_tool.in"],
cmd: "cp $(in) $(out)",
}`,
}
testCases := []bp2buildTestCase{
{
description: "filegroup with does not specify srcs",
moduleTypeUnderTest: "filegroup",
moduleTypeUnderTestFactory: android.FileGroupFactory,
moduleTypeUnderTestBp2BuildMutator: android.FilegroupBp2Build,
blueprint: `filegroup {
name: "fg_foo",
bazel_module: { bp2build_available: true },
}`,
expectedBazelTargets: []string{
`filegroup(
name = "fg_foo",
)`,
},
},
{
description: "filegroup with no srcs",
moduleTypeUnderTest: "filegroup",
moduleTypeUnderTestFactory: android.FileGroupFactory,
moduleTypeUnderTestBp2BuildMutator: android.FilegroupBp2Build,
blueprint: `filegroup {
name: "fg_foo",
srcs: [],
bazel_module: { bp2build_available: true },
}`,
expectedBazelTargets: []string{
`filegroup(
name = "fg_foo",
)`,
},
},
{
description: "filegroup with srcs",
moduleTypeUnderTest: "filegroup",
moduleTypeUnderTestFactory: android.FileGroupFactory,
moduleTypeUnderTestBp2BuildMutator: android.FilegroupBp2Build,
blueprint: `filegroup {
name: "fg_foo",
srcs: ["a", "b"],
bazel_module: { bp2build_available: true },
}`,
expectedBazelTargets: []string{`filegroup(
name = "fg_foo",
srcs = [
"a",
"b",
],
)`,
},
},
{
description: "filegroup with excludes srcs",
moduleTypeUnderTest: "filegroup",
moduleTypeUnderTestFactory: android.FileGroupFactory,
moduleTypeUnderTestBp2BuildMutator: android.FilegroupBp2Build,
blueprint: `filegroup {
name: "fg_foo",
srcs: ["a", "b"],
exclude_srcs: ["a"],
bazel_module: { bp2build_available: true },
}`,
expectedBazelTargets: []string{`filegroup(
name = "fg_foo",
srcs = ["b"],
)`,
},
},
{
description: "filegroup with glob",
moduleTypeUnderTest: "filegroup",
moduleTypeUnderTestFactory: android.FileGroupFactory,
moduleTypeUnderTestBp2BuildMutator: android.FilegroupBp2Build,
blueprint: `filegroup {
name: "foo",
srcs: ["**/*.txt"],
bazel_module: { bp2build_available: true },
}`,
expectedBazelTargets: []string{`filegroup(
name = "foo",
srcs = [
"other/a.txt",
"other/b.txt",
"other/subdir/a.txt",
],
)`,
},
filesystem: map[string]string{
"other/a.txt": "",
"other/b.txt": "",
"other/subdir/a.txt": "",
"other/file": "",
},
},
{
description: "filegroup with glob in subdir",
moduleTypeUnderTest: "filegroup",
moduleTypeUnderTestFactory: android.FileGroupFactory,
moduleTypeUnderTestBp2BuildMutator: android.FilegroupBp2Build,
blueprint: `filegroup {
name: "foo",
srcs: ["a.txt"],
bazel_module: { bp2build_available: true },
}`,
dir: "other",
expectedBazelTargets: []string{`filegroup(
name = "fg_foo",
srcs = [
"a.txt",
"b.txt",
"subdir/a.txt",
],
)`,
},
filesystem: map[string]string{
"other/Android.bp": `filegroup {
name: "fg_foo",
srcs: ["**/*.txt"],
bazel_module: { bp2build_available: true },
}`,
"other/a.txt": "",
"other/b.txt": "",
"other/subdir/a.txt": "",
"other/file": "",
},
},
{
description: "depends_on_other_dir_module",
moduleTypeUnderTest: "filegroup",
moduleTypeUnderTestFactory: android.FileGroupFactory,
moduleTypeUnderTestBp2BuildMutator: android.FilegroupBp2Build,
blueprint: `filegroup {
name: "foobar",
srcs: [
":foo",
"c",
],
bazel_module: { bp2build_available: true },
}`,
expectedBazelTargets: []string{`filegroup(
name = "foobar",
srcs = [
"//other:foo",
"c",
],
)`,
},
filesystem: map[string]string{
"other/Android.bp": `filegroup {
name: "foo",
srcs: ["a", "b"],
}`,
},
},
{
description: "genrule with command line variable replacements",
moduleTypeUnderTest: "genrule",
moduleTypeUnderTestFactory: genrule.GenRuleFactory,
moduleTypeUnderTestBp2BuildMutator: genrule.GenruleBp2Build,
blueprint: `genrule {
name: "foo.tool",
out: ["foo_tool.out"],
srcs: ["foo_tool.in"],
cmd: "cp $(in) $(out)",
bazel_module: { bp2build_available: true },
}
genrule {
name: "foo",
out: ["foo.out"],
srcs: ["foo.in"],
tools: [":foo.tool"],
cmd: "$(location :foo.tool) --genDir=$(genDir) arg $(in) $(out)",
bazel_module: { bp2build_available: true },
}`,
expectedBazelTargets: []string{
`genrule(
name = "foo",
cmd = "$(location :foo.tool) --genDir=$(GENDIR) arg $(SRCS) $(OUTS)",
outs = ["foo.out"],
srcs = ["foo.in"],
tools = [":foo.tool"],
)`,
`genrule(
name = "foo.tool",
cmd = "cp $(SRCS) $(OUTS)",
outs = ["foo_tool.out"],
srcs = ["foo_tool.in"],
)`,
},
},
{
description: "genrule using $(locations :label)",
moduleTypeUnderTest: "genrule",
moduleTypeUnderTestFactory: genrule.GenRuleFactory,
moduleTypeUnderTestBp2BuildMutator: genrule.GenruleBp2Build,
blueprint: `genrule {
name: "foo.tools",
out: ["foo_tool.out", "foo_tool2.out"],
srcs: ["foo_tool.in"],
cmd: "cp $(in) $(out)",
bazel_module: { bp2build_available: true },
}
genrule {
name: "foo",
out: ["foo.out"],
srcs: ["foo.in"],
tools: [":foo.tools"],
cmd: "$(locations :foo.tools) -s $(out) $(in)",
bazel_module: { bp2build_available: true },
}`,
expectedBazelTargets: []string{`genrule(
name = "foo",
cmd = "$(locations :foo.tools) -s $(OUTS) $(SRCS)",
outs = ["foo.out"],
srcs = ["foo.in"],
tools = [":foo.tools"],
)`,
`genrule(
name = "foo.tools",
cmd = "cp $(SRCS) $(OUTS)",
outs = [
"foo_tool.out",
"foo_tool2.out",
],
srcs = ["foo_tool.in"],
)`,
},
},
{
description: "genrule using $(locations //absolute:label)",
moduleTypeUnderTest: "genrule",
moduleTypeUnderTestFactory: genrule.GenRuleFactory,
moduleTypeUnderTestBp2BuildMutator: genrule.GenruleBp2Build,
blueprint: `genrule {
name: "foo",
out: ["foo.out"],
srcs: ["foo.in"],
tool_files: [":foo.tool"],
cmd: "$(locations :foo.tool) -s $(out) $(in)",
bazel_module: { bp2build_available: true },
}`,
expectedBazelTargets: []string{`genrule(
name = "foo",
cmd = "$(locations //other:foo.tool) -s $(OUTS) $(SRCS)",
outs = ["foo.out"],
srcs = ["foo.in"],
tools = ["//other:foo.tool"],
)`,
},
filesystem: otherGenruleBp,
},
{
description: "genrule srcs using $(locations //absolute:label)",
moduleTypeUnderTest: "genrule",
moduleTypeUnderTestFactory: genrule.GenRuleFactory,
moduleTypeUnderTestBp2BuildMutator: genrule.GenruleBp2Build,
blueprint: `genrule {
name: "foo",
out: ["foo.out"],
srcs: [":other.tool"],
tool_files: [":foo.tool"],
cmd: "$(locations :foo.tool) -s $(out) $(location :other.tool)",
bazel_module: { bp2build_available: true },
}`,
expectedBazelTargets: []string{`genrule(
name = "foo",
cmd = "$(locations //other:foo.tool) -s $(OUTS) $(location //other:other.tool)",
outs = ["foo.out"],
srcs = ["//other:other.tool"],
tools = ["//other:foo.tool"],
)`,
},
filesystem: otherGenruleBp,
},
{
description: "genrule using $(location) label should substitute first tool label automatically",
moduleTypeUnderTest: "genrule",
moduleTypeUnderTestFactory: genrule.GenRuleFactory,
moduleTypeUnderTestBp2BuildMutator: genrule.GenruleBp2Build,
blueprint: `genrule {
name: "foo",
out: ["foo.out"],
srcs: ["foo.in"],
tool_files: [":foo.tool", ":other.tool"],
cmd: "$(location) -s $(out) $(in)",
bazel_module: { bp2build_available: true },
}`,
expectedBazelTargets: []string{`genrule(
name = "foo",
cmd = "$(location //other:foo.tool) -s $(OUTS) $(SRCS)",
outs = ["foo.out"],
srcs = ["foo.in"],
tools = [
"//other:foo.tool",
"//other:other.tool",
],
)`,
},
filesystem: otherGenruleBp,
},
{
description: "genrule using $(locations) label should substitute first tool label automatically",
moduleTypeUnderTest: "genrule",
moduleTypeUnderTestFactory: genrule.GenRuleFactory,
moduleTypeUnderTestBp2BuildMutator: genrule.GenruleBp2Build,
blueprint: `genrule {
name: "foo",
out: ["foo.out"],
srcs: ["foo.in"],
tools: [":foo.tool", ":other.tool"],
cmd: "$(locations) -s $(out) $(in)",
bazel_module: { bp2build_available: true },
}`,
expectedBazelTargets: []string{`genrule(
name = "foo",
cmd = "$(locations //other:foo.tool) -s $(OUTS) $(SRCS)",
outs = ["foo.out"],
srcs = ["foo.in"],
tools = [
"//other:foo.tool",
"//other:other.tool",
],
)`,
},
filesystem: otherGenruleBp,
},
{
description: "genrule without tools or tool_files can convert successfully",
moduleTypeUnderTest: "genrule",
moduleTypeUnderTestFactory: genrule.GenRuleFactory,
moduleTypeUnderTestBp2BuildMutator: genrule.GenruleBp2Build,
blueprint: `genrule {
name: "foo",
out: ["foo.out"],
srcs: ["foo.in"],
cmd: "cp $(in) $(out)",
bazel_module: { bp2build_available: true },
}`,
expectedBazelTargets: []string{`genrule(
name = "foo",
cmd = "cp $(SRCS) $(OUTS)",
outs = ["foo.out"],
srcs = ["foo.in"],
)`,
},
},
}
dir := "."
for _, testCase := range testCases {
fs := make(map[string][]byte)
toParse := []string{
"Android.bp",
}
for f, content := range testCase.filesystem {
if strings.HasSuffix(f, "Android.bp") {
toParse = append(toParse, f)
}
fs[f] = []byte(content)
}
config := android.TestConfig(buildDir, nil, testCase.blueprint, fs)
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, errs) {
continue
}
_, errs = ctx.ResolveDependencies(config)
if errored(t, testCase, errs) {
continue
}
checkDir := dir
if testCase.dir != "" {
checkDir = testCase.dir
}
codegenCtx := NewCodegenContext(config, *ctx.Context, Bp2Build)
bazelTargets := generateBazelTargetsForDir(codegenCtx, checkDir)
if actualCount, expectedCount := len(bazelTargets), len(testCase.expectedBazelTargets); actualCount != expectedCount {
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,
)
}
}
}
}
}
type bp2buildMutator = func(android.TopDownMutatorContext)
func TestBp2BuildInlinesDefaults(t *testing.T) {
testCases := []struct {
moduleTypesUnderTest map[string]android.ModuleFactory
bp2buildMutatorsUnderTest map[string]bp2buildMutator
bp string
expectedBazelTarget string
description string
}{
{
moduleTypesUnderTest: map[string]android.ModuleFactory{
"genrule": genrule.GenRuleFactory,
"genrule_defaults": func() android.Module { return genrule.DefaultsFactory() },
},
bp2buildMutatorsUnderTest: map[string]bp2buildMutator{
"genrule": genrule.GenruleBp2Build,
},
bp: `genrule_defaults {
name: "gen_defaults",
cmd: "do-something $(in) $(out)",
}
genrule {
name: "gen",
out: ["out"],
srcs: ["in1"],
defaults: ["gen_defaults"],
bazel_module: { bp2build_available: true },
}
`,
expectedBazelTarget: `genrule(
name = "gen",
cmd = "do-something $(SRCS) $(OUTS)",
outs = ["out"],
srcs = ["in1"],
)`,
description: "genrule applies properties from a genrule_defaults dependency if not specified",
},
{
moduleTypesUnderTest: map[string]android.ModuleFactory{
"genrule": genrule.GenRuleFactory,
"genrule_defaults": func() android.Module { return genrule.DefaultsFactory() },
},
bp2buildMutatorsUnderTest: map[string]bp2buildMutator{
"genrule": genrule.GenruleBp2Build,
},
bp: `genrule_defaults {
name: "gen_defaults",
out: ["out-from-defaults"],
srcs: ["in-from-defaults"],
cmd: "cmd-from-defaults",
}
genrule {
name: "gen",
out: ["out"],
srcs: ["in1"],
defaults: ["gen_defaults"],
cmd: "do-something $(in) $(out)",
bazel_module: { bp2build_available: true },
}
`,
expectedBazelTarget: `genrule(
name = "gen",
cmd = "do-something $(SRCS) $(OUTS)",
outs = [
"out-from-defaults",
"out",
],
srcs = [
"in-from-defaults",
"in1",
],
)`,
description: "genrule does merges properties from a genrule_defaults dependency, latest-first",
},
{
moduleTypesUnderTest: map[string]android.ModuleFactory{
"genrule": genrule.GenRuleFactory,
"genrule_defaults": func() android.Module { return genrule.DefaultsFactory() },
},
bp2buildMutatorsUnderTest: map[string]bp2buildMutator{
"genrule": genrule.GenruleBp2Build,
},
bp: `genrule_defaults {
name: "gen_defaults1",
cmd: "cp $(in) $(out)",
}
genrule_defaults {
name: "gen_defaults2",
srcs: ["in1"],
}
genrule {
name: "gen",
out: ["out"],
defaults: ["gen_defaults1", "gen_defaults2"],
bazel_module: { bp2build_available: true },
}
`,
expectedBazelTarget: `genrule(
name = "gen",
cmd = "cp $(SRCS) $(OUTS)",
outs = ["out"],
srcs = ["in1"],
)`,
description: "genrule applies properties from list of genrule_defaults",
},
{
moduleTypesUnderTest: map[string]android.ModuleFactory{
"genrule": genrule.GenRuleFactory,
"genrule_defaults": func() android.Module { return genrule.DefaultsFactory() },
},
bp2buildMutatorsUnderTest: map[string]bp2buildMutator{
"genrule": genrule.GenruleBp2Build,
},
bp: `genrule_defaults {
name: "gen_defaults1",
defaults: ["gen_defaults2"],
cmd: "cmd1 $(in) $(out)", // overrides gen_defaults2's cmd property value.
}
genrule_defaults {
name: "gen_defaults2",
defaults: ["gen_defaults3"],
cmd: "cmd2 $(in) $(out)",
out: ["out-from-2"],
srcs: ["in1"],
}
genrule_defaults {
name: "gen_defaults3",
out: ["out-from-3"],
srcs: ["srcs-from-3"],
}
genrule {
name: "gen",
out: ["out"],
defaults: ["gen_defaults1"],
bazel_module: { bp2build_available: true },
}
`,
expectedBazelTarget: `genrule(
name = "gen",
cmd = "cmd1 $(SRCS) $(OUTS)",
outs = [
"out-from-3",
"out-from-2",
"out",
],
srcs = [
"srcs-from-3",
"in1",
],
)`,
description: "genrule applies properties from genrule_defaults transitively",
},
}
dir := "."
for _, testCase := range testCases {
config := android.TestConfig(buildDir, nil, testCase.bp, nil)
ctx := android.NewTestContext(config)
for m, factory := range testCase.moduleTypesUnderTest {
ctx.RegisterModuleType(m, factory)
}
for mutator, f := range testCase.bp2buildMutatorsUnderTest {
ctx.RegisterBp2BuildMutator(mutator, f)
}
ctx.RegisterForBazelConversion()
_, errs := ctx.ParseFileList(dir, []string{"Android.bp"})
android.FailIfErrored(t, errs)
_, errs = ctx.ResolveDependencies(config)
android.FailIfErrored(t, errs)
codegenCtx := NewCodegenContext(config, *ctx.Context, Bp2Build)
bazelTargets := generateBazelTargetsForDir(codegenCtx, dir)
if actualCount := len(bazelTargets); actualCount != 1 {
t.Fatalf("%s: Expected 1 bazel target, got %d", testCase.description, actualCount)
}
actualBazelTarget := bazelTargets[0]
if actualBazelTarget.content != testCase.expectedBazelTarget {
t.Errorf(
"%s: Expected generated Bazel target to be '%s', got '%s'",
testCase.description,
testCase.expectedBazelTarget,
actualBazelTarget.content,
)
}
}
}
func TestAllowlistingBp2buildTargetsExplicitly(t *testing.T) {
testCases := []struct {
moduleTypeUnderTest string
moduleTypeUnderTestFactory android.ModuleFactory
moduleTypeUnderTestBp2BuildMutator bp2buildMutator
bp string
expectedCount int
description string
}{
{
description: "explicitly unavailable",
moduleTypeUnderTest: "filegroup",
moduleTypeUnderTestFactory: android.FileGroupFactory,
moduleTypeUnderTestBp2BuildMutator: android.FilegroupBp2Build,
bp: `filegroup {
name: "foo",
srcs: ["a", "b"],
bazel_module: { bp2build_available: false },
}`,
expectedCount: 0,
},
{
description: "implicitly unavailable",
moduleTypeUnderTest: "filegroup",
moduleTypeUnderTestFactory: android.FileGroupFactory,
moduleTypeUnderTestBp2BuildMutator: android.FilegroupBp2Build,
bp: `filegroup {
name: "foo",
srcs: ["a", "b"],
}`,
expectedCount: 0,
},
{
description: "explicitly available",
moduleTypeUnderTest: "filegroup",
moduleTypeUnderTestFactory: android.FileGroupFactory,
moduleTypeUnderTestBp2BuildMutator: android.FilegroupBp2Build,
bp: `filegroup {
name: "foo",
srcs: ["a", "b"],
bazel_module: { bp2build_available: true },
}`,
expectedCount: 1,
},
{
description: "generates more than 1 target if needed",
moduleTypeUnderTest: "custom",
moduleTypeUnderTestFactory: customModuleFactory,
moduleTypeUnderTestBp2BuildMutator: customBp2BuildMutatorFromStarlark,
bp: `custom {
name: "foo",
bazel_module: { bp2build_available: true },
}`,
expectedCount: 3,
},
}
dir := "."
for _, testCase := range testCases {
t.Run(testCase.description, func(t *testing.T) {
config := android.TestConfig(buildDir, nil, testCase.bp, nil)
ctx := android.NewTestContext(config)
ctx.RegisterModuleType(testCase.moduleTypeUnderTest, testCase.moduleTypeUnderTestFactory)
ctx.RegisterBp2BuildMutator(testCase.moduleTypeUnderTest, testCase.moduleTypeUnderTestBp2BuildMutator)
ctx.RegisterForBazelConversion()
_, errs := ctx.ParseFileList(dir, []string{"Android.bp"})
android.FailIfErrored(t, errs)
_, errs = ctx.ResolveDependencies(config)
android.FailIfErrored(t, errs)
codegenCtx := NewCodegenContext(config, *ctx.Context, Bp2Build)
bazelTargets := generateBazelTargetsForDir(codegenCtx, dir)
if actualCount := len(bazelTargets); actualCount != testCase.expectedCount {
t.Fatalf("%s: Expected %d bazel target, got %d", testCase.description, testCase.expectedCount, actualCount)
}
})
}
}
func TestAllowlistingBp2buildTargetsWithConfig(t *testing.T) {
testCases := []struct {
moduleTypeUnderTest string
moduleTypeUnderTestFactory android.ModuleFactory
moduleTypeUnderTestBp2BuildMutator bp2buildMutator
expectedCount map[string]int
description string
bp2buildConfig android.Bp2BuildConfig
checkDir string
fs map[string]string
}{
{
description: "test bp2build config package and subpackages config",
moduleTypeUnderTest: "filegroup",
moduleTypeUnderTestFactory: android.FileGroupFactory,
moduleTypeUnderTestBp2BuildMutator: android.FilegroupBp2Build,
expectedCount: map[string]int{
"migrated": 1,
"migrated/but_not_really": 0,
"migrated/but_not_really/but_really": 1,
"not_migrated": 0,
"also_not_migrated": 0,
},
bp2buildConfig: android.Bp2BuildConfig{
"migrated": android.Bp2BuildDefaultTrueRecursively,
"migrated/but_not_really": android.Bp2BuildDefaultFalse,
"not_migrated": android.Bp2BuildDefaultFalse,
},
fs: map[string]string{
"migrated/Android.bp": `filegroup { name: "a" }`,
"migrated/but_not_really/Android.bp": `filegroup { name: "b" }`,
"migrated/but_not_really/but_really/Android.bp": `filegroup { name: "c" }`,
"not_migrated/Android.bp": `filegroup { name: "d" }`,
"also_not_migrated/Android.bp": `filegroup { name: "e" }`,
},
},
{
description: "test bp2build config opt-in and opt-out",
moduleTypeUnderTest: "filegroup",
moduleTypeUnderTestFactory: android.FileGroupFactory,
moduleTypeUnderTestBp2BuildMutator: android.FilegroupBp2Build,
expectedCount: map[string]int{
"package-opt-in": 2,
"package-opt-in/subpackage": 0,
"package-opt-out": 1,
"package-opt-out/subpackage": 0,
},
bp2buildConfig: android.Bp2BuildConfig{
"package-opt-in": android.Bp2BuildDefaultFalse,
"package-opt-out": android.Bp2BuildDefaultTrueRecursively,
},
fs: map[string]string{
"package-opt-in/Android.bp": `
filegroup { name: "opt-in-a" }
filegroup { name: "opt-in-b", bazel_module: { bp2build_available: true } }
filegroup { name: "opt-in-c", bazel_module: { bp2build_available: true } }
`,
"package-opt-in/subpackage/Android.bp": `
filegroup { name: "opt-in-d" } // parent package not configured to DefaultTrueRecursively
`,
"package-opt-out/Android.bp": `
filegroup { name: "opt-out-a" }
filegroup { name: "opt-out-b", bazel_module: { bp2build_available: false } }
filegroup { name: "opt-out-c", bazel_module: { bp2build_available: false } }
`,
"package-opt-out/subpackage/Android.bp": `
filegroup { name: "opt-out-g", bazel_module: { bp2build_available: false } }
filegroup { name: "opt-out-h", bazel_module: { bp2build_available: false } }
`,
},
},
}
dir := "."
for _, testCase := range testCases {
fs := make(map[string][]byte)
toParse := []string{
"Android.bp",
}
for f, content := range testCase.fs {
if strings.HasSuffix(f, "Android.bp") {
toParse = append(toParse, f)
}
fs[f] = []byte(content)
}
config := android.TestConfig(buildDir, nil, "", fs)
ctx := android.NewTestContext(config)
ctx.RegisterModuleType(testCase.moduleTypeUnderTest, testCase.moduleTypeUnderTestFactory)
ctx.RegisterBp2BuildMutator(testCase.moduleTypeUnderTest, testCase.moduleTypeUnderTestBp2BuildMutator)
ctx.RegisterBp2BuildConfig(testCase.bp2buildConfig)
ctx.RegisterForBazelConversion()
_, errs := ctx.ParseFileList(dir, toParse)
android.FailIfErrored(t, errs)
_, errs = ctx.ResolveDependencies(config)
android.FailIfErrored(t, errs)
codegenCtx := NewCodegenContext(config, *ctx.Context, Bp2Build)
// For each directory, test that the expected number of generated targets is correct.
for dir, expectedCount := range testCase.expectedCount {
bazelTargets := generateBazelTargetsForDir(codegenCtx, dir)
if actualCount := len(bazelTargets); actualCount != expectedCount {
t.Fatalf(
"%s: Expected %d bazel target for %s package, got %d",
testCase.description,
expectedCount,
dir,
actualCount)
}
}
}
}
func TestCombineBuildFilesBp2buildTargets(t *testing.T) {
testCases := []bp2buildTestCase{
{
description: "filegroup bazel_module.label",
moduleTypeUnderTest: "filegroup",
moduleTypeUnderTestFactory: android.FileGroupFactory,
moduleTypeUnderTestBp2BuildMutator: android.FilegroupBp2Build,
blueprint: `filegroup {
name: "fg_foo",
bazel_module: { label: "//other:fg_foo" },
}`,
expectedBazelTargets: []string{
`// BUILD file`,
},
filesystem: map[string]string{
"other/BUILD.bazel": `// BUILD file`,
},
},
{
description: "multiple bazel_module.label same BUILD",
moduleTypeUnderTest: "filegroup",
moduleTypeUnderTestFactory: android.FileGroupFactory,
moduleTypeUnderTestBp2BuildMutator: android.FilegroupBp2Build,
blueprint: `filegroup {
name: "fg_foo",
bazel_module: { label: "//other:fg_foo" },
}
filegroup {
name: "foo",
bazel_module: { label: "//other:foo" },
}`,
expectedBazelTargets: []string{
`// BUILD file`,
},
filesystem: map[string]string{
"other/BUILD.bazel": `// BUILD file`,
},
},
{
description: "filegroup bazel_module.label and bp2build in subdir",
moduleTypeUnderTest: "filegroup",
moduleTypeUnderTestFactory: android.FileGroupFactory,
moduleTypeUnderTestBp2BuildMutator: android.FilegroupBp2Build,
dir: "other",
blueprint: ``,
filesystem: map[string]string{
"other/Android.bp": `filegroup {
name: "fg_foo",
bazel_module: {
bp2build_available: true,
},
}
filegroup {
name: "fg_bar",
bazel_module: {
label: "//other:fg_bar"
},
}`,
"other/BUILD.bazel": `// definition for fg_bar`,
},
expectedBazelTargets: []string{
`filegroup(
name = "fg_foo",
)`, `// definition for fg_bar`,
},
},
{
description: "filegroup bazel_module.label and filegroup bp2build",
moduleTypeUnderTest: "filegroup",
moduleTypeUnderTestFactory: android.FileGroupFactory,
moduleTypeUnderTestBp2BuildMutator: android.FilegroupBp2Build,
blueprint: `filegroup {
name: "fg_foo",
bazel_module: {
label: "//other:fg_foo",
},
}
filegroup {
name: "fg_bar",
bazel_module: {
bp2build_available: true,
},
}`,
expectedBazelTargets: []string{
`filegroup(
name = "fg_bar",
)`,
`// BUILD file`,
},
filesystem: map[string]string{
"other/BUILD.bazel": `// BUILD file`,
},
},
}
dir := "."
for _, testCase := range testCases {
t.Run(testCase.description, func(t *testing.T) {
fs := make(map[string][]byte)
toParse := []string{
"Android.bp",
}
for f, content := range testCase.filesystem {
if strings.HasSuffix(f, "Android.bp") {
toParse = append(toParse, f)
}
fs[f] = []byte(content)
}
config := android.TestConfig(buildDir, nil, testCase.blueprint, fs)
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, errs) {
return
}
_, errs = ctx.ResolveDependencies(config)
if errored(t, testCase, errs) {
return
}
checkDir := dir
if testCase.dir != "" {
checkDir = testCase.dir
}
bazelTargets := generateBazelTargetsForDir(NewCodegenContext(config, *ctx.Context, Bp2Build), checkDir)
bazelTargets.sort()
actualCount := len(bazelTargets)
expectedCount := len(testCase.expectedBazelTargets)
if actualCount != expectedCount {
t.Errorf("Expected %d bazel target, got %d\n%s", expectedCount, actualCount, bazelTargets)
}
if !strings.Contains(bazelTargets.String(), "Section: Handcrafted targets. ") {
t.Errorf("Expected string representation of bazelTargets to contain handcrafted section header.")
}
for i, target := range bazelTargets {
actualContent := target.content
expectedContent := testCase.expectedBazelTargets[i]
if expectedContent != actualContent {
t.Errorf(
"Expected generated Bazel target to be '%s', got '%s'",
expectedContent,
actualContent,
)
}
}
})
}
}
func TestGlobExcludeSrcs(t *testing.T) {
testCases := []bp2buildTestCase{
{
description: "filegroup top level exclude_srcs",
moduleTypeUnderTest: "filegroup",
moduleTypeUnderTestFactory: android.FileGroupFactory,
moduleTypeUnderTestBp2BuildMutator: android.FilegroupBp2Build,
blueprint: `filegroup {
name: "fg_foo",
srcs: ["**/*.txt"],
exclude_srcs: ["c.txt"],
bazel_module: { bp2build_available: true },
}`,
expectedBazelTargets: []string{`filegroup(
name = "fg_foo",
srcs = [
"a.txt",
"b.txt",
"//dir:e.txt",
"//dir:f.txt",
],
)`,
},
filesystem: map[string]string{
"a.txt": "",
"b.txt": "",
"c.txt": "",
"dir/Android.bp": "",
"dir/e.txt": "",
"dir/f.txt": "",
},
},
{
description: "filegroup in subdir exclude_srcs",
moduleTypeUnderTest: "filegroup",
moduleTypeUnderTestFactory: android.FileGroupFactory,
moduleTypeUnderTestBp2BuildMutator: android.FilegroupBp2Build,
blueprint: "",
dir: "dir",
filesystem: map[string]string{
"dir/Android.bp": `filegroup {
name: "fg_foo",
srcs: ["**/*.txt"],
exclude_srcs: ["b.txt"],
bazel_module: { bp2build_available: true },
}
`,
"dir/a.txt": "",
"dir/b.txt": "",
"dir/subdir/Android.bp": "",
"dir/subdir/e.txt": "",
"dir/subdir/f.txt": "",
},
expectedBazelTargets: []string{`filegroup(
name = "fg_foo",
srcs = [
"a.txt",
"//dir/subdir:e.txt",
"//dir/subdir:f.txt",
],
)`,
},
},
}
dir := "."
for _, testCase := range testCases {
fs := make(map[string][]byte)
toParse := []string{
"Android.bp",
}
for f, content := range testCase.filesystem {
if strings.HasSuffix(f, "Android.bp") {
toParse = append(toParse, f)
}
fs[f] = []byte(content)
}
config := android.TestConfig(buildDir, nil, testCase.blueprint, fs)
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, errs) {
continue
}
_, errs = ctx.ResolveDependencies(config)
if errored(t, testCase, errs) {
continue
}
checkDir := dir
if testCase.dir != "" {
checkDir = testCase.dir
}
bazelTargets := generateBazelTargetsForDir(NewCodegenContext(config, *ctx.Context, Bp2Build), checkDir)
if actualCount, expectedCount := len(bazelTargets), len(testCase.expectedBazelTargets); actualCount != expectedCount {
t.Errorf("%s: Expected %d bazel target, got %d\n%s", testCase.description, expectedCount, actualCount, bazelTargets)
} 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,
)
}
}
}
}
}