| // Copyright 2022 The Fuchsia Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| package project |
| |
| import ( |
| "context" |
| "fmt" |
| "log" |
| "path/filepath" |
| "sort" |
| |
| "go.fuchsia.dev/fuchsia/tools/check-licenses/file" |
| "go.fuchsia.dev/fuchsia/tools/check-licenses/util" |
| ) |
| |
| // Using the AllProjects map and the output of "fx gn gen", |
| // filter out all projects that don't appear in the dependency tree of |
| // Config.Target. |
| func FilterProjects() error { |
| var gen *util.Gen |
| var err error |
| |
| if Config.GenIntermediateFile != "" { |
| log.Printf("-> Loading gen file from %s...\n", Config.GenIntermediateFile) |
| gen, err = util.LoadGen(Config.GenIntermediateFile) |
| } else { |
| // Acquire a handle to the "gn" binary on the local workstation. |
| gn, err := util.NewGn(Config.GnPath, Config.BuildDir) |
| if err != nil { |
| return err |
| } |
| log.Printf(" -> Generating project.json file...\n") |
| // Run "fx gn <>" command, and retrieve the output data. |
| err = gn.GenerateProjectFile(context.Background()) |
| if err != nil { |
| return err |
| } |
| gen, err = util.LoadGen(Config.GenProjectFile) |
| if err != nil { |
| return err |
| } |
| if err = gen.FilterTargetsInDependencyTree(Config.Target, Config.PruneTargets); err != nil { |
| return err |
| } |
| } |
| |
| // Generate a map: |
| // [filepath for every file in project X] -> [Project X] |
| // With this mapping, we can match GN targets and file inputs |
| // to check-license Project structs. |
| fileMap, err := getFileMap() |
| if err != nil { |
| return err |
| } |
| |
| // Find Projects that match each target in the dependency tree. |
| RootProject, err = processGenOutput(gen, fileMap) |
| if err != nil { |
| return err |
| } |
| |
| dedupedLicenseDataMap := make(map[string][]*file.FileData) |
| for _, p := range FilteredProjects { |
| for _, lf := range p.LicenseFiles { |
| data, err := lf.Data() |
| if err != nil { |
| return err |
| } |
| for _, ld := range data { |
| key := string(ld.Data()) |
| if _, ok := dedupedLicenseDataMap[key]; !ok { |
| dedupedLicenseDataMap[key] = make([]*file.FileData, 0) |
| } |
| dedupedLicenseDataMap[key] = append(dedupedLicenseDataMap[key], ld) |
| } |
| } |
| } |
| |
| for _, v := range dedupedLicenseDataMap { |
| sort.SliceStable(v, func(i, j int) bool { |
| return v[i].LibraryName() > string(v[j].LibraryName()) |
| }) |
| DedupedLicenseData = append(DedupedLicenseData, v) |
| } |
| |
| sort.SliceStable(DedupedLicenseData, func(i, j int) bool { |
| return string(DedupedLicenseData[i][0].Data()) > string(DedupedLicenseData[j][0].Data()) |
| }) |
| |
| return nil |
| } |
| |
| func processGenOutput(gen *util.Gen, fileMap map[string]*Project) (*Project, error) { |
| for _, t := range gen.Targets { |
| var project *Project |
| var ok bool |
| for _, possibleProjectName := range t.CleanNames { |
| if project, ok = fileMap[possibleProjectName]; ok { |
| if _, ok := FilteredProjects[project.Root]; !ok { |
| plusVal(FilteredProjectReasons, fmt.Sprintf("Adding %s because of %s\n", project.Root, possibleProjectName)) |
| } |
| |
| // Project 'project' represents GN target 't'. |
| // Break out of this loop and proceed. |
| break |
| } |
| } |
| if project == nil { |
| // Some directories (e.g. test directories) are skipped, |
| // so projects won't be found for those files. |
| // TODO(jcecil): Make this a failing error. |
| // return nil, fmt.Errorf("Failed to find project matching name %v\n", t.CleanNames) |
| continue |
| } |
| |
| // Use the same process on t.CleanDeps to find all projects |
| // that match the GN target 't''s dependencies. |
| // Add those projects to the project.Children map. |
| for _, d := range t.CleanDeps { |
| if child, ok := fileMap[d]; ok && child.Root != project.Root { |
| project.Children[child.Root] = child |
| if _, ok := FilteredProjects[child.Root]; !ok { |
| plusVal( |
| FilteredProjectReasons, |
| fmt.Sprintf("Adding %s because of %s\n", child.Root, d)) |
| } |
| FilteredProjects[child.Root] = child |
| } |
| } |
| |
| FilteredProjects[project.Root] = project |
| } |
| |
| rootProject := fileMap[Config.Target] |
| if rootProject == nil { |
| // TODO(https://fxbug.dev/42066952): Understand why sometimes //:default is not found in the fileMap |
| //return nil, fmt.Errorf("failed to find root project using target [%s]", Config.Target) |
| rootProject = AllProjects["."] |
| } |
| |
| return rootProject, nil |
| } |
| |
| func getFileMap() (map[string]*Project, error) { |
| // Create a mapping that goes from file path to project, |
| // so we can retrieve the projects that match dependencies in the |
| // gn gen file. |
| fileMap := make(map[string]*Project, 0) |
| for _, p := range AllProjects { |
| allFiles := make([]*file.File, 0) |
| allFiles = append(allFiles, p.RegularFiles...) |
| allFiles = append(allFiles, p.LicenseFiles...) |
| for _, f := range allFiles { |
| filePath := "//" + f.RelPath() |
| folderPath := "//" + filepath.Dir(f.RelPath()) |
| |
| // "gn gen" may reveal that the current workspace |
| // has a dependency on a LICENSE file. |
| // That LICENSE file may be used in two or more |
| // different projects across fuchsia.git. |
| // There's no way for us to tell which project |
| // actually contributes to the build. |
| // |
| // We want to deterministically generate the final |
| // NOTICE file, so in this situation we simply choose |
| // the project that comes first alphabetically. |
| // |
| // In practice this simple strategy should be OK. |
| // "gn desc" / "gn gen" will undoubtedly also have |
| // dependencies on other files in the project, which |
| // will ensure that the correct project is included |
| // (even if we occasionally include an unrelated one). |
| if otherP, ok := fileMap[filePath]; ok { |
| if p.Root < otherP.Root { |
| fileMap[filePath] = p |
| fileMap[folderPath] = p |
| } |
| } else { |
| fileMap[filePath] = p |
| fileMap[folderPath] = p |
| } |
| } |
| } |
| |
| return fileMap, nil |
| } |