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 "fmt" 20 "os" 21 "path/filepath" 22 "strings" 23 "sync" 24 25 "tools/treble/build/report/app" 26) 27 28// 29// Repo and project related functions 30// 31type project struct { 32 Name string // Name 33 GitProj *app.GitProject // Git project data 34} 35 36var unknownProject = &project{Name: "unknown", GitProj: &app.GitProject{}} 37 38// Convert repo project to project with source files and revision 39// information 40func resolveProject(ctx context.Context, repoProj *app.RepoProject, remote *app.RepoRemote, proj ProjectDependencies, getFiles bool, upstreamBranch string) *project { 41 42 path := repoProj.Path 43 if path == "" { 44 path = repoProj.Name 45 } 46 gitDir := "" 47 if strings.HasPrefix(path, "overlays/") { 48 // Assume two levels of overlay path (overlay/XYZ) 49 path = strings.Join(strings.Split(path, "/")[2:], "/") 50 // The overlays .git symbolic links are not mapped correctly 51 // into the jails. Resolve them here, inside the nsjail the 52 // absolute path for all git repos will be in the form of 53 // /src/.git/ 54 symlink, _ := os.Readlink(filepath.Join(path, ".git")) 55 parts := strings.Split(symlink, "/") 56 repostart := 0 57 for ; repostart < len(parts); repostart++ { 58 if parts[repostart] != ".." { 59 if repostart > 1 { 60 repostart-- 61 parts[repostart] = "/src" 62 } 63 break 64 } 65 } 66 gitDir = filepath.Join(parts[repostart:]...) 67 68 } 69 gitProj, err := proj.Project(ctx, path, gitDir, remote.Name, repoProj.Revision) 70 if err != nil { 71 return nil 72 } 73 out := &project{Name: repoProj.Name, GitProj: gitProj} 74 if getFiles { 75 _ = proj.PopulateFiles(ctx, gitProj, upstreamBranch) 76 } 77 return out 78} 79 80// Get the build file for a given filename, this is a two step lookup. 81// First find the project associated with the file via the file cache, 82// then resolve the file via the project found. 83// 84// Most files will be relative paths from the repo workspace 85func lookupProjectFile(ctx context.Context, rtx *Context, filename string) (*project, *app.GitTreeObj) { 86 if proj, exists := rtx.Info.FileCache[filename]; exists { 87 repoName := (filename)[len(proj.GitProj.RepoDir)+1:] 88 if gitObj, exists := proj.GitProj.Files[repoName]; exists { 89 return proj, gitObj 90 } 91 return proj, nil 92 } else { 93 // Try resolving any symlinks 94 if realpath, err := filepath.EvalSymlinks(filename); err == nil { 95 if realpath != filename { 96 return lookupProjectFile(ctx, rtx, realpath) 97 } 98 } 99 100 if strings.HasPrefix(filename, rtx.RepoBase) { 101 // Some dependencies pick up the full path try stripping out 102 relpath := (filename)[len(rtx.RepoBase):] 103 return lookupProjectFile(ctx, rtx, relpath) 104 } 105 } 106 return unknownProject, &app.GitTreeObj{Filename: filename, Sha: ""} 107} 108 109// Create a mapping of projects from the input source manifest 110func resolveProjectMap(ctx context.Context, rtx *Context, manifestFile string, getFiles bool, upstreamBranch string) *ProjectInfo { 111 // Parse the manifest file 112 manifest, err := rtx.Repo.Manifest(manifestFile) 113 if err != nil { 114 return nil 115 } 116 info := &ProjectInfo{} 117 // Create map of remotes 118 remotes := make(map[string]*app.RepoRemote) 119 var defRemotePtr *app.RepoRemote 120 for i, _ := range manifest.Remotes { 121 remotes[manifest.Remotes[i].Name] = &manifest.Remotes[i] 122 } 123 124 defRemotePtr, exists := remotes[manifest.Default.Remote] 125 if !exists { 126 fmt.Printf("Failed to find default remote") 127 } 128 info.FileCache = make(map[string]*project) 129 info.ProjMap = make(map[string]*project) 130 131 var wg sync.WaitGroup 132 projChan := make(chan *project) 133 repoChan := make(chan *app.RepoProject) 134 135 for i := 0; i < rtx.WorkerCount; i++ { 136 wg.Add(1) 137 go func() { 138 for repoProj := range repoChan { 139 remotePtr := defRemotePtr 140 if manifest.Projects[i].Remote != nil { 141 remotePtr = remotes[*manifest.Projects[i].Remote] 142 } 143 proj := resolveProject(ctx, repoProj, remotePtr, rtx.Project, getFiles, upstreamBranch) 144 if proj != nil { 145 projChan <- proj 146 } else { 147 projChan <- &project{Name: repoProj.Name} 148 } 149 } 150 wg.Done() 151 }() 152 } 153 go func() { 154 wg.Wait() 155 close(projChan) 156 }() 157 go func() { 158 for i, _ := range manifest.Projects { 159 repoChan <- &manifest.Projects[i] 160 } 161 close(repoChan) 162 }() 163 for r := range projChan { 164 if r.GitProj != nil { 165 info.ProjMap[r.Name] = r 166 if len(r.GitProj.Files) > 0 { 167 for n := range r.GitProj.Files { 168 info.FileCache[filepath.Join(r.GitProj.RepoDir, n)] = r 169 } 170 171 } 172 173 } else { 174 fmt.Printf("Failed to resolve %s\n", r.Name) 175 } 176 } 177 return info 178} 179