gopkg.in/bitherhq/go-bither.v1@v1.7.1/build/update-license.go (about)

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