github.com/mvdan/u-root-coreutils@v0.0.0-20230122170626-c2eef2898555/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"
    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 := os.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  	// Iterate over files.
   118  	for _, file := range files {
   119  		// Test rules.
   120  		trimmedPath := strings.TrimPrefix(file, pkgPath)
   121  		foundAccept, foundReject := false, false
   122  		// First go through accepted patterns
   123  		for _, r := range config.accept {
   124  			if r.MatchString(trimmedPath) {
   125  				foundAccept = true
   126  			}
   127  		}
   128  		// Then go through rejected patterns. Rejection patterns override
   129  		// acceptance patterns.
   130  		for _, r := range config.reject {
   131  			if r.MatchString(trimmedPath) {
   132  				foundReject = true
   133  			}
   134  		}
   135  		if foundReject || !foundAccept {
   136  			continue
   137  		}
   138  
   139  		// Make sure it is not a directory.
   140  		info, err := os.Stat(file)
   141  		if err != nil {
   142  			log.Fatalln("cannot stat", file, err)
   143  		}
   144  		if info.IsDir() {
   145  			continue
   146  		}
   147  
   148  		// Read from the file.
   149  		r, err := os.Open(file)
   150  		if err != nil {
   151  			log.Fatalln("cannot open", file, err)
   152  		}
   153  		defer r.Close()
   154  		contents, err := io.ReadAll(r)
   155  		if err != nil {
   156  			log.Fatalln("cannot read", file, err)
   157  		}
   158  		// License check only makes sense for human authored code.
   159  		// We should skip the license check if the code is generated by
   160  		// a tool.
   161  		// https://golang.org/s/generatedcode
   162  		if generated.Match(contents) {
   163  			continue
   164  		}
   165  		var foundone bool
   166  		for _, l := range config.licensesRegexps {
   167  			if l.Match(contents) {
   168  				foundone = true
   169  				break
   170  			}
   171  		}
   172  		if !foundone {
   173  			p := trimmedPath
   174  			if *absPath {
   175  				p = file
   176  			}
   177  			incorrect = append(incorrect, p)
   178  		}
   179  	}
   180  	if err != nil {
   181  		log.Fatal(err)
   182  	}
   183  
   184  	// Print files with incorrect licenses.
   185  	if len(incorrect) > 0 {
   186  		fmt.Println(strings.Join(incorrect, "\n"))
   187  		os.Exit(1)
   188  	}
   189  }