blob: f56873ba3228aa61f77f0c4c9541180d8009c9f8 [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 result
import (
"fmt"
"net/http"
"sort"
"strings"
"time"
classifierLib "github.com/google/licenseclassifier/v2"
"go.fuchsia.dev/fuchsia/tools/check-licenses/directory"
"go.fuchsia.dev/fuchsia/tools/check-licenses/project"
"go.fuchsia.dev/fuchsia/tools/check-licenses/project/readme"
)
type Check struct {
Name string `json: "name"`
Allowlist map[string]bool `json:"allowlist"`
}
func RunChecks() error {
if err := AllFuchsiaAuthorSourceFilesMustHaveCopyrightHeaders(); err != nil {
return err
}
if err := AllFilesAndFoldersMustBeIncludedInAProject(); err != nil {
return err
}
if err := AllLicenseTextsMustBeRecognized(); err != nil {
return err
}
if err := AllLicensePatternUsagesMustBeApproved(); err != nil {
return err
}
if err := AllComplianceWorksheetLinksAreGood(); err != nil {
return err
}
if err := AllProjectsMustHaveALicense(); err != nil {
return err
}
if err := AllReadmeFuchsiaFilesMustBeFormattedCorrectly(); err != nil {
// TODO: Make this blocking error after README.fuchsia format changes land.
// return err
fmt.Printf("*** Warning *** This will become an error in the future.\n\n%v\n", err)
}
return nil
}
// =============================================================================
func AllFuchsiaAuthorSourceFilesMustHaveCopyrightHeaders() error {
name := "AllFuchsiaAuthorSourceFilesMustHaveCopyrightHeaders"
// Retrieve allowlists from config files
allowlist := make(map[string]bool, 0)
for _, c := range Config.Checks {
if c.Name == name {
for k, v := range c.Allowlist {
allowlist[k] = v
}
}
}
var b strings.Builder
b.WriteString("All source files owned by The Fuchsia Authors must contain a copyright header.\n")
b.WriteString("The following files have missing or incorrectly worded copyright header information:\n\n")
var fuchsia *project.Project
for _, p := range project.FilteredProjects {
if p.Root == "." || p.Root == Config.FuchsiaDir {
fuchsia = p
break
}
}
if fuchsia == nil {
return fmt.Errorf("Couldn't find Fuchsia project to verify this check!!\n")
}
count := 0
OUTER:
for _, f := range fuchsia.SearchableRegularFiles {
if _, ok := allowlist[f.RelPath()]; ok {
continue
}
fdList, err := f.Data()
if err != nil {
return fmt.Errorf("Found a file that hasn't been parsed yet?? %v | %v\n", f.AbsPath(), err)
}
for _, fd := range fdList {
results := fd.SearchResults()
if results != nil {
for _, m := range results.Matches {
if m.Name == "FuchsiaCopyright" {
continue OUTER
}
}
}
b.WriteString(fmt.Sprintf("-> %v\n", f.RelPath()))
count = count + 1
}
}
b.WriteString(fmt.Sprintf("\nPlease add the standard Fuchsia copyright header info to the above %v files.\n", count))
if count > 0 {
return fmt.Errorf(b.String())
}
return nil
}
func AllLicenseTextsMustBeRecognized() error {
name := "AllLicenseTextsMustBeRecognized"
var foundUnrecognizedMatch bool
var b strings.Builder
// Retrieve allowlists from config files
allowlist := make(map[string]bool, 0)
for _, c := range Config.Checks {
if c.Name == name {
for k, v := range c.Allowlist {
allowlist[k] = v
}
}
}
b.WriteString("Found unrecognized license texts - please add the relevant license pattern(s) to //tools/check-licenses/assets/patterns/* and have it(them) reviewed by the OSRB team:\n\n")
for _, p := range project.FilteredProjects {
for _, l := range p.LicenseFiles {
data, err := l.Data()
if err != nil {
return err
}
for _, fd := range data {
results := fd.SearchResults()
if results == nil {
foundUnrecognizedMatch = true
b.WriteString(fmt.Sprintf("-> %s\n", l.RelPath()))
continue
}
}
}
}
if foundUnrecognizedMatch {
return fmt.Errorf(b.String())
}
return nil
}
func AllLicensePatternUsagesMustBeApproved() error {
name := "AllLicensePatternUsagesMustBeApproved"
var b strings.Builder
// Retrieve allowlists from config files
allowlist := make(map[string]bool, 0)
for _, c := range Config.Checks {
if c.Name != name {
continue
}
for k, v := range c.Allowlist {
allowlist[k] = v
}
}
for _, p := range project.FilteredProjects {
for _, l := range p.LicenseFiles {
if _, ok := allowlist[l.RelPath()]; ok {
continue
}
data, _ := l.Data()
for _, fd := range data {
results := fd.SearchResults()
if results == nil {
continue
}
for _, m := range results.Matches {
if _, ok := allowlist[l.RelPath()]; ok {
continue
}
switch {
case m.MatchType == "Copyright":
continue
case m.MatchType == "Approved":
continue
case strings.HasPrefix(m.MatchType, "_"):
continue
}
if isProjectAllowlisted(p.Root, m) {
continue
}
b.WriteString(fmt.Sprintf("File %v from project %s was not approved to use license pattern %s (%s | %s)\n",
l.RelPath(), p.Root, m.Name, m.MatchType, m.Variant))
}
}
}
}
result := b.String()
if len(result) > 0 {
description := "\n\nIf a given project uses a license that is not globally approved (http://shortn/_hp7ShC5lIf),\n"
description = fmt.Sprintf("%sthen an allowlist entry must exist granting that project explicit privilege to use it.\n", description)
description = fmt.Sprintf("%sEncountered license texts that were not approved for usage:\n\n%v", description, result)
description = fmt.Sprintf("%s\nEither remove the projects / licenses, or add an allowlist entry", description)
description = fmt.Sprintf("%s\nto //tools/check-licenses/assets/allowlists/* and have it reviewed by the OSRB team.\n", description)
return fmt.Errorf("%s", description)
}
return nil
}
func isProjectAllowlisted(relpath string, m *classifierLib.Match) bool {
for _, al := range Config.AllowLists {
if al.MatchType != m.MatchType || al.Name != m.Name {
continue
}
for _, e := range al.Entries {
for _, p := range e.Projects {
if p == relpath {
return true
}
}
}
}
return false
}
func AllComplianceWorksheetLinksAreGood() error {
if !Config.CheckURLs {
fmt.Println("Not checking URLs")
return nil
}
numBadLinks := 0
checkedURLs := make(map[string]bool, 0)
for _, p := range project.FilteredProjects {
for _, l := range p.LicenseFiles {
data, _ := l.Data()
for _, fd := range data {
url := fd.URL()
if checkedURLs[url] {
continue
}
if strings.Contains(url, "3rd party library") {
continue
}
if url != "" {
fmt.Printf(" -> %s\n", url)
resp, err := http.Get(url)
if err != nil {
fmt.Printf("%v-%v %v: %v \n", p.Root, l.RelPath(), url, err)
numBadLinks = numBadLinks + 1
} else if resp.Status != "200 OK" {
fmt.Printf("%v-%v %v: %v\n", p.Root, l.RelPath(), url, resp.Status)
numBadLinks = numBadLinks + 1
}
checkedURLs[url] = true
time.Sleep(200 * time.Millisecond)
}
}
}
}
if numBadLinks > 0 {
return fmt.Errorf("Encountered %d bad license URLs.\n", numBadLinks)
}
return nil
}
func AllProjectsMustHaveALicense() error {
name := "AllProjectsMustHaveALicense"
var b strings.Builder
b.WriteString("All projects should include relevant license information, and a README.fuchsia file pointing to the file.\n")
b.WriteString("The following projects were found without any license information:\n\n")
// Retrieve allowlists from config files
allowlist := make(map[string]bool, 0)
for _, c := range Config.Checks {
if c.Name == name {
for k, v := range c.Allowlist {
allowlist[k] = v
}
}
}
badReadmes := make([]string, 0)
for _, p := range project.FilteredProjects {
if _, ok := allowlist[p.Root]; ok {
continue
}
if len(p.LicenseFiles) == 0 {
badReadmes = append(badReadmes, fmt.Sprintf("-> %v (README.fuchsia file: %v)\n", p.Root, p.ReadmeFile.ReadmePath))
}
}
sort.Strings(badReadmes)
if len(badReadmes) > 0 {
for _, s := range badReadmes {
b.WriteString(s)
}
}
b.WriteString("\nPlease add a LICENSE file to the above projects, and point to them in the associated README.fuchsia file.\n")
if len(badReadmes) > 0 {
return fmt.Errorf(b.String())
}
return nil
}
func AllFilesAndFoldersMustBeIncludedInAProject() error {
name := "AllFilesAndFoldersMustBeIncludedInAProject"
var b strings.Builder
count := 0
// Retrieve allowlists from config files
allowlist := make(map[string]bool, 0)
for _, c := range Config.Checks {
if c.Name == name {
for k, v := range c.Allowlist {
allowlist[k] = v
}
}
}
b.WriteString("All files and folders must have proper license attribution.\n")
b.WriteString("This means a license file needs to accompany all first and third party projects,\n")
b.WriteString("and a README.fuchsia file must exist and specify where the license file lives.\n")
b.WriteString("The following directories are not included in a project:\n\n")
for _, d := range directory.AllDirectories {
if d.Project == project.UnknownProject && len(d.Files) > 0 {
if _, ok := allowlist[d.Path]; !ok {
b.WriteString(fmt.Sprintf("-> %s\n", d.Path))
count = count + 1
}
}
}
b.WriteString("\nPlease add a LICENSE file to the above projects, and point to them in an associated README.fuchsia file.\n")
b.WriteString("If this is urgent and you cannot complete the above steps, add the above paths to the relevant allowlist\n")
b.WriteString("in tools/check-licenses/result/_config.json (last resort).\n")
if count > 0 {
return fmt.Errorf(b.String())
}
return nil
}
func AllReadmeFuchsiaFilesMustBeFormattedCorrectly() error {
var b strings.Builder
b.WriteString("All README.fuchsia files must be formatted correctly, using known directives.\n")
b.WriteString("https://fuchsia.dev/fuchsia-src/development/source_code/third-party-metadata\n")
b.WriteString("The following README files are malformed:\n\n")
count := 0
for _, r := range readme.AllReadmes {
if len(r.MalformedLines) > 0 {
b.WriteString(fmt.Sprintf(" -> %s [%s]\n",
r.ReadmePath, r.MalformedLines[0]))
count = count + 1
}
}
b.WriteString("\nPlease fix the above README files.\n")
if count == 0 {
return nil
}
return fmt.Errorf(b.String())
}