find bazel-related files and add them to bazel.list and ninja deps

This retriggers soong_build whenever a new bzl, WORKSPACE, or
BUILD.bazel file is changed or added.

Test: Manually verified on bionic/libc genrules with manual changes to
related BUILD/bzl/WORKSPACE files -- these all retrigger builds.
Test: Updated finder_test.go

Change-Id: I634384f88781a6b6db32f5d6bf9c07e179e14c39
diff --git a/android/bazel_handler.go b/android/bazel_handler.go
index d4f6e4c..4f17fec 100644
--- a/android/bazel_handler.go
+++ b/android/bazel_handler.go
@@ -18,11 +18,15 @@
 	"bytes"
 	"errors"
 	"fmt"
+	"io/ioutil"
 	"os"
 	"os/exec"
+	"path/filepath"
 	"runtime"
 	"strings"
 	"sync"
+
+	"github.com/google/blueprint/bootstrap"
 )
 
 // Map key to describe bazel cquery requests.
@@ -237,3 +241,28 @@
 	context.requests = map[cqueryKey]bool{}
 	return nil
 }
+
+// Singleton used for registering BUILD file ninja dependencies (needed
+// for correctness of builds which use Bazel.
+func BazelSingleton() Singleton {
+	return &bazelSingleton{}
+}
+
+type bazelSingleton struct{}
+
+func (c *bazelSingleton) GenerateBuildActions(ctx SingletonContext) {
+	if ctx.Config().BazelContext.BazelEnabled() {
+		bazelBuildList := absolutePath(filepath.Join(
+			filepath.Dir(bootstrap.ModuleListFile), "bazel.list"))
+		ctx.AddNinjaFileDeps(bazelBuildList)
+
+		data, err := ioutil.ReadFile(bazelBuildList)
+		if err != nil {
+			ctx.Errorf(err.Error())
+		}
+		files := strings.Split(strings.TrimSpace(string(data)), "\n")
+		for _, file := range files {
+			ctx.AddNinjaFileDeps(file)
+		}
+	}
+}
diff --git a/android/register.go b/android/register.go
index 036a811..ad3df7e 100644
--- a/android/register.go
+++ b/android/register.go
@@ -104,6 +104,8 @@
 
 	registerMutators(ctx.Context, preArch, preDeps, postDeps, finalDeps)
 
+	ctx.RegisterSingletonType("bazeldeps", SingletonFactoryAdaptor(BazelSingleton))
+
 	// Register phony just before makevars so it can write out its phony rules as Make rules
 	ctx.RegisterSingletonType("phony", SingletonFactoryAdaptor(phonySingletonFactory))
 
diff --git a/finder/finder.go b/finder/finder.go
index 6513fa3..5413fa6 100644
--- a/finder/finder.go
+++ b/finder/finder.go
@@ -103,6 +103,9 @@
 
 	// IncludeFiles are file names to include as matches
 	IncludeFiles []string
+
+	// IncludeSuffixes are filename suffixes to include as matches.
+	IncludeSuffixes []string
 }
 
 // a cacheConfig stores the inputs that determine what should be included in the cache
@@ -1310,6 +1313,20 @@
 	return stats
 }
 
+func (f *Finder) shouldIncludeFile(fileName string) bool {
+	for _, includedName := range f.cacheMetadata.Config.IncludeFiles {
+		if fileName == includedName {
+			return true
+		}
+	}
+	for _, includeSuffix := range f.cacheMetadata.Config.IncludeSuffixes {
+		if strings.HasSuffix(fileName, includeSuffix) {
+			return true
+		}
+	}
+	return false
+}
+
 // pruneCacheCandidates removes the items that we don't want to include in our persistent cache
 func (f *Finder) pruneCacheCandidates(items *DirEntries) {
 
@@ -1326,13 +1343,9 @@
 	// remove any files that aren't the ones we want to include
 	writeIndex := 0
 	for _, fileName := range items.FileNames {
-		// include only these files
-		for _, includedName := range f.cacheMetadata.Config.IncludeFiles {
-			if fileName == includedName {
-				items.FileNames[writeIndex] = fileName
-				writeIndex++
-				break
-			}
+		if f.shouldIncludeFile(fileName) {
+			items.FileNames[writeIndex] = fileName
+			writeIndex++
 		}
 	}
 	// resize
diff --git a/finder/finder_test.go b/finder/finder_test.go
index 88b0c05..788dbdd 100644
--- a/finder/finder_test.go
+++ b/finder/finder_test.go
@@ -21,6 +21,7 @@
 	"os"
 	"path/filepath"
 	"sort"
+	"strings"
 	"testing"
 
 	"android/soong/finder/fs"
@@ -92,6 +93,7 @@
 			nil,
 			nil,
 			[]string{"findme.txt", "skipme.txt"},
+			nil,
 		},
 	)
 	defer finder.Shutdown()
@@ -104,6 +106,46 @@
 	fs.AssertSameResponse(t, foundPaths, absoluteMatches)
 }
 
+// runTestWithSuffixes creates a few files, searches for findme.txt or any file
+// with suffix `.findme_ext` and checks for the expected matches
+func runTestWithSuffixes(t *testing.T, existentPaths []string, expectedMatches []string) {
+	filesystem := newFs()
+	root := "/tmp"
+	filesystem.MkDirs(root)
+	for _, path := range existentPaths {
+		fs.Create(t, filepath.Join(root, path), filesystem)
+	}
+
+	finder := newFinder(t,
+		filesystem,
+		CacheParams{
+			"/cwd",
+			[]string{root},
+			nil,
+			nil,
+			[]string{"findme.txt", "skipme.txt"},
+			[]string{".findme_ext"},
+		},
+	)
+	defer finder.Shutdown()
+
+	foundPaths := finder.FindMatching(root,
+		func(entries DirEntries) (dirs []string, files []string) {
+			matches := []string{}
+			for _, foundName := range entries.FileNames {
+				if foundName == "findme.txt" || strings.HasSuffix(foundName, ".findme_ext") {
+					matches = append(matches, foundName)
+				}
+			}
+			return entries.DirNames, matches
+		})
+	absoluteMatches := []string{}
+	for i := range expectedMatches {
+		absoluteMatches = append(absoluteMatches, filepath.Join(root, expectedMatches[i]))
+	}
+	fs.AssertSameResponse(t, foundPaths, absoluteMatches)
+}
+
 // testAgainstSeveralThreadcounts runs the given test for each threadcount that we care to test
 func testAgainstSeveralThreadcounts(t *testing.T, tester func(t *testing.T, numThreads int)) {
 	// test singlethreaded, multithreaded, and also using the same number of threads as
@@ -135,6 +177,13 @@
 	)
 }
 
+func TestIncludeFilesAndSuffixes(t *testing.T) {
+	runTestWithSuffixes(t,
+		[]string{"findme.txt", "skipme.txt", "alsome.findme_ext"},
+		[]string{"findme.txt", "alsome.findme_ext"},
+	)
+}
+
 func TestNestedDirectories(t *testing.T) {
 	runSimpleTest(t,
 		[]string{"findme.txt", "skipme.txt", "subdir/findme.txt", "subdir/skipme.txt"},
@@ -142,6 +191,13 @@
 	)
 }
 
+func TestNestedDirectoriesWithSuffixes(t *testing.T) {
+	runTestWithSuffixes(t,
+		[]string{"findme.txt", "skipme.txt", "subdir/findme.txt", "subdir/skipme.txt", "subdir/alsome.findme_ext"},
+		[]string{"findme.txt", "subdir/findme.txt", "subdir/alsome.findme_ext"},
+	)
+}
+
 func TestEmptyDirectory(t *testing.T) {
 	runSimpleTest(t,
 		[]string{},
diff --git a/ui/build/finder.go b/ui/build/finder.go
index c019ea2..7a85657 100644
--- a/ui/build/finder.go
+++ b/ui/build/finder.go
@@ -63,10 +63,13 @@
 			"AndroidProducts.mk",
 			"Android.bp",
 			"Blueprints",
+			"BUILD.bazel",
 			"CleanSpec.mk",
 			"OWNERS",
 			"TEST_MAPPING",
+			"WORKSPACE",
 		},
+		IncludeSuffixes: []string{".bzl"},
 	}
 	dumpDir := config.FileListDir()
 	f, err = finder.New(cacheParams, filesystem, logger.New(ioutil.Discard),
@@ -77,6 +80,16 @@
 	return f
 }
 
+func findBazelFiles(entries finder.DirEntries) (dirNames []string, fileNames []string) {
+	matches := []string{}
+	for _, foundName := range entries.FileNames {
+		if foundName == "BUILD.bazel" || foundName == "WORKSPACE" || strings.HasSuffix(foundName, ".bzl") {
+			matches = append(matches, foundName)
+		}
+	}
+	return entries.DirNames, matches
+}
+
 // FindSources searches for source files known to <f> and writes them to the filesystem for
 // use later.
 func FindSources(ctx Context, config Config, f *finder.Finder) {
@@ -99,6 +112,12 @@
 		ctx.Fatalf("Could not export product list: %v", err)
 	}
 
+	bazelFiles := f.FindMatching(".", findBazelFiles)
+	err = dumpListToFile(ctx, config, bazelFiles, filepath.Join(dumpDir, "bazel.list"))
+	if err != nil {
+		ctx.Fatalf("Could not export bazel BUILD list: %v", err)
+	}
+
 	cleanSpecs := f.FindFirstNamedAt(".", "CleanSpec.mk")
 	err = dumpListToFile(ctx, config, cleanSpecs, filepath.Join(dumpDir, "CleanSpec.mk.list"))
 	if err != nil {