github.com/xyproto/u-root@v6.0.1-0.20200302025726-5528e0c77a3c+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  )
    26  
    27  type rule struct {
    28  	*regexp.Regexp
    29  	invert bool
    30  }
    31  
    32  func accept(s string) rule {
    33  	return rule{
    34  		regexp.MustCompile("^" + s + "$"),
    35  		false,
    36  	}
    37  }
    38  
    39  func reject(s string) rule {
    40  	return rule{
    41  		regexp.MustCompile("^" + s + "$"),
    42  		true,
    43  	}
    44  }
    45  
    46  // Config contains the rules for license checking.
    47  type Config struct {
    48  	// Licenses is a list of acceptable license headers. Each license is
    49  	// represented by an array of strings, one string per line, without the
    50  	// trailing \n .
    51  	Licenses        [][]string
    52  	licensesRegexps []*regexp.Regexp
    53  	// GoPkg is the Go package name to check for licenses
    54  	GoPkg string
    55  	// Accept is a list of file patterns to include in the license checking
    56  	Accept []string
    57  	accept []rule
    58  	// Reject is a list of file patterns to exclude from the license checking
    59  	Reject []string
    60  	reject []rule
    61  }
    62  
    63  // CompileRegexps compiles the regular expressions coming from the JSON
    64  // configuration, and returns an error if an invalid regexp is found.
    65  func (c *Config) CompileRegexps() error {
    66  	for _, licenseRegexps := range c.Licenses {
    67  		licenseRegexp := strings.Join(licenseRegexps, "\n")
    68  		re, err := regexp.Compile(licenseRegexp)
    69  		if err != nil {
    70  			return err
    71  		}
    72  		c.licensesRegexps = append(c.licensesRegexps, re)
    73  	}
    74  
    75  	c.accept = make([]rule, 0, len(c.Accept))
    76  	for _, rule := range c.Accept {
    77  		c.accept = append(c.accept, accept(rule))
    78  	}
    79  
    80  	c.reject = make([]rule, 0, len(c.Reject))
    81  	for _, rule := range c.Reject {
    82  		c.reject = append(c.reject, reject(rule))
    83  	}
    84  
    85  	return nil
    86  }
    87  
    88  func main() {
    89  	flag.Parse()
    90  
    91  	if *configFile == "" {
    92  		log.Fatal("Config file name cannot be empty")
    93  	}
    94  	buf, err := ioutil.ReadFile(*configFile)
    95  	if err != nil {
    96  		log.Fatalf("Failed to read file %s: %v", *configFile, err)
    97  	}
    98  	var config Config
    99  	if err := json.Unmarshal(buf, &config); err != nil {
   100  		log.Fatalf("Cannot unmarshal JSON from config file %s: %v", *configFile, err)
   101  	}
   102  	if err := config.CompileRegexps(); err != nil {
   103  		log.Fatalf("Failed to compile regexps from JSON config: %v", err)
   104  	}
   105  
   106  	pkgPath := os.ExpandEnv(config.GoPkg)
   107  	incorrect := []string{}
   108  
   109  	// List files added to u-root.
   110  	out, err := exec.Command("git", "ls-files").Output()
   111  	if err != nil {
   112  		log.Fatalln("error running git ls-files:", err)
   113  	}
   114  	files := strings.Fields(string(out))
   115  
   116  	rules := append(config.accept, config.reject...)
   117  
   118  	// Iterate over files.
   119  outer:
   120  	for _, file := range files {
   121  		// Test rules.
   122  		trimmedPath := strings.TrimPrefix(file, pkgPath)
   123  		for _, r := range rules {
   124  			if r.MatchString(trimmedPath) == r.invert {
   125  				continue outer
   126  			}
   127  		}
   128  
   129  		// Make sure it is not a directory.
   130  		info, err := os.Stat(file)
   131  		if err != nil {
   132  			log.Fatalln("cannot stat", file, err)
   133  		}
   134  		if info.IsDir() {
   135  			continue
   136  		}
   137  
   138  		// Read from the file.
   139  		r, err := os.Open(file)
   140  		if err != nil {
   141  			log.Fatalln("cannot open", file, err)
   142  		}
   143  		defer r.Close()
   144  		contents, err := ioutil.ReadAll(r)
   145  		if err != nil {
   146  			log.Fatalln("cannot read", file, err)
   147  		}
   148  		var foundone bool
   149  		for _, l := range config.licensesRegexps {
   150  			if l.Match(contents) {
   151  				foundone = true
   152  				break
   153  			}
   154  		}
   155  		if !foundone {
   156  			p := trimmedPath
   157  			if *absPath {
   158  				p = file
   159  			}
   160  			incorrect = append(incorrect, p)
   161  		}
   162  	}
   163  	if err != nil {
   164  		log.Fatal(err)
   165  	}
   166  
   167  	// Print files with incorrect licenses.
   168  	if len(incorrect) > 0 {
   169  		fmt.Println(strings.Join(incorrect, "\n"))
   170  		os.Exit(1)
   171  	}
   172  }