diff --git a/android/allowlists/allowlists.go b/android/allowlists/allowlists.go index 875ed346b..f826f5c3a 100644 --- a/android/allowlists/allowlists.go +++ b/android/allowlists/allowlists.go @@ -461,6 +461,9 @@ var ( // TODO(b/266459895): remove this and the placeholder BUILD file after re-enabling libunwindstack "external/rust/crates/rustc-demangle-capi":/* recursive = */ false, + + // Used for testing purposes only. Should not actually exist in the real source tree. + "testpkg/keep_build_file":/* recursive = */ false, } Bp2buildModuleAlwaysConvertList = []string{ @@ -845,9 +848,6 @@ var ( "libbase_ndk", // TODO(b/186826477): fails to link libctscamera2_jni for device (required for CtsCameraTestCases) "bouncycastle", // TODO(b/274474005): Need support for custom system_modules. - // python protos - "libprotobuf-python", // Has a handcrafted alternative - // genrule incompatibilities "brotli-fuzzer-corpus", // TODO(b/202015218): outputs are in location incompatible with bazel genrule handling. "platform_tools_properties", "build_tools_source_properties", // TODO(b/203369847): multiple genrules in the same package creating the same file diff --git a/android/bazel.go b/android/bazel.go index d32663483..e631ed474 100644 --- a/android/bazel.go +++ b/android/bazel.go @@ -537,6 +537,12 @@ func registerBp2buildConversionMutator(ctx RegisterMutatorsContext) { } func convertWithBp2build(ctx TopDownMutatorContext) { + if ctx.Config().HasBazelBuildTargetInSource(ctx) { + // Defer to the BUILD target. Generating an additional target would + // cause a BUILD file conflict. + return + } + bModule, ok := ctx.Module().(Bazelable) if !ok || !bModule.shouldConvertWithBp2build(ctx, ctx.Module()) { return diff --git a/android/config.go b/android/config.go index d4703ff03..90dbe0bf7 100644 --- a/android/config.go +++ b/android/config.go @@ -291,6 +291,10 @@ type config struct { // "--bazel-force-enabled-modules" bazelForceEnabledModules map[string]struct{} + // Names of Bazel targets as defined by BUILD files in the source tree, + // keyed by the directory in which they are defined. + bazelTargetsByDir map[string][]string + // If true, for any requests to Bazel, communicate with a Bazel proxy using // unix sockets, instead of spawning Bazel as a subprocess. UseBazelProxy bool @@ -2002,6 +2006,20 @@ func (c *config) LogMixedBuild(ctx BaseModuleContext, useBazel bool) { } } +func (c *config) HasBazelBuildTargetInSource(ctx BaseModuleContext) bool { + moduleName := ctx.Module().Name() + for _, buildTarget := range c.bazelTargetsByDir[ctx.ModuleDir()] { + if moduleName == buildTarget { + return true + } + } + return false +} + +func (c *config) SetBazelBuildFileTargets(bazelTargetsByDir map[string][]string) { + c.bazelTargetsByDir = bazelTargetsByDir +} + // ApiSurfaces directory returns the source path inside the api_surfaces repo // (relative to workspace root). func (c *config) ApiSurfacesDir(s ApiSurface, version string) string { diff --git a/cmd/soong_build/main.go b/cmd/soong_build/main.go index 2e6b6d44a..e903c0a94 100644 --- a/cmd/soong_build/main.go +++ b/cmd/soong_build/main.go @@ -21,6 +21,7 @@ import ( "fmt" "os" "path/filepath" + "regexp" "strings" "time" @@ -762,6 +763,43 @@ func excludedFromSymlinkForest(ctx *android.Context, verbose bool) []string { return excluded } +// buildTargetsByPackage parses Bazel BUILD.bazel and BUILD files under +// the workspace, and returns a map containing names of Bazel targets defined in +// these BUILD files. +// For example, maps "//foo/bar" to ["baz", "qux"] if `//foo/bar:{baz,qux}` exist. +func buildTargetsByPackage(ctx *android.Context) map[string][]string { + existingBazelFiles, err := getExistingBazelRelatedFiles(topDir) + maybeQuit(err, "Error determining existing Bazel-related files") + + result := map[string][]string{} + + // Search for instances of `name = "$NAME"` (with arbitrary spacing). + targetNameRegex := regexp.MustCompile(`(?m)^\s*name\s*=\s*\"([^\"]+)\"`) + + for _, path := range existingBazelFiles { + if !ctx.Config().Bp2buildPackageConfig.ShouldKeepExistingBuildFileForDir(filepath.Dir(path)) { + continue + } + fullPath := shared.JoinPath(topDir, path) + sourceDir := filepath.Dir(path) + fileInfo, err := os.Stat(fullPath) + maybeQuit(err, "Error accessing Bazel file '%s'", fullPath) + + if !fileInfo.IsDir() && + (fileInfo.Name() == "BUILD" || fileInfo.Name() == "BUILD.bazel") { + // Process this BUILD file. + buildFileContent, err := os.ReadFile(fullPath) + maybeQuit(err, "Error reading Bazel file '%s'", fullPath) + + matches := targetNameRegex.FindAllStringSubmatch(string(buildFileContent), -1) + for _, match := range matches { + result[sourceDir] = append(result[sourceDir], match[1]) + } + } + } + return result +} + // 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. @@ -769,6 +807,10 @@ func runBp2Build(ctx *android.Context, extraNinjaDeps []string, metricsDir strin var codegenMetrics *bp2build.CodegenMetrics ctx.EventHandler.Do("bp2build", func() { + ctx.EventHandler.Do("read_build", func() { + ctx.Config().SetBazelBuildFileTargets(buildTargetsByPackage(ctx)) + }) + // Propagate "allow misssing dependencies" bit. This is normally set in // newContext(), but we create ctx without calling that method. ctx.SetAllowMissingDependencies(ctx.Config().AllowMissingDependencies()) diff --git a/tests/bp2build_bazel_test.sh b/tests/bp2build_bazel_test.sh index 71e6af009..1ee3a3249 100755 --- a/tests/bp2build_bazel_test.sh +++ b/tests/bp2build_bazel_test.sh @@ -232,6 +232,63 @@ function test_bp2build_generates_all_buildfiles { eval "${_save_trap}" } +function test_build_files_take_precedence { + _save_trap=$(trap -p EXIT) + trap '[[ $? -ne 0 ]] && echo Are you running this locally? Try changing --sandbox_tmpfs_path to something other than /tmp/ in build/bazel/linux.bazelrc.' EXIT + _build_files_take_precedence + eval "${_save_trap}" +} + +function _build_files_take_precedence { + setup + + # This specific directory is hardcoded in bp2build as being one + # where the BUILD file should be intentionally kept. + mkdir -p testpkg/keep_build_file + cat > testpkg/keep_build_file/Android.bp <<'EOF' +genrule { + name: "print_origin", + cmd: "echo 'from_soong' > $(out)", + out: [ + "origin.txt", + ], + bazel_module: { + bp2build_available: true, + }, + } +EOF + + run_soong bp2build + run_bazel build --config=android --config=bp2build --config=ci //testpkg/keep_build_file:print_origin + + local -r output_file="$(find -L bazel-out -name origin.txt)" + if [[ ! -f "${output_file}" ]]; then + fail "Expected origin.txt to be generated, but was missing" + fi + if ! grep from_soong "${output_file}"; then + fail "Expected to find 'from_soong' in '${output_file}'" + fi + + cat > testpkg/keep_build_file/BUILD.bazel <<'EOF' +genrule( + name = "print_origin", + outs = ["origin.txt"], + cmd = "echo 'from_bazel' > $@", +) +EOF + + # Clean the workspace. There is a test infrastructure bug where run_bazel + # will symlink Android.bp files in the source directory again and thus + # pollute the workspace. + # TODO: b/286059878 - Remove this clean after the underlying bug is fixed. + run_soong clean + run_soong bp2build + run_bazel build --config=android --config=bp2build --config=ci //testpkg/keep_build_file:print_origin + if ! grep from_bazel "${output_file}"; then + fail "Expected to find 'from_bazel' in '${output_file}'" + fi +} + function test_bp2build_symlinks_files { setup mkdir -p foo