Avoid deleting bp2build directory
With this CL, we avoid deleting the bp2build directory and regenerating
all BUILD files. Instead, we regenerate BUILD files which have changes
on the filesystem, and delete old BUILD files which should no longer
exist.
This improves incremental Bazel server performance by about ~5 seconds.
Previously, Bazel would have cache misses for regenerated packages, and
would thus need to analyze them even if no meaningful changes took
place.
For ease of implementation, we avoid removing stale (empty) directories
from the bp2build workspace, but this should have no effect on the
build. (These will be removed on next clean)
Test: New integration tests
Test: Manual benchmarking in conjunction with persistent bazel server
Change-Id: I3e489ff403be34040122876012329060a2506366
diff --git a/bp2build/bp2build.go b/bp2build/bp2build.go
index 062eba8..d1dfb9d 100644
--- a/bp2build/bp2build.go
+++ b/bp2build/bp2build.go
@@ -17,21 +17,57 @@
import (
"fmt"
"os"
+ "path/filepath"
"strings"
"android/soong/android"
"android/soong/bazel"
+ "android/soong/shared"
)
+func deleteFilesExcept(ctx *CodegenContext, rootOutputPath android.OutputPath, except []BazelFile) {
+ // Delete files that should no longer be present.
+ bp2buildDirAbs := shared.JoinPath(ctx.topDir, rootOutputPath.String())
+
+ filesToDelete := make(map[string]struct{})
+ err := filepath.Walk(bp2buildDirAbs,
+ func(path string, info os.FileInfo, err error) error {
+ if err != nil {
+ return err
+ }
+ if !info.IsDir() {
+ relPath, err := filepath.Rel(bp2buildDirAbs, path)
+ if err != nil {
+ return err
+ }
+ filesToDelete[relPath] = struct{}{}
+ }
+ return nil
+ })
+ if err != nil {
+ fmt.Printf("ERROR reading %s: %s", bp2buildDirAbs, err)
+ os.Exit(1)
+ }
+
+ for _, bazelFile := range except {
+ filePath := filepath.Join(bazelFile.Dir, bazelFile.Basename)
+ delete(filesToDelete, filePath)
+ }
+ for f, _ := range filesToDelete {
+ absPath := shared.JoinPath(bp2buildDirAbs, f)
+ if err := os.RemoveAll(absPath); err != nil {
+ fmt.Printf("ERROR deleting %s: %s", absPath, err)
+ os.Exit(1)
+ }
+ }
+}
+
// Codegen is the backend of bp2build. The code generator is responsible for
// writing .bzl files that are equivalent to Android.bp files that are capable
// of being built with Bazel.
func Codegen(ctx *CodegenContext) *CodegenMetrics {
// This directory stores BUILD files that could be eventually checked-in.
bp2buildDir := android.PathForOutput(ctx, "bp2build")
- if err := android.RemoveAllOutputDir(bp2buildDir); err != nil {
- fmt.Printf("ERROR: Encountered error while cleaning %s: %s", bp2buildDir, err.Error())
- }
res, errs := GenerateBazelTargets(ctx, true)
if len(errs) > 0 {
@@ -44,6 +80,12 @@
}
bp2buildFiles := CreateBazelFiles(ctx.Config(), nil, res.buildFileToTargets, ctx.mode)
writeFiles(ctx, bp2buildDir, bp2buildFiles)
+ // Delete files under the bp2build root which weren't just written. An
+ // alternative would have been to delete the whole directory and write these
+ // files. However, this would regenerate files which were otherwise unchanged
+ // since the last bp2build run, which would have negative incremental
+ // performance implications.
+ deleteFilesExcept(ctx, bp2buildDir, bp2buildFiles)
injectionFiles, err := CreateSoongInjectionDirFiles(ctx, res.metrics)
if err != nil {
@@ -51,7 +93,6 @@
os.Exit(1)
}
writeFiles(ctx, android.PathForOutput(ctx, bazel.SoongInjectionDirName), injectionFiles)
-
return &res.metrics
}
diff --git a/tests/bp2build_bazel_test.sh b/tests/bp2build_bazel_test.sh
index 878b4a1..1ff1b5b 100755
--- a/tests/bp2build_bazel_test.sh
+++ b/tests/bp2build_bazel_test.sh
@@ -21,6 +21,68 @@
fi
}
+# Tests that, if bp2build reruns due to a blueprint file changing, that
+# BUILD files whose contents are unchanged are not regenerated.
+function test_bp2build_unchanged {
+ setup
+
+ mkdir -p pkg
+ touch pkg/x.txt
+ cat > pkg/Android.bp <<'EOF'
+filegroup {
+ name: "x",
+ srcs: ["x.txt"],
+ bazel_module: {bp2build_available: true},
+ }
+EOF
+
+ run_soong bp2build
+ local -r buildfile_mtime1=$(stat -c "%y" out/soong/bp2build/pkg/BUILD.bazel)
+ local -r marker_mtime1=$(stat -c "%y" out/soong/bp2build_workspace_marker)
+
+ # Force bp2build to rerun by updating the timestamp of a blueprint file.
+ touch pkg/Android.bp
+
+ run_soong bp2build
+ local -r buildfile_mtime2=$(stat -c "%y" out/soong/bp2build/pkg/BUILD.bazel)
+ local -r marker_mtime2=$(stat -c "%y" out/soong/bp2build_workspace_marker)
+
+ if [[ "$marker_mtime1" == "$marker_mtime2" ]]; then
+ fail "Expected bp2build marker file to change"
+ fi
+ if [[ "$buildfile_mtime1" != "$buildfile_mtime2" ]]; then
+ fail "BUILD.bazel was updated even though contents are same"
+ fi
+}
+
+# Tests that blueprint files that are deleted are not present when the
+# bp2build tree is regenerated.
+function test_bp2build_deleted_blueprint {
+ setup
+
+ mkdir -p pkg
+ touch pkg/x.txt
+ cat > pkg/Android.bp <<'EOF'
+filegroup {
+ name: "x",
+ srcs: ["x.txt"],
+ bazel_module: {bp2build_available: true},
+ }
+EOF
+
+ run_soong bp2build
+ if [[ ! -e "./out/soong/bp2build/pkg/BUILD.bazel" ]]; then
+ fail "Expected pkg/BUILD.bazel to be generated"
+ fi
+
+ rm pkg/Android.bp
+
+ run_soong bp2build
+ if [[ -e "./out/soong/bp2build/pkg/BUILD.bazel" ]]; then
+ fail "Expected pkg/BUILD.bazel to be deleted"
+ fi
+}
+
function test_bp2build_null_build_with_globs {
setup