github.com/theQRL/go-zond@v0.1.1/build/update-license.go (about) 1 // Copyright 2018 The go-ethereum Authors 2 // This file is part of the go-ethereum library. 3 // 4 // The go-ethereum library is free software: you can redistribute it and/or modify 5 // it under the terms of the GNU Lesser General Public License as published by 6 // the Free Software Foundation, either version 3 of the License, or 7 // (at your option) any later version. 8 // 9 // The go-ethereum library is distributed in the hope that it will be useful, 10 // but WITHOUT ANY WARRANTY; without even the implied warranty of 11 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 // GNU Lesser General Public License for more details. 13 // 14 // You should have received a copy of the GNU Lesser General Public License 15 // along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>. 16 17 //go:build none 18 // +build none 19 20 /* 21 This command generates GPL license headers on top of all source files. 22 You can run it once per month, before cutting a release or just 23 whenever you feel like it. 24 25 go run update-license.go 26 27 All authors (people who have contributed code) are listed in the 28 AUTHORS file. The author names are mapped and deduplicated using the 29 .mailmap file. You can use .mailmap to set the canonical name and 30 address for each author. See git-shortlog(1) for an explanation of the 31 .mailmap format. 32 33 Please review the resulting diff to check whether the correct 34 copyright assignments are performed. 35 */ 36 37 package main 38 39 import ( 40 "bufio" 41 "bytes" 42 "fmt" 43 "log" 44 "os" 45 "os/exec" 46 "path/filepath" 47 "regexp" 48 "runtime" 49 "strconv" 50 "strings" 51 "sync" 52 "text/template" 53 "time" 54 55 "golang.org/x/exp/slices" 56 ) 57 58 var ( 59 // only files with these extensions will be considered 60 extensions = []string{".go", ".js", ".qml"} 61 62 // paths with any of these prefixes will be skipped 63 skipPrefixes = []string{ 64 // boring stuff 65 "vendor/", "tests/testdata/", "build/", 66 67 // don't relicense vendored sources 68 "cmd/internal/browser", 69 "common/bitutil/bitutil", 70 "common/prque/", 71 "consensus/ethash/xor.go", 72 "crypto/blake2b/", 73 "crypto/bn256/", 74 "crypto/bls12381/", 75 "crypto/ecies/", 76 "graphql/graphiql.go", 77 "internal/jsre/deps", 78 "log/", 79 "metrics/", 80 "signer/rules/deps", 81 82 // skip special licenses 83 "crypto/secp256k1", // Relicensed to BSD-3 via https://github.com/theQRL/go-zond/pull/17225 84 } 85 86 // paths with this prefix are licensed as GPL. all other files are LGPL. 87 gplPrefixes = []string{"cmd/"} 88 89 // this regexp must match the entire license comment at the 90 // beginning of each file. 91 licenseCommentRE = regexp.MustCompile(`^//\s*(Copyright|This file is part of).*?\n(?://.*?\n)*\n*`) 92 93 // this text appears at the start of AUTHORS 94 authorsFileHeader = "# This is the official list of go-ethereum authors for copyright purposes.\n\n" 95 ) 96 97 // this template generates the license comment. 98 // its input is an info structure. 99 var licenseT = template.Must(template.New("").Parse(` 100 // Copyright {{.Year}} The go-ethereum Authors 101 // This file is part of {{.Whole false}}. 102 // 103 // {{.Whole true}} is free software: you can redistribute it and/or modify 104 // it under the terms of the GNU {{.License}} as published by 105 // the Free Software Foundation, either version 3 of the License, or 106 // (at your option) any later version. 107 // 108 // {{.Whole true}} is distributed in the hope that it will be useful, 109 // but WITHOUT ANY WARRANTY; without even the implied warranty of 110 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 111 // GNU {{.License}} for more details. 112 // 113 // You should have received a copy of the GNU {{.License}} 114 // along with {{.Whole false}}. If not, see <http://www.gnu.org/licenses/>. 115 116 `[1:])) 117 118 type info struct { 119 file string 120 Year int64 121 } 122 123 func (i info) License() string { 124 if i.gpl() { 125 return "General Public License" 126 } 127 return "Lesser General Public License" 128 } 129 130 func (i info) ShortLicense() string { 131 if i.gpl() { 132 return "GPL" 133 } 134 return "LGPL" 135 } 136 137 func (i info) Whole(startOfSentence bool) string { 138 if i.gpl() { 139 return "go-ethereum" 140 } 141 if startOfSentence { 142 return "The go-ethereum library" 143 } 144 return "the go-ethereum library" 145 } 146 147 func (i info) gpl() bool { 148 for _, p := range gplPrefixes { 149 if strings.HasPrefix(i.file, p) { 150 return true 151 } 152 } 153 return false 154 } 155 156 func main() { 157 var ( 158 files = getFiles() 159 filec = make(chan string) 160 infoc = make(chan *info, 20) 161 wg sync.WaitGroup 162 ) 163 164 writeAuthors(files) 165 166 go func() { 167 for _, f := range files { 168 filec <- f 169 } 170 close(filec) 171 }() 172 for i := runtime.NumCPU(); i >= 0; i-- { 173 // getting file info is slow and needs to be parallel. 174 // it traverses git history for each file. 175 wg.Add(1) 176 go getInfo(filec, infoc, &wg) 177 } 178 go func() { 179 wg.Wait() 180 close(infoc) 181 }() 182 writeLicenses(infoc) 183 } 184 185 func skipFile(path string) bool { 186 if strings.Contains(path, "/testdata/") { 187 return true 188 } 189 for _, p := range skipPrefixes { 190 if strings.HasPrefix(path, p) { 191 return true 192 } 193 } 194 return false 195 } 196 197 func getFiles() []string { 198 cmd := exec.Command("git", "ls-tree", "-r", "--name-only", "HEAD") 199 var files []string 200 err := doLines(cmd, func(line string) { 201 if skipFile(line) { 202 return 203 } 204 ext := filepath.Ext(line) 205 for _, wantExt := range extensions { 206 if ext == wantExt { 207 goto keep 208 } 209 } 210 return 211 keep: 212 files = append(files, line) 213 }) 214 if err != nil { 215 log.Fatal("error getting files:", err) 216 } 217 return files 218 } 219 220 var authorRegexp = regexp.MustCompile(`\s*[0-9]+\s*(.*)`) 221 222 func gitAuthors(files []string) []string { 223 cmds := []string{"shortlog", "-s", "-n", "-e", "HEAD", "--"} 224 cmds = append(cmds, files...) 225 cmd := exec.Command("git", cmds...) 226 var authors []string 227 err := doLines(cmd, func(line string) { 228 m := authorRegexp.FindStringSubmatch(line) 229 if len(m) > 1 { 230 authors = append(authors, m[1]) 231 } 232 }) 233 if err != nil { 234 log.Fatalln("error getting authors:", err) 235 } 236 return authors 237 } 238 239 func readAuthors() []string { 240 content, err := os.ReadFile("AUTHORS") 241 if err != nil && !os.IsNotExist(err) { 242 log.Fatalln("error reading AUTHORS:", err) 243 } 244 var authors []string 245 for _, a := range bytes.Split(content, []byte("\n")) { 246 if len(a) > 0 && a[0] != '#' { 247 authors = append(authors, string(a)) 248 } 249 } 250 // Retranslate existing authors through .mailmap. 251 // This should catch email address changes. 252 authors = mailmapLookup(authors) 253 return authors 254 } 255 256 func mailmapLookup(authors []string) []string { 257 if len(authors) == 0 { 258 return nil 259 } 260 cmds := []string{"check-mailmap", "--"} 261 cmds = append(cmds, authors...) 262 cmd := exec.Command("git", cmds...) 263 var translated []string 264 err := doLines(cmd, func(line string) { 265 translated = append(translated, line) 266 }) 267 if err != nil { 268 log.Fatalln("error translating authors:", err) 269 } 270 return translated 271 } 272 273 func writeAuthors(files []string) { 274 var ( 275 dedup = make(map[string]bool) 276 list []string 277 ) 278 // Add authors that Git reports as contributors. 279 // This is the primary source of author information. 280 for _, a := range gitAuthors(files) { 281 if la := strings.ToLower(a); !dedup[la] { 282 list = append(list, a) 283 dedup[la] = true 284 } 285 } 286 // Add existing authors from the file. This should ensure that we 287 // never lose authors, even if Git stops listing them. We can also 288 // add authors manually this way. 289 for _, a := range readAuthors() { 290 if la := strings.ToLower(a); !dedup[la] { 291 list = append(list, a) 292 dedup[la] = true 293 } 294 } 295 // Write sorted list of authors back to the file. 296 slices.SortFunc(list, func(a, b string) bool { 297 return strings.ToLower(a) < strings.ToLower(b) 298 }) 299 content := new(bytes.Buffer) 300 content.WriteString(authorsFileHeader) 301 for _, a := range list { 302 content.WriteString(a) 303 content.WriteString("\n") 304 } 305 fmt.Println("writing AUTHORS") 306 if err := os.WriteFile("AUTHORS", content.Bytes(), 0644); err != nil { 307 log.Fatalln(err) 308 } 309 } 310 311 func getInfo(files <-chan string, out chan<- *info, wg *sync.WaitGroup) { 312 for file := range files { 313 stat, err := os.Lstat(file) 314 if err != nil { 315 fmt.Printf("ERROR %s: %v\n", file, err) 316 continue 317 } 318 if !stat.Mode().IsRegular() { 319 continue 320 } 321 if isGenerated(file) { 322 continue 323 } 324 info, err := fileInfo(file) 325 if err != nil { 326 fmt.Printf("ERROR %s: %v\n", file, err) 327 continue 328 } 329 out <- info 330 } 331 wg.Done() 332 } 333 334 func isGenerated(file string) bool { 335 fd, err := os.Open(file) 336 if err != nil { 337 return false 338 } 339 defer fd.Close() 340 buf := make([]byte, 2048) 341 n, err := fd.Read(buf) 342 if err != nil { 343 return false 344 } 345 buf = buf[:n] 346 for _, l := range bytes.Split(buf, []byte("\n")) { 347 if bytes.HasPrefix(l, []byte("// Code generated")) { 348 return true 349 } 350 } 351 return false 352 } 353 354 // fileInfo finds the lowest year in which the given file was committed. 355 func fileInfo(file string) (*info, error) { 356 info := &info{file: file, Year: int64(time.Now().Year())} 357 cmd := exec.Command("git", "log", "--follow", "--find-renames=80", "--find-copies=80", "--pretty=format:%ai", "--", file) 358 err := doLines(cmd, func(line string) { 359 y, err := strconv.ParseInt(line[:4], 10, 64) 360 if err != nil { 361 fmt.Printf("cannot parse year: %q", line[:4]) 362 } 363 if y < info.Year { 364 info.Year = y 365 } 366 }) 367 return info, err 368 } 369 370 func writeLicenses(infos <-chan *info) { 371 for i := range infos { 372 writeLicense(i) 373 } 374 } 375 376 func writeLicense(info *info) { 377 fi, err := os.Stat(info.file) 378 if os.IsNotExist(err) { 379 fmt.Println("skipping (does not exist)", info.file) 380 return 381 } 382 if err != nil { 383 log.Fatalf("error stat'ing %s: %v\n", info.file, err) 384 } 385 content, err := os.ReadFile(info.file) 386 if err != nil { 387 log.Fatalf("error reading %s: %v\n", info.file, err) 388 } 389 // Construct new file content. 390 buf := new(bytes.Buffer) 391 licenseT.Execute(buf, info) 392 if m := licenseCommentRE.FindIndex(content); m != nil && m[0] == 0 { 393 buf.Write(content[:m[0]]) 394 buf.Write(content[m[1]:]) 395 } else { 396 buf.Write(content) 397 } 398 // Write it to the file. 399 if bytes.Equal(content, buf.Bytes()) { 400 fmt.Println("skipping (no changes)", info.file) 401 return 402 } 403 fmt.Println("writing", info.ShortLicense(), info.file) 404 if err := os.WriteFile(info.file, buf.Bytes(), fi.Mode()); err != nil { 405 log.Fatalf("error writing %s: %v", info.file, err) 406 } 407 } 408 409 func doLines(cmd *exec.Cmd, f func(string)) error { 410 stdout, err := cmd.StdoutPipe() 411 if err != nil { 412 return err 413 } 414 if err := cmd.Start(); err != nil { 415 return err 416 } 417 s := bufio.NewScanner(stdout) 418 for s.Scan() { 419 f(s.Text()) 420 } 421 if s.Err() != nil { 422 return s.Err() 423 } 424 if err := cmd.Wait(); err != nil { 425 return fmt.Errorf("%v (for %s)", err, strings.Join(cmd.Args, " ")) 426 } 427 return nil 428 }