github.com/jonasnick/go-ethereum@v0.7.12-0.20150216215225-22176f05d387/update-license.go (about) 1 // +build none 2 /* 3 This command generates GPL license headers on top of all source files. 4 You can run it once per month, before cutting a release or just 5 whenever you feel like it. 6 7 go run update-license.go 8 9 The copyright in each file is assigned to any authors for which git 10 can find commits in the file's history. It will try to follow renames 11 throughout history. The author names are mapped and deduplicated using 12 the .mailmap file. You can use .mailmap to set the canonical name and 13 address for each author. See git-shortlog(1) for an explanation 14 of the .mailmap format. 15 16 Please review the resulting diff to check whether the correct 17 copyright assignments are performed. 18 */ 19 package main 20 21 import ( 22 "bufio" 23 "bytes" 24 "fmt" 25 "io/ioutil" 26 "os" 27 "os/exec" 28 "path" 29 "regexp" 30 "runtime" 31 "sort" 32 "strings" 33 "sync" 34 "text/template" 35 ) 36 37 var ( 38 // only files with these extensions will be considered 39 extensions = []string{".go", ".js", ".qml"} 40 41 // paths with any of these prefixes will be skipped 42 skipPrefixes = []string{"tests/files/", "cmd/mist/assets/ext/", "cmd/mist/assets/muted/"} 43 44 // paths with this prefix are licensed as GPL. all other files are LGPL. 45 gplPrefixes = []string{"cmd/"} 46 47 // this regexp must match the entire license comment at the 48 // beginning of each file. 49 licenseCommentRE = regexp.MustCompile(`(?s)^/\*\s*(Copyright|This file is part of) .*?\*/\n*`) 50 51 // this line is used when git doesn't find any authors for a file 52 defaultCopyright = "Copyright (C) 2014 Jeffrey Wilcke <jeffrey@ethereum.org>" 53 ) 54 55 // this template generates the license comment. 56 // its input is an info structure. 57 var licenseT = template.Must(template.New("").Parse(`/* 58 {{.Copyrights}} 59 60 This file is part of go-ethereum 61 62 go-ethereum is free software: you can redistribute it and/or modify 63 it under the terms of the GNU {{.License}} as published by 64 the Free Software Foundation, either version 3 of the License, or 65 (at your option) any later version. 66 67 go-ethereum is distributed in the hope that it will be useful, 68 but WITHOUT ANY WARRANTY; without even the implied warranty of 69 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 70 GNU {{.License}} for more details. 71 72 You should have received a copy of the GNU {{.License}} 73 along with go-ethereum. If not, see <http://www.gnu.org/licenses/>. 74 */ 75 76 `)) 77 78 type info struct { 79 file string 80 mode os.FileMode 81 authors map[string][]string // map keys are authors, values are years 82 gpl bool 83 } 84 85 func (i info) Copyrights() string { 86 var lines []string 87 for name, years := range i.authors { 88 lines = append(lines, "Copyright (C) "+strings.Join(years, ", ")+" "+name) 89 } 90 if len(lines) == 0 { 91 lines = []string{defaultCopyright} 92 } 93 sort.Strings(lines) 94 return strings.Join(lines, "\n\t") 95 } 96 97 func (i info) License() string { 98 if i.gpl { 99 return "General Public License" 100 } else { 101 return "Lesser General Public License" 102 } 103 } 104 105 func (i info) ShortLicense() string { 106 if i.gpl { 107 return "GPL" 108 } else { 109 return "LGPL" 110 } 111 } 112 113 func (i *info) addAuthorYear(name, year string) { 114 for _, y := range i.authors[name] { 115 if y == year { 116 return 117 } 118 } 119 i.authors[name] = append(i.authors[name], year) 120 sort.Strings(i.authors[name]) 121 } 122 123 func main() { 124 files := make(chan string) 125 infos := make(chan *info) 126 wg := new(sync.WaitGroup) 127 128 go getFiles(files) 129 for i := runtime.NumCPU(); i >= 0; i-- { 130 // getting file info is slow and needs to be parallel 131 wg.Add(1) 132 go getInfo(files, infos, wg) 133 } 134 go func() { wg.Wait(); close(infos) }() 135 writeLicenses(infos) 136 } 137 138 func getFiles(out chan<- string) { 139 cmd := exec.Command("git", "ls-tree", "-r", "--name-only", "HEAD") 140 err := doLines(cmd, func(line string) { 141 for _, p := range skipPrefixes { 142 if strings.HasPrefix(line, p) { 143 return 144 } 145 } 146 ext := path.Ext(line) 147 for _, wantExt := range extensions { 148 if ext == wantExt { 149 goto send 150 } 151 } 152 return 153 154 send: 155 out <- line 156 }) 157 if err != nil { 158 fmt.Println("error getting files:", err) 159 } 160 close(out) 161 } 162 163 func getInfo(files <-chan string, out chan<- *info, wg *sync.WaitGroup) { 164 for file := range files { 165 stat, err := os.Lstat(file) 166 if err != nil { 167 fmt.Printf("ERROR %s: %v\n", file, err) 168 continue 169 } 170 if !stat.Mode().IsRegular() { 171 continue 172 } 173 info, err := fileInfo(file) 174 if err != nil { 175 fmt.Printf("ERROR %s: %v\n", file, err) 176 continue 177 } 178 info.mode = stat.Mode() 179 out <- info 180 } 181 wg.Done() 182 } 183 184 func fileInfo(file string) (*info, error) { 185 info := &info{file: file, authors: make(map[string][]string)} 186 for _, p := range gplPrefixes { 187 if strings.HasPrefix(file, p) { 188 info.gpl = true 189 break 190 } 191 } 192 cmd := exec.Command("git", "log", "--follow", "--find-copies", "--pretty=format:%aI | %aN <%aE>", "--", file) 193 err := doLines(cmd, func(line string) { 194 sep := strings.IndexByte(line, '|') 195 year, name := line[:4], line[sep+2:] 196 info.addAuthorYear(name, year) 197 }) 198 return info, err 199 } 200 201 func writeLicenses(infos <-chan *info) { 202 buf := new(bytes.Buffer) 203 for info := range infos { 204 content, err := ioutil.ReadFile(info.file) 205 if err != nil { 206 fmt.Printf("ERROR: couldn't read %s: %v\n", info.file, err) 207 continue 208 } 209 210 // construct new file content 211 buf.Reset() 212 licenseT.Execute(buf, info) 213 if m := licenseCommentRE.FindIndex(content); m != nil && m[0] == 0 { 214 buf.Write(content[m[1]:]) 215 } else { 216 buf.Write(content) 217 } 218 219 if !bytes.Equal(content, buf.Bytes()) { 220 fmt.Println("writing", info.ShortLicense(), info.file) 221 if err := ioutil.WriteFile(info.file, buf.Bytes(), info.mode); err != nil { 222 fmt.Printf("ERROR: couldn't write %s: %v", info.file, err) 223 } 224 } 225 } 226 } 227 228 func doLines(cmd *exec.Cmd, f func(string)) error { 229 stdout, err := cmd.StdoutPipe() 230 if err != nil { 231 return err 232 } 233 if err := cmd.Start(); err != nil { 234 return err 235 } 236 s := bufio.NewScanner(stdout) 237 for s.Scan() { 238 f(s.Text()) 239 } 240 if s.Err() != nil { 241 return s.Err() 242 } 243 if err := cmd.Wait(); err != nil { 244 return fmt.Errorf("%v (for %s)", err, strings.Join(cmd.Args, " ")) 245 } 246 return nil 247 }