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 }