github.com/linapex/ethereum-go-chinese@v0.0.0-20190316121929-f8b7a73c3fa1/build/update-license.go (about) 1 2 //<developer> 3 // <name>linapex 曹一峰</name> 4 // <email>linapex@163.com</email> 5 // <wx>superexc</wx> 6 // <qqgroup>128148617</qqgroup> 7 // <url>https://jsq.ink</url> 8 // <role>pku engineer</role> 9 // <date>2019-03-16 19:16:32</date> 10 //</624450066853138432> 11 12 13 //+不建 14 15 /* 16 此命令在所有源文件的基础上生成GPL许可证头。 17 您可以每月运行一次,在剪切发布之前或只是 18 无论什么时候你喜欢。 19 20 运行update-license.go 21 22 所有作者(贡献代码的人)都列在 23 作者档案。使用 24 邮件映射文件。可以使用.mailmap设置规范名称和 25 每个作者的地址。有关 26 .mailmap格式。 27 28 请查看结果差异,以检查是否正确 29 执行版权分配。 30 **/ 31 32 33 package main 34 35 import ( 36 "bufio" 37 "bytes" 38 "fmt" 39 "io/ioutil" 40 "log" 41 "os" 42 "os/exec" 43 "path/filepath" 44 "regexp" 45 "runtime" 46 "sort" 47 "strconv" 48 "strings" 49 "sync" 50 "text/template" 51 "time" 52 ) 53 54 var ( 55 //只考虑具有这些扩展名的文件 56 extensions = []string{".go", ".js", ".qml"} 57 58 //将跳过具有任何这些前缀的路径 59 skipPrefixes = []string{ 60 //镗孔材料 61 "vendor/", "tests/testdata/", "build/", 62 //不要保留自动贩卖的来源 63 "cmd/internal/browser", 64 "consensus/ethash/xor.go", 65 "crypto/bn256/", 66 "crypto/ecies/", 67 "crypto/secp256k1/curve.go", 68 "crypto/sha3/", 69 "internal/jsre/deps", 70 "log/", 71 "common/bitutil/bitutil", 72 //不许可生成的文件 73 "contracts/chequebook/contract/code.go", 74 } 75 76 //具有此前缀的路径被授权为gpl。所有其他文件都是lgpl。 77 gplPrefixes = []string{"cmd/"} 78 79 //此regexp必须与 80 //每个文件的开头。 81 licenseCommentRE = regexp.MustCompile(`^//\s*(版权此文件是的一部分)。*?n?//*?\n)*\n*` 82 83 // 84 authorsFileHeader = "# This is the official list of go-ethereum authors for copyright purposes.\n\n" 85 ) 86 87 //此模板生成许可证注释。 88 //它的输入是一个信息结构。 89 var licenseT = template.Must(template.New("").Parse(` 90 //版权.year Go Ethereum作者 91 //此文件是.whole false的一部分。 92 // 93 //.整个真是免费软件:您可以重新发布和/或修改它 94 //根据GNU许可证的条款 95 //自由软件基金会,或者许可证的第3版,或者 96 //(由您选择)任何更高版本。 97 // 98 //。整个真分布的目的是希望它有用, 99 //但没有任何保证;甚至没有 100 //适销性或特定用途的适用性。见 101 //GNU许可证了解更多详细信息。 102 // 103 //您应该已经收到一份GNU许可证的副本。 104 //连同。整个错误。如果没有,请参见<http://www.gnu.org/licenses/>。 105 106 `[1:])) 107 108 type info struct { 109 file string 110 Year int64 111 } 112 113 func (i info) License() string { 114 if i.gpl() { 115 return "General Public License" 116 } 117 return "Lesser General Public License" 118 } 119 120 func (i info) ShortLicense() string { 121 if i.gpl() { 122 return "GPL" 123 } 124 return "LGPL" 125 } 126 127 func (i info) Whole(startOfSentence bool) string { 128 if i.gpl() { 129 return "go-ethereum" 130 } 131 if startOfSentence { 132 return "The go-ethereum library" 133 } 134 return "the go-ethereum library" 135 } 136 137 func (i info) gpl() bool { 138 for _, p := range gplPrefixes { 139 if strings.HasPrefix(i.file, p) { 140 return true 141 } 142 } 143 return false 144 } 145 146 func main() { 147 var ( 148 files = getFiles() 149 filec = make(chan string) 150 infoc = make(chan *info, 20) 151 wg sync.WaitGroup 152 ) 153 154 writeAuthors(files) 155 156 go func() { 157 for _, f := range files { 158 filec <- f 159 } 160 close(filec) 161 }() 162 for i := runtime.NumCPU(); i >= 0; i-- { 163 //获取文件信息很慢,需要并行处理。 164 //它遍历每个文件的git历史记录。 165 wg.Add(1) 166 go getInfo(filec, infoc, &wg) 167 } 168 go func() { 169 wg.Wait() 170 close(infoc) 171 }() 172 writeLicenses(infoc) 173 } 174 175 func skipFile(path string) bool { 176 if strings.Contains(path, "/testdata/") { 177 return true 178 } 179 for _, p := range skipPrefixes { 180 if strings.HasPrefix(path, p) { 181 return true 182 } 183 } 184 return false 185 } 186 187 func getFiles() []string { 188 cmd := exec.Command("git", "ls-tree", "-r", "--name-only", "HEAD") 189 var files []string 190 err := doLines(cmd, func(line string) { 191 if skipFile(line) { 192 return 193 } 194 ext := filepath.Ext(line) 195 for _, wantExt := range extensions { 196 if ext == wantExt { 197 goto keep 198 } 199 } 200 return 201 keep: 202 files = append(files, line) 203 }) 204 if err != nil { 205 log.Fatal("error getting files:", err) 206 } 207 return files 208 } 209 210 var authorRegexp = regexp.MustCompile(`\s*[0-9]+\s*(.*)`) 211 212 func gitAuthors(files []string) []string { 213 cmds := []string{"shortlog", "-s", "-n", "-e", "HEAD", "--"} 214 cmds = append(cmds, files...) 215 cmd := exec.Command("git", cmds...) 216 var authors []string 217 err := doLines(cmd, func(line string) { 218 m := authorRegexp.FindStringSubmatch(line) 219 if len(m) > 1 { 220 authors = append(authors, m[1]) 221 } 222 }) 223 if err != nil { 224 log.Fatalln("error getting authors:", err) 225 } 226 return authors 227 } 228 229 func readAuthors() []string { 230 content, err := ioutil.ReadFile("AUTHORS") 231 if err != nil && !os.IsNotExist(err) { 232 log.Fatalln("error reading AUTHORS:", err) 233 } 234 var authors []string 235 for _, a := range bytes.Split(content, []byte("\n")) { 236 if len(a) > 0 && a[0] != '#' { 237 authors = append(authors, string(a)) 238 } 239 } 240 //通过.mailmap重新传输现有作者。 241 //这将捕获电子邮件地址更改。 242 authors = mailmapLookup(authors) 243 return authors 244 } 245 246 func mailmapLookup(authors []string) []string { 247 if len(authors) == 0 { 248 return nil 249 } 250 cmds := []string{"check-mailmap", "--"} 251 cmds = append(cmds, authors...) 252 cmd := exec.Command("git", cmds...) 253 var translated []string 254 err := doLines(cmd, func(line string) { 255 translated = append(translated, line) 256 }) 257 if err != nil { 258 log.Fatalln("error translating authors:", err) 259 } 260 return translated 261 } 262 263 func writeAuthors(files []string) { 264 merge := make(map[string]bool) 265 //添加Git作为贡献者X报告的作者。 266 //这是作者信息的主要来源。 267 for _, a := range gitAuthors(files) { 268 merge[a] = true 269 } 270 //从文件添加现有作者。这应该确保我们 271 //永远不要失去作者,即使Git不再列出他们。我们也可以 272 //以这种方式手动添加作者。 273 for _, a := range readAuthors() { 274 merge[a] = true 275 } 276 //将排序的作者列表写回文件。 277 var result []string 278 for a := range merge { 279 result = append(result, a) 280 } 281 sort.Strings(result) 282 content := new(bytes.Buffer) 283 content.WriteString(authorsFileHeader) 284 for _, a := range result { 285 content.WriteString(a) 286 content.WriteString("\n") 287 } 288 fmt.Println("writing AUTHORS") 289 if err := ioutil.WriteFile("AUTHORS", content.Bytes(), 0644); err != nil { 290 log.Fatalln(err) 291 } 292 } 293 294 func getInfo(files <-chan string, out chan<- *info, wg *sync.WaitGroup) { 295 for file := range files { 296 stat, err := os.Lstat(file) 297 if err != nil { 298 fmt.Printf("ERROR %s: %v\n", file, err) 299 continue 300 } 301 if !stat.Mode().IsRegular() { 302 continue 303 } 304 if isGenerated(file) { 305 continue 306 } 307 info, err := fileInfo(file) 308 if err != nil { 309 fmt.Printf("ERROR %s: %v\n", file, err) 310 continue 311 } 312 out <- info 313 } 314 wg.Done() 315 } 316 317 func isGenerated(file string) bool { 318 fd, err := os.Open(file) 319 if err != nil { 320 return false 321 } 322 defer fd.Close() 323 buf := make([]byte, 2048) 324 n, _ := fd.Read(buf) 325 buf = buf[:n] 326 for _, l := range bytes.Split(buf, []byte("\n")) { 327 if bytes.HasPrefix(l, []byte("//代码生成”)); 328 return true 329 } 330 } 331 return false 332 } 333 334 //fileinfo查找提交给定文件的最低年份。 335 func fileInfo(file string) (*info, error) { 336 info := &info{file: file, Year: int64(time.Now().Year())} 337 cmd := exec.Command("git", "log", "--follow", "--find-renames=80", "--find-copies=80", "--pretty=format:%ai", "--", file) 338 err := doLines(cmd, func(line string) { 339 y, err := strconv.ParseInt(line[:4], 10, 64) 340 if err != nil { 341 fmt.Printf("cannot parse year: %q", line[:4]) 342 } 343 if y < info.Year { 344 info.Year = y 345 } 346 }) 347 return info, err 348 } 349 350 func writeLicenses(infos <-chan *info) { 351 for i := range infos { 352 writeLicense(i) 353 } 354 } 355 356 func writeLicense(info *info) { 357 fi, err := os.Stat(info.file) 358 if os.IsNotExist(err) { 359 fmt.Println("skipping (does not exist)", info.file) 360 return 361 } 362 if err != nil { 363 log.Fatalf("error stat'ing %s: %v\n", info.file, err) 364 } 365 content, err := ioutil.ReadFile(info.file) 366 if err != nil { 367 log.Fatalf("error reading %s: %v\n", info.file, err) 368 } 369 //构造新的文件内容。 370 buf := new(bytes.Buffer) 371 licenseT.Execute(buf, info) 372 if m := licenseCommentRE.FindIndex(content); m != nil && m[0] == 0 { 373 buf.Write(content[:m[0]]) 374 buf.Write(content[m[1]:]) 375 } else { 376 buf.Write(content) 377 } 378 //将其写入文件。 379 if bytes.Equal(content, buf.Bytes()) { 380 fmt.Println("skipping (no changes)", info.file) 381 return 382 } 383 fmt.Println("writing", info.ShortLicense(), info.file) 384 if err := ioutil.WriteFile(info.file, buf.Bytes(), fi.Mode()); err != nil { 385 log.Fatalf("error writing %s: %v", info.file, err) 386 } 387 } 388 389 func doLines(cmd *exec.Cmd, f func(string)) error { 390 stdout, err := cmd.StdoutPipe() 391 if err != nil { 392 return err 393 } 394 if err := cmd.Start(); err != nil { 395 return err 396 } 397 s := bufio.NewScanner(stdout) 398 for s.Scan() { 399 f(s.Text()) 400 } 401 if s.Err() != nil { 402 return s.Err() 403 } 404 if err := cmd.Wait(); err != nil { 405 return fmt.Errorf("%v (for %s)", err, strings.Join(cmd.Args, " ")) 406 } 407 return nil 408 } 409