| // 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 filetree |
| |
| import ( |
| "fmt" |
| "os" |
| "path/filepath" |
| "sort" |
| "sync" |
| |
| "go.fuchsia.dev/fuchsia/tools/check-licenses/project" |
| ) |
| |
| // FileTree is an in memory representation of the state of the repository. |
| type FileTree struct { |
| Name string `json:"name,omitempty"` |
| Path string `json:"path,omitempty"` |
| FilePaths []string `json:"files,omitempty"` |
| Children []*FileTree `json:"children,omitempty"` |
| Parent *FileTree `json:"-"` |
| Project *project.Project `json:"project,omitempty"` |
| |
| sync.RWMutex |
| } |
| |
| // Order implements sort.Interface for []*FileTree based on the Path field. |
| type Order []*FileTree |
| |
| func (a Order) Len() int { return len(a) } |
| func (a Order) Swap(i, j int) { a[i], a[j] = a[j], a[i] } |
| func (a Order) Less(i, j int) bool { return a[i].Path < a[j].Path } |
| |
| // NewFileTree returns an instance of FileTree. |
| func NewFileTree(root string, parent *FileTree) (*FileTree, error) { |
| ft := FileTree{} |
| if RootFileTree == nil { |
| RootFileTree = &ft |
| } |
| |
| ft.Name = filepath.Base(root) |
| ft.Path = root |
| ft.Parent = parent |
| |
| // If we are not at a "barrier" directory (e.g. prebuilt, third_party), |
| // then the license info of the parent directory also applies to this directory. |
| // Propagate that information down here. |
| if !project.IsBarrier(root) && parent != nil { |
| ft.Project = parent.Project |
| } |
| |
| directoryContents, err := os.ReadDir(root) |
| if err != nil { |
| return nil, err |
| } |
| |
| // First, find the README.fuchsia file in the current directory (if it exists), |
| // or retrieve the project instance from any predefined "readmes" directory. |
| p, err := project.NewProject(filepath.Join(root, "README.fuchsia"), root) |
| if err != nil { |
| if os.IsNotExist(err) { |
| // There is no README.fuchsia file in the current directory. |
| // That's OK. Continue. |
| } else { |
| return nil, err |
| } |
| } else { |
| ft.Project = p |
| } |
| |
| // Then traverse the rest of the contents of this directory. |
| for _, item := range directoryContents { |
| path := filepath.Join(root, item.Name()) |
| if Config.shouldSkip(path) { |
| plusVal(Skipped, path) |
| continue |
| } |
| |
| // Directories |
| if item.IsDir() { |
| plus1(NumFolders) |
| child, err := NewFileTree(path, &ft) |
| if err != nil { |
| return nil, err |
| } |
| ft.Children = append(ft.Children, child) |
| continue |
| } |
| |
| // Files |
| fi, err := os.Stat(path) |
| if err != nil { |
| // Likely a symlink issue |
| continue |
| } |
| if fi.Size() == 0 { |
| // Ignore empty files |
| continue |
| } else { |
| plus1(NumFiles) |
| ft.FilePaths = append(ft.FilePaths, path) |
| } |
| } |
| |
| sort.Sort(Order(ft.Children)) |
| |
| // Verify that all files in the current directory belong to a project. |
| if ft.Project == nil { |
| for _, f := range ft.FilePaths { |
| plusVal(FileMissingProject, f) |
| } |
| if len(ft.FilePaths) > 0 { |
| plusVal(FolderMissingProject, ft.Path) |
| if Config.ExitOnMissingProject { |
| return nil, fmt.Errorf("Filetree %v has no associated projects", root) |
| } |
| } |
| } |
| if ft.Project != nil { |
| ft.Project.AddFiles(ft.FilePaths) |
| } |
| |
| AllFileTrees[ft.Path] = &ft |
| return &ft, nil |
| } |