Always merge build files
Previous behavior:
- Packge not listed in bp2buildKeepExistingBuildFile:
- Use bp2build generated build file
- Package listed in bp2buildKeepExistingBuildFile:
- Use handcrafted build file even if there were allowlisted bp2build
modules in the same package.
- Package listed in bp2buildKeepExistingBuildFile and a soong module has
a bp2build: { label } attribute:
- Merge the handcrafted and bp2build generated build files
New behavior:
- Packge not listed in bp2buildKeepExistingBuildFile:
- Use bp2build generated build file
- Package listed in bp2buildKeepExistingBuildFile:
- Merge with bp2build generated build file.
Bug: 234167862
Test: ./build/bazel/ci/bp2build.sh
Change-Id: Ifbaf4f8f0f5158b5b2bd6d534eb2311e2e5f399b
diff --git a/bp2build/symlink_forest.go b/bp2build/symlink_forest.go
index 78e7b0e..023ec96 100644
--- a/bp2build/symlink_forest.go
+++ b/bp2build/symlink_forest.go
@@ -1,12 +1,27 @@
+// Copyright 2022 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"
"fmt"
"io/ioutil"
"os"
"path/filepath"
+ "regexp"
+ "android/soong/android"
"android/soong/shared"
)
@@ -58,6 +73,57 @@
return result
}
+func mergeBuildFiles(output string, srcBuildFile string, generatedBuildFile string, verbose bool) error {
+
+ srcBuildFileContent, err := os.ReadFile(srcBuildFile)
+ if err != nil {
+ return err
+ }
+
+ generatedBuildFileContent, err := os.ReadFile(generatedBuildFile)
+ if err != nil {
+ return err
+ }
+
+ // There can't be a package() call in both the source and generated BUILD files.
+ // bp2build will generate a package() call for licensing information, but if
+ // there's no licensing information, it will still generate a package() call
+ // that just sets default_visibility=public. If the handcrafted build file
+ // also has a package() call, we'll allow it to override the bp2build
+ // generated one if it doesn't have any licensing information. If the bp2build
+ // one has licensing information and the handcrafted one exists, we'll leave
+ // them both in for bazel to throw an error.
+ packageRegex := regexp.MustCompile(`(?m)^package\s*\(`)
+ packageDefaultVisibilityRegex := regexp.MustCompile(`(?m)^package\s*\(\s*default_visibility\s*=\s*\[\s*"//visibility:public",?\s*]\s*\)`)
+ if packageRegex.Find(srcBuildFileContent) != nil {
+ if verbose && packageDefaultVisibilityRegex.Find(generatedBuildFileContent) != nil {
+ fmt.Fprintf(os.Stderr, "Both '%s' and '%s' have a package() target, removing the first one\n",
+ generatedBuildFile, srcBuildFile)
+ }
+ generatedBuildFileContent = packageDefaultVisibilityRegex.ReplaceAll(generatedBuildFileContent, []byte{})
+ }
+
+ outFile, err := os.Create(output)
+ if err != nil {
+ return err
+ }
+
+ _, err = outFile.Write(generatedBuildFileContent)
+ if err != nil {
+ return err
+ }
+
+ if generatedBuildFileContent[len(generatedBuildFileContent)-1] != '\n' {
+ _, err = outFile.WriteString("\n")
+ if err != nil {
+ return err
+ }
+ }
+
+ _, err = outFile.Write(srcBuildFileContent)
+ return err
+}
+
// Calls readdir() and returns it as a map from the basename of the files in dir
// to os.FileInfo.
func readdirToMap(dir string) map[string]os.FileInfo {
@@ -125,6 +191,17 @@
srcDirMap := readdirToMap(shared.JoinPath(topdir, srcDir))
buildFilesMap := readdirToMap(shared.JoinPath(topdir, buildFilesDir))
+ renamingBuildFile := false
+ if _, ok := srcDirMap["BUILD"]; ok {
+ if _, ok := srcDirMap["BUILD.bazel"]; !ok {
+ if _, ok := buildFilesMap["BUILD.bazel"]; ok {
+ renamingBuildFile = true
+ srcDirMap["BUILD.bazel"] = srcDirMap["BUILD"]
+ delete(srcDirMap, "BUILD")
+ }
+ }
+ }
+
allEntries := make(map[string]bool)
for n := range srcDirMap {
allEntries[n] = true
@@ -148,21 +225,28 @@
// The full paths of children in the input trees and in the output tree
forestChild := shared.JoinPath(forestDir, f)
srcChild := shared.JoinPath(srcDir, f)
+ if f == "BUILD.bazel" && renamingBuildFile {
+ srcChild = shared.JoinPath(srcDir, "BUILD")
+ }
buildFilesChild := shared.JoinPath(buildFilesDir, f)
// Descend in the exclusion tree, if there are any excludes left
- var excludeChild *node
- if exclude == nil {
- excludeChild = nil
- } else {
- excludeChild = exclude.children[f]
+ var excludeChild *node = nil
+ if exclude != nil {
+ if f == "BUILD.bazel" && renamingBuildFile {
+ excludeChild = exclude.children["BUILD"]
+ } else {
+ excludeChild = exclude.children[f]
+ }
}
srcChildEntry, sExists := srcDirMap[f]
buildFilesChildEntry, bExists := buildFilesMap[f]
- excluded := excludeChild != nil && excludeChild.excluded
- if excluded {
+ if excludeChild != nil && excludeChild.excluded {
+ if bExists {
+ symlinkIntoForest(topdir, forestChild, buildFilesChild)
+ }
continue
}
@@ -198,13 +282,15 @@
// Both are directories. Descend.
plantSymlinkForestRecursive(cfg, topdir, forestChild, buildFilesChild, srcChild, excludeChild, acc, okay)
} else if !sDir && !bDir {
- // Neither is a directory. Prioritize BUILD files generated by bp2build
- // over any BUILD file imported into external/.
- if cfg.IsEnvTrue("BP2BUILD_VERBOSE") {
- fmt.Fprintf(os.Stderr, "Both '%s' and '%s' exist, symlinking the former to '%s'\n",
- buildFilesChild, srcChild, forestChild)
+ // Neither is a directory. Merge them.
+ srcBuildFile := shared.JoinPath(topdir, srcChild)
+ generatedBuildFile := shared.JoinPath(topdir, buildFilesChild)
+ err = mergeBuildFiles(shared.JoinPath(topdir, forestChild), srcBuildFile, generatedBuildFile, cfg.IsEnvTrue("BP2BUILD_VERBOSE"))
+ if err != nil {
+ fmt.Fprintf(os.Stderr, "Error merging %s and %s: %s",
+ srcBuildFile, generatedBuildFile, err)
+ *okay = false
}
- symlinkIntoForest(topdir, forestChild, buildFilesChild)
} else {
// Both exist and one is a file. This is an error.
fmt.Fprintf(os.Stderr,