1// Copyright 2017 Google Inc. All rights reserved.
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7//     http://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14
15package main
16
17import (
18	"errors"
19	"flag"
20	"fmt"
21	"io"
22	"io/ioutil"
23	"log"
24	"os"
25	"runtime/pprof"
26	"sort"
27	"strings"
28	"time"
29
30	"android/soong/finder"
31	"android/soong/finder/fs"
32)
33
34var (
35	// configuration of what to find
36	excludeDirs     string
37	filenamesToFind string
38	pruneFiles      string
39
40	// other configuration
41	cpuprofile    string
42	verbose       bool
43	dbPath        string
44	numIterations int
45)
46
47func init() {
48	flag.StringVar(&cpuprofile, "cpuprofile", "",
49		"filepath of profile file to write (optional)")
50	flag.BoolVar(&verbose, "v", false, "log additional information")
51	flag.StringVar(&dbPath, "db", "", "filepath of cache db")
52
53	flag.StringVar(&excludeDirs, "exclude-dirs", "",
54		"comma-separated list of directory names to exclude from search")
55	flag.StringVar(&filenamesToFind, "names", "",
56		"comma-separated list of filenames to find")
57	flag.StringVar(&pruneFiles, "prune-files", "",
58		"filenames that if discovered will exclude their entire directory "+
59			"(including sibling files and directories)")
60	flag.IntVar(&numIterations, "count", 1,
61		"number of times to run. This is intended for use with --cpuprofile"+
62			" , to increase profile accuracy")
63}
64
65var usage = func() {
66	fmt.Printf("usage: finder -name <fileName> --db <dbPath> <searchDirectory> [<searchDirectory>...]\n")
67	flag.PrintDefaults()
68}
69
70func main() {
71	err := run()
72	if err != nil {
73		fmt.Fprintf(os.Stderr, "%v\n", err.Error())
74		os.Exit(1)
75	}
76}
77
78func stringToList(input string) []string {
79	return strings.Split(input, ",")
80}
81
82func run() error {
83	startTime := time.Now()
84	flag.Parse()
85
86	if cpuprofile != "" {
87		f, err := os.Create(cpuprofile)
88		if err != nil {
89			return fmt.Errorf("Error opening cpuprofile: %s", err)
90		}
91		pprof.StartCPUProfile(f)
92		defer f.Close()
93		defer pprof.StopCPUProfile()
94	}
95
96	var writer io.Writer
97	if verbose {
98		writer = os.Stderr
99	} else {
100		writer = ioutil.Discard
101	}
102
103	// TODO: replace Lshortfile with Llongfile when bug 63821638 is done
104	logger := log.New(writer, "", log.Ldate|log.Lmicroseconds|log.Lshortfile)
105
106	logger.Printf("Finder starting at %v\n", startTime)
107
108	rootPaths := flag.Args()
109	if len(rootPaths) < 1 {
110		usage()
111		return fmt.Errorf(
112			"Must give at least one <searchDirectory>")
113	}
114
115	workingDir, err := os.Getwd()
116	if err != nil {
117		return err
118	}
119	params := finder.CacheParams{
120		WorkingDirectory: workingDir,
121		RootDirs:         rootPaths,
122		ExcludeDirs:      stringToList(excludeDirs),
123		PruneFiles:       stringToList(pruneFiles),
124		IncludeFiles:     stringToList(filenamesToFind),
125	}
126	if dbPath == "" {
127		usage()
128		return errors.New("Param 'db' must be nonempty")
129	}
130
131	matches := []string{}
132	for i := 0; i < numIterations; i++ {
133		matches, err = runFind(params, logger)
134		if err != nil {
135			return err
136		}
137	}
138	findDuration := time.Since(startTime)
139	logger.Printf("Found these %v inodes in %v :\n", len(matches), findDuration)
140	sort.Strings(matches)
141	for _, match := range matches {
142		fmt.Println(match)
143	}
144	logger.Printf("End of %v inodes\n", len(matches))
145	logger.Printf("Finder completed in %v\n", time.Since(startTime))
146	return nil
147}
148
149func runFind(params finder.CacheParams, logger *log.Logger) (paths []string, err error) {
150	service, err := finder.New(params, fs.OsFs, logger, dbPath)
151	if err != nil {
152		return []string{}, err
153	}
154	defer service.Shutdown()
155	return service.FindAll(), nil
156}
157