1// Copyright 2022 The Android Open Source Project
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7//      http://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14
15package report
16
17import (
18	"context"
19	"errors"
20	"fmt"
21	"io/fs"
22	"path/filepath"
23
24	"tools/treble/build/report/app"
25)
26
27// Find all binary executables under the given directory along with the number
28// of symlinks
29//
30func binaryExecutables(ctx context.Context, dir string, recursive bool) ([]string, int, error) {
31	var files []string
32	numSymLinks := 0
33	err := filepath.WalkDir(dir, func(path string, d fs.DirEntry, err error) error {
34		if err != nil {
35			return err
36		}
37		if !d.IsDir() {
38			if info, err := d.Info(); err == nil {
39				if info.Mode()&0111 != 0 {
40					files = append(files, path)
41				}
42				if d.Type()&fs.ModeSymlink != 0 {
43					numSymLinks++
44				}
45			}
46		} else {
47			if !recursive {
48				if path != dir {
49					return filepath.SkipDir
50				}
51			}
52		}
53		return nil
54	})
55
56	return files, numSymLinks, err
57}
58
59// Resolve the manifest
60func (rtx *Context) ResolveProjectMap(ctx context.Context, manifest string, upstreamBranch string) {
61	if rtx.Info == nil {
62		rtx.Info = resolveProjectMap(ctx, rtx, manifest, true, upstreamBranch)
63	}
64}
65
66// Find host tools
67func ResolveHostTools(ctx context.Context, hostToolPath string) (*app.HostReport, error) {
68	out := &app.HostReport{Path: hostToolPath}
69	out.Targets, out.SymLinks, _ = binaryExecutables(ctx, hostToolPath, true)
70	return out, nil
71}
72
73// Run reports
74
75//
76// Run report request
77//
78// Setup routines to:
79//    - resolve the manifest projects
80//    - resolve build queries
81//
82// Once the manifest projects have been resolved the build
83// queries can be fully resolved
84//
85func RunReport(ctx context.Context, rtx *Context, req *app.ReportRequest) (*app.Report, error) {
86	inChan, targetCh := targetResolvers(ctx, rtx)
87	go func() {
88		for i, _ := range req.Targets {
89			inChan <- req.Targets[i]
90		}
91		close(inChan)
92	}()
93
94	// Resolve the build inputs into build target projects
95	buildTargetChan := resolveBuildInputs(ctx, rtx, targetCh)
96
97	out := &app.Report{Targets: make(map[string]*app.BuildTarget)}
98	for bt := range buildTargetChan {
99		out.Targets[bt.Name] = bt
100	}
101
102	return out, nil
103}
104
105// Resolve commit into git commit info
106func ResolveCommit(ctx context.Context, rtx *Context, commit *app.ProjectCommit) (*app.GitCommit, []string, error) {
107	if proj, exists := rtx.Info.ProjMap[commit.Project]; exists {
108		info, err := rtx.Project.CommitInfo(ctx, proj.GitProj, commit.Revision)
109		files := []string{}
110		if err == nil {
111			for _, f := range info.Files {
112				if f.Type != app.GitFileRemoved {
113					files = append(files, filepath.Join(proj.GitProj.RepoDir, f.Filename))
114				}
115			}
116		}
117		return info, files, err
118	}
119	return nil, nil, errors.New(fmt.Sprintf("Unknown project %s", commit.Project))
120
121}
122
123// Run query report based on the input request.
124//
125// For each input file query the target and
126// create a set of the inputs and outputs associated
127// with all the input files.
128//
129//
130func RunQuery(ctx context.Context, rtx *Context, req *app.QueryRequest) (*app.QueryResponse, error) {
131	inChan, queryCh := queryResolvers(ctx, rtx)
132
133	go func() {
134		// Convert source files to outputs
135		for _, target := range req.Files {
136			inChan <- target
137		}
138		close(inChan)
139	}()
140
141	inFiles := make(map[string]bool)
142	outFiles := make(map[string]bool)
143	unknownSrcFiles := make(map[string]bool)
144	for result := range queryCh {
145		if result.error {
146			unknownSrcFiles[result.source] = true
147		} else {
148			for _, outFile := range result.query.Outputs {
149				outFiles[outFile] = true
150			}
151			for _, inFile := range result.query.Inputs {
152				inFiles[inFile] = true
153			}
154
155		}
156	}
157
158	out := &app.QueryResponse{}
159	for k, _ := range outFiles {
160		out.OutputFiles = append(out.OutputFiles, k)
161	}
162	for k, _ := range inFiles {
163		out.InputFiles = append(out.InputFiles, k)
164	}
165	for k, _ := range unknownSrcFiles {
166		out.UnknownFiles = append(out.UnknownFiles, k)
167	}
168
169	return out, nil
170}
171
172// Get paths
173func RunPaths(ctx context.Context, rtx *Context, target string, singlePath bool, files []string) []*app.BuildPath {
174	out := []*app.BuildPath{}
175	inChan, pathCh := pathsResolvers(ctx, rtx, target, singlePath)
176	// Convert source files to outputs
177	go func() {
178		for _, f := range files {
179			inChan <- f
180		}
181		close(inChan)
182	}()
183
184	for result := range pathCh {
185		if !result.error {
186			out = append(out, result.path)
187		}
188	}
189	return out
190
191}
192