blob: e480df651473e73e0415c3249142a2460c0f5ee2 [file] [log] [blame]
// 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 report
import (
"context"
"errors"
"fmt"
"io/fs"
"path/filepath"
"tools/treble/build/report/app"
)
// Find all binary executables under the given directory along with the number
// of symlinks
//
func binaryExecutables(ctx context.Context, dir string, recursive bool) ([]string, int, error) {
var files []string
numSymLinks := 0
err := filepath.WalkDir(dir, func(path string, d fs.DirEntry, err error) error {
if err != nil {
return err
}
if !d.IsDir() {
if info, err := d.Info(); err == nil {
if info.Mode()&0111 != 0 {
files = append(files, path)
}
if d.Type()&fs.ModeSymlink != 0 {
numSymLinks++
}
}
} else {
if !recursive {
if path != dir {
return filepath.SkipDir
}
}
}
return nil
})
return files, numSymLinks, err
}
// Run reports
//
// Run report request
//
// Setup routines to:
// - resolve the manifest projects
// - resolve build queries
//
// Once the manifest projects have been resolved the build
// 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)
out := &app.Report{}
if req.IncludeHostTools {
out.Host = &app.HostReport{Path: req.HostToolPath, SymLinks: hostTargetSymLinks}
}
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)
}
}
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))
}
}
}
} else {
return nil, errors.New(fmt.Sprintf("Failed to find commit %s:%s", commit.Project, commit.Revision))
}
}
return files, nil
}
// Run query report based on the input request.
//
// For each input file query the target and
// create a set of the inputs and outputs associated
// with all the input files.
//
//
func RunQuery(ctx context.Context, rtx *Context, req *app.QueryRequest) (*app.QueryResponse, error) {
inChan, queryCh := queryResolvers(ctx, rtx)
go func() {
// Convert source files to outputs
for _, target := range req.Files {
inChan <- target
}
close(inChan)
}()
inFiles := make(map[string]bool)
outFiles := make(map[string]bool)
unknownSrcFiles := make(map[string]bool)
for result := range queryCh {
if result.error {
unknownSrcFiles[result.source] = true
} else {
for _, outFile := range result.query.Outputs {
outFiles[outFile] = true
}
for _, inFile := range result.query.Inputs {
inFiles[inFile] = true
}
}
}
out := &app.QueryResponse{}
for k, _ := range outFiles {
out.OutputFiles = append(out.OutputFiles, k)
}
for k, _ := range inFiles {
out.InputFiles = append(out.InputFiles, k)
}
for k, _ := range unknownSrcFiles {
out.UnknownFiles = append(out.UnknownFiles, k)
}
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 {
out := []*app.BuildPath{}
inChan, pathCh := pathsResolvers(ctx, rtx, target)
// Convert source files to outputs
go func() {
for _, f := range files {
inChan <- f
}
close(inChan)
}()
for result := range pathCh {
if !result.error {
out = append(out, result.path)
}
}
return out
}