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