356 lines
10 KiB
Go
356 lines
10 KiB
Go
// Copyright 2022 The Android Open Source Project
|
|
//
|
|
// 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 (
|
|
"bufio"
|
|
"context"
|
|
"encoding/json"
|
|
"errors"
|
|
"flag"
|
|
"fmt"
|
|
"io"
|
|
"log"
|
|
"os"
|
|
"runtime"
|
|
"strings"
|
|
"time"
|
|
|
|
"tools/treble/build/report/app"
|
|
"tools/treble/build/report/local"
|
|
"tools/treble/build/report/report"
|
|
)
|
|
|
|
type Build interface {
|
|
Build(ctx context.Context, target string) *app.BuildCmdResult
|
|
}
|
|
|
|
type tool interface {
|
|
Run(ctx context.Context, rtx *report.Context, rsp *response) error
|
|
PrintText(w io.Writer, rsp *response, verbose bool)
|
|
}
|
|
type repoFlags []app.ProjectCommit
|
|
|
|
func (r *repoFlags) Set(value string) error {
|
|
commit := app.ProjectCommit{}
|
|
items := strings.Split(value, ":")
|
|
if len(items) > 2 {
|
|
return (errors.New("Invalid repo value expected (proj:sha) format"))
|
|
}
|
|
commit.Project = items[0]
|
|
if len(items) > 1 {
|
|
commit.Revision = items[1]
|
|
}
|
|
*r = append(*r, commit)
|
|
return nil
|
|
}
|
|
func (r *repoFlags) String() string {
|
|
items := []string{}
|
|
for _, fl := range *r {
|
|
items = append(items, fmt.Sprintf("%s:%s", fl.Project, fl.Revision))
|
|
}
|
|
return strings.Join(items, " ")
|
|
}
|
|
|
|
var (
|
|
// Common flags
|
|
ninjaDbPtr = flag.String("ninja", local.DefNinjaDb(), "Set the .ninja file to use when building metrics")
|
|
ninjaExcPtr = flag.String("ninja_cmd", local.DefNinjaExc(), "Set the ninja executable")
|
|
ninjaTimeoutStr = flag.String("ninja_timeout", local.DefaultNinjaTimeout, "Default ninja timeout")
|
|
buildTimeoutStr = flag.String("build_timeout", local.DefaultNinjaBuildTimeout, "Default build timeout")
|
|
manifestPtr = flag.String("manifest", local.DefManifest(), "Set the location of the manifest file")
|
|
upstreamPtr = flag.String("upstream", "", "Upstream branch to compare files against")
|
|
repoBasePtr = flag.String("repo_base", local.DefRepoBase(), "Set the repo base directory")
|
|
workerCountPtr = flag.Int("worker_count", runtime.NumCPU(), "Number of worker routines")
|
|
buildWorkerCountPtr = flag.Int("build_worker_count", local.MaxNinjaCliWorkers, "Number of build worker routines")
|
|
clientServerPtr = flag.Bool("client_server", false, "Run client server mode")
|
|
buildPtr = flag.Bool("build", false, "Build targets")
|
|
jsonPtr = flag.Bool("json", false, "Print json data")
|
|
verbosePtr = flag.Bool("v", false, "Print verbose text data")
|
|
outputPtr = flag.String("o", "", "Output to file")
|
|
projsPtr = flag.Bool("projects", false, "Include project repo data")
|
|
|
|
hostFlags = flag.NewFlagSet("host", flag.ExitOnError)
|
|
queryFlags = flag.NewFlagSet("query", flag.ExitOnError)
|
|
pathsFlags = flag.NewFlagSet("paths", flag.ExitOnError)
|
|
)
|
|
|
|
// Add profiling data
|
|
type profTime struct {
|
|
Description string `json:"description"`
|
|
DurationSecs float64 `json:"duration"`
|
|
}
|
|
|
|
type commit struct {
|
|
Project app.ProjectCommit `json:"project"`
|
|
Commit *app.GitCommit `json:"commit"`
|
|
}
|
|
|
|
// Use one structure for output for now
|
|
type response struct {
|
|
Commits []commit `json:"commits,omitempty"`
|
|
Inputs []string `json:"files,omitempty"`
|
|
BuildFiles []*app.BuildCmdResult `json:"build_files,omitempty"`
|
|
Targets []string `json:"targets,omitempty"`
|
|
Report *app.Report `json:"report,omitempty"`
|
|
|
|
// Subcommand data
|
|
Query *app.QueryResponse `json:"query,omitempty"`
|
|
Paths []*app.BuildPath `json:"build_paths,omitempty"`
|
|
Host *app.HostReport `json:"host,omitempty"`
|
|
Projects map[string]*app.GitProject `json:"projects,omitempty"`
|
|
// Profile data
|
|
Profile []*profTime `json:"profile"`
|
|
}
|
|
|
|
func main() {
|
|
startTime := time.Now()
|
|
ctx := context.Background()
|
|
rsp := &response{}
|
|
|
|
var addProfileData = func(desc string) {
|
|
rsp.Profile = append(rsp.Profile, &profTime{Description: desc, DurationSecs: time.Since(startTime).Seconds()})
|
|
startTime = time.Now()
|
|
}
|
|
flag.Parse()
|
|
|
|
ninjaTimeout, err := time.ParseDuration(*ninjaTimeoutStr)
|
|
if err != nil {
|
|
log.Fatalf("Invalid ninja timeout %s", *ninjaTimeoutStr)
|
|
}
|
|
|
|
buildTimeout, err := time.ParseDuration(*buildTimeoutStr)
|
|
if err != nil {
|
|
log.Fatalf("Invalid build timeout %s", *buildTimeoutStr)
|
|
}
|
|
|
|
subArgs := flag.Args()
|
|
defBuildTarget := "droid"
|
|
log.SetFlags(log.LstdFlags | log.Llongfile)
|
|
|
|
ninja := local.NewNinjaCli(*ninjaExcPtr, *ninjaDbPtr, ninjaTimeout, buildTimeout, *clientServerPtr)
|
|
|
|
if *clientServerPtr {
|
|
ninjaServ := local.NewNinjaServer(*ninjaExcPtr, *ninjaDbPtr)
|
|
defer ninjaServ.Kill()
|
|
go func() {
|
|
|
|
ninjaServ.Start(ctx)
|
|
}()
|
|
if err := ninja.WaitForServer(ctx, int(ninjaTimeout.Seconds())); err != nil {
|
|
log.Fatalf("Failed to connect to server")
|
|
}
|
|
}
|
|
rtx := &report.Context{
|
|
RepoBase: *repoBasePtr,
|
|
Repo: &report.RepoMan{},
|
|
Build: ninja,
|
|
Project: local.NewGitCli(),
|
|
WorkerCount: *workerCountPtr,
|
|
BuildWorkerCount: *buildWorkerCountPtr,
|
|
}
|
|
|
|
var subcommand tool
|
|
var commits repoFlags
|
|
if len(subArgs) > 0 {
|
|
switch subArgs[0] {
|
|
case "host":
|
|
hostToolPathPtr := hostFlags.String("hostbin", local.DefHostBinPath(), "Set the output directory for host tools")
|
|
hostFlags.Parse(subArgs[1:])
|
|
|
|
subcommand = &hostReport{toolPath: *hostToolPathPtr}
|
|
rsp.Targets = hostFlags.Args()
|
|
|
|
case "query":
|
|
queryFlags.Var(&commits, "repo", "Repo:SHA to query")
|
|
queryFlags.Parse(subArgs[1:])
|
|
subcommand = &queryReport{}
|
|
rsp.Targets = queryFlags.Args()
|
|
|
|
case "paths":
|
|
pathsFlags.Var(&commits, "repo", "Repo:SHA to build")
|
|
singlePathPtr := pathsFlags.Bool("1", false, "Get single path to output target")
|
|
pathsFlags.Parse(subArgs[1:])
|
|
|
|
subcommand = &pathsReport{build_target: defBuildTarget, single: *singlePathPtr}
|
|
|
|
rsp.Inputs = pathsFlags.Args()
|
|
|
|
default:
|
|
rsp.Targets = subArgs
|
|
}
|
|
}
|
|
addProfileData("Init")
|
|
rtx.ResolveProjectMap(ctx, *manifestPtr, *upstreamPtr)
|
|
addProfileData("Project Map")
|
|
|
|
// Add project to output if requested
|
|
if *projsPtr == true {
|
|
rsp.Projects = make(map[string]*app.GitProject)
|
|
for k, p := range rtx.Info.ProjMap {
|
|
rsp.Projects[k] = p.GitProj
|
|
}
|
|
}
|
|
|
|
// Resolve any commits
|
|
if len(commits) > 0 {
|
|
log.Printf("Resolving %s", commits.String())
|
|
for _, c := range commits {
|
|
commit := commit{Project: c}
|
|
info, files, err := report.ResolveCommit(ctx, rtx, &c)
|
|
if err != nil {
|
|
log.Fatalf("Failed to resolve commit %s:%s", c.Project, c.Revision)
|
|
}
|
|
commit.Commit = info
|
|
rsp.Commits = append(rsp.Commits, commit)
|
|
|
|
// Add files to list of inputs
|
|
rsp.Inputs = append(rsp.Inputs, files...)
|
|
}
|
|
addProfileData("Commit Resolution")
|
|
}
|
|
|
|
// Run any sub tools
|
|
if subcommand != nil {
|
|
if err := subcommand.Run(ctx, rtx, rsp); err != nil {
|
|
log.Fatal(err)
|
|
}
|
|
addProfileData(subArgs[0])
|
|
}
|
|
|
|
buildErrors := 0
|
|
if *buildPtr {
|
|
// Only support default builder (non server-client)
|
|
builder := local.NewNinjaCli(local.DefNinjaExc(), *ninjaDbPtr, ninjaTimeout, buildTimeout, false /*clientMode*/)
|
|
for _, t := range rsp.Targets {
|
|
log.Printf("Building %s\n", t)
|
|
res := builder.Build(ctx, t)
|
|
addProfileData(fmt.Sprintf("Build %s", t))
|
|
log.Printf("%s\n", res.Output)
|
|
if res.Success != true {
|
|
buildErrors++
|
|
}
|
|
rsp.BuildFiles = append(rsp.BuildFiles, res)
|
|
}
|
|
}
|
|
|
|
// Generate report
|
|
log.Printf("Generating report for targets %s", rsp.Targets)
|
|
req := &app.ReportRequest{Targets: rsp.Targets}
|
|
rsp.Report, err = report.RunReport(ctx, rtx, req)
|
|
addProfileData("Report")
|
|
if err != nil {
|
|
log.Fatal(fmt.Sprintf("Report failure <%s>", err))
|
|
}
|
|
|
|
if *jsonPtr {
|
|
b, _ := json.MarshalIndent(rsp, "", "\t")
|
|
if *outputPtr == "" {
|
|
os.Stdout.Write(b)
|
|
} else {
|
|
os.WriteFile(*outputPtr, b, 0644)
|
|
}
|
|
} else {
|
|
if *outputPtr == "" {
|
|
printTextReport(os.Stdout, subcommand, rsp, *verbosePtr)
|
|
} else {
|
|
file, err := os.Create(*outputPtr)
|
|
if err != nil {
|
|
log.Fatalf("Failed to create output file %s (%s)", *outputPtr, err)
|
|
}
|
|
w := bufio.NewWriter(file)
|
|
printTextReport(w, subcommand, rsp, *verbosePtr)
|
|
w.Flush()
|
|
}
|
|
|
|
}
|
|
|
|
if buildErrors > 0 {
|
|
log.Fatal(fmt.Sprintf("Failed to build %d targets", buildErrors))
|
|
}
|
|
}
|
|
|
|
func printTextReport(w io.Writer, subcommand tool, rsp *response, verbose bool) {
|
|
fmt.Fprintln(w, "Metric Report")
|
|
if subcommand != nil {
|
|
subcommand.PrintText(w, rsp, verbose)
|
|
}
|
|
|
|
if len(rsp.Commits) > 0 {
|
|
fmt.Fprintln(w, "")
|
|
fmt.Fprintln(w, " Commit Results")
|
|
for _, c := range rsp.Commits {
|
|
fmt.Fprintf(w, " %-120s : %s\n", c.Project.Project, c.Project.Revision)
|
|
fmt.Fprintf(w, " SHA : %s\n", c.Commit.Sha)
|
|
fmt.Fprintf(w, " Files : \n")
|
|
for _, f := range c.Commit.Files {
|
|
fmt.Fprintf(w, " %s %s\n", f.Type.String(), f.Filename)
|
|
}
|
|
}
|
|
}
|
|
if len(rsp.BuildFiles) > 0 {
|
|
fmt.Fprintln(w, "")
|
|
fmt.Fprintln(w, " Build Files")
|
|
for _, b := range rsp.BuildFiles {
|
|
fmt.Fprintf(w, " %-120s : %t \n", b.Name, b.Success)
|
|
}
|
|
}
|
|
|
|
targetPrint := func(target *app.BuildTarget) {
|
|
fmt.Fprintf(w, " %-20s : %s\n", "Name", target.Name)
|
|
fmt.Fprintf(w, " %-20s : %d\n", "Build Steps", target.Steps)
|
|
fmt.Fprintf(w, " %-20s \n", "Inputs")
|
|
fmt.Fprintf(w, " %-20s : %d\n", "Files", target.FileCount)
|
|
fmt.Fprintf(w, " %-20s : %d\n", "Projects", len(target.Projects))
|
|
fmt.Fprintln(w)
|
|
for name, proj := range target.Projects {
|
|
forkCount := 0
|
|
for _, file := range proj.Files {
|
|
if file.BranchDiff != nil {
|
|
forkCount++
|
|
}
|
|
}
|
|
fmt.Fprintf(w, " %-120s : %d ", name, len(proj.Files))
|
|
if forkCount != 0 {
|
|
fmt.Fprintf(w, " (%d)\n", forkCount)
|
|
} else {
|
|
fmt.Fprintf(w, " \n")
|
|
}
|
|
|
|
if verbose {
|
|
for _, file := range proj.Files {
|
|
var fork string
|
|
if file.BranchDiff != nil {
|
|
fork = fmt.Sprintf("(%d+ %d-)", file.BranchDiff.AddedLines, file.BranchDiff.DeletedLines)
|
|
}
|
|
fmt.Fprintf(w, " %-20s %s\n", fork, file.Filename)
|
|
}
|
|
|
|
}
|
|
}
|
|
|
|
}
|
|
fmt.Fprintln(w, " Targets")
|
|
for _, t := range rsp.Report.Targets {
|
|
targetPrint(t)
|
|
}
|
|
|
|
fmt.Fprintln(w, " Run Times")
|
|
for _, p := range rsp.Profile {
|
|
fmt.Fprintf(w, " %-30s : %f secs\n", p.Description, p.DurationSecs)
|
|
}
|
|
|
|
}
|