github.com/core-coin/go-core/v2@v2.1.9/build/update-license.go (about) 1 // Copyright 2015 by The go-core Authors 2 // This file is part of the go-core library. 3 // 4 // The go-core 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-core 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-core library. If not, see <http://www.gnu.org/licenses/>. 16 17 //go:build none 18 // +build none 19 20 /* 21 This command generates GPL license headers on top of all source files. 22 You can run it once per month, before cutting a release or just 23 whenever you feel like it. 24 25 go run update-license.go 26 27 Please review the resulting diff to check whether the correct 28 copyright assignments are performed. 29 */ 30 31 package main 32 33 import ( 34 "bufio" 35 "bytes" 36 "fmt" 37 "io/ioutil" 38 "log" 39 "os" 40 "os/exec" 41 "path/filepath" 42 "regexp" 43 "runtime" 44 "strconv" 45 "strings" 46 "sync" 47 "text/template" 48 "time" 49 ) 50 51 var ( 52 // only files with these extensions will be considered 53 extensions = []string{".go", ".js", ".qml"} 54 55 // paths with any of these prefixes will be skipped 56 skipPrefixes = []string{ 57 // boring stuff 58 "vendor/", "tests/testdata/", "build/", 59 60 // don't relicense vendored sources 61 "cmd/internal/browser", 62 "common/bitutil/bitutil", 63 "common/prque/", 64 "crypto/bn256/", 65 "crypto/ecies/", 66 "graphql/graphiql.go", 67 "internal/jsre/deps", 68 "log/", 69 "metrics/", 70 "signer/rules/deps", 71 } 72 73 // paths with this prefix are licensed as GPL. all other files are LGPL. 74 gplPrefixes = []string{"cmd/"} 75 76 // this regexp must match the entire license comment at the 77 // beginning of each file. 78 licenseCommentRE = regexp.MustCompile(`^//\s*(Copyright|This file is part of).*?\n(?://.*?\n)*\n*`) 79 ) 80 81 // this template generates the license comment. 82 // its input is an info structure. 83 var licenseT = template.Must(template.New("").Parse(` 84 // Copyright {{.Year}} by the Authors 85 // This file is part of {{.Whole false}}. 86 // 87 // {{.Whole true}} is free software: you can redistribute it and/or modify 88 // it under the terms of the GNU {{.License}} as published by 89 // the Free Software Foundation, either version 3 of the License, or 90 // (at your option) any later version. 91 // 92 // {{.Whole true}} is distributed in the hope that it will be useful, 93 // but WITHOUT ANY WARRANTY; without even the implied warranty of 94 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 95 // GNU {{.License}} for more details. 96 // 97 // You should have received a copy of the GNU {{.License}} 98 // along with {{.Whole false}}. If not, see <http://www.gnu.org/licenses/>. 99 100 `[1:])) 101 102 type info struct { 103 file string 104 Year int64 105 } 106 107 func (i info) License() string { 108 if i.gpl() { 109 return "General Public License" 110 } 111 return "Lesser General Public License" 112 } 113 114 func (i info) ShortLicense() string { 115 if i.gpl() { 116 return "GPL" 117 } 118 return "LGPL" 119 } 120 121 func (i info) Whole(startOfSentence bool) string { 122 if i.gpl() { 123 return "go-core" 124 } 125 if startOfSentence { 126 return "The go-core library" 127 } 128 return "the go-core library" 129 } 130 131 func (i info) gpl() bool { 132 for _, p := range gplPrefixes { 133 if strings.HasPrefix(i.file, p) { 134 return true 135 } 136 } 137 return false 138 } 139 140 func main() { 141 var ( 142 files = getFiles() 143 filec = make(chan string) 144 infoc = make(chan *info, 20) 145 wg sync.WaitGroup 146 ) 147 148 go func() { 149 for _, f := range files { 150 filec <- f 151 } 152 close(filec) 153 }() 154 for i := runtime.NumCPU(); i >= 0; i-- { 155 // getting file info is slow and needs to be parallel. 156 // it traverses git history for each file. 157 wg.Add(1) 158 go getInfo(filec, infoc, &wg) 159 } 160 go func() { 161 wg.Wait() 162 close(infoc) 163 }() 164 writeLicenses(infoc) 165 } 166 167 func skipFile(path string) bool { 168 if strings.Contains(path, "/testdata/") { 169 return true 170 } 171 for _, p := range skipPrefixes { 172 if strings.HasPrefix(path, p) { 173 return true 174 } 175 } 176 return false 177 } 178 179 func getFiles() []string { 180 cmd := exec.Command("git", "ls-tree", "-r", "--name-only", "HEAD") 181 var files []string 182 err := doLines(cmd, func(line string) { 183 if skipFile(line) { 184 return 185 } 186 ext := filepath.Ext(line) 187 for _, wantExt := range extensions { 188 if ext == wantExt { 189 goto keep 190 } 191 } 192 return 193 keep: 194 files = append(files, line) 195 }) 196 if err != nil { 197 log.Fatal("error getting files:", err) 198 } 199 return files 200 } 201 202 func getInfo(files <-chan string, out chan<- *info, wg *sync.WaitGroup) { 203 for file := range files { 204 stat, err := os.Lstat(file) 205 if err != nil { 206 fmt.Printf("ERROR %s: %v\n", file, err) 207 continue 208 } 209 if !stat.Mode().IsRegular() { 210 continue 211 } 212 if isGenerated(file) { 213 continue 214 } 215 info, err := fileInfo(file) 216 if err != nil { 217 fmt.Printf("ERROR %s: %v\n", file, err) 218 continue 219 } 220 out <- info 221 } 222 wg.Done() 223 } 224 225 func isGenerated(file string) bool { 226 fd, err := os.Open(file) 227 if err != nil { 228 return false 229 } 230 defer fd.Close() 231 buf := make([]byte, 2048) 232 n, _ := fd.Read(buf) 233 buf = buf[:n] 234 for _, l := range bytes.Split(buf, []byte("\n")) { 235 if bytes.HasPrefix(l, []byte("// Code generated")) { 236 return true 237 } 238 } 239 return false 240 } 241 242 // fileInfo finds the lowest year in which the given file was committed. 243 func fileInfo(file string) (*info, error) { 244 info := &info{file: file, Year: int64(time.Now().Year())} 245 cmd := exec.Command("git", "log", "--follow", "--find-renames=80", "--find-copies=80", "--pretty=format:%ai", "--", file) 246 err := doLines(cmd, func(line string) { 247 y, err := strconv.ParseInt(line[:4], 10, 64) 248 if err != nil { 249 fmt.Printf("cannot parse year: %q", line[:4]) 250 } 251 if y < info.Year { 252 info.Year = y 253 } 254 }) 255 return info, err 256 } 257 258 func writeLicenses(infos <-chan *info) { 259 for i := range infos { 260 writeLicense(i) 261 } 262 } 263 264 func writeLicense(info *info) { 265 fi, err := os.Stat(info.file) 266 if os.IsNotExist(err) { 267 fmt.Println("skipping (does not exist)", info.file) 268 return 269 } 270 if err != nil { 271 log.Fatalf("error stat'ing %s: %v\n", info.file, err) 272 } 273 content, err := ioutil.ReadFile(info.file) 274 if err != nil { 275 log.Fatalf("error reading %s: %v\n", info.file, err) 276 } 277 // Construct new file content. 278 buf := new(bytes.Buffer) 279 licenseT.Execute(buf, info) 280 if m := licenseCommentRE.FindIndex(content); m != nil && m[0] == 0 { 281 buf.Write(content[:m[0]]) 282 buf.Write(content[m[1]:]) 283 } else { 284 buf.Write(content) 285 } 286 // Write it to the file. 287 if bytes.Equal(content, buf.Bytes()) { 288 fmt.Println("skipping (no changes)", info.file) 289 return 290 } 291 fmt.Println("writing", info.ShortLicense(), info.file) 292 if err := ioutil.WriteFile(info.file, buf.Bytes(), fi.Mode()); err != nil { 293 log.Fatalf("error writing %s: %v", info.file, err) 294 } 295 } 296 297 func doLines(cmd *exec.Cmd, f func(string)) error { 298 stdout, err := cmd.StdoutPipe() 299 if err != nil { 300 return err 301 } 302 if err := cmd.Start(); err != nil { 303 return err 304 } 305 s := bufio.NewScanner(stdout) 306 for s.Scan() { 307 f(s.Text()) 308 } 309 if s.Err() != nil { 310 return s.Err() 311 } 312 if err := cmd.Wait(); err != nil { 313 return fmt.Errorf("%v (for %s)", err, strings.Join(cmd.Args, " ")) 314 } 315 return nil 316 }