blob: 21441add3b0a10de9c2bf5ac0a17a0e609744a01 [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 directory
import (
"fmt"
"os"
"path/filepath"
"sort"
"go.fuchsia.dev/fuchsia/tools/check-licenses/file"
"go.fuchsia.dev/fuchsia/tools/check-licenses/project"
"go.fuchsia.dev/fuchsia/tools/check-licenses/project/readme"
)
// Directory is an in memory representation of the state of the repository.
type Directory struct {
Name string `json:"name,omitempty"`
Path string `json:"path,omitempty"`
Files []*file.File `json:"files,omitempty"`
Children []*Directory `json:"children,omitempty"`
Parent *Directory `json:"-"`
Project *project.Project `json:"-"`
}
// Order implements sort.Interface for []*Directory based on the Path field.
type Order []*Directory
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 }
// NewDirectory returns an instance of Directory.
func NewDirectory(root string, parent *Directory) (*Directory, error) {
return newDirectoryWithConfig(root, parent, Config)
}
// Call NewDirectory with a passed-in config variable (instead of using
// the static Config variable). This allows for easier testing.
func newDirectoryWithConfig(root string, parent *Directory, config *DirectoryConfig) (*Directory, error) {
d := Directory{}
if RootDirectory == nil {
RootDirectory = &d
}
d.Name = filepath.Base(root)
d.Path = root
d.Parent = parent
d.Project = project.UnknownProject
// 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 {
d.Project = parent.Project
}
if !project.IsBarrier(root) && parent != nil {
// 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.
d.Project = parent.Project
}
var r *readme.Readme
var p *project.Project
var err error
// If a README.fuchsia file exists in the current directory, load it.
if readmePath, exists := readmeFileExists(root); exists {
if r, err = readme.NewReadmeFromFile(readmePath); err != nil {
return nil, fmt.Errorf("error loading readme file [%s]: %w\n",
readmePath, err)
}
} else if readmeFileWillNeverExist(root) {
// Some 3P projects don't have (and never will have) a README.fuchsia file.
// In those cases, generate an in-memory README file that describes
// the project.
if r, err = readme.NewReadmeCustom(root); err != nil {
return nil, fmt.Errorf("error creating custom readme [%s]: %w\n",
root, err)
}
}
// If this project was already created during initialization, set it here.
var ok bool
if p, ok = project.AllProjects[root]; ok {
d.Project = p
r = d.Project.ReadmeFile
} else if r != nil {
// Create a new Project using the readme data.
p, err = project.NewProject(r, root)
if err != nil {
return nil, err
}
d.Project = p
}
directoryContents, err := os.ReadDir(root)
if err != nil {
return nil, err
}
// Then traverse the rest of the contents of this directory.
for _, item := range directoryContents {
path := filepath.Join(root, item.Name())
// Check the config file to see if we should skip this file / folder.
if config.shouldSkip(path) {
plusVal(Skipped, path)
continue
}
// Directories
if item.IsDir() {
plus1(NumFolders)
child, err := newDirectoryWithConfig(path, &d, config)
if err != nil {
return nil, err
}
d.Children = append(d.Children, child)
continue
}
// Files
fileType := file.RegularFile
if file.IsPossibleLicenseFile(path) {
fileType = file.SingleLicense
}
f, err := file.LoadFile(path, fileType, d.Project.Name)
if err != nil {
// Likely a symlink issue
continue
} else {
plus1(NumFiles)
d.Files = append(d.Files, f)
d.Project.AddFile(f)
}
}
sort.Sort(Order(d.Children))
AllDirectories[d.Path] = &d
return &d, nil
}