blob: 38570e0b6792107e56e22805a89bd39dbeec4d7e [file] [log] [blame]
// 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
}