| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248 |
- // +build none
- /*
- This command generates GPL license headers on top of all source files.
- You can run it once per month, before cutting a release or just
- whenever you feel like it.
- go run update-license.go
- The copyright in each file is assigned to any authors for which git
- can find commits in the file's history. It will try to follow renames
- throughout history. The author names are mapped and deduplicated using
- the .mailmap file. You can use .mailmap to set the canonical name and
- address for each author. See git-shortlog(1) for an explanation
- of the .mailmap format.
- Please review the resulting diff to check whether the correct
- copyright assignments are performed.
- */
- package main
- import (
- "bufio"
- "bytes"
- "fmt"
- "io/ioutil"
- "os"
- "os/exec"
- "path"
- "regexp"
- "runtime"
- "sort"
- "strings"
- "sync"
- "text/template"
- )
- var (
- // only files with these extensions will be considered
- extensions = []string{".go", ".js", ".qml"}
- // paths with any of these prefixes will be skipped
- skipPrefixes = []string{"Godeps/", "tests/files/", "cmd/mist/assets/ext/", "cmd/mist/assets/muted/"}
- // paths with this prefix are licensed as GPL. all other files are LGPL.
- gplPrefixes = []string{"cmd/"}
- // this regexp must match the entire license comment at the
- // beginning of each file.
- licenseCommentRE = regexp.MustCompile(`(?s)^/\*\s*(Copyright|This file is part of) .*?\*/\n*`)
- // this line is used when git doesn't find any authors for a file
- defaultCopyright = "Copyright (C) 2014 Jeffrey Wilcke <jeffrey@ethereum.org>"
- )
- // this template generates the license comment.
- // its input is an info structure.
- var licenseT = template.Must(template.New("").Parse(`/*
- {{.Copyrights}}
- This file is part of go-ethereum
- go-ethereum is free software: you can redistribute it and/or modify
- it under the terms of the GNU {{.License}} as published by
- the Free Software Foundation, either version 3 of the License, or
- (at your option) any later version.
- go-ethereum is distributed in the hope that it will be useful,
- but WITHOUT ANY WARRANTY; without even the implied warranty of
- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- GNU {{.License}} for more details.
- You should have received a copy of the GNU {{.License}}
- along with go-ethereum. If not, see <http://www.gnu.org/licenses/>.
- */
- `))
- type info struct {
- file string
- mode os.FileMode
- authors map[string][]string // map keys are authors, values are years
- gpl bool
- }
- func (i info) Copyrights() string {
- var lines []string
- for name, years := range i.authors {
- lines = append(lines, "Copyright (C) "+strings.Join(years, ", ")+" "+name)
- }
- if len(lines) == 0 {
- lines = []string{defaultCopyright}
- }
- sort.Strings(lines)
- return strings.Join(lines, "\n\t")
- }
- func (i info) License() string {
- if i.gpl {
- return "General Public License"
- } else {
- return "Lesser General Public License"
- }
- }
- func (i info) ShortLicense() string {
- if i.gpl {
- return "GPL"
- } else {
- return "LGPL"
- }
- }
- func (i *info) addAuthorYear(name, year string) {
- for _, y := range i.authors[name] {
- if y == year {
- return
- }
- }
- i.authors[name] = append(i.authors[name], year)
- sort.Strings(i.authors[name])
- }
- func main() {
- files := make(chan string)
- infos := make(chan *info)
- wg := new(sync.WaitGroup)
- go getFiles(files)
- for i := runtime.NumCPU(); i >= 0; i-- {
- // getting file info is slow and needs to be parallel
- wg.Add(1)
- go getInfo(files, infos, wg)
- }
- go func() { wg.Wait(); close(infos) }()
- writeLicenses(infos)
- }
- func getFiles(out chan<- string) {
- cmd := exec.Command("git", "ls-tree", "-r", "--name-only", "HEAD")
- err := doLines(cmd, func(line string) {
- for _, p := range skipPrefixes {
- if strings.HasPrefix(line, p) {
- return
- }
- }
- ext := path.Ext(line)
- for _, wantExt := range extensions {
- if ext == wantExt {
- goto send
- }
- }
- return
- send:
- out <- line
- })
- if err != nil {
- fmt.Println("error getting files:", err)
- }
- close(out)
- }
- func getInfo(files <-chan string, out chan<- *info, wg *sync.WaitGroup) {
- for file := range files {
- stat, err := os.Lstat(file)
- if err != nil {
- fmt.Printf("ERROR %s: %v\n", file, err)
- continue
- }
- if !stat.Mode().IsRegular() {
- continue
- }
- info, err := fileInfo(file)
- if err != nil {
- fmt.Printf("ERROR %s: %v\n", file, err)
- continue
- }
- info.mode = stat.Mode()
- out <- info
- }
- wg.Done()
- }
- func fileInfo(file string) (*info, error) {
- info := &info{file: file, authors: make(map[string][]string)}
- for _, p := range gplPrefixes {
- if strings.HasPrefix(file, p) {
- info.gpl = true
- break
- }
- }
- cmd := exec.Command("git", "log", "--follow", "--find-copies", "--pretty=format:%ai | %aN <%aE>", "--", file)
- err := doLines(cmd, func(line string) {
- sep := strings.IndexByte(line, '|')
- year, name := line[:4], line[sep+2:]
- info.addAuthorYear(name, year)
- })
- return info, err
- }
- func writeLicenses(infos <-chan *info) {
- buf := new(bytes.Buffer)
- for info := range infos {
- content, err := ioutil.ReadFile(info.file)
- if err != nil {
- fmt.Printf("ERROR: couldn't read %s: %v\n", info.file, err)
- continue
- }
- // construct new file content
- buf.Reset()
- licenseT.Execute(buf, info)
- if m := licenseCommentRE.FindIndex(content); m != nil && m[0] == 0 {
- buf.Write(content[m[1]:])
- } else {
- buf.Write(content)
- }
- if !bytes.Equal(content, buf.Bytes()) {
- fmt.Println("writing", info.ShortLicense(), info.file)
- if err := ioutil.WriteFile(info.file, buf.Bytes(), info.mode); err != nil {
- fmt.Printf("ERROR: couldn't write %s: %v", info.file, err)
- }
- }
- }
- }
- func doLines(cmd *exec.Cmd, f func(string)) error {
- stdout, err := cmd.StdoutPipe()
- if err != nil {
- return err
- }
- if err := cmd.Start(); err != nil {
- return err
- }
- s := bufio.NewScanner(stdout)
- for s.Scan() {
- f(s.Text())
- }
- if s.Err() != nil {
- return s.Err()
- }
- if err := cmd.Wait(); err != nil {
- return fmt.Errorf("%v (for %s)", err, strings.Join(cmd.Args, " "))
- }
- return nil
- }
|