| // 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" |
| "fmt" |
| "os" |
| "path/filepath" |
| "strings" |
| "sync" |
| |
| "tools/treble/build/report/app" |
| ) |
| |
| // |
| // Repo and project related functions |
| // |
| type project struct { |
| Name string // Name |
| GitProj *app.GitProject // Git project data |
| } |
| |
| 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, upstreamBranch string) *project { |
| |
| path := repoProj.Path |
| if path == "" { |
| path = repoProj.Name |
| } |
| gitDir := "" |
| if strings.HasPrefix(path, "overlays/") { |
| // Assume two levels of overlay path (overlay/XYZ) |
| path = strings.Join(strings.Split(path, "/")[2:], "/") |
| // The overlays .git symbolic links are not mapped correctly |
| // into the jails. Resolve them here, inside the nsjail the |
| // absolute path for all git repos will be in the form of |
| // /src/.git/ |
| symlink, _ := os.Readlink(filepath.Join(path, ".git")) |
| parts := strings.Split(symlink, "/") |
| repostart := 0 |
| for ; repostart < len(parts); repostart++ { |
| if parts[repostart] != ".." { |
| if repostart > 1 { |
| repostart-- |
| parts[repostart] = "/src" |
| } |
| break |
| } |
| } |
| gitDir = filepath.Join(parts[repostart:]...) |
| |
| } |
| gitProj, err := proj.Project(ctx, path, gitDir, remote.Name, repoProj.Revision) |
| if err != nil { |
| return nil |
| } |
| out := &project{Name: repoProj.Name, GitProj: gitProj} |
| if getFiles { |
| _ = proj.PopulateFiles(ctx, gitProj, upstreamBranch) |
| } |
| return out |
| } |
| |
| // Get the build file for a given filename, this is a two step lookup. |
| // First find the project associated with the file via the file cache, |
| // then resolve the file via the project found. |
| // |
| // Most files will be relative paths from the repo workspace |
| 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, rtx, realpath) |
| } |
| } |
| |
| if strings.HasPrefix(filename, rtx.RepoBase) { |
| // Some dependencies pick up the full path try stripping out |
| relpath := (filename)[len(rtx.RepoBase):] |
| return lookupProjectFile(ctx, rtx, relpath) |
| } |
| } |
| 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, 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() { |
| for i, _ := range manifest.Projects { |
| repoChan <- &manifest.Projects[i] |
| } |
| close(repoChan) |
| }() |
| 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 |
| } |