blob: 95f96b86b9b825181c9ec07da498b62f4fadbf34 [file] [log] [blame]
// Copyright 2020 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 checklicenses
import (
"context"
"errors"
"fmt"
"io/ioutil"
"os"
"path/filepath"
"regexp"
"runtime/trace"
"sort"
"strings"
)
// Licenses is an object that facilitates operations on each License object in bulk
type Licenses struct {
licenses []*License
}
// NewLicenses returns a Licenses object with each license pattern loaded from
// the .lic folder location specified in Config
func NewLicenses(ctx context.Context, root string, prohibitedLicenseTypes []string) (*Licenses, error) {
defer trace.StartRegion(ctx, "NewLicenses").End()
licensesPath := []string{}
err := filepath.Walk(root,
func(path string, info os.FileInfo, err error) error {
if info.IsDir() {
return nil
}
licensesPath = append(licensesPath, path)
return nil
})
if err != nil {
return nil, err
}
l := &Licenses{}
for _, path := range licensesPath {
bytes, err := ioutil.ReadFile(path)
if err != nil {
return nil, err
}
regex := string(bytes)
// Update regex to ignore multiple white spaces, newlines, comments.
// But first, trim whitespace away so we don't include unnecessary
// comment syntax.
regex = strings.Trim(regex, "\n ")
regex = strings.ReplaceAll(regex, "\n", `[\s\\#\*\/]*`)
regex = strings.ReplaceAll(regex, " ", `[\s\\#\*\/]*`)
re, err := regexp.Compile(regex)
if err != nil {
return nil, fmt.Errorf("%s: %w", path, err)
}
base := filepath.Base(path)
l.licenses = append(
l.licenses,
&License{
pattern: re,
Category: base,
ValidType: contains(prohibitedLicenseTypes, filepath.Base(base)),
matches: map[string]*Match{},
matchChannel: make(chan *Match, 10),
})
}
if len(l.licenses) == 0 {
return nil, errors.New("no licenses")
}
// Reorder the files putting fuchsia licenses first, then shortest first.
sort.Slice(l.licenses, func(i, j int) bool {
a := strings.Contains(l.licenses[i].Category, "fuchsia")
b := strings.Contains(l.licenses[j].Category, "fuchsia")
if a != b {
return a
}
return len(l.licenses[i].pattern.String()) < len(l.licenses[j].pattern.String())
})
return l, nil
}
func (l *Licenses) GetFilesWithProhibitedLicenses() []string {
var filesWithProhibitedLicenses []string
set := map[string]bool{}
for _, license := range l.licenses {
if license.ValidType {
continue
}
for _, match := range license.matches {
for _, file_path := range match.files {
if _, found := set[file_path]; !found {
set[file_path] = true
filesWithProhibitedLicenses = append(filesWithProhibitedLicenses, file_path)
}
}
}
}
return filesWithProhibitedLicenses
}
func (l *Licenses) MatchSingleLicenseFile(data []byte, path string, metrics *Metrics, file_tree *FileTree) {
// TODO(solomonokinard) deduplicate Match*File()
for _, license := range l.licenses {
if m := license.pattern.Find(data); m != nil {
metrics.increment("num_single_license_file_match")
license.matchAuthors(string(m), data, path)
file_tree.Lock()
file_tree.SingleLicenseFiles[path] = append(file_tree.SingleLicenseFiles[path], license)
file_tree.Unlock()
}
}
}
// MatchFile returns true if any License matches input data
// along with the license that matched. It returns false and nil
// if there were no matches.
func (l *Licenses) MatchFile(data []byte, path string, metrics *Metrics) (bool, *License) {
for _, license := range l.licenses {
if m := license.pattern.Find(data); m != nil {
metrics.increment("num_licensed")
license.matchAuthors(string(m), data, path)
return true, license
}
}
return false, nil
}
func contains(matches []string, item string) bool {
for _, m := range matches {
if strings.Contains(item, m) {
return false
}
}
return true
}