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