• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1// Copyright 2022 The Android Open Source Project
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	"bufio"
19	"context"
20	"encoding/json"
21	"errors"
22	"flag"
23	"fmt"
24	"io"
25	"log"
26	"os"
27	"runtime"
28	"strings"
29	"time"
30
31	"tools/treble/build/report/app"
32	"tools/treble/build/report/local"
33	"tools/treble/build/report/report"
34)
35
36type Build interface {
37	Build(ctx context.Context, target string) *app.BuildCmdResult
38}
39
40type tool interface {
41	Run(ctx context.Context, rtx *report.Context, rsp *response) error
42	PrintText(w io.Writer, rsp *response, verbose bool)
43}
44type repoFlags []app.ProjectCommit
45
46func (r *repoFlags) Set(value string) error {
47	commit := app.ProjectCommit{}
48	items := strings.Split(value, ":")
49	if len(items) > 2 {
50		return (errors.New("Invalid repo value expected (proj:sha) format"))
51	}
52	commit.Project = items[0]
53	if len(items) > 1 {
54		commit.Revision = items[1]
55	}
56	*r = append(*r, commit)
57	return nil
58}
59func (r *repoFlags) String() string {
60	items := []string{}
61	for _, fl := range *r {
62		items = append(items, fmt.Sprintf("%s:%s", fl.Project, fl.Revision))
63	}
64	return strings.Join(items, " ")
65}
66
67var (
68	// Common flags
69	ninjaDbPtr          = flag.String("ninja", local.DefNinjaDb(), "Set the .ninja file to use when building metrics")
70	ninjaExcPtr         = flag.String("ninja_cmd", local.DefNinjaExc(), "Set the ninja executable")
71	ninjaTimeoutStr     = flag.String("ninja_timeout", local.DefaultNinjaTimeout, "Default ninja timeout")
72	buildTimeoutStr     = flag.String("build_timeout", local.DefaultNinjaBuildTimeout, "Default build timeout")
73	manifestPtr         = flag.String("manifest", local.DefManifest(), "Set the location of the manifest file")
74	upstreamPtr         = flag.String("upstream", "", "Upstream branch to compare files against")
75	repoBasePtr         = flag.String("repo_base", local.DefRepoBase(), "Set the repo base directory")
76	workerCountPtr      = flag.Int("worker_count", runtime.NumCPU(), "Number of worker routines")
77	buildWorkerCountPtr = flag.Int("build_worker_count", local.MaxNinjaCliWorkers, "Number of build worker routines")
78	clientServerPtr     = flag.Bool("client_server", false, "Run client server mode")
79	buildPtr            = flag.Bool("build", false, "Build targets")
80	jsonPtr             = flag.Bool("json", false, "Print json data")
81	verbosePtr          = flag.Bool("v", false, "Print verbose text data")
82	outputPtr           = flag.String("o", "", "Output to file")
83	projsPtr            = flag.Bool("projects", false, "Include project repo data")
84
85	hostFlags  = flag.NewFlagSet("host", flag.ExitOnError)
86	queryFlags = flag.NewFlagSet("query", flag.ExitOnError)
87	pathsFlags = flag.NewFlagSet("paths", flag.ExitOnError)
88)
89
90// Add profiling data
91type profTime struct {
92	Description  string  `json:"description"`
93	DurationSecs float64 `json:"duration"`
94}
95
96type commit struct {
97	Project app.ProjectCommit `json:"project"`
98	Commit  *app.GitCommit    `json:"commit"`
99}
100
101// Use one structure for output for now
102type response struct {
103	Commits    []commit              `json:"commits,omitempty"`
104	Inputs     []string              `json:"files,omitempty"`
105	BuildFiles []*app.BuildCmdResult `json:"build_files,omitempty"`
106	Targets    []string              `json:"targets,omitempty"`
107	Report     *app.Report           `json:"report,omitempty"`
108
109	// Subcommand data
110	Query    *app.QueryResponse         `json:"query,omitempty"`
111	Paths    []*app.BuildPath           `json:"build_paths,omitempty"`
112	Host     *app.HostReport            `json:"host,omitempty"`
113	Projects map[string]*app.GitProject `json:"projects,omitempty"`
114	// Profile data
115	Profile []*profTime `json:"profile"`
116}
117
118func main() {
119	startTime := time.Now()
120	ctx := context.Background()
121	rsp := &response{}
122
123	var addProfileData = func(desc string) {
124		rsp.Profile = append(rsp.Profile, &profTime{Description: desc, DurationSecs: time.Since(startTime).Seconds()})
125		startTime = time.Now()
126	}
127	flag.Parse()
128
129	ninjaTimeout, err := time.ParseDuration(*ninjaTimeoutStr)
130	if err != nil {
131		log.Fatalf("Invalid ninja timeout %s", *ninjaTimeoutStr)
132	}
133
134	buildTimeout, err := time.ParseDuration(*buildTimeoutStr)
135	if err != nil {
136		log.Fatalf("Invalid build timeout %s", *buildTimeoutStr)
137	}
138
139	subArgs := flag.Args()
140	defBuildTarget := "droid"
141	log.SetFlags(log.LstdFlags | log.Llongfile)
142
143	ninja := local.NewNinjaCli(*ninjaExcPtr, *ninjaDbPtr, ninjaTimeout, buildTimeout, *clientServerPtr)
144
145	if *clientServerPtr {
146		ninjaServ := local.NewNinjaServer(*ninjaExcPtr, *ninjaDbPtr)
147		defer ninjaServ.Kill()
148		go func() {
149
150			ninjaServ.Start(ctx)
151		}()
152		if err := ninja.WaitForServer(ctx, int(ninjaTimeout.Seconds())); err != nil {
153			log.Fatalf("Failed to connect to server")
154		}
155	}
156	rtx := &report.Context{
157		RepoBase:         *repoBasePtr,
158		Repo:             &report.RepoMan{},
159		Build:            ninja,
160		Project:          local.NewGitCli(),
161		WorkerCount:      *workerCountPtr,
162		BuildWorkerCount: *buildWorkerCountPtr,
163	}
164
165	var subcommand tool
166	var commits repoFlags
167	if len(subArgs) > 0 {
168		switch subArgs[0] {
169		case "host":
170			hostToolPathPtr := hostFlags.String("hostbin", local.DefHostBinPath(), "Set the output directory for host tools")
171			hostFlags.Parse(subArgs[1:])
172
173			subcommand = &hostReport{toolPath: *hostToolPathPtr}
174			rsp.Targets = hostFlags.Args()
175
176		case "query":
177			queryFlags.Var(&commits, "repo", "Repo:SHA to query")
178			queryFlags.Parse(subArgs[1:])
179			subcommand = &queryReport{}
180			rsp.Targets = queryFlags.Args()
181
182		case "paths":
183			pathsFlags.Var(&commits, "repo", "Repo:SHA to build")
184			singlePathPtr := pathsFlags.Bool("1", false, "Get single path to output target")
185			pathsFlags.Parse(subArgs[1:])
186
187			subcommand = &pathsReport{build_target: defBuildTarget, single: *singlePathPtr}
188
189			rsp.Inputs = pathsFlags.Args()
190
191		default:
192			rsp.Targets = subArgs
193		}
194	}
195	addProfileData("Init")
196	rtx.ResolveProjectMap(ctx, *manifestPtr, *upstreamPtr)
197	addProfileData("Project Map")
198
199	// Add project to output if requested
200	if *projsPtr == true {
201		rsp.Projects = make(map[string]*app.GitProject)
202		for k, p := range rtx.Info.ProjMap {
203			rsp.Projects[k] = p.GitProj
204		}
205	}
206
207	// Resolve any commits
208	if len(commits) > 0 {
209		log.Printf("Resolving %s", commits.String())
210		for _, c := range commits {
211			commit := commit{Project: c}
212			info, files, err := report.ResolveCommit(ctx, rtx, &c)
213			if err != nil {
214				log.Fatalf("Failed to resolve commit %s:%s", c.Project, c.Revision)
215			}
216			commit.Commit = info
217			rsp.Commits = append(rsp.Commits, commit)
218
219			// Add files to list of inputs
220			rsp.Inputs = append(rsp.Inputs, files...)
221		}
222		addProfileData("Commit Resolution")
223	}
224
225	// Run any sub tools
226	if subcommand != nil {
227		if err := subcommand.Run(ctx, rtx, rsp); err != nil {
228			log.Fatal(err)
229		}
230		addProfileData(subArgs[0])
231	}
232
233	buildErrors := 0
234	if *buildPtr {
235		// Only support default builder (non server-client)
236		builder := local.NewNinjaCli(local.DefNinjaExc(), *ninjaDbPtr, ninjaTimeout, buildTimeout, false /*clientMode*/)
237		for _, t := range rsp.Targets {
238			log.Printf("Building %s\n", t)
239			res := builder.Build(ctx, t)
240			addProfileData(fmt.Sprintf("Build %s", t))
241			log.Printf("%s\n", res.Output)
242			if res.Success != true {
243				buildErrors++
244			}
245			rsp.BuildFiles = append(rsp.BuildFiles, res)
246		}
247	}
248
249	// Generate report
250	log.Printf("Generating report for targets %s", rsp.Targets)
251	req := &app.ReportRequest{Targets: rsp.Targets}
252	rsp.Report, err = report.RunReport(ctx, rtx, req)
253	addProfileData("Report")
254	if err != nil {
255		log.Fatal(fmt.Sprintf("Report failure <%s>", err))
256	}
257
258	if *jsonPtr {
259		b, _ := json.MarshalIndent(rsp, "", "\t")
260		if *outputPtr == "" {
261			os.Stdout.Write(b)
262		} else {
263			os.WriteFile(*outputPtr, b, 0644)
264		}
265	} else {
266		if *outputPtr == "" {
267			printTextReport(os.Stdout, subcommand, rsp, *verbosePtr)
268		} else {
269			file, err := os.Create(*outputPtr)
270			if err != nil {
271				log.Fatalf("Failed to create output file %s (%s)", *outputPtr, err)
272			}
273			w := bufio.NewWriter(file)
274			printTextReport(w, subcommand, rsp, *verbosePtr)
275			w.Flush()
276		}
277
278	}
279
280	if buildErrors > 0 {
281		log.Fatal(fmt.Sprintf("Failed to build %d targets", buildErrors))
282	}
283}
284
285func printTextReport(w io.Writer, subcommand tool, rsp *response, verbose bool) {
286	fmt.Fprintln(w, "Metric Report")
287	if subcommand != nil {
288		subcommand.PrintText(w, rsp, verbose)
289	}
290
291	if len(rsp.Commits) > 0 {
292		fmt.Fprintln(w, "")
293		fmt.Fprintln(w, "  Commit Results")
294		for _, c := range rsp.Commits {
295			fmt.Fprintf(w, "   %-120s : %s\n", c.Project.Project, c.Project.Revision)
296			fmt.Fprintf(w, "       SHA   : %s\n", c.Commit.Sha)
297			fmt.Fprintf(w, "       Files : \n")
298			for _, f := range c.Commit.Files {
299				fmt.Fprintf(w, "         %s  %s\n", f.Type.String(), f.Filename)
300			}
301		}
302	}
303	if len(rsp.BuildFiles) > 0 {
304		fmt.Fprintln(w, "")
305		fmt.Fprintln(w, "  Build Files")
306		for _, b := range rsp.BuildFiles {
307			fmt.Fprintf(w, "            %-120s : %t \n", b.Name, b.Success)
308		}
309	}
310
311	targetPrint := func(target *app.BuildTarget) {
312		fmt.Fprintf(w, "      %-20s       : %s\n", "Name", target.Name)
313		fmt.Fprintf(w, "         %-20s    : %d\n", "Build Steps", target.Steps)
314		fmt.Fprintf(w, "         %-20s        \n", "Inputs")
315		fmt.Fprintf(w, "            %-20s : %d\n", "Files", target.FileCount)
316		fmt.Fprintf(w, "            %-20s : %d\n", "Projects", len(target.Projects))
317		fmt.Fprintln(w)
318		for name, proj := range target.Projects {
319			forkCount := 0
320			for _, file := range proj.Files {
321				if file.BranchDiff != nil {
322					forkCount++
323				}
324			}
325			fmt.Fprintf(w, "            %-120s : %d ", name, len(proj.Files))
326			if forkCount != 0 {
327				fmt.Fprintf(w, " (%d)\n", forkCount)
328			} else {
329				fmt.Fprintf(w, " \n")
330			}
331
332			if verbose {
333				for _, file := range proj.Files {
334					var fork string
335					if file.BranchDiff != nil {
336						fork = fmt.Sprintf("(%d+ %d-)", file.BranchDiff.AddedLines, file.BranchDiff.DeletedLines)
337					}
338					fmt.Fprintf(w, "               %-20s %s\n", fork, file.Filename)
339				}
340
341			}
342		}
343
344	}
345	fmt.Fprintln(w, "  Targets")
346	for _, t := range rsp.Report.Targets {
347		targetPrint(t)
348	}
349
350	fmt.Fprintln(w, "  Run Times")
351	for _, p := range rsp.Profile {
352		fmt.Fprintf(w, "     %-30s : %f secs\n", p.Description, p.DurationSecs)
353	}
354
355}
356