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 }