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