github.com/u-root/u-root@v7.0.1-0.20200915234505-ad7babab0a8e+incompatible/tools/checklicenses/checklicenses.go (about)

     1  // Copyright 2017-2018 the u-root Authors. All rights reserved
     2  // Use of this source code is governed by a BSD-style
     3  // license that can be found in the LICENSE file.
     4  
     5  // Run with `go run checklicenses.go`. This script has one drawback:
     6  // - It does not correct the licenses; it simply outputs a list of files which
     7  //   do not conform and returns 1 if the list is non-empty.
     8  package main
     9  
    10  import (
    11  	"encoding/json"
    12  	"flag"
    13  	"fmt"
    14  	"io/ioutil"
    15  	"log"
    16  	"os"
    17  	"os/exec"
    18  	"regexp"
    19  	"strings"
    20  )
    21  
    22  var (
    23  	absPath    = flag.Bool("a", false, "Print absolute paths")
    24  	configFile = flag.String("c", "", "Configuration file in JSON format")
    25  	generated  = regexp.MustCompilePOSIX(`^// Code generated .* DO NOT EDIT\.$`)
    26  )
    27  
    28  type rule struct {
    29  	*regexp.Regexp
    30  	invert bool
    31  }
    32  
    33  func accept(s string) rule {
    34  	return rule{
    35  		regexp.MustCompile("^" + s + "$"),
    36  		false,
    37  	}
    38  }
    39  
    40  func reject(s string) rule {
    41  	return rule{
    42  		regexp.MustCompile("^" + s + "$"),
    43  		true,
    44  	}
    45  }
    46  
    47  // Config contains the rules for license checking.
    48  type Config struct {
    49  	// Licenses is a list of acceptable license headers. Each license is
    50  	// represented by an array of strings, one string per line, without the
    51  	// trailing \n .
    52  	Licenses        [][]string
    53  	licensesRegexps []*regexp.Regexp
    54  	// GoPkg is the Go package name to check for licenses
    55  	GoPkg string
    56  	// Accept is a list of file patterns to include in the license checking
    57  	Accept []string
    58  	accept []rule
    59  	// Reject is a list of file patterns to exclude from the license checking
    60  	Reject []string
    61  	reject []rule
    62  }
    63  
    64  // CompileRegexps compiles the regular expressions coming from the JSON
    65  // configuration, and returns an error if an invalid regexp is found.
    66  func (c *Config) CompileRegexps() error {
    67  	for _, licenseRegexps := range c.Licenses {
    68  		licenseRegexp := strings.Join(licenseRegexps, "\n")
    69  		re, err := regexp.Compile(licenseRegexp)
    70  		if err != nil {
    71  			return err
    72  		}
    73  		c.licensesRegexps = append(c.licensesRegexps, re)
    74  	}
    75  
    76  	c.accept = make([]rule, 0, len(c.Accept))
    77  	for _, rule := range c.Accept {
    78  		c.accept = append(c.accept, accept(rule))
    79  	}
    80  
    81  	c.reject = make([]rule, 0, len(c.Reject))
    82  	for _, rule := range c.Reject {
    83  		c.reject = append(c.reject, reject(rule))
    84  	}
    85  
    86  	return nil
    87  }
    88  
    89  func main() {
    90  	flag.Parse()
    91  
    92  	if *configFile == "" {
    93  		log.Fatal("Config file name cannot be empty")
    94  	}
    95  	buf, err := ioutil.ReadFile(*configFile)
    96  	if err != nil {
    97  		log.Fatalf("Failed to read file %s: %v", *configFile, err)
    98  	}
    99  	var config Config
   100  	if err := json.Unmarshal(buf, &config); err != nil {
   101  		log.Fatalf("Cannot unmarshal JSON from config file %s: %v", *configFile, err)
   102  	}
   103  	if err := config.CompileRegexps(); err != nil {
   104  		log.Fatalf("Failed to compile regexps from JSON config: %v", err)
   105  	}
   106  
   107  	pkgPath := os.ExpandEnv(config.GoPkg)
   108  	incorrect := []string{}
   109  
   110  	// List files added to u-root.
   111  	out, err := exec.Command("git", "ls-files").Output()
   112  	if err != nil {
   113  		log.Fatalln("error running git ls-files:", err)
   114  	}
   115  	files := strings.Fields(string(out))
   116  
   117  	rules := append(config.accept, config.reject...)
   118  
   119  	// Iterate over files.
   120  outer:
   121  	for _, file := range files {
   122  		// Test rules.
   123  		trimmedPath := strings.TrimPrefix(file, pkgPath)
   124  		for _, r := range rules {
   125  			if r.MatchString(trimmedPath) == r.invert {
   126  				continue outer
   127  			}
   128  		}
   129  
   130  		// Make sure it is not a directory.
   131  		info, err := os.Stat(file)
   132  		if err != nil {
   133  			log.Fatalln("cannot stat", file, err)
   134  		}
   135  		if info.IsDir() {
   136  			continue
   137  		}
   138  
   139  		// Read from the file.
   140  		r, err := os.Open(file)
   141  		if err != nil {
   142  			log.Fatalln("cannot open", file, err)
   143  		}
   144  		defer r.Close()
   145  		contents, err := ioutil.ReadAll(r)
   146  		if err != nil {
   147  			log.Fatalln("cannot read", file, err)
   148  		}
   149  		// License check only makes sense for human authored code.
   150  		// We should skip the license check if the code is generated by
   151  		// a tool.
   152  		// https://golang.org/s/generatedcode
   153  		if generated.Match(contents) {
   154  			continue
   155  		}
   156  		var foundone bool
   157  		for _, l := range config.licensesRegexps {
   158  			if l.Match(contents) {
   159  				foundone = true
   160  				break
   161  			}
   162  		}
   163  		if !foundone {
   164  			p := trimmedPath
   165  			if *absPath {
   166  				p = file
   167  			}
   168  			incorrect = append(incorrect, p)
   169  		}
   170  	}
   171  	if err != nil {
   172  		log.Fatal(err)
   173  	}
   174  
   175  	// Print files with incorrect licenses.
   176  	if len(incorrect) > 0 {
   177  		fmt.Println(strings.Join(incorrect, "\n"))
   178  		os.Exit(1)
   179  	}
   180  }