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