Revert "Revert "Cacheable, multithreaded finder.""

Bug: 62455338
Test: m -j

This reverts commit d1abeb9d982b11fdf4047176d213acc8197c375f.

Change-Id: I9f73031636157511b5f1c6ce8a205e9bc91669ff
diff --git a/finder/cmd/Android.bp b/finder/cmd/Android.bp
new file mode 100644
index 0000000..9dc84ae
--- /dev/null
+++ b/finder/cmd/Android.bp
@@ -0,0 +1,29 @@
+// 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.
+
+//
+// fast, parallel, caching implementation of `find`
+//
+
+blueprint_go_binary {
+    name: "finder",
+    srcs: [
+        "finder.go",
+    ],
+    deps: [
+        "soong-finder"
+    ],
+}
+
+
diff --git a/finder/cmd/finder.go b/finder/cmd/finder.go
new file mode 100644
index 0000000..9da1660
--- /dev/null
+++ b/finder/cmd/finder.go
@@ -0,0 +1,149 @@
+// 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 main
+
+import (
+	"errors"
+	"flag"
+	"fmt"
+	"io"
+	"io/ioutil"
+	"log"
+	"os"
+	"runtime/pprof"
+	"sort"
+	"strings"
+	"time"
+
+	"android/soong/finder"
+	"android/soong/fs"
+)
+
+var (
+	// configuration of what to find
+	excludeDirs     string
+	filenamesToFind string
+	pruneFiles      string
+
+	// other configuration
+	cpuprofile    string
+	verbose       bool
+	dbPath        string
+	numIterations int
+)
+
+func init() {
+	flag.StringVar(&cpuprofile, "cpuprofile", "",
+		"filepath of profile file to write (optional)")
+	flag.BoolVar(&verbose, "v", false, "log additional information")
+	flag.StringVar(&dbPath, "db", "", "filepath of cache db")
+
+	flag.StringVar(&excludeDirs, "exclude-dirs", "",
+		"comma-separated list of directory names to exclude from search")
+	flag.StringVar(&filenamesToFind, "names", "",
+		"comma-separated list of filenames to find")
+	flag.StringVar(&pruneFiles, "prune-files", "",
+		"filenames that if discovered will exclude their entire directory "+
+			"(including sibling files and directories)")
+	flag.IntVar(&numIterations, "count", 1,
+		"number of times to run. This is intended for use with --cpuprofile"+
+			" , to increase profile accuracy")
+}
+
+var usage = func() {
+	fmt.Printf("usage: finder -name <fileName> --db <dbPath> <searchDirectory> [<searchDirectory>...]\n")
+	flag.PrintDefaults()
+}
+
+func main() {
+	err := run()
+	if err != nil {
+		fmt.Fprintf(os.Stderr, "%v\n", err.Error())
+		os.Exit(1)
+	}
+}
+
+func stringToList(input string) []string {
+	return strings.Split(input, ",")
+}
+
+func run() error {
+	startTime := time.Now()
+	flag.Parse()
+
+	if cpuprofile != "" {
+		f, err := os.Create(cpuprofile)
+		if err != nil {
+			return fmt.Errorf("Error opening cpuprofile: %s", err)
+		}
+		pprof.StartCPUProfile(f)
+		defer f.Close()
+		defer pprof.StopCPUProfile()
+	}
+
+	var writer io.Writer
+	if verbose {
+		writer = os.Stderr
+	} else {
+		writer = ioutil.Discard
+	}
+
+	// TODO: replace Lshortfile with Llongfile when bug 63821638 is done
+	logger := log.New(writer, "", log.Ldate|log.Lmicroseconds|log.Lshortfile)
+
+	logger.Printf("Finder starting at %v\n", startTime)
+
+	rootPaths := flag.Args()
+	if len(rootPaths) < 1 {
+		usage()
+		return fmt.Errorf(
+			"Must give at least one <searchDirectory>")
+	}
+
+	workingDir, err := os.Getwd()
+	if err != nil {
+		return err
+	}
+	params := finder.CacheParams{
+		WorkingDirectory: workingDir,
+		RootDirs:         rootPaths,
+		ExcludeDirs:      stringToList(excludeDirs),
+		PruneFiles:       stringToList(pruneFiles),
+		IncludeFiles:     stringToList(filenamesToFind),
+	}
+	if dbPath == "" {
+		usage()
+		return errors.New("Param 'db' must be nonempty")
+	}
+	matches := []string{}
+	for i := 0; i < numIterations; i++ {
+		matches = runFind(params, logger)
+	}
+	findDuration := time.Since(startTime)
+	logger.Printf("Found these %v inodes in %v :\n", len(matches), findDuration)
+	sort.Strings(matches)
+	for _, match := range matches {
+		fmt.Println(match)
+	}
+	logger.Printf("End of %v inodes\n", len(matches))
+	logger.Printf("Finder completed in %v\n", time.Since(startTime))
+	return nil
+}
+
+func runFind(params finder.CacheParams, logger *log.Logger) (paths []string) {
+	service := finder.New(params, fs.OsFs, logger, dbPath)
+	defer service.Shutdown()
+	return service.FindAll()
+}