diff options
author | Rob Seymour <rseymour@google.com> | 2022-06-22 19:43:54 +0000 |
---|---|---|
committer | Automerger Merge Worker <android-build-automerger-merge-worker@system.gserviceaccount.com> | 2022-06-22 19:43:54 +0000 |
commit | 19b570ff5c93bb111b69b89edeccb7a178c3aea8 (patch) | |
tree | 4b7ce172d8a870940c0cfa3ce25eb6ac03e5873b | |
parent | c572ee9baa080550ade3fd27451951aadce8e76c (diff) | |
parent | 08841703410cf054c5f45b2f0d04771ab3790a0e (diff) | |
download | treble-19b570ff5c93bb111b69b89edeccb7a178c3aea8.tar.gz |
Use paths to determine build targets for treble_build am: ca63627bbe am: 54176d5cbb am: 0884170341
Original change: https://android-review.googlesource.com/c/platform/tools/treble/+/2132556
Change-Id: If9adf77844bc02c9cbc05433d68875b1996a8ca1
Signed-off-by: Automerger Merge Worker <android-build-automerger-merge-worker@system.gserviceaccount.com>
-rw-r--r-- | build/report/app/build.go | 27 | ||||
-rw-r--r-- | build/report/app/git.go | 41 | ||||
-rw-r--r-- | build/report/app/report.go | 28 | ||||
-rw-r--r-- | build/report/cmd/Android.bp | 6 | ||||
-rw-r--r-- | build/report/cmd/host.go | 75 | ||||
-rw-r--r-- | build/report/cmd/main.go | 262 | ||||
-rw-r--r-- | build/report/cmd/outputs.go | 88 | ||||
-rw-r--r-- | build/report/cmd/paths.go | 64 | ||||
-rw-r--r-- | build/report/cmd/query.go | 44 | ||||
-rw-r--r-- | build/report/cmd/report.go | 122 | ||||
-rw-r--r-- | build/report/local/git.go | 91 | ||||
-rw-r--r-- | build/report/local/git_test.go | 37 | ||||
-rw-r--r-- | build/report/local/ninja.go | 47 | ||||
-rw-r--r-- | build/report/local/ninja_test.go | 28 | ||||
-rw-r--r-- | build/report/report/build.go | 83 | ||||
-rw-r--r-- | build/report/report/dependencies.go | 4 | ||||
-rw-r--r-- | build/report/report/projects.go | 166 | ||||
-rw-r--r-- | build/report/report/report_test.go | 57 | ||||
-rw-r--r-- | build/report/report/run.go | 96 | ||||
-rw-r--r-- | build/report/report/types.go | 20 |
20 files changed, 832 insertions, 554 deletions
diff --git a/build/report/app/build.go b/build/report/app/build.go index fffeb95..8b78e23 100644 --- a/build/report/app/build.go +++ b/build/report/app/build.go @@ -39,26 +39,12 @@ type BuildPath struct { Paths []string `json:paths"` } -// Build file -type BuildFile struct { - Name string `json:"name"` // Source file name - Revision string `json:"revision"` // SHA revision -} - -// Build project -type BuildProject struct { - Path string `json:"path"` // Project path - Name string `json:"name"` // Project name - Revision string `json:"revision"` // SHA revision of project - Files []BuildFile `json:"files"` // Project files -} - // Build target type BuildTarget struct { - Name string `json:"name"` // Target name - Steps int `json:"build_steps"` // Number of steps to build target - FileCount int `json:"files"` // Number of input files for a target - Projects map[string]*BuildProject `json:"projects"` // Inputs projects/files of a target + Name string `json:"name"` // Target name + Steps int `json:"build_steps"` // Number of steps to build target + FileCount int `json:"files"` // Number of input files for a target + Projects map[string]*GitProject `json:"projects"` // Inputs projects/files of a target } // Build command result @@ -67,3 +53,8 @@ type BuildCmdResult struct { Output []string `json:"output"` Success bool `json:"success"` } + +// Build dependencies +type BuildDeps struct { + Targets map[string][]string `json:"targets"` +} diff --git a/build/report/app/git.go b/build/report/app/git.go index 9ebed29..7cd21a4 100644 --- a/build/report/app/git.go +++ b/build/report/app/git.go @@ -14,22 +14,31 @@ package app +// GIT diff +type GitDiff struct { + AddedLines int `json:"added_lines"` + DeletedLines int `json:"deleted_lines"` + BinaryDiff bool `json:"binary_diff"` +} + // GIT tree object (files,dirs...) type GitTreeObj struct { - Permissions string `json:"permissions"` - Type string `json:"type"` - Sha string `json:"sha"` - Filename string `json:"filename"` + Permissions string `json:"permissions"` + Type string `json:"type"` + Sha string `json:"sha"` + Filename string `json:"filename"` + BranchDiff *GitDiff `json:"branch_diff"` } // GitProject type GitProject struct { - WorkDir string `json:"working_dir"` // Working directory - GitDir string `json:"git_dir"` // GIT directory - Remote string `json:"remote"` // Remote Name - RemoteUrl string `json:"remote_url"` // Remote URL - Revision string `json:"revision"` // Revision (SHA) - Files []GitTreeObj `json:"files"` // Files within the project + RepoDir string `json:"repo_dir"` // Relative directory within repo + WorkDir string `json:"working_dir"` // Working directory + GitDir string `json:"git_dir"` // GIT directory + Remote string `json:"remote"` // Remote Name + RemoteUrl string `json:"remote_url"` // Remote URL + Revision string `json:"revision"` // Revision (SHA) + Files map[string]*GitTreeObj `json:"files"` // Files within the project } type GitCommitFileType int @@ -50,3 +59,15 @@ type GitCommit struct { Sha string `json:"sha"` Files []GitCommitFile `json:"files"` } + +func (t GitCommitFileType) String() string { + switch t { + case GitFileModified: + return "M" + case GitFileAdded: + return "A" + case GitFileRemoved: + return "R" + } + return "" +} diff --git a/build/report/app/report.go b/build/report/app/report.go index afe86ec..bc9c7ab 100644 --- a/build/report/app/report.go +++ b/build/report/app/report.go @@ -16,23 +16,19 @@ package app // Report request structure type ReportRequest struct { - IncludeHostTools bool `json:"include_host_tools"` // Get target information for all host tools found - HostToolPath string `json:"host_tool_path"` // Location of output host tools - ManifestFile string `json:"manifest"` // Repo manifest file - Targets []string `json:"targets"` // Targets -} - -// Host tool report response data -type HostReport struct { - Path string `json:"path"` // Path to find host tools - SymLinks int `json:"sym_links"` // Number of symlinks found - Targets []*BuildTarget `json:"targets"` // Build targets for tools found + Targets []string `json:"targets"` // Targets } // Report response data type Report struct { - Host *HostReport `json:"host"` // Host response - Targets []*BuildTarget `json:"targets"` // Build target data + Targets map[string]*BuildTarget `json:"targets"` // Build target data +} + +// Host tool report response data +type HostReport struct { + Path string `json:"path"` // Path to find host tools + SymLinks int `json:"sym_links"` // Number of symlinks found + Targets []string `json:"targets"` // Target for tools found } // Project level commit @@ -41,12 +37,6 @@ type ProjectCommit struct { Revision string `json:"revision"` // Revision } -// Project level commits -type ProjectCommits struct { - ManifestFile string `json:"manifest"` // Repo manifest file - Commits []ProjectCommit `json:"commits"` // Commits to resolve -} - // Query request type QueryRequest struct { Files []string `json:"files"` // Files to resolve diff --git a/build/report/cmd/Android.bp b/build/report/cmd/Android.bp index 9b53fe9..ec83692 100644 --- a/build/report/cmd/Android.bp +++ b/build/report/cmd/Android.bp @@ -24,10 +24,10 @@ package { blueprint_go_binary { name: "treble_build", srcs: [ + "host.go", "main.go", - "outputs.go", - "report.go", - + "paths.go", + "query.go", ], deps: [ "treble_report_app", diff --git a/build/report/cmd/host.go b/build/report/cmd/host.go new file mode 100644 index 0000000..0b8e65f --- /dev/null +++ b/build/report/cmd/host.go @@ -0,0 +1,75 @@ +// 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 ( + "context" + "fmt" + "io" + "path/filepath" + + "tools/treble/build/report/report" +) + +type hostReport struct { + toolPath string +} + +// Determine host tools +func (h *hostReport) Run(ctx context.Context, rtx *report.Context, rsp *response) error { + var err error + rsp.Host, err = report.ResolveHostTools(ctx, h.toolPath) + if err != nil { + return err + } + rsp.Targets = append(rsp.Targets, rsp.Host.Targets...) + return nil +} + +func (h *hostReport) PrintText(w io.Writer, rsp *response, verbose bool) { + if rsp.Host != nil { + // Get the unique number of inputs + hostSourceFileMap := make(map[string]bool) + hostSourceProjectMap := make(map[string]bool) + + for _, t := range rsp.Host.Targets { + // Find target in report + if bt, exists := rsp.Report.Targets[t]; exists { + for name, proj := range bt.Projects { + hostSourceProjectMap[name] = true + for f := range proj.Files { + hostSourceFileMap[filepath.Join(name, f)] = true + } + } + // Remove the target from being printed + delete(rsp.Report.Targets, t) + } + } + + fmt.Fprintln(w, " Host Tools") + fmt.Fprintf(w, " %-20s : %s\n", "Directory", rsp.Host.Path) + fmt.Fprintf(w, " %-20s : %d\n", "Tools", len(rsp.Host.Targets)) + fmt.Fprintf(w, " %-20s : %d\n", "Prebuilts", rsp.Host.SymLinks) + fmt.Fprintf(w, " %-20s : %d\n", "Inputs", len(hostSourceFileMap)) + fmt.Fprintf(w, " %-20s : %d\n", "Projects", len(hostSourceProjectMap)) + + if verbose { + for proj, _ := range hostSourceProjectMap { + fmt.Fprintf(w, " %s\n", proj) + } + } + } + +} diff --git a/build/report/cmd/main.go b/build/report/cmd/main.go index 2d704b5..0264ff3 100644 --- a/build/report/cmd/main.go +++ b/build/report/cmd/main.go @@ -15,11 +15,15 @@ package main import ( + "bufio" "context" + "encoding/json" "errors" "flag" "fmt" + "io" "log" + "os" "runtime" "strings" @@ -28,6 +32,14 @@ import ( "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 { @@ -53,63 +65,233 @@ func (r *repoFlags) String() string { 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") - manifestPtr = flag.String("manifest", local.DefManifest(), "Set the location of the manifest file") - repoBasePtr = flag.String("repo_base", local.DefRepoBase(), "Set the repo base directory") - workerCountPtr = flag.Int("worker_count", runtime.NumCPU(), "Number of worker routines") - - reportFlags = flag.NewFlagSet("report", flag.ExitOnError) - outputsFlags = flag.NewFlagSet("outputs", flag.ExitOnError) + 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") + 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") + 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") + + hostFlags = flag.NewFlagSet("host", flag.ExitOnError) + queryFlags = flag.NewFlagSet("query", flag.ExitOnError) + pathsFlags = flag.NewFlagSet("paths", flag.ExitOnError) ) +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"` +} + func main() { ctx := context.Background() - flag.Parse() + rsp := &response{} - subCmds := strings.Join([]string{"report", "outputs"}, " ") + flag.Parse() subArgs := flag.Args() if len(subArgs) < 1 { - log.Fatalf("Expected a sub-command. Possible sub-commands %s", subCmds) + // Nothing to do + return } + defBuildTarget := "droid" log.SetFlags(log.LstdFlags | log.Llongfile) ninja := local.NewNinjaCli(*ninjaExcPtr, *ninjaDbPtr) rtx := &report.Context{ - RepoBase: *repoBasePtr, - Repo: &report.RepoMan{}, - Build: ninja, - Project: local.NewGitCli(), - WorkerCount: *workerCountPtr} + RepoBase: *repoBasePtr, + Repo: &report.RepoMan{}, + Build: ninja, + Project: local.NewGitCli(), + WorkerCount: *workerCountPtr, + BuildWorkerCount: *buildWorkerCountPtr, + } + + var subcommand tool + var commits repoFlags switch subArgs[0] { - case "report": - incHostToolPtr := reportFlags.Bool("host", false, "Include host tool metrics") - hostToolPathPtr := reportFlags.String("hostbin", local.DefHostBinPath(), "Set the output directory for host tools") - jsonPtr := reportFlags.Bool("json", false, "Print json data") - verbosePtr := reportFlags.Bool("v", false, "Print verbose data") - outputPtr := reportFlags.String("o", "", "Output to file") - - reportFlags.Parse(subArgs[1:]) - reportExc(ctx, rtx, - &reportArgs{incHostTools: *incHostToolPtr, hostToolPath: *hostToolPathPtr, - manifest: *manifestPtr, jsonOut: *jsonPtr, verbose: *verbosePtr, - outFile: *outputPtr}, - reportFlags.Args()) - - case "outputs": - var commits repoFlags - outputsFlags.Var(&commits, "repo", "Repo:SHA to build") - buildPtr := outputsFlags.Bool("build", false, "Build outputs") - outputPtr := outputsFlags.String("o", "", "Output to file") - outputsFlags.Parse(subArgs[1:]) - outputsExc(ctx, rtx, - &outputArgs{manifest: *manifestPtr, build: *buildPtr, builder: ninja, outputFile: *outputPtr}, - commits, outputsFlags.Args()) + 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.Targets = pathsFlags.Args() default: - log.Fatalf("Unknown sub-command <%s>. Possible sub-commands %s", subCmds) + rsp.Targets = subArgs + } + + rtx.ResolveProjectMap(ctx, *manifestPtr, *upstreamPtr) + // 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...) + } + } + + // Run any sub tools + if subcommand != nil { + if err := subcommand.Run(ctx, rtx, rsp); err != nil { + log.Fatal(err) + } + } + + buildErrors := 0 + if *buildPtr { + for _, t := range rsp.Targets { + log.Printf("Building %s\n", t) + res := ninja.Build(ctx, t) + log.Printf("%s\n", res.Output) + if res.Success != true { + buildErrors++ + } + rsp.BuildFiles = append(rsp.BuildFiles, res) + } + } + + // Generate report + var err error + log.Printf("Generating report for targets %s", rsp.Targets) + req := &app.ReportRequest{Targets: rsp.Targets} + rsp.Report, err = report.RunReport(ctx, rtx, req) + 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) } } diff --git a/build/report/cmd/outputs.go b/build/report/cmd/outputs.go deleted file mode 100644 index 86d2dd6..0000000 --- a/build/report/cmd/outputs.go +++ /dev/null @@ -1,88 +0,0 @@ -// 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 ( - "context" - "encoding/json" - "log" - "os" - - "tools/treble/build/report/app" - "tools/treble/build/report/report" -) - -type response struct { - Commits *app.ProjectCommits `json:"commits"` - Query *app.QueryRequest `json:"query"` - QueryRsp *app.QueryResponse `json:"query_result"` - BuildFiles []*app.BuildCmdResult `json:"build_files"` -} - -type Build interface { - Build(ctx context.Context, target string) *app.BuildCmdResult -} -type outputArgs struct { - manifest string - build bool - builder Build - outputFile string -} - -func outputsExc(ctx context.Context, rtx *report.Context, args *outputArgs, commits repoFlags, files []string) { - ret := response{} - if len(commits) > 0 { - log.Printf("Resolving %s", commits.String()) - ret.Commits = &app.ProjectCommits{ManifestFile: *manifestPtr, Commits: commits} - commitFiles, err := report.ResolveCommits(ctx, rtx, ret.Commits) - if err != nil { - log.Fatalf("Failed to resolve commits %s", commits.String()) - } - files = append(files, commitFiles...) - } - - log.Printf("Querying files %s \n", files) - ret.Query = &app.QueryRequest{Files: files} - var err error - ret.QueryRsp, err = report.RunQuery(ctx, rtx, ret.Query) - if err != nil { - log.Fatalf("Failed to query outputs %s\n", err) - } - error := false - if args.build { - log.Println("Filtering output files") - buildFiles := report.RunPathFilter(ctx, rtx, "droid", ret.QueryRsp.OutputFiles) - for _, f := range buildFiles { - log.Printf("Building %s\n", f) - res := args.builder.Build(ctx, f) - log.Printf("%s\n", res.Output) - if res.Success != true { - error = true - } - ret.BuildFiles = append(ret.BuildFiles, res) - } - } - - b, _ := json.MarshalIndent(ret, "", "\t") - if args.outputFile == "" { - os.Stdout.Write(b) - } else { - os.WriteFile(args.outputFile, b, 0644) - } - - if error { - log.Fatal("Failed to build outputs") - } -} diff --git a/build/report/cmd/paths.go b/build/report/cmd/paths.go new file mode 100644 index 0000000..4c28d4f --- /dev/null +++ b/build/report/cmd/paths.go @@ -0,0 +1,64 @@ +// 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 ( + "context" + "fmt" + "io" + "log" + + "tools/treble/build/report/report" +) + +type pathsReport struct { + build_target string // Target used to filter build request + single bool // Get single path +} + +func (p pathsReport) Run(ctx context.Context, rtx *report.Context, rsp *response) error { + + log.Printf("Resolving paths for %s (single : %v)\n", rsp.Inputs, p.single) + rsp.Paths = report.RunPaths(ctx, rtx, p.build_target, p.single, rsp.Inputs) + + // The path is returned in an array in the form [build_target, path_stop1,...,path_stopN,source_file] + // Choose the closest build target (path_stopN) to the source file to build to reduce the amount that + // is built. + const buildPathIndex = 2 + build_targets := make(map[string]bool) + for _, path := range rsp.Paths { + // Default to build closest build target + if len(path.Paths) > buildPathIndex { + build_targets[path.Paths[len(path.Paths)-buildPathIndex]] = true + } + } + for b := range build_targets { + rsp.Targets = append(rsp.Targets, b) + } + + return nil +} + +func (h *pathsReport) PrintText(w io.Writer, rsp *response, verbose bool) { + if len(rsp.Paths) > 0 { + fmt.Fprintln(w, " Paths") + for _, p := range rsp.Paths { + // Provide path from target to dependency with the + // path length, since target and dependency are in the + // path subtract them out from length + fmt.Fprintf(w, " %s..(%d)..%-s\n", p.Target, len(p.Paths)-2, p.Dependency) + } + } +} diff --git a/build/report/cmd/query.go b/build/report/cmd/query.go new file mode 100644 index 0000000..e4c7f0f --- /dev/null +++ b/build/report/cmd/query.go @@ -0,0 +1,44 @@ +// 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 ( + "context" + "io" + "log" + + "tools/treble/build/report/app" + "tools/treble/build/report/report" +) + +// Command arguments +type queryReport struct { +} + +// Run query +func (o queryReport) Run(ctx context.Context, rtx *report.Context, rsp *response) error { + var err error + log.Printf("Querying files %s\n", rsp.Inputs) + req := &app.QueryRequest{Files: rsp.Inputs} + rsp.Query, err = report.RunQuery(ctx, rtx, req) + if err != nil { + return err + } + + return nil + +} +func (h *queryReport) PrintText(w io.Writer, rsp *response, verbose bool) { +} diff --git a/build/report/cmd/report.go b/build/report/cmd/report.go deleted file mode 100644 index be50814..0000000 --- a/build/report/cmd/report.go +++ /dev/null @@ -1,122 +0,0 @@ -// 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" - "fmt" - "io" - "log" - "os" - "path/filepath" - - "tools/treble/build/report/app" - "tools/treble/build/report/report" -) - -type reportArgs struct { - incHostTools bool - hostToolPath string - manifest string - jsonOut bool - verbose bool - outFile string -} - -func reportExc(ctx context.Context, rtx *report.Context, args *reportArgs, targets []string) { - req := &app.ReportRequest{IncludeHostTools: args.incHostTools, - HostToolPath: args.hostToolPath, - ManifestFile: args.manifest, - Targets: targets} - - out, err := report.RunReport(ctx, rtx, req) - - if err != nil { - log.Fatalf("Failed to run report : %s\n", err) - } - if args.jsonOut { - b, _ := json.MarshalIndent(out, "", "\t") - if args.outFile == "" { - os.Stdout.Write(b) - } else { - os.WriteFile(args.outFile, b, 0644) - } - } else { - if args.outFile == "" { - printTextReport(os.Stdout, out, args.verbose) - } else { - file, err := os.Create(args.outFile) - if err != nil { - log.Fatalf("Failed to create output file %s (%s)", args.outFile, err) - } - w := bufio.NewWriter(file) - printTextReport(w, out, args.verbose) - } - } - -} - -func printTextReport(w io.Writer, report *app.Report, verbose bool) { - fmt.Fprintln(w, "Metric Report") - if report.Host != nil { - // Get the unique number of inputs - hostSourceFileMap := make(map[string]bool) - hostSourceProjectMap := make(map[string]bool) - for i, _ := range report.Host.Targets { - for j, _ := range report.Host.Targets[i].Projects { - hostSourceProjectMap[report.Host.Targets[i].Projects[j].Name] = true - for k, _ := range report.Host.Targets[i].Projects[j].Files { - hostSourceFileMap[filepath.Join(report.Host.Targets[i].Projects[j].Path, - report.Host.Targets[i].Projects[j].Files[k].Name)] = true - } - } - } - fmt.Fprintln(w, " Host Tools") - fmt.Fprintf(w, " %-20s : %s\n", "Directory", report.Host.Path) - fmt.Fprintf(w, " %-20s : %d\n", "Tools", len(report.Host.Targets)) - fmt.Fprintf(w, " %-20s : %d\n", "Prebuilts", report.Host.SymLinks) - fmt.Fprintf(w, " %-20s : %d\n", "Inputs", len(hostSourceFileMap)) - fmt.Fprintf(w, " %-20s : %d\n", "Projects", len(hostSourceProjectMap)) - - if verbose { - for proj, _ := range hostSourceProjectMap { - fmt.Fprintf(w, " %s\n", proj) - } - } - } - if len(report.Targets) != 0 { - fmt.Fprintln(w, " Targets") - for i, _ := range report.Targets { - fmt.Fprintf(w, " %-20s : %s\n", "Name", report.Targets[i].Name) - fmt.Fprintf(w, " %-20s : %d\n", "Build Steps", report.Targets[i].Steps) - fmt.Fprintf(w, " %-20s \n", "Inputs") - fmt.Fprintf(w, " %-20s : %d\n", "Files", report.Targets[i].FileCount) - fmt.Fprintf(w, " %-20s : %d\n", "Projects", len(report.Targets[i].Projects)) - fmt.Fprintln(w) - for _, proj := range report.Targets[i].Projects { - fmt.Fprintf(w, " %-120s : %d\n", proj.Name, len(proj.Files)) - if verbose { - for _, file := range proj.Files { - fmt.Fprintf(w, " %-20s : %s\n", file.Revision, file.Name) - } - } - } - - } - - } -} diff --git a/build/report/local/git.go b/build/report/local/git.go index 2a7dc04..2793042 100644 --- a/build/report/local/git.go +++ b/build/report/local/git.go @@ -24,6 +24,7 @@ import ( "context" "errors" "fmt" + "strconv" "strings" "time" @@ -32,10 +33,11 @@ import ( // Separate out the executable to allow tests to override the results type gitExec interface { - ProjectInfo(ctx context.Context, gitDir string, workDir string) (out *bytes.Buffer, err error) - RemoteUrl(ctx context.Context, gitDir string, workDir string, remote string) (*bytes.Buffer, error) - Tree(ctx context.Context, gitDir string, workDir string, revision string) (*bytes.Buffer, error) - CommitInfo(ctx context.Context, gitDir string, workDir string, revision string) (*bytes.Buffer, error) + ProjectInfo(ctx context.Context, gitDir, workDir string) (out *bytes.Buffer, err error) + RemoteUrl(ctx context.Context, gitDir, workDir, remote string) (*bytes.Buffer, error) + Tree(ctx context.Context, gitDir, workDir, revision string) (*bytes.Buffer, error) + CommitInfo(ctx context.Context, gitDir, workDir, revision string) (*bytes.Buffer, error) + DiffBranches(ctx context.Context, gitDir, workDir, upstream, sha string) (*bytes.Buffer, error) } type gitCli struct { @@ -43,7 +45,7 @@ type gitCli struct { } // Create GIT project based on input parameters -func (cli gitCli) Project(ctx context.Context, path string, gitDir string, remote string, revision string, getFiles bool) (*app.GitProject, error) { +func (cli gitCli) Project(ctx context.Context, path, gitDir, remote, revision string) (*app.GitProject, error) { workDir := path // Set defaults if remote == "" { @@ -64,11 +66,13 @@ func (cli gitCli) Project(ctx context.Context, path string, gitDir string, remot } } // Create project to use to run commands - out := &app.GitProject{WorkDir: workDir, + out := &app.GitProject{ + RepoDir: path, + WorkDir: workDir, GitDir: gitDir, Remote: remote, Revision: revision, - Files: []app.GitTreeObj{}} + Files: make(map[string]*app.GitTreeObj)} // Remote URL if raw, err := cli.git.RemoteUrl(ctx, gitDir, workDir, remote); err == nil { @@ -78,18 +82,33 @@ func (cli gitCli) Project(ctx context.Context, path string, gitDir string, remot } } - // all files in repo - if getFiles { - if raw, err := cli.git.Tree(ctx, gitDir, workDir, revision); err == nil { - lsFiles, err := parseLsTree(raw) - if err == nil { - out.Files = *lsFiles + return out, nil +} + +// Get all files in the repository if, upstream branch is provided mark which files differ from upstream +func (cli gitCli) PopulateFiles(ctx context.Context, proj *app.GitProject, upstream string) error { + if raw, err := cli.git.Tree(ctx, proj.GitDir, proj.WorkDir, proj.Revision); err == nil { + lsFiles, err := parseLsTree(raw) + if err == nil { + for _, file := range lsFiles { + proj.Files[file.Filename] = file + } + } + if upstream != "" { + + if diff, err := cli.git.DiffBranches(ctx, proj.GitDir, proj.WorkDir, upstream, proj.Revision); err == nil { + if diffFiles, err := parseBranchDiff(diff); err == nil { + for f, d := range diffFiles { + if file, exists := proj.Files[f]; exists { + file.BranchDiff = d + } + } + } } } } - return out, nil - + return nil } // Get the commit information associated with the input sha @@ -128,15 +147,36 @@ func parseRemoteUrl(data *bytes.Buffer) (url string, err error) { } // parse ls-tree -func parseLsTree(data *bytes.Buffer) (*[]app.GitTreeObj, error) { - out := &[]app.GitTreeObj{} +func parseLsTree(data *bytes.Buffer) ([]*app.GitTreeObj, error) { + out := []*app.GitTreeObj{} s := bufio.NewScanner(data) for s.Scan() { - obj := app.GitTreeObj{} + obj := &app.GitTreeObj{} // TODO // Filename could contain a <space> as quotepath is turned off, truncating the name here fmt.Sscanf(s.Text(), "%s %s %s %s", &obj.Permissions, &obj.Type, &obj.Sha, &obj.Filename) - *out = append(*out, obj) + out = append(out, obj) + } + return out, nil +} + +// parse branch diff (diff --num-stat) +func parseBranchDiff(data *bytes.Buffer) (map[string]*app.GitDiff, error) { + out := make(map[string]*app.GitDiff) + s := bufio.NewScanner(data) + for s.Scan() { + d := &app.GitDiff{} + var fname, added, deleted string + _, err := fmt.Sscanf(s.Text(), "%s %s %s", &added, &deleted, &fname) + if err == nil { + if added == "-" || deleted == "-" { + d.BinaryDiff = true + } else { + d.AddedLines, _ = strconv.Atoi(added) + d.DeletedLines, _ = strconv.Atoi(deleted) + } + } + out[fname] = d } return out, nil } @@ -184,21 +224,24 @@ func (git *gitCmd) runDirCmd(ctx context.Context, gitDir string, workDir string, return out, nil } -func (git *gitCmd) ProjectInfo(ctx context.Context, gitDir string, workDir string) (*bytes.Buffer, error) { +func (git *gitCmd) ProjectInfo(ctx context.Context, gitDir, workDir string) (*bytes.Buffer, error) { return git.runDirCmd(ctx, gitDir, workDir, []string{"rev-parse", "--show-toplevel", "HEAD"}) } -func (git *gitCmd) RemoteUrl(ctx context.Context, gitDir string, workDir string, remote string) (*bytes.Buffer, error) { +func (git *gitCmd) RemoteUrl(ctx context.Context, gitDir, workDir, remote string) (*bytes.Buffer, error) { return git.runDirCmd(ctx, gitDir, workDir, []string{"remote", "get-url", remote}) } -func (git *gitCmd) Tree(ctx context.Context, gitDir string, workDir string, revision string) (*bytes.Buffer, error) { +func (git *gitCmd) Tree(ctx context.Context, gitDir, workDir, revision string) (*bytes.Buffer, error) { cmdArgs := []string{"-c", "core.quotepath=off", "ls-tree", "--full-name", revision, "-r", "-t"} return git.runDirCmd(ctx, gitDir, workDir, cmdArgs) } -func (git *gitCmd) CommitInfo(ctx context.Context, gitDir string, workDir string, sha string) (*bytes.Buffer, error) { +func (git *gitCmd) CommitInfo(ctx context.Context, gitDir, workDir, sha string) (*bytes.Buffer, error) { cmdArgs := []string{"diff-tree", "-r", "-m", "--name-status", "--root", sha} return git.runDirCmd(ctx, gitDir, workDir, cmdArgs) } - +func (git *gitCmd) DiffBranches(ctx context.Context, gitDir, workDir, upstream, sha string) (*bytes.Buffer, error) { + cmdArgs := []string{"diff", "--numstat", fmt.Sprintf("%s...%s", upstream, sha)} + return git.runDirCmd(ctx, gitDir, workDir, cmdArgs) +} func NewGitCli() *gitCli { cli := &gitCli{git: &gitCmd{cmd: "git", timeout: 100000 * time.Millisecond}} return cli diff --git a/build/report/local/git_test.go b/build/report/local/git_test.go index 332f80f..d5345ae 100644 --- a/build/report/local/git_test.go +++ b/build/report/local/git_test.go @@ -29,24 +29,28 @@ type TestCmd struct { text string } type gitTestCli struct { - revParse *TestCmd - remoteUrl *TestCmd - tree *TestCmd - commit *TestCmd + revParse *TestCmd + remoteUrl *TestCmd + tree *TestCmd + commit *TestCmd + diffBranch *TestCmd } -func (g *gitTestCli) ProjectInfo(ctx context.Context, gitDir string, workDir string) (*bytes.Buffer, error) { +func (g *gitTestCli) ProjectInfo(ctx context.Context, gitDir, workDir string) (*bytes.Buffer, error) { return bytes.NewBufferString(g.revParse.text), g.revParse.err } -func (g *gitTestCli) RemoteUrl(ctx context.Context, gitDir string, workDir string, remote string) (*bytes.Buffer, error) { +func (g *gitTestCli) RemoteUrl(ctx context.Context, gitDir, workDir, remote string) (*bytes.Buffer, error) { return bytes.NewBufferString(g.remoteUrl.text), g.remoteUrl.err } -func (g *gitTestCli) Tree(ctx context.Context, gitDir string, workDir string, revision string) (*bytes.Buffer, error) { +func (g *gitTestCli) Tree(ctx context.Context, gitDir, workDir, revision string) (*bytes.Buffer, error) { return bytes.NewBufferString(g.tree.text), g.tree.err } -func (g *gitTestCli) CommitInfo(ctx context.Context, gitDir string, workDir string, sha string) (*bytes.Buffer, error) { +func (g *gitTestCli) CommitInfo(ctx context.Context, gitDir, workDir, sha string) (*bytes.Buffer, error) { return bytes.NewBufferString(g.commit.text), g.tree.err } +func (g *gitTestCli) DiffBranches(ctx context.Context, gitDir, workDir, upstream, sha string) (*bytes.Buffer, error) { + return bytes.NewBufferString(g.diffBranch.text), g.tree.err +} func Test_git(t *testing.T) { @@ -82,12 +86,14 @@ func Test_git(t *testing.T) { revCmd: &TestCmd{text: "/abs/path/to/work/dir\nsha_revision\n", err: nil}, remoteCmd: &TestCmd{text: "http://url/workdir", err: nil}, treeCmd: &TestCmd{text: "", err: nil}, - res: &app.GitProject{WorkDir: "/abs/path/to/work/dir", + res: &app.GitProject{ + RepoDir: "work/dir", + WorkDir: "/abs/path/to/work/dir", GitDir: ".git", Remote: "origin", RemoteUrl: "http://url/workdir", Revision: "sha_revision", - Files: []app.GitTreeObj{}}, + Files: make(map[string]*app.GitTreeObj)}, }, // Test empty commit commit: commitTest{ @@ -106,12 +112,14 @@ func Test_git(t *testing.T) { revCmd: &TestCmd{text: "/abs/path/to/work/dir\nsha_revision\n", err: nil}, remoteCmd: &TestCmd{text: "http://url/workdir", err: nil}, treeCmd: &TestCmd{text: "100644 blob 0000000000000000000000000000000000000001 file.1\n", err: nil}, - res: &app.GitProject{WorkDir: "/abs/path/to/work/dir", + res: &app.GitProject{ + RepoDir: "work/dir", + WorkDir: "/abs/path/to/work/dir", GitDir: ".git", Remote: "origin", RemoteUrl: "http://url/workdir", Revision: "sha_revision", - Files: []app.GitTreeObj{{Permissions: "100644", Type: "blob", + Files: map[string]*app.GitTreeObj{"file.1": &app.GitTreeObj{Permissions: "100644", Type: "blob", Sha: "0000000000000000000000000000000000000001", Filename: "file.1"}}}, }, commit: commitTest{ @@ -136,10 +144,13 @@ func Test_git(t *testing.T) { commit: test.commit.cmd, }} - proj, err := git.Project(nil, test.path, test.gitDir, test.remote, test.revision, test.getFiles) + proj, err := git.Project(nil, test.path, test.gitDir, test.remote, test.revision) if err != nil { t.Fatal("Failed to parse project") } + if test.getFiles { + _ = git.PopulateFiles(nil, proj, "") + } if !reflect.DeepEqual(*proj, *test.project.res) { t.Errorf("Project = %+v; want %+v", *proj, *test.project.res) } diff --git a/build/report/local/ninja.go b/build/report/local/ninja.go index 3684c3b..012bcc7 100644 --- a/build/report/local/ninja.go +++ b/build/report/local/ninja.go @@ -26,6 +26,9 @@ import ( "tools/treble/build/report/app" ) +// Performance degrades running multiple CLIs +const MaxNinjaCliWorkers = 4 + // Separate out the executable to allow tests to override the results type ninjaExec interface { Command(ctx context.Context, target string) (*bytes.Buffer, error) @@ -33,6 +36,7 @@ type ninjaExec interface { Query(ctx context.Context, target string) (*bytes.Buffer, error) Path(ctx context.Context, target string, dependency string) (*bytes.Buffer, error) Paths(ctx context.Context, target string, dependency string) (*bytes.Buffer, error) + Deps(ctx context.Context) (*bytes.Buffer, error) Build(ctx context.Context, target string) (*bytes.Buffer, error) } @@ -124,6 +128,33 @@ func parseBuild(target string, data *bytes.Buffer, success bool) *app.BuildCmdRe return out } +// parse deps command +func parseDeps(data *bytes.Buffer) (*app.BuildDeps, error) { + out := &app.BuildDeps{Targets: make(map[string][]string)} + s := bufio.NewScanner(data) + curTarget := "" + var deps []string + for s.Scan() { + line := strings.TrimSpace(s.Text()) + // Check if it's a new target + tokens := strings.Split(line, ":") + if len(tokens) > 1 { + if curTarget != "" { + out.Targets[curTarget] = deps + } + deps = []string{} + curTarget = tokens[0] + } else if line != "" { + deps = append(deps, line) + } + + } + if curTarget != "" { + out.Targets[curTarget] = deps + } + return out, nil +} + // // Command line interface to ninja binary. // @@ -135,6 +166,7 @@ func parseBuild(target string, data *bytes.Buffer, success bool) *app.BuildCmdRe // Query() -t query // Path() -t path // Paths() -t paths +// Deps() -t deps // // @@ -172,6 +204,10 @@ func (n *ninjaCmd) Path(ctx context.Context, target string, dependency string) ( func (n *ninjaCmd) Paths(ctx context.Context, target string, dependency string) (*bytes.Buffer, error) { return n.runTool(ctx, "paths", []string{target, dependency}) } +func (n *ninjaCmd) Deps(ctx context.Context) (*bytes.Buffer, error) { + return n.runTool(ctx, "deps", []string{}) +} + func (n *ninjaCmd) Build(ctx context.Context, target string) (*bytes.Buffer, error) { args := append([]string{ @@ -234,6 +270,15 @@ func (cli *ninjaCli) Paths(ctx context.Context, target string, dependency string return parsePaths(target, dependency, raw) } +// ninja -t deps +func (cli *ninjaCli) Deps(ctx context.Context) (*app.BuildDeps, error) { + raw, err := cli.n.Deps(ctx) + if err != nil { + return nil, err + } + return parseDeps(raw) +} + // Build given target func (cli *ninjaCli) Build(ctx context.Context, target string) *app.BuildCmdResult { raw, err := cli.n.Build(ctx, target) @@ -242,5 +287,5 @@ func (cli *ninjaCli) Build(ctx context.Context, target string) *app.BuildCmdResu } func NewNinjaCli(cmd string, db string) *ninjaCli { cli := &ninjaCli{n: &ninjaCmd{cmd: cmd, db: db, timeout: 100000 * time.Millisecond, buildTimeout: 300000 * time.Millisecond}} - return (cli) + return cli } diff --git a/build/report/local/ninja_test.go b/build/report/local/ninja_test.go index 3941e4e..40e2043 100644 --- a/build/report/local/ninja_test.go +++ b/build/report/local/ninja_test.go @@ -29,6 +29,7 @@ type ninjaTest struct { query *TestCmd path *TestCmd paths *TestCmd + deps *TestCmd build *TestCmd } @@ -47,6 +48,9 @@ func (n *ninjaTest) Path(ctx context.Context, target string, dependency string) func (n *ninjaTest) Paths(ctx context.Context, target string, dependency string) (*bytes.Buffer, error) { return bytes.NewBufferString(n.paths.text), n.paths.err } +func (n *ninjaTest) Deps(ctx context.Context) (*bytes.Buffer, error) { + return bytes.NewBufferString(n.deps.text), n.deps.err +} func (n *ninjaTest) Build(ctx context.Context, target string) (*bytes.Buffer, error) { return bytes.NewBufferString(n.build.text), n.build.err } @@ -72,6 +76,10 @@ func Test_ninja(t *testing.T) { cmd *TestCmd res []*app.BuildPath } + type depsTest struct { + cmd *TestCmd + res *app.BuildDeps + } type buildTest struct { cmd *TestCmd res *app.BuildCmdResult @@ -84,6 +92,7 @@ func Test_ninja(t *testing.T) { input inputTest path pathTest paths pathsTest + deps depsTest build buildTest }{ { @@ -111,6 +120,14 @@ func Test_ninja(t *testing.T) { &app.BuildPath{Target: "test", Dependency: "dependency", Paths: []string{"test", "mid4", "dependency"}}, }, }, + deps: depsTest{ + cmd: &TestCmd{text: "some/build/library.so: #deps1\n dependentFile1.S\n dependentFile2.S\nsome/build/library2.so: #deps1\n dependentFile1.S\n dependentFile3.S\n"}, + res: &app.BuildDeps{Targets: map[string][]string{ + "some/build/library.so": []string{"dependentFile1.S", "dependentFile2.S"}, + "some/build/library2.so": []string{"dependentFile1.S", "dependentFile3.S"}, + }, + }, + }, build: buildTest{ cmd: &TestCmd{text: "", err: nil}, res: &app.BuildCmdResult{Name: "test", Output: []string{}, Success: true}}, @@ -124,6 +141,7 @@ func Test_ninja(t *testing.T) { input: test.input.cmd, path: test.path.cmd, paths: test.paths.cmd, + deps: test.deps.cmd, build: test.build.cmd, } n := &ninjaCli{n: exec} @@ -177,6 +195,16 @@ func Test_ninja(t *testing.T) { } } + if test.deps.cmd != nil { + if res, err := n.Deps(nil); err != nil { + t.Errorf("Deps error %s", err) + } else { + if !reflect.DeepEqual(res, test.deps.res) { + t.Errorf("Deps result %v; want %v", res, test.deps.res) + } + } + + } if test.build.cmd != nil { res := n.Build(nil, test.target) if !reflect.DeepEqual(*res, *test.build.res) { diff --git a/build/report/report/build.go b/build/report/report/build.go index aedd8b4..cc0c9bf 100644 --- a/build/report/report/build.go +++ b/build/report/report/build.go @@ -16,6 +16,7 @@ package report import ( "context" + "fmt" "sync" "tools/treble/build/report/app" @@ -41,24 +42,28 @@ type buildPathData struct { // // create build target from using repo data // -func createBuildTarget(ctx context.Context, repo *repo, buildTarget *buildTargetData) *app.BuildTarget { - +func createBuildTarget(ctx context.Context, rtx *Context, buildTarget *buildTargetData) *app.BuildTarget { out := &app.BuildTarget{Name: buildTarget.input.Target, Steps: buildTarget.buildSteps, - Projects: make(map[string]*app.BuildProject), + Projects: make(map[string]*app.GitProject), FileCount: len(buildTarget.input.Files), } - for i, _ := range buildTarget.input.Files { - proj, buildFile := lookupProjectFile(ctx, repo, &buildTarget.input.Files[i]) + for _, f := range buildTarget.input.Files { + proj, buildFile := lookupProjectFile(ctx, rtx, f) if buildFile != nil { if buildProj, exists := out.Projects[proj.Name]; exists { - buildProj.Files = append(buildProj.Files, *buildFile) + buildProj.Files[buildFile.Filename] = buildFile } else { out.Projects[proj.Name] = - &app.BuildProject{Path: proj.RepoPath, - Name: proj.Name, Revision: proj.GitProj.Revision, - Files: []app.BuildFile{*buildFile}} + &app.GitProject{ + RepoDir: proj.GitProj.RepoDir, + WorkDir: proj.GitProj.WorkDir, + GitDir: proj.GitProj.GitDir, + Remote: proj.GitProj.Remote, + RemoteUrl: proj.GitProj.RemoteUrl, + Revision: proj.GitProj.Revision, + Files: map[string]*app.GitTreeObj{buildFile.Filename: buildFile}} } } } @@ -70,7 +75,7 @@ func targetResolvers(ctx context.Context, rtx *Context) (chan string, chan *buil var wg sync.WaitGroup inChan := make(chan string) outChan := make(chan *buildTargetData) - for i := 0; i < rtx.WorkerCount; i++ { + for i := 0; i < rtx.BuildWorkerCount; i++ { wg.Add(1) go func() { for targetName := range inChan { @@ -80,7 +85,11 @@ func targetResolvers(ctx context.Context, rtx *Context) (chan string, chan *buil buildSteps = len(cmds.Cmds) } input, err := rtx.Build.Input(ctx, targetName) - outChan <- &buildTargetData{input: input, buildSteps: buildSteps, error: err != nil} + if input == nil { + fmt.Printf("Failed to get input %s (%s)\n", targetName, err) + } else { + outChan <- &buildTargetData{input: input, buildSteps: buildSteps, error: err != nil} + } } wg.Done() }() @@ -95,14 +104,14 @@ func targetResolvers(ctx context.Context, rtx *Context) (chan string, chan *buil // // Setup routines to resolve build input targets to BuildTarget -func resolveBuildInputs(ctx context.Context, rtx *Context, repo *repo, inChan chan *buildTargetData) chan *app.BuildTarget { +func resolveBuildInputs(ctx context.Context, rtx *Context, inChan chan *buildTargetData) chan *app.BuildTarget { var wg sync.WaitGroup outChan := make(chan *app.BuildTarget) - for i := 0; i < rtx.WorkerCount; i++ { + for i := 0; i < rtx.BuildWorkerCount; i++ { wg.Add(1) go func() { for buildTarget := range inChan { - outChan <- createBuildTarget(ctx, repo, buildTarget) + outChan <- createBuildTarget(ctx, rtx, buildTarget) } wg.Done() }() @@ -119,7 +128,7 @@ func queryResolvers(ctx context.Context, rtx *Context) (chan string, chan *build var wg sync.WaitGroup inChan := make(chan string) outChan := make(chan *buildSourceData) - for i := 0; i < rtx.WorkerCount; i++ { + for i := 0; i < rtx.BuildWorkerCount; i++ { wg.Add(1) go func() { for srcName := range inChan { @@ -137,45 +146,27 @@ func queryResolvers(ctx context.Context, rtx *Context) (chan string, chan *build return inChan, outChan } -// Setup routines to resolve path -func pathResolvers(ctx context.Context, rtx *Context, target string) (chan string, chan *buildPathData) { - var wg sync.WaitGroup - inChan := make(chan string) - outChan := make(chan *buildPathData) - for i := 0; i < rtx.WorkerCount; i++ { - wg.Add(1) - go func() { - for dep := range inChan { - path, err := rtx.Build.Path(ctx, target, dep) - outChan <- &buildPathData{filename: dep, path: path, error: err != nil} - } - wg.Done() - }() - } - go func() { - wg.Wait() - close(outChan) - }() - - return inChan, outChan -} - // Setup routines to resolve paths -func pathsResolvers(ctx context.Context, rtx *Context, target string) (chan string, chan *buildPathData) { +func pathsResolvers(ctx context.Context, rtx *Context, target string, singlePath bool) (chan string, chan *buildPathData) { var wg sync.WaitGroup inChan := make(chan string) outChan := make(chan *buildPathData) - for i := 0; i < rtx.WorkerCount; i++ { + for i := 0; i < rtx.BuildWorkerCount; i++ { wg.Add(1) go func() { for dep := range inChan { - paths, err := rtx.Build.Paths(ctx, target, dep) - if err != nil { - outChan <- &buildPathData{filename: dep, path: nil, error: true} + if singlePath { + path, err := rtx.Build.Path(ctx, target, dep) + outChan <- &buildPathData{filename: dep, path: path, error: err != nil} } else { - for _, path := range paths { - - outChan <- &buildPathData{filename: dep, path: path, error: false} + paths, err := rtx.Build.Paths(ctx, target, dep) + if err != nil { + outChan <- &buildPathData{filename: dep, path: nil, error: true} + } else { + for _, path := range paths { + + outChan <- &buildPathData{filename: dep, path: path, error: false} + } } } } diff --git a/build/report/report/dependencies.go b/build/report/report/dependencies.go index 34b84d4..f2b21b5 100644 --- a/build/report/report/dependencies.go +++ b/build/report/report/dependencies.go @@ -26,10 +26,12 @@ type BuildDependencies interface { Query(ctx context.Context, target string) (*app.BuildQuery, error) Path(ctx context.Context, target string, dependency string) (*app.BuildPath, error) Paths(ctx context.Context, target string, dependency string) ([]*app.BuildPath, error) + Deps(ctx context.Context) (*app.BuildDeps, error) } type ProjectDependencies interface { - Project(ctx context.Context, path string, gitDir string, remote string, revision string, getFiles bool) (*app.GitProject, error) + Project(ctx context.Context, path string, gitDir string, remote string, revision string) (*app.GitProject, error) + PopulateFiles(ctx context.Context, proj *app.GitProject, upstream string) error CommitInfo(ctx context.Context, proj *app.GitProject, sha string) (*app.GitCommit, error) } diff --git a/build/report/report/projects.go b/build/report/report/projects.go index d5b2347..eaec5bf 100644 --- a/build/report/report/projects.go +++ b/build/report/report/projects.go @@ -20,6 +20,7 @@ import ( "os" "path/filepath" "strings" + "sync" "tools/treble/build/report/app" ) @@ -28,60 +29,15 @@ import ( // Repo and project related functions // type project struct { - Name string // Name - RepoPath string // Path in repo - GitProj *app.GitProject // Git project data - ObjMap map[string]app.GitTreeObj // Mapping of filename to git tree object + Name string // Name + GitProj *app.GitProject // Git project data } -var unknownProject = &project{Name: "unknown", RepoPath: "unknown", GitProj: &app.GitProject{}} - -// Repo containing a map of projects, this also contains a -// map between a source file and the project it belongs to -// allowing a quicker lookup of source file to project -type repo struct { - RepoBase string // Absolute path to repo base - ProjMap map[string]*project // Map project name to project - FileCache map[string]*project // map source files to project -} - -// Create a mapping of projects from the input source manifest -func createProjectMap(ctx context.Context, manifest *app.RepoManifest, repoBase string, proj ProjectDependencies, getFiles bool) *repo { - if !strings.HasSuffix(repoBase, "/") { - repoBase += "/" - } - repo := &repo{RepoBase: repoBase} - // Create map of remotes - remotes := make(map[string]*app.RepoRemote) - var defRemotePtr *app.RepoRemote - for i, _ := range manifest.Remotes { - remotes[manifest.Remotes[i].Name] = &manifest.Remotes[i] - } - - defRemotePtr, exists := remotes[manifest.Default.Remote] - if !exists { - fmt.Printf("Failed to find default remote") - } - repo.FileCache = make(map[string]*project) - repo.ProjMap = make(map[string]*project) - for i, _ := range manifest.Projects { - - remotePtr := defRemotePtr - if manifest.Projects[i].Remote != nil { - remotePtr = remotes[*manifest.Projects[i].Remote] - } - proj := resolveProject(ctx, &manifest.Projects[i], remotePtr, proj, getFiles, &repo.FileCache) - if proj != nil { - // Add the remote information - repo.ProjMap[proj.Name] = proj - } - } - return repo -} +var unknownProject = &project{Name: "unknown", GitProj: &app.GitProject{}} // Convert repo project to project with source files and revision // information -func resolveProject(ctx context.Context, repoProj *app.RepoProject, remote *app.RepoRemote, proj ProjectDependencies, getFiles bool, fileCache *map[string]*project) *project { +func resolveProject(ctx context.Context, repoProj *app.RepoProject, remote *app.RepoRemote, proj ProjectDependencies, getFiles bool, upstreamBranch string) *project { path := repoProj.Path if path == "" { @@ -110,17 +66,13 @@ func resolveProject(ctx context.Context, repoProj *app.RepoProject, remote *app. gitDir = filepath.Join(parts[repostart:]...) } - gitProj, err := proj.Project(ctx, path, gitDir, remote.Name, repoProj.Revision, getFiles) + gitProj, err := proj.Project(ctx, path, gitDir, remote.Name, repoProj.Revision) if err != nil { return nil } - out := &project{Name: repoProj.Name, RepoPath: path, GitProj: gitProj} - if len(gitProj.Files) > 0 { - out.ObjMap = make(map[string]app.GitTreeObj) - for _, obj := range gitProj.Files { - (*fileCache)[filepath.Join(path, obj.Filename)] = out - out.ObjMap[obj.Filename] = obj - } + out := &project{Name: repoProj.Name, GitProj: gitProj} + if getFiles { + _ = proj.PopulateFiles(ctx, gitProj, upstreamBranch) } return out } @@ -130,43 +82,97 @@ func resolveProject(ctx context.Context, repoProj *app.RepoProject, remote *app. // then resolve the file via the project found. // // Most files will be relative paths from the repo workspace -func lookupProjectFile(ctx context.Context, repo *repo, filename *string) (*project, *app.BuildFile) { - if proj, exists := repo.FileCache[*filename]; exists { - repoName := (*filename)[len(proj.RepoPath)+1:] - if gitObj, exists := proj.ObjMap[repoName]; exists { - return proj, &app.BuildFile{Name: gitObj.Filename, Revision: gitObj.Sha} +func lookupProjectFile(ctx context.Context, rtx *Context, filename string) (*project, *app.GitTreeObj) { + if proj, exists := rtx.Info.FileCache[filename]; exists { + repoName := (filename)[len(proj.GitProj.RepoDir)+1:] + if gitObj, exists := proj.GitProj.Files[repoName]; exists { + return proj, gitObj } return proj, nil } else { // Try resolving any symlinks - if realpath, err := filepath.EvalSymlinks(*filename); err == nil { - if realpath != *filename { - return lookupProjectFile(ctx, repo, &realpath) + if realpath, err := filepath.EvalSymlinks(filename); err == nil { + if realpath != filename { + return lookupProjectFile(ctx, rtx, realpath) } } - if strings.HasPrefix(*filename, repo.RepoBase) { + if strings.HasPrefix(filename, rtx.RepoBase) { // Some dependencies pick up the full path try stripping out - relpath := (*filename)[len(repo.RepoBase)+1:] - return lookupProjectFile(ctx, repo, &relpath) + relpath := (filename)[len(rtx.RepoBase):] + return lookupProjectFile(ctx, rtx, relpath) } } - return unknownProject, &app.BuildFile{Name: *filename, Revision: ""} + return unknownProject, &app.GitTreeObj{Filename: filename, Sha: ""} } // Create a mapping of projects from the input source manifest -func resolveProjectMap(ctx context.Context, rtx *Context, manifestFile string, getFiles bool) chan *repo { - outChan := make(chan *repo) +func resolveProjectMap(ctx context.Context, rtx *Context, manifestFile string, getFiles bool, upstreamBranch string) *ProjectInfo { + // Parse the manifest file + manifest, err := rtx.Repo.Manifest(manifestFile) + if err != nil { + return nil + } + info := &ProjectInfo{} + // Create map of remotes + remotes := make(map[string]*app.RepoRemote) + var defRemotePtr *app.RepoRemote + for i, _ := range manifest.Remotes { + remotes[manifest.Remotes[i].Name] = &manifest.Remotes[i] + } + + defRemotePtr, exists := remotes[manifest.Default.Remote] + if !exists { + fmt.Printf("Failed to find default remote") + } + info.FileCache = make(map[string]*project) + info.ProjMap = make(map[string]*project) + + var wg sync.WaitGroup + projChan := make(chan *project) + repoChan := make(chan *app.RepoProject) + + for i := 0; i < rtx.WorkerCount; i++ { + wg.Add(1) + go func() { + for repoProj := range repoChan { + remotePtr := defRemotePtr + if manifest.Projects[i].Remote != nil { + remotePtr = remotes[*manifest.Projects[i].Remote] + } + proj := resolveProject(ctx, repoProj, remotePtr, rtx.Project, getFiles, upstreamBranch) + if proj != nil { + projChan <- proj + } else { + projChan <- &project{Name: repoProj.Name} + } + } + wg.Done() + }() + } + go func() { + wg.Wait() + close(projChan) + }() go func() { - defer close(outChan) - // Parse the manifest file - xmlRepo, err := rtx.Repo.Manifest(manifestFile) - if err != nil { - return + for i, _ := range manifest.Projects { + repoChan <- &manifest.Projects[i] } - // Convert manifest into projects with source files - repo := createProjectMap(ctx, xmlRepo, rtx.RepoBase, rtx.Project, getFiles) - outChan <- repo + close(repoChan) }() - return outChan + for r := range projChan { + if r.GitProj != nil { + info.ProjMap[r.Name] = r + if len(r.GitProj.Files) > 0 { + for n := range r.GitProj.Files { + info.FileCache[filepath.Join(r.GitProj.RepoDir, n)] = r + } + + } + + } else { + fmt.Printf("Failed to resolve %s\n", r.Name) + } + } + return info } diff --git a/build/report/report/report_test.go b/build/report/report/report_test.go index d259fa7..7aa3891 100644 --- a/build/report/report/report_test.go +++ b/build/report/report/report_test.go @@ -35,6 +35,7 @@ type reportTest struct { projects map[string]*app.GitProject commits map[*app.GitProject]map[string]*app.GitCommit + deps *app.BuildDeps projectCommits map[string]int } @@ -81,7 +82,10 @@ func (r *reportTest) Paths(ctx context.Context, target string, dependency string return r.multipaths[target][dependency], nil } -func (r *reportTest) Project(ctx context.Context, path string, gitDir string, remote string, revision string, getFiles bool) (*app.GitProject, error) { +func (r *reportTest) Deps(ctx context.Context) (*app.BuildDeps, error) { + return r.deps, nil +} +func (r *reportTest) Project(ctx context.Context, path string, gitDir string, remote string, revision string) (*app.GitProject, error) { var err error out := r.projects[path] if out == nil { @@ -89,7 +93,9 @@ func (r *reportTest) Project(ctx context.Context, path string, gitDir string, re } return out, err } - +func (r *reportTest) PopulateFiles(ctx context.Context, proj *app.GitProject, upstream string) error { + return nil +} func (r *reportTest) CommitInfo(ctx context.Context, proj *app.GitProject, sha string) (*app.GitCommit, error) { var err error out := r.commits[proj][sha] @@ -113,6 +119,22 @@ func projName(i int) string { return "proj." + strconv.Itoa(i) } +func fileName(i int) (filename string, sha string) { + iString := strconv.Itoa(i) + return "source." + iString, "sha." + iString +} +func createFile(i int) *app.GitTreeObj { + fname, sha := fileName(i) + return &app.GitTreeObj{Permissions: "100644", Type: "blob", Filename: fname, Sha: sha} +} +func createProject(name string) *app.GitProject { + return &app.GitProject{ + RepoDir: name, WorkDir: name, GitDir: ".git", Remote: "origin", + RemoteUrl: "origin_url", Revision: name + "_sha", + Files: make(map[string]*app.GitTreeObj)} + +} + // Create basic test data for given inputs func createTest(projCount int, fileCount int) *reportTest { test := &reportTest{ @@ -132,14 +154,11 @@ func createTest(projCount int, fileCount int) *reportTest { for i := 0; i <= projCount; i++ { name := projName(i) - proj := &app.GitProject{ - WorkDir: name, GitDir: ".git", Remote: "origin", - RemoteUrl: "origin_url", Revision: name + "_sha"} + proj := createProject(name) for i := 0; i <= fileCount; i++ { - iString := strconv.Itoa(i) - treeObj := app.GitTreeObj{Permissions: "100644", Type: "blob", Filename: "source." + iString, Sha: iString} - proj.Files = append(proj.Files, treeObj) + treeObj := createFile(i) + proj.Files[treeObj.Filename] = treeObj } test.projects[name] = proj @@ -194,14 +213,14 @@ func Test_report(t *testing.T) { var targets []string // Build expected output while creating the targets - var resTargets []*app.BuildTarget + resTargets := make(map[string]*app.BuildTarget) for _, target := range targetDefs { res := &app.BuildTarget{Name: target.name, Steps: target.cmds, FileCount: len(target.inputFiles), - Projects: make(map[string]*app.BuildProject), + Projects: make(map[string]*app.GitProject), } // Add files to the build target @@ -209,17 +228,16 @@ func Test_report(t *testing.T) { for _, in := range target.inputFiles { // Get project by name pName := projName(in.proj) + bf := createFile(in.file) p := test.projects[pName] - bf := &app.BuildFile{Name: p.Files[in.file].Filename, - Revision: p.Files[in.file].Sha} inputFiles = append(inputFiles, - fmt.Sprintf("%s/%s", p.WorkDir, bf.Name)) + fmt.Sprintf("%s/%s", p.WorkDir, bf.Filename)) if _, exists := res.Projects[pName]; !exists { - res.Projects[pName] = &app.BuildProject{Path: pName, Name: pName, Revision: p.Revision} + res.Projects[pName] = createProject(pName) } - res.Projects[pName].Files = append(res.Projects[pName].Files, *bf) + res.Projects[pName].Files[bf.Filename] = bf } // Create test data @@ -231,17 +249,18 @@ func Test_report(t *testing.T) { Outputs: createStrings("target.out.", target.outputTargets)} targets = append(targets, target.name) - resTargets = append(resTargets, res) + resTargets[res.Name] = res } - rtx := &Context{RepoBase: "/src", Repo: test, Build: test, Project: test, WorkerCount: 1} - req := &app.ReportRequest{ManifestFile: "test_file", Targets: targets} + rtx := &Context{RepoBase: "/src", Repo: test, Build: test, Project: test, WorkerCount: 1, BuildWorkerCount: 1} + rtx.ResolveProjectMap(nil, "test_file", "") + req := &app.ReportRequest{Targets: targets} rsp, err := RunReport(nil, rtx, req) if err != nil { t.Errorf("Failed to run report for request %+v", req) } else { if !reflect.DeepEqual(rsp.Targets, resTargets) { - t.Errorf("Target not expected") + t.Errorf("Got targets %+v, expected %+v", rsp.Targets, resTargets) } } } diff --git a/build/report/report/run.go b/build/report/report/run.go index e480df6..fd03088 100644 --- a/build/report/report/run.go +++ b/build/report/report/run.go @@ -56,6 +56,20 @@ func binaryExecutables(ctx context.Context, dir string, recursive bool) ([]strin return files, numSymLinks, err } +// Resolve the manifest +func (rtx *Context) ResolveProjectMap(ctx context.Context, manifest string, upstreamBranch string) { + if rtx.Info == nil { + rtx.Info = resolveProjectMap(ctx, rtx, manifest, true, upstreamBranch) + } +} + +// Find host tools +func ResolveHostTools(ctx context.Context, hostToolPath string) (*app.HostReport, error) { + out := &app.HostReport{Path: hostToolPath} + out.Targets, out.SymLinks, _ = binaryExecutables(ctx, hostToolPath, true) + return out, nil +} + // Run reports // @@ -69,69 +83,40 @@ func binaryExecutables(ctx context.Context, dir string, recursive bool) ([]strin // queries can be fully resolved // func RunReport(ctx context.Context, rtx *Context, req *app.ReportRequest) (*app.Report, error) { - - repoCh := resolveProjectMap(ctx, rtx, req.ManifestFile, true) inChan, targetCh := targetResolvers(ctx, rtx) - hostTargetSymLinks := 0 - hostTargetMap := make(map[string]bool) go func() { for i, _ := range req.Targets { inChan <- req.Targets[i] } - - if req.IncludeHostTools { - hostTargets, symLinks, _ := binaryExecutables(ctx, req.HostToolPath, true) - hostTargetSymLinks = symLinks - for i, _ := range hostTargets { - inChan <- hostTargets[i] - hostTargetMap[hostTargets[i]] = true - } - } close(inChan) }() - // Wait for repo projects to be resolved - repo := <-repoCh // Resolve the build inputs into build target projects - buildTargetChan := resolveBuildInputs(ctx, rtx, repo, targetCh) + buildTargetChan := resolveBuildInputs(ctx, rtx, targetCh) - out := &app.Report{} - if req.IncludeHostTools { - out.Host = &app.HostReport{Path: req.HostToolPath, SymLinks: hostTargetSymLinks} - } + out := &app.Report{Targets: make(map[string]*app.BuildTarget)} for bt := range buildTargetChan { - if _, exists := hostTargetMap[bt.Name]; exists { - out.Host.Targets = append(out.Host.Targets, bt) - } else { - out.Targets = append(out.Targets, bt) - } + out.Targets[bt.Name] = bt } return out, nil } -// Resolve set of commits into set of files -func ResolveCommits(ctx context.Context, rtx *Context, req *app.ProjectCommits) ([]string, error) { - // Resolve project map, don't need the repo files here - repo := <-resolveProjectMap(ctx, rtx, req.ManifestFile, false) - - files := []string{} - // Resolve any commits - for _, commit := range req.Commits { - if proj, exists := repo.ProjMap[commit.Project]; exists { - info, err := rtx.Project.CommitInfo(ctx, proj.GitProj, commit.Revision) - if err == nil { - for _, f := range info.Files { - if f.Type != app.GitFileRemoved { - files = append(files, filepath.Join(proj.RepoPath, f.Filename)) - } +// Resolve commit into git commit info +func ResolveCommit(ctx context.Context, rtx *Context, commit *app.ProjectCommit) (*app.GitCommit, []string, error) { + if proj, exists := rtx.Info.ProjMap[commit.Project]; exists { + info, err := rtx.Project.CommitInfo(ctx, proj.GitProj, commit.Revision) + files := []string{} + if err == nil { + for _, f := range info.Files { + if f.Type != app.GitFileRemoved { + files = append(files, filepath.Join(proj.GitProj.RepoDir, f.Filename)) } } - } else { - return nil, errors.New(fmt.Sprintf("Failed to find commit %s:%s", commit.Project, commit.Revision)) } + return info, files, err } - return files, nil + return nil, nil, errors.New(fmt.Sprintf("Unknown project %s", commit.Project)) } @@ -184,29 +169,10 @@ func RunQuery(ctx context.Context, rtx *Context, req *app.QueryRequest) (*app.Qu return out, nil } -// Check if path exists between target and outputs provided, return outputs that have a -// path to target. Only return valid paths via the output any errors are dropped -func RunPathFilter(ctx context.Context, rtx *Context, target string, outputs []string) []string { - var filter []string - inChan, pathCh := pathResolvers(ctx, rtx, target) - // Convert source files to outputs - go func() { - for _, out := range outputs { - inChan <- out - } - close(inChan) - }() - for result := range pathCh { - if !result.error { - filter = append(filter, result.filename) - } - } - return filter -} - -func RunPaths(ctx context.Context, rtx *Context, target string, files []string) []*app.BuildPath { +// Get paths +func RunPaths(ctx context.Context, rtx *Context, target string, singlePath bool, files []string) []*app.BuildPath { out := []*app.BuildPath{} - inChan, pathCh := pathsResolvers(ctx, rtx, target) + inChan, pathCh := pathsResolvers(ctx, rtx, target, singlePath) // Convert source files to outputs go func() { for _, f := range files { diff --git a/build/report/report/types.go b/build/report/report/types.go index 707864b..8610d43 100644 --- a/build/report/report/types.go +++ b/build/report/report/types.go @@ -25,11 +25,21 @@ func (r *RepoMan) Manifest(filename string) (*app.RepoManifest, error) { return app.ParseXml(filename) } +// Project information containing a map of projects, this also contains a +// map between a source file and the project it belongs to +// allowing a quicker lookup of source file to project +type ProjectInfo struct { + ProjMap map[string]*project // Map project name to project + FileCache map[string]*project // Map source files to project +} + // Report context type Context struct { - RepoBase string // Absolute repo base directory - Repo RepoDependencies // Repo interface - Build BuildDependencies // Build interface - Project ProjectDependencies // Project interface - WorkerCount int // Number of worker threads + RepoBase string // Absolute path to repo base + Repo RepoDependencies // Repo interface + Build BuildDependencies // Build interface + Project ProjectDependencies // Project interface + WorkerCount int // Number of worker threads + BuildWorkerCount int // Number of build worker threads + Info *ProjectInfo // Project information } |