github.com/codingfuture/orig-energi3@v0.8.4/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 // +build none 18 19 /* 20 This command generates GPL license headers on top of all source files. 21 You can run it once per month, before cutting a release or just 22 whenever you feel like it. 23 24 go run update-license.go 25 26 All authors (people who have contributed code) are listed in the 27 AUTHORS file. The author names are mapped and deduplicated using the 28 .mailmap file. You can use .mailmap to set the canonical name and 29 address for each author. See git-shortlog(1) for an explanation of the 30 .mailmap format. 31 32 Please review the resulting diff to check whether the correct 33 copyright assignments are performed. 34 */ 35 36 package main 37 38 import ( 39 "bufio" 40 "bytes" 41 "fmt" 42 "io/ioutil" 43 "log" 44 "os" 45 "os/exec" 46 "path/filepath" 47 "regexp" 48 "runtime" 49 "sort" 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 // don't relicense vendored sources 66 "cmd/internal/browser", 67 "consensus/ethash/xor.go", 68 "crypto/bn256/", 69 "crypto/ecies/", 70 "crypto/secp256k1/curve.go", 71 "crypto/sha3/", 72 "internal/jsre/deps", 73 "log/", 74 "common/bitutil/bitutil", 75 // don't license generated files 76 "contracts/chequebook/contract/code.go", 77 } 78 79 // paths with this prefix are licensed as GPL. all other files are LGPL. 80 gplPrefixes = []string{"cmd/"} 81 82 // this regexp must match the entire license comment at the 83 // beginning of each file. 84 licenseCommentRE = regexp.MustCompile(`^//\s*(Copyright|This file is part of).*?\n(?://.*?\n)*\n*`) 85 86 // this text appears at the start of AUTHORS 87 authorsFileHeader = "# This is the official list of go-ethereum authors for copyright purposes.\n\n" 88 89 // 90 energiAuthorsFileHeader = "# This is the official list of Energi Core authors for copyright purposes.\n\n" 91 energiAuthorsRE = regexp.MustCompile(`@(energi.team|futoin)`) 92 93 // The commit after which the Energi source starts 94 //energiForkPoint = "4bcc0a37ab70cb79b16893556cffdaad6974e7d8" 95 96 // Date of Energi fork refactoring start 97 energiForkDate = "2019-06-01" 98 99 // The year when work on the initial project "ETHFork" has started 100 energiStartYear int64 = 2018 101 ) 102 103 // this template generates the license comment. 104 // its input is an info structure. 105 var licenseT = template.Must(template.New("").Parse(` 106 // Copyright {{.Year}} The go-ethereum Authors 107 // This file is part of {{.Whole false}}. 108 // 109 // {{.Whole true}} is free software: you can redistribute it and/or modify 110 // it under the terms of the GNU {{.License}} as published by 111 // the Free Software Foundation, either version 3 of the License, or 112 // (at your option) any later version. 113 // 114 // {{.Whole true}} is distributed in the hope that it will be useful, 115 // but WITHOUT ANY WARRANTY; without even the implied warranty of 116 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 117 // GNU {{.License}} for more details. 118 // 119 // You should have received a copy of the GNU {{.License}} 120 // along with {{.Whole false}}. If not, see <http://www.gnu.org/licenses/>. 121 122 `[1:])) 123 124 var energiLicenseT = template.Must(template.New("").Parse(` 125 // Copyright {{.YearEnergi}} The Energi Core Authors 126 // Copyright {{.Year}} The go-ethereum Authors 127 // This file is part of {{.WholeEnergi false}}. 128 // 129 // {{.WholeEnergi true}} is free software: you can redistribute it and/or modify 130 // it under the terms of the GNU {{.License}} as published by 131 // the Free Software Foundation, either version 3 of the License, or 132 // (at your option) any later version. 133 // 134 // {{.WholeEnergi true}} is distributed in the hope that it will be useful, 135 // but WITHOUT ANY WARRANTY; without even the implied warranty of 136 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 137 // GNU {{.License}} for more details. 138 // 139 // You should have received a copy of the GNU {{.License}} 140 // along with {{.WholeEnergi false}}. If not, see <http://www.gnu.org/licenses/>. 141 142 `[1:])) 143 144 var onlyEnergiLicenseT = template.Must(template.New("").Parse(` 145 // Copyright {{.YearEnergi}} The Energi Core Authors 146 // This file is part of {{.WholeEnergi false}}. 147 // 148 // {{.WholeEnergi true}} is free software: you can redistribute it and/or modify 149 // it under the terms of the GNU {{.License}} as published by 150 // the Free Software Foundation, either version 3 of the License, or 151 // (at your option) any later version. 152 // 153 // {{.WholeEnergi true}} is distributed in the hope that it will be useful, 154 // but WITHOUT ANY WARRANTY; without even the implied warranty of 155 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 156 // GNU {{.License}} for more details. 157 // 158 // You should have received a copy of the GNU {{.License}} 159 // along with {{.WholeEnergi false}}. If not, see <http://www.gnu.org/licenses/>. 160 161 `[1:])) 162 163 type info struct { 164 file string 165 Year int64 166 YearEnergi int64 167 IsEnergi bool 168 OnlyEnergi bool 169 } 170 171 func (i info) License() string { 172 if i.gpl() { 173 return "General Public License" 174 } 175 return "Lesser General Public License" 176 } 177 178 func (i info) ShortLicense() string { 179 if i.gpl() { 180 return "GPL" 181 } 182 return "LGPL" 183 } 184 185 func (i info) Whole(startOfSentence bool) string { 186 if i.gpl() { 187 return "go-ethereum" 188 } 189 if startOfSentence { 190 return "The go-ethereum library" 191 } 192 return "the go-ethereum library" 193 } 194 195 func (i info) WholeEnergi(startOfSentence bool) string { 196 if i.gpl() { 197 return "Energi Core" 198 } 199 if startOfSentence { 200 return "The Energi Core library" 201 } 202 return "the Energi Core library" 203 } 204 205 func (i info) gpl() bool { 206 for _, p := range gplPrefixes { 207 if strings.HasPrefix(i.file, p) { 208 return true 209 } 210 } 211 return false 212 } 213 214 func main() { 215 var ( 216 files = getFiles() 217 filec = make(chan string) 218 infoc = make(chan *info, 20) 219 wg sync.WaitGroup 220 ) 221 222 writeAuthors(files) 223 224 go func() { 225 for _, f := range files { 226 filec <- f 227 } 228 close(filec) 229 }() 230 for i := runtime.NumCPU(); i >= 0; i-- { 231 // getting file info is slow and needs to be parallel. 232 // it traverses git history for each file. 233 wg.Add(1) 234 go getInfo(filec, infoc, &wg) 235 } 236 go func() { 237 wg.Wait() 238 close(infoc) 239 }() 240 writeLicenses(infoc) 241 } 242 243 func skipFile(path string) bool { 244 if strings.Contains(path, "/testdata/") { 245 return true 246 } 247 for _, p := range skipPrefixes { 248 if strings.HasPrefix(path, p) { 249 return true 250 } 251 } 252 return false 253 } 254 255 func getFiles() []string { 256 cmd := exec.Command("git", "ls-tree", "-r", "--name-only", "HEAD") 257 var files []string 258 err := doLines(cmd, func(line string) { 259 if skipFile(line) { 260 return 261 } 262 ext := filepath.Ext(line) 263 for _, wantExt := range extensions { 264 if ext == wantExt { 265 goto keep 266 } 267 } 268 return 269 keep: 270 files = append(files, line) 271 }) 272 if err != nil { 273 log.Fatal("error getting files:", err) 274 } 275 return files 276 } 277 278 var authorRegexp = regexp.MustCompile(`\s*[0-9]+\s*(.*)`) 279 280 func gitAuthors(files []string) []string { 281 cmds := []string{"shortlog", "-s", "-n", "-e", "HEAD", "--"} 282 cmds = append(cmds, files...) 283 cmd := exec.Command("git", cmds...) 284 var authors []string 285 err := doLines(cmd, func(line string) { 286 m := authorRegexp.FindStringSubmatch(line) 287 if len(m) > 1 { 288 authors = append(authors, m[1]) 289 } 290 }) 291 if err != nil { 292 log.Fatalln("error getting authors:", err) 293 } 294 return authors 295 } 296 297 func readAuthors() []string { 298 content, err := ioutil.ReadFile("AUTHORS") 299 if err != nil && !os.IsNotExist(err) { 300 log.Fatalln("error reading AUTHORS:", err) 301 } 302 var authors []string 303 for _, a := range bytes.Split(content, []byte("\n")) { 304 if len(a) > 0 && a[0] != '#' { 305 authors = append(authors, string(a)) 306 } 307 } 308 // Retranslate existing authors through .mailmap. 309 // This should catch email address changes. 310 authors = mailmapLookup(authors) 311 return authors 312 } 313 314 func mailmapLookup(authors []string) []string { 315 if len(authors) == 0 { 316 return nil 317 } 318 cmds := []string{"check-mailmap", "--"} 319 cmds = append(cmds, authors...) 320 cmd := exec.Command("git", cmds...) 321 var translated []string 322 err := doLines(cmd, func(line string) { 323 translated = append(translated, line) 324 }) 325 if err != nil { 326 log.Fatalln("error translating authors:", err) 327 } 328 return translated 329 } 330 331 func writeAuthors(files []string) { 332 merge := make(map[string]bool) 333 // Add authors that Git reports as contributorxs. 334 // This is the primary source of author information. 335 for _, a := range gitAuthors(files) { 336 merge[a] = true 337 } 338 // Add existing authors from the file. This should ensure that we 339 // never lose authors, even if Git stops listing them. We can also 340 // add authors manually this way. 341 for _, a := range readAuthors() { 342 merge[a] = true 343 } 344 // Write sorted list of authors back to the file. 345 var energi_result []string 346 var result []string 347 for a := range merge { 348 if m := energiAuthorsRE.FindStringIndex(a); m != nil { 349 energi_result = append(energi_result, a) 350 } else { 351 result = append(result, a) 352 } 353 } 354 sort.Strings(energi_result) 355 sort.Strings(result) 356 content := new(bytes.Buffer) 357 content.WriteString(energiAuthorsFileHeader) 358 for _, a := range energi_result { 359 content.WriteString(a) 360 content.WriteString("\n") 361 } 362 content.WriteString("\n") 363 content.WriteString(authorsFileHeader) 364 for _, a := range result { 365 content.WriteString(a) 366 content.WriteString("\n") 367 } 368 fmt.Println("writing AUTHORS") 369 if err := ioutil.WriteFile("AUTHORS", content.Bytes(), 0644); err != nil { 370 log.Fatalln(err) 371 } 372 } 373 374 func getInfo(files <-chan string, out chan<- *info, wg *sync.WaitGroup) { 375 for file := range files { 376 stat, err := os.Lstat(file) 377 if err != nil { 378 fmt.Printf("ERROR %s: %v\n", file, err) 379 continue 380 } 381 if !stat.Mode().IsRegular() { 382 continue 383 } 384 if isGenerated(file) { 385 continue 386 } 387 info, err := fileInfo(file) 388 if err != nil { 389 fmt.Printf("ERROR %s: %v\n", file, err) 390 continue 391 } 392 out <- info 393 } 394 wg.Done() 395 } 396 397 func isGenerated(file string) bool { 398 fd, err := os.Open(file) 399 if err != nil { 400 return false 401 } 402 defer fd.Close() 403 buf := make([]byte, 2048) 404 n, _ := fd.Read(buf) 405 buf = buf[:n] 406 for _, l := range bytes.Split(buf, []byte("\n")) { 407 if bytes.HasPrefix(l, []byte("// Code generated")) { 408 return true 409 } 410 } 411 return false 412 } 413 414 // fileInfo finds the lowest year in which the given file was committed. 415 func fileInfo(file string) (*info, error) { 416 info := &info{file: file, Year: int64(time.Now().Year()), IsEnergi: false} 417 cmd := exec.Command("git", "log", "--follow", "--find-renames=80", "--find-copies=80", "--pretty=format:%ai", "--", file) 418 err := doLines(cmd, func(line string) { 419 // Character comparison should be OK for ISO format 420 if line[:len(energiForkDate)] >= energiForkDate { 421 info.IsEnergi = true 422 info.OnlyEnergi = true 423 } 424 if line[:len(energiForkDate)] < energiForkDate { 425 info.OnlyEnergi = false 426 } 427 y, err := strconv.ParseInt(line[:4], 10, 64) 428 if err != nil { 429 fmt.Printf("cannot parse year: %q", line[:4]) 430 } 431 if y < info.Year { 432 info.Year = y 433 } 434 }) 435 if info.Year >= energiStartYear { 436 info.YearEnergi = info.Year 437 // TODO: revise on cherry-picks 438 info.Year = energiStartYear 439 } else { 440 info.YearEnergi = energiStartYear 441 } 442 return info, err 443 } 444 445 func writeLicenses(infos <-chan *info) { 446 for i := range infos { 447 writeLicense(i) 448 } 449 } 450 451 func writeLicense(info *info) { 452 fi, err := os.Stat(info.file) 453 if os.IsNotExist(err) { 454 fmt.Println("skipping (does not exist)", info.file) 455 return 456 } 457 if err != nil { 458 log.Fatalf("error stat'ing %s: %v\n", info.file, err) 459 } 460 content, err := ioutil.ReadFile(info.file) 461 if err != nil { 462 log.Fatalf("error reading %s: %v\n", info.file, err) 463 } 464 // Construct new file content. 465 buf := new(bytes.Buffer) 466 if info.IsEnergi { 467 if info.OnlyEnergi { 468 onlyEnergiLicenseT.Execute(buf, info) 469 } else { 470 energiLicenseT.Execute(buf, info) 471 } 472 } else { 473 licenseT.Execute(buf, info) 474 } 475 if m := licenseCommentRE.FindIndex(content); m != nil && m[0] == 0 { 476 buf.Write(content[:m[0]]) 477 buf.Write(content[m[1]:]) 478 } else { 479 buf.Write(content) 480 } 481 // Write it to the file. 482 if bytes.Equal(content, buf.Bytes()) { 483 fmt.Println("skipping (no changes)", info.file) 484 return 485 } 486 fmt.Println("writing", info.ShortLicense(), info.file) 487 if err := ioutil.WriteFile(info.file, buf.Bytes(), fi.Mode()); err != nil { 488 log.Fatalf("error writing %s: %v", info.file, err) 489 } 490 } 491 492 func doLines(cmd *exec.Cmd, f func(string)) error { 493 stdout, err := cmd.StdoutPipe() 494 if err != nil { 495 return err 496 } 497 if err := cmd.Start(); err != nil { 498 return err 499 } 500 s := bufio.NewScanner(stdout) 501 for s.Scan() { 502 f(s.Text()) 503 } 504 if s.Err() != nil { 505 return s.Err() 506 } 507 if err := cmd.Wait(); err != nil { 508 return fmt.Errorf("%v (for %s)", err, strings.Join(cmd.Args, " ")) 509 } 510 return nil 511 }