github.com/dotlike13/wemix30_go@v1.8.23/build/update-license.go (about)

     1  // Copyright 2018 The go-ethereum Authors
     2  // This file is part of the go-ethereum library.
     3  //
     4  // The go-ethereum 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-ethereum 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-ethereum library. If not, see <http://www.gnu.org/licenses/>.
    16  
    17  // +build none
    18  
    19  /*
    20  This command generates GPL license headers on top of all source files.
    21  You can run it once per month, before cutting a release or just
    22  whenever you feel like it.
    23  
    24  	go run update-license.go
    25  
    26  All authors (people who have contributed code) are listed in the
    27  AUTHORS file. The author names are mapped and deduplicated using the
    28  .mailmap file. You can use .mailmap to set the canonical name and
    29  address for each author. See git-shortlog(1) for an explanation of the
    30  .mailmap format.
    31  
    32  Please review the resulting diff to check whether the correct
    33  copyright assignments are performed.
    34  */
    35  
    36  package main
    37  
    38  import (
    39  	"bufio"
    40  	"bytes"
    41  	"fmt"
    42  	"io/ioutil"
    43  	"log"
    44  	"os"
    45  	"os/exec"
    46  	"path/filepath"
    47  	"regexp"
    48  	"runtime"
    49  	"sort"
    50  	"strconv"
    51  	"strings"
    52  	"sync"
    53  	"text/template"
    54  	"time"
    55  )
    56  
    57  var (
    58  	// only files with these extensions will be considered
    59  	extensions = []string{".go", ".js", ".qml"}
    60  
    61  	// paths with any of these prefixes will be skipped
    62  	skipPrefixes = []string{
    63  		// boring stuff
    64  		"vendor/", "tests/testdata/", "build/",
    65  		// don't relicense vendored sources
    66  		"cmd/internal/browser",
    67  		"consensus/ethash/xor.go",
    68  		"crypto/bn256/",
    69  		"crypto/ecies/",
    70  		"crypto/secp256k1/curve.go",
    71  		"crypto/sha3/",
    72  		"internal/jsre/deps",
    73  		"log/",
    74  		"common/bitutil/bitutil",
    75  		// don't license generated files
    76  		"contracts/chequebook/contract/code.go",
    77  	}
    78  
    79  	// paths with this prefix are licensed as GPL. all other files are LGPL.
    80  	gplPrefixes = []string{"cmd/"}
    81  
    82  	// this regexp must match the entire license comment at the
    83  	// beginning of each file.
    84  	licenseCommentRE = regexp.MustCompile(`^//\s*(Copyright|This file is part of).*?\n(?://.*?\n)*\n*`)
    85  
    86  	// this text appears at the start of AUTHORS
    87  	authorsFileHeader = "# This is the official list of go-ethereum authors for copyright purposes.\n\n"
    88  )
    89  
    90  // this template generates the license comment.
    91  // its input is an info structure.
    92  var licenseT = template.Must(template.New("").Parse(`
    93  // Copyright {{.Year}} The go-ethereum Authors
    94  // This file is part of {{.Whole false}}.
    95  //
    96  // {{.Whole true}} is free software: you can redistribute it and/or modify
    97  // it under the terms of the GNU {{.License}} as published by
    98  // the Free Software Foundation, either version 3 of the License, or
    99  // (at your option) any later version.
   100  //
   101  // {{.Whole true}} is distributed in the hope that it will be useful,
   102  // but WITHOUT ANY WARRANTY; without even the implied warranty of
   103  // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
   104  // GNU {{.License}} for more details.
   105  //
   106  // You should have received a copy of the GNU {{.License}}
   107  // along with {{.Whole false}}. If not, see <http://www.gnu.org/licenses/>.
   108  
   109  `[1:]))
   110  
   111  type info struct {
   112  	file string
   113  	Year int64
   114  }
   115  
   116  func (i info) License() string {
   117  	if i.gpl() {
   118  		return "General Public License"
   119  	}
   120  	return "Lesser General Public License"
   121  }
   122  
   123  func (i info) ShortLicense() string {
   124  	if i.gpl() {
   125  		return "GPL"
   126  	}
   127  	return "LGPL"
   128  }
   129  
   130  func (i info) Whole(startOfSentence bool) string {
   131  	if i.gpl() {
   132  		return "go-ethereum"
   133  	}
   134  	if startOfSentence {
   135  		return "The go-ethereum library"
   136  	}
   137  	return "the go-ethereum library"
   138  }
   139  
   140  func (i info) gpl() bool {
   141  	for _, p := range gplPrefixes {
   142  		if strings.HasPrefix(i.file, p) {
   143  			return true
   144  		}
   145  	}
   146  	return false
   147  }
   148  
   149  func main() {
   150  	var (
   151  		files = getFiles()
   152  		filec = make(chan string)
   153  		infoc = make(chan *info, 20)
   154  		wg    sync.WaitGroup
   155  	)
   156  
   157  	writeAuthors(files)
   158  
   159  	go func() {
   160  		for _, f := range files {
   161  			filec <- f
   162  		}
   163  		close(filec)
   164  	}()
   165  	for i := runtime.NumCPU(); i >= 0; i-- {
   166  		// getting file info is slow and needs to be parallel.
   167  		// it traverses git history for each file.
   168  		wg.Add(1)
   169  		go getInfo(filec, infoc, &wg)
   170  	}
   171  	go func() {
   172  		wg.Wait()
   173  		close(infoc)
   174  	}()
   175  	writeLicenses(infoc)
   176  }
   177  
   178  func skipFile(path string) bool {
   179  	if strings.Contains(path, "/testdata/") {
   180  		return true
   181  	}
   182  	for _, p := range skipPrefixes {
   183  		if strings.HasPrefix(path, p) {
   184  			return true
   185  		}
   186  	}
   187  	return false
   188  }
   189  
   190  func getFiles() []string {
   191  	cmd := exec.Command("git", "ls-tree", "-r", "--name-only", "HEAD")
   192  	var files []string
   193  	err := doLines(cmd, func(line string) {
   194  		if skipFile(line) {
   195  			return
   196  		}
   197  		ext := filepath.Ext(line)
   198  		for _, wantExt := range extensions {
   199  			if ext == wantExt {
   200  				goto keep
   201  			}
   202  		}
   203  		return
   204  	keep:
   205  		files = append(files, line)
   206  	})
   207  	if err != nil {
   208  		log.Fatal("error getting files:", err)
   209  	}
   210  	return files
   211  }
   212  
   213  var authorRegexp = regexp.MustCompile(`\s*[0-9]+\s*(.*)`)
   214  
   215  func gitAuthors(files []string) []string {
   216  	cmds := []string{"shortlog", "-s", "-n", "-e", "HEAD", "--"}
   217  	cmds = append(cmds, files...)
   218  	cmd := exec.Command("git", cmds...)
   219  	var authors []string
   220  	err := doLines(cmd, func(line string) {
   221  		m := authorRegexp.FindStringSubmatch(line)
   222  		if len(m) > 1 {
   223  			authors = append(authors, m[1])
   224  		}
   225  	})
   226  	if err != nil {
   227  		log.Fatalln("error getting authors:", err)
   228  	}
   229  	return authors
   230  }
   231  
   232  func readAuthors() []string {
   233  	content, err := ioutil.ReadFile("AUTHORS")
   234  	if err != nil && !os.IsNotExist(err) {
   235  		log.Fatalln("error reading AUTHORS:", err)
   236  	}
   237  	var authors []string
   238  	for _, a := range bytes.Split(content, []byte("\n")) {
   239  		if len(a) > 0 && a[0] != '#' {
   240  			authors = append(authors, string(a))
   241  		}
   242  	}
   243  	// Retranslate existing authors through .mailmap.
   244  	// This should catch email address changes.
   245  	authors = mailmapLookup(authors)
   246  	return authors
   247  }
   248  
   249  func mailmapLookup(authors []string) []string {
   250  	if len(authors) == 0 {
   251  		return nil
   252  	}
   253  	cmds := []string{"check-mailmap", "--"}
   254  	cmds = append(cmds, authors...)
   255  	cmd := exec.Command("git", cmds...)
   256  	var translated []string
   257  	err := doLines(cmd, func(line string) {
   258  		translated = append(translated, line)
   259  	})
   260  	if err != nil {
   261  		log.Fatalln("error translating authors:", err)
   262  	}
   263  	return translated
   264  }
   265  
   266  func writeAuthors(files []string) {
   267  	merge := make(map[string]bool)
   268  	// Add authors that Git reports as contributorxs.
   269  	// This is the primary source of author information.
   270  	for _, a := range gitAuthors(files) {
   271  		merge[a] = true
   272  	}
   273  	// Add existing authors from the file. This should ensure that we
   274  	// never lose authors, even if Git stops listing them. We can also
   275  	// add authors manually this way.
   276  	for _, a := range readAuthors() {
   277  		merge[a] = true
   278  	}
   279  	// Write sorted list of authors back to the file.
   280  	var result []string
   281  	for a := range merge {
   282  		result = append(result, a)
   283  	}
   284  	sort.Strings(result)
   285  	content := new(bytes.Buffer)
   286  	content.WriteString(authorsFileHeader)
   287  	for _, a := range result {
   288  		content.WriteString(a)
   289  		content.WriteString("\n")
   290  	}
   291  	fmt.Println("writing AUTHORS")
   292  	if err := ioutil.WriteFile("AUTHORS", content.Bytes(), 0644); err != nil {
   293  		log.Fatalln(err)
   294  	}
   295  }
   296  
   297  func getInfo(files <-chan string, out chan<- *info, wg *sync.WaitGroup) {
   298  	for file := range files {
   299  		stat, err := os.Lstat(file)
   300  		if err != nil {
   301  			fmt.Printf("ERROR %s: %v\n", file, err)
   302  			continue
   303  		}
   304  		if !stat.Mode().IsRegular() {
   305  			continue
   306  		}
   307  		if isGenerated(file) {
   308  			continue
   309  		}
   310  		info, err := fileInfo(file)
   311  		if err != nil {
   312  			fmt.Printf("ERROR %s: %v\n", file, err)
   313  			continue
   314  		}
   315  		out <- info
   316  	}
   317  	wg.Done()
   318  }
   319  
   320  func isGenerated(file string) bool {
   321  	fd, err := os.Open(file)
   322  	if err != nil {
   323  		return false
   324  	}
   325  	defer fd.Close()
   326  	buf := make([]byte, 2048)
   327  	n, _ := fd.Read(buf)
   328  	buf = buf[:n]
   329  	for _, l := range bytes.Split(buf, []byte("\n")) {
   330  		if bytes.HasPrefix(l, []byte("// Code generated")) {
   331  			return true
   332  		}
   333  	}
   334  	return false
   335  }
   336  
   337  // fileInfo finds the lowest year in which the given file was committed.
   338  func fileInfo(file string) (*info, error) {
   339  	info := &info{file: file, Year: int64(time.Now().Year())}
   340  	cmd := exec.Command("git", "log", "--follow", "--find-renames=80", "--find-copies=80", "--pretty=format:%ai", "--", file)
   341  	err := doLines(cmd, func(line string) {
   342  		y, err := strconv.ParseInt(line[:4], 10, 64)
   343  		if err != nil {
   344  			fmt.Printf("cannot parse year: %q", line[:4])
   345  		}
   346  		if y < info.Year {
   347  			info.Year = y
   348  		}
   349  	})
   350  	return info, err
   351  }
   352  
   353  func writeLicenses(infos <-chan *info) {
   354  	for i := range infos {
   355  		writeLicense(i)
   356  	}
   357  }
   358  
   359  func writeLicense(info *info) {
   360  	fi, err := os.Stat(info.file)
   361  	if os.IsNotExist(err) {
   362  		fmt.Println("skipping (does not exist)", info.file)
   363  		return
   364  	}
   365  	if err != nil {
   366  		log.Fatalf("error stat'ing %s: %v\n", info.file, err)
   367  	}
   368  	content, err := ioutil.ReadFile(info.file)
   369  	if err != nil {
   370  		log.Fatalf("error reading %s: %v\n", info.file, err)
   371  	}
   372  	// Construct new file content.
   373  	buf := new(bytes.Buffer)
   374  	licenseT.Execute(buf, info)
   375  	if m := licenseCommentRE.FindIndex(content); m != nil && m[0] == 0 {
   376  		buf.Write(content[:m[0]])
   377  		buf.Write(content[m[1]:])
   378  	} else {
   379  		buf.Write(content)
   380  	}
   381  	// Write it to the file.
   382  	if bytes.Equal(content, buf.Bytes()) {
   383  		fmt.Println("skipping (no changes)", info.file)
   384  		return
   385  	}
   386  	fmt.Println("writing", info.ShortLicense(), info.file)
   387  	if err := ioutil.WriteFile(info.file, buf.Bytes(), fi.Mode()); err != nil {
   388  		log.Fatalf("error writing %s: %v", info.file, err)
   389  	}
   390  }
   391  
   392  func doLines(cmd *exec.Cmd, f func(string)) error {
   393  	stdout, err := cmd.StdoutPipe()
   394  	if err != nil {
   395  		return err
   396  	}
   397  	if err := cmd.Start(); err != nil {
   398  		return err
   399  	}
   400  	s := bufio.NewScanner(stdout)
   401  	for s.Scan() {
   402  		f(s.Text())
   403  	}
   404  	if s.Err() != nil {
   405  		return s.Err()
   406  	}
   407  	if err := cmd.Wait(); err != nil {
   408  		return fmt.Errorf("%v (for %s)", err, strings.Join(cmd.Args, " "))
   409  	}
   410  	return nil
   411  }