mirror of https://github.com/ethereum/go-ethereum
commit
59c5a2f519
@ -0,0 +1,12 @@ |
||||
Jeffrey Wilcke <jeffrey@ethereum.org> |
||||
Jeffrey Wilcke <jeffrey@ethereum.org> <geffobscura@gmail.com> |
||||
Jeffrey Wilcke <jeffrey@ethereum.org> <obscuren@obscura.com> |
||||
Jeffrey Wilcke <jeffrey@ethereum.org> <obscuren@users.noreply.github.com> |
||||
|
||||
Viktor Trón <viktor.tron@gmail.com> |
||||
|
||||
Joseph Goulden <joegoulden@gmail.com> |
||||
|
||||
Nick Savers <nicksavers@gmail.com> |
||||
|
||||
Maran Hidskes <maran.hidskes@gmail.com> |
@ -0,0 +1,247 @@ |
||||
// +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{"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 |
||||
} |
Loading…
Reference in new issue