| // 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 |
| } |