Run the Finder and make its results available to Kati

The Finder runs roughly 200ms faster than findleaves.py in aosp,
and runs roughly 400ms faster in internal master.

Bug: 64363847
Test: m -j

Change-Id: I62db8dacc90871e913576fe2443021fb1749a483
diff --git a/ui/build/finder.go b/ui/build/finder.go
new file mode 100644
index 0000000..05dec3a
--- /dev/null
+++ b/ui/build/finder.go
@@ -0,0 +1,102 @@
+// Copyright 2017 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 build
+
+import (
+	"android/soong/finder"
+	"android/soong/fs"
+	"android/soong/ui/logger"
+	"bytes"
+	"io/ioutil"
+	"os"
+	"path/filepath"
+	"strings"
+)
+
+// This file provides an interface to the Finder for use in Soong UI
+// This file stores configuration information about which files to find
+
+// NewSourceFinder returns a new Finder configured to search for source files.
+// Callers of NewSourceFinder should call <f.Shutdown()> when done
+func NewSourceFinder(ctx Context, config Config) (f *finder.Finder) {
+	ctx.BeginTrace("find modules")
+	defer ctx.EndTrace()
+
+	dir, err := os.Getwd()
+	if err != nil {
+		ctx.Fatalf("No working directory for module-finder: %v", err.Error())
+	}
+	cacheParams := finder.CacheParams{
+		WorkingDirectory: dir,
+		RootDirs:         []string{"."},
+		ExcludeDirs:      []string{".git", ".repo"},
+		PruneFiles:       []string{".out-dir", ".find-ignore"},
+		IncludeFiles:     []string{"Android.mk", "Android.bp", "Blueprints", "CleanSpec.mk"},
+	}
+	dumpDir := config.FileListDir()
+	f, err = finder.New(cacheParams, fs.OsFs, logger.New(ioutil.Discard),
+		filepath.Join(dumpDir, "files.db"))
+	if err != nil {
+		ctx.Fatalf("Could not create module-finder: %v", err)
+	}
+	return f
+}
+
+// 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) {
+	// note that dumpDir in FindSources may be different than dumpDir in NewSourceFinder
+	// if a caller such as multiproduct_kati wants to share one Finder among several builds
+	dumpDir := config.FileListDir()
+	os.MkdirAll(dumpDir, 0777)
+
+	androidMks := f.FindFirstNamedAt(".", "Android.mk")
+	err := dumpListToFile(androidMks, filepath.Join(dumpDir, "Android.mk.list"))
+	if err != nil {
+		ctx.Fatalf("Could not export module list: %v", err)
+	}
+
+	cleanSpecs := f.FindFirstNamedAt(".", "CleanSpec.mk")
+	dumpListToFile(cleanSpecs, filepath.Join(dumpDir, "CleanSpec.mk.list"))
+	if err != nil {
+		ctx.Fatalf("Could not export module list: %v", err)
+	}
+
+	isBlueprintFile := func(dir finder.DirEntries) (dirs []string, files []string) {
+		files = []string{}
+		for _, file := range dir.FileNames {
+			if file == "Android.bp" || file == "Blueprints" {
+				files = append(files, file)
+			}
+		}
+
+		return dir.DirNames, files
+	}
+	androidBps := f.FindMatching(".", isBlueprintFile)
+	err = dumpListToFile(androidBps, filepath.Join(dumpDir, "Android.bp.list"))
+	if err != nil {
+		ctx.Fatalf("Could not find modules: %v", err)
+	}
+}
+
+func dumpListToFile(list []string, filePath string) (err error) {
+	desiredText := strings.Join(list, "\n")
+	desiredBytes := []byte(desiredText)
+	actualBytes, readErr := ioutil.ReadFile(filePath)
+	if readErr != nil || !bytes.Equal(desiredBytes, actualBytes) {
+		err = ioutil.WriteFile(filePath, desiredBytes, 0777)
+	}
+	return err
+}