Build the symlink tree on multiple threads.

This makes it take ~5 seconds on AOSP instead of ~10. Frankly, the
speedup is somewhat disappointing but at least the code is not
complicated.

Test: Presubmits.
Change-Id: Icf94d7ca8bd80c458d014f4cf4cc1be7138deaa6
This commit is contained in:
Lukacs T. Berki
2022-10-27 09:55:38 +00:00
parent c541cd27fa
commit 647e7abfa2

View File

@@ -20,6 +20,8 @@ import (
"os"
"path/filepath"
"regexp"
"sync"
"sync/atomic"
"android/soong/shared"
)
@@ -39,9 +41,12 @@ type instructionsNode struct {
type symlinkForestContext struct {
verbose bool
topdir string // $TOPDIR
deps []string // Files/directories read while constructing the forest
okay bool // Whether the forest was successfully constructed
topdir string // $TOPDIR
// State
wg sync.WaitGroup
depCh chan string
okay atomic.Bool // Whether the forest was successfully constructed
}
// Ensures that the node for the given path exists in the tree and returns it.
@@ -188,6 +193,8 @@ func isDir(path string, fi os.FileInfo) bool {
// instructions. Collects every directory encountered during the traversal of
// srcDir .
func plantSymlinkForestRecursive(context *symlinkForestContext, instructions *instructionsNode, forestDir string, buildFilesDir string, srcDir string) {
defer context.wg.Done()
if instructions != nil && instructions.excluded {
// This directory is not needed, bail out
return
@@ -196,7 +203,7 @@ func plantSymlinkForestRecursive(context *symlinkForestContext, instructions *in
// We don't add buildFilesDir here because the bp2build files marker files is
// already a dependency which covers it. If we ever wanted to turn this into
// a generic symlink forest creation tool, we'd need to add it, too.
context.deps = append(context.deps, srcDir)
context.depCh <- srcDir
srcDirMap := readdirToMap(shared.JoinPath(context.topdir, srcDir))
buildFilesMap := readdirToMap(shared.JoinPath(context.topdir, buildFilesDir))
@@ -267,7 +274,8 @@ func plantSymlinkForestRecursive(context *symlinkForestContext, instructions *in
if bDir && instructionsChild != nil {
// Not in the source tree, but we have to exclude something from under
// this subtree, so descend
plantSymlinkForestRecursive(context, instructionsChild, forestChild, buildFilesChild, srcChild)
context.wg.Add(1)
go plantSymlinkForestRecursive(context, instructionsChild, forestChild, buildFilesChild, srcChild)
} else {
// Not in the source tree, symlink BUILD file
symlinkIntoForest(context.topdir, forestChild, buildFilesChild)
@@ -276,33 +284,35 @@ func plantSymlinkForestRecursive(context *symlinkForestContext, instructions *in
if sDir && instructionsChild != nil {
// Not in the build file tree, but we have to exclude something from
// under this subtree, so descend
plantSymlinkForestRecursive(context, instructionsChild, forestChild, buildFilesChild, srcChild)
context.wg.Add(1)
go plantSymlinkForestRecursive(context, instructionsChild, forestChild, buildFilesChild, srcChild)
} else {
// Not in the build file tree, symlink source tree, carry on
symlinkIntoForest(context.topdir, forestChild, srcChild)
}
} else if sDir && bDir {
// Both are directories. Descend.
plantSymlinkForestRecursive(context, instructionsChild, forestChild, buildFilesChild, srcChild)
context.wg.Add(1)
go plantSymlinkForestRecursive(context, instructionsChild, forestChild, buildFilesChild, srcChild)
} else if !sDir && !bDir {
// Neither is a directory. Merge them.
srcBuildFile := shared.JoinPath(context.topdir, srcChild)
generatedBuildFile := shared.JoinPath(context.topdir, buildFilesChild)
// The Android.bp file that codegen used to produce `buildFilesChild` is
// already a dependency, we can ignore `buildFilesChild`.
context.deps = append(context.deps, srcChild)
context.depCh <- srcChild
err = mergeBuildFiles(shared.JoinPath(context.topdir, forestChild), srcBuildFile, generatedBuildFile, context.verbose)
if err != nil {
fmt.Fprintf(os.Stderr, "Error merging %s and %s: %s",
srcBuildFile, generatedBuildFile, err)
context.okay = false
context.okay.Store(false)
}
} else {
// Both exist and one is a file. This is an error.
fmt.Fprintf(os.Stderr,
"Conflict in workspace symlink tree creation: both '%s' and '%s' exist and exactly one is a directory\n",
srcChild, buildFilesChild)
context.okay = false
context.okay.Store(false)
}
}
}
@@ -315,16 +325,29 @@ func PlantSymlinkForest(verbose bool, topdir string, forest string, buildFiles s
context := &symlinkForestContext{
verbose: verbose,
topdir: topdir,
deps: make([]string, 0),
okay: true,
depCh: make(chan string),
}
context.okay.Store(true)
os.RemoveAll(shared.JoinPath(topdir, forest))
instructions := instructionsFromExcludePathList(exclude)
plantSymlinkForestRecursive(context, instructions, forest, buildFiles, ".")
if !context.okay {
go func() {
context.wg.Add(1)
plantSymlinkForestRecursive(context, instructions, forest, buildFiles, ".")
context.wg.Wait()
close(context.depCh)
}()
deps := make([]string, 0)
for dep := range context.depCh {
deps = append(deps, dep)
}
if !context.okay.Load() {
os.Exit(1)
}
return context.deps
return deps
}