github.com/codingfuture/orig-energi3@v0.8.4/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  	energiAuthorsFileHeader = "# This is the official list of Energi Core authors for copyright purposes.\n\n"
    91  	energiAuthorsRE         = regexp.MustCompile(`@(energi.team|futoin)`)
    92  
    93  	// The commit after which the Energi source starts
    94  	//energiForkPoint = "4bcc0a37ab70cb79b16893556cffdaad6974e7d8"
    95  
    96  	// Date of Energi fork refactoring start
    97  	energiForkDate = "2019-06-01"
    98  
    99  	// The year when work on the initial project "ETHFork" has started
   100  	energiStartYear int64 = 2018
   101  )
   102  
   103  // this template generates the license comment.
   104  // its input is an info structure.
   105  var licenseT = template.Must(template.New("").Parse(`
   106  // Copyright {{.Year}} The go-ethereum Authors
   107  // This file is part of {{.Whole false}}.
   108  //
   109  // {{.Whole true}} is free software: you can redistribute it and/or modify
   110  // it under the terms of the GNU {{.License}} as published by
   111  // the Free Software Foundation, either version 3 of the License, or
   112  // (at your option) any later version.
   113  //
   114  // {{.Whole true}} is distributed in the hope that it will be useful,
   115  // but WITHOUT ANY WARRANTY; without even the implied warranty of
   116  // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
   117  // GNU {{.License}} for more details.
   118  //
   119  // You should have received a copy of the GNU {{.License}}
   120  // along with {{.Whole false}}. If not, see <http://www.gnu.org/licenses/>.
   121  
   122  `[1:]))
   123  
   124  var energiLicenseT = template.Must(template.New("").Parse(`
   125  // Copyright {{.YearEnergi}} The Energi Core Authors
   126  // Copyright {{.Year}} The go-ethereum Authors
   127  // This file is part of {{.WholeEnergi false}}.
   128  //
   129  // {{.WholeEnergi true}} is free software: you can redistribute it and/or modify
   130  // it under the terms of the GNU {{.License}} as published by
   131  // the Free Software Foundation, either version 3 of the License, or
   132  // (at your option) any later version.
   133  //
   134  // {{.WholeEnergi true}} is distributed in the hope that it will be useful,
   135  // but WITHOUT ANY WARRANTY; without even the implied warranty of
   136  // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
   137  // GNU {{.License}} for more details.
   138  //
   139  // You should have received a copy of the GNU {{.License}}
   140  // along with {{.WholeEnergi false}}. If not, see <http://www.gnu.org/licenses/>.
   141  
   142  `[1:]))
   143  
   144  var onlyEnergiLicenseT = template.Must(template.New("").Parse(`
   145  // Copyright {{.YearEnergi}} The Energi Core Authors
   146  // This file is part of {{.WholeEnergi false}}.
   147  //
   148  // {{.WholeEnergi true}} is free software: you can redistribute it and/or modify
   149  // it under the terms of the GNU {{.License}} as published by
   150  // the Free Software Foundation, either version 3 of the License, or
   151  // (at your option) any later version.
   152  //
   153  // {{.WholeEnergi true}} is distributed in the hope that it will be useful,
   154  // but WITHOUT ANY WARRANTY; without even the implied warranty of
   155  // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
   156  // GNU {{.License}} for more details.
   157  //
   158  // You should have received a copy of the GNU {{.License}}
   159  // along with {{.WholeEnergi false}}. If not, see <http://www.gnu.org/licenses/>.
   160  
   161  `[1:]))
   162  
   163  type info struct {
   164  	file       string
   165  	Year       int64
   166  	YearEnergi int64
   167  	IsEnergi   bool
   168  	OnlyEnergi bool
   169  }
   170  
   171  func (i info) License() string {
   172  	if i.gpl() {
   173  		return "General Public License"
   174  	}
   175  	return "Lesser General Public License"
   176  }
   177  
   178  func (i info) ShortLicense() string {
   179  	if i.gpl() {
   180  		return "GPL"
   181  	}
   182  	return "LGPL"
   183  }
   184  
   185  func (i info) Whole(startOfSentence bool) string {
   186  	if i.gpl() {
   187  		return "go-ethereum"
   188  	}
   189  	if startOfSentence {
   190  		return "The go-ethereum library"
   191  	}
   192  	return "the go-ethereum library"
   193  }
   194  
   195  func (i info) WholeEnergi(startOfSentence bool) string {
   196  	if i.gpl() {
   197  		return "Energi Core"
   198  	}
   199  	if startOfSentence {
   200  		return "The Energi Core library"
   201  	}
   202  	return "the Energi Core library"
   203  }
   204  
   205  func (i info) gpl() bool {
   206  	for _, p := range gplPrefixes {
   207  		if strings.HasPrefix(i.file, p) {
   208  			return true
   209  		}
   210  	}
   211  	return false
   212  }
   213  
   214  func main() {
   215  	var (
   216  		files = getFiles()
   217  		filec = make(chan string)
   218  		infoc = make(chan *info, 20)
   219  		wg    sync.WaitGroup
   220  	)
   221  
   222  	writeAuthors(files)
   223  
   224  	go func() {
   225  		for _, f := range files {
   226  			filec <- f
   227  		}
   228  		close(filec)
   229  	}()
   230  	for i := runtime.NumCPU(); i >= 0; i-- {
   231  		// getting file info is slow and needs to be parallel.
   232  		// it traverses git history for each file.
   233  		wg.Add(1)
   234  		go getInfo(filec, infoc, &wg)
   235  	}
   236  	go func() {
   237  		wg.Wait()
   238  		close(infoc)
   239  	}()
   240  	writeLicenses(infoc)
   241  }
   242  
   243  func skipFile(path string) bool {
   244  	if strings.Contains(path, "/testdata/") {
   245  		return true
   246  	}
   247  	for _, p := range skipPrefixes {
   248  		if strings.HasPrefix(path, p) {
   249  			return true
   250  		}
   251  	}
   252  	return false
   253  }
   254  
   255  func getFiles() []string {
   256  	cmd := exec.Command("git", "ls-tree", "-r", "--name-only", "HEAD")
   257  	var files []string
   258  	err := doLines(cmd, func(line string) {
   259  		if skipFile(line) {
   260  			return
   261  		}
   262  		ext := filepath.Ext(line)
   263  		for _, wantExt := range extensions {
   264  			if ext == wantExt {
   265  				goto keep
   266  			}
   267  		}
   268  		return
   269  	keep:
   270  		files = append(files, line)
   271  	})
   272  	if err != nil {
   273  		log.Fatal("error getting files:", err)
   274  	}
   275  	return files
   276  }
   277  
   278  var authorRegexp = regexp.MustCompile(`\s*[0-9]+\s*(.*)`)
   279  
   280  func gitAuthors(files []string) []string {
   281  	cmds := []string{"shortlog", "-s", "-n", "-e", "HEAD", "--"}
   282  	cmds = append(cmds, files...)
   283  	cmd := exec.Command("git", cmds...)
   284  	var authors []string
   285  	err := doLines(cmd, func(line string) {
   286  		m := authorRegexp.FindStringSubmatch(line)
   287  		if len(m) > 1 {
   288  			authors = append(authors, m[1])
   289  		}
   290  	})
   291  	if err != nil {
   292  		log.Fatalln("error getting authors:", err)
   293  	}
   294  	return authors
   295  }
   296  
   297  func readAuthors() []string {
   298  	content, err := ioutil.ReadFile("AUTHORS")
   299  	if err != nil && !os.IsNotExist(err) {
   300  		log.Fatalln("error reading AUTHORS:", err)
   301  	}
   302  	var authors []string
   303  	for _, a := range bytes.Split(content, []byte("\n")) {
   304  		if len(a) > 0 && a[0] != '#' {
   305  			authors = append(authors, string(a))
   306  		}
   307  	}
   308  	// Retranslate existing authors through .mailmap.
   309  	// This should catch email address changes.
   310  	authors = mailmapLookup(authors)
   311  	return authors
   312  }
   313  
   314  func mailmapLookup(authors []string) []string {
   315  	if len(authors) == 0 {
   316  		return nil
   317  	}
   318  	cmds := []string{"check-mailmap", "--"}
   319  	cmds = append(cmds, authors...)
   320  	cmd := exec.Command("git", cmds...)
   321  	var translated []string
   322  	err := doLines(cmd, func(line string) {
   323  		translated = append(translated, line)
   324  	})
   325  	if err != nil {
   326  		log.Fatalln("error translating authors:", err)
   327  	}
   328  	return translated
   329  }
   330  
   331  func writeAuthors(files []string) {
   332  	merge := make(map[string]bool)
   333  	// Add authors that Git reports as contributorxs.
   334  	// This is the primary source of author information.
   335  	for _, a := range gitAuthors(files) {
   336  		merge[a] = true
   337  	}
   338  	// Add existing authors from the file. This should ensure that we
   339  	// never lose authors, even if Git stops listing them. We can also
   340  	// add authors manually this way.
   341  	for _, a := range readAuthors() {
   342  		merge[a] = true
   343  	}
   344  	// Write sorted list of authors back to the file.
   345  	var energi_result []string
   346  	var result []string
   347  	for a := range merge {
   348  		if m := energiAuthorsRE.FindStringIndex(a); m != nil {
   349  			energi_result = append(energi_result, a)
   350  		} else {
   351  			result = append(result, a)
   352  		}
   353  	}
   354  	sort.Strings(energi_result)
   355  	sort.Strings(result)
   356  	content := new(bytes.Buffer)
   357  	content.WriteString(energiAuthorsFileHeader)
   358  	for _, a := range energi_result {
   359  		content.WriteString(a)
   360  		content.WriteString("\n")
   361  	}
   362  	content.WriteString("\n")
   363  	content.WriteString(authorsFileHeader)
   364  	for _, a := range result {
   365  		content.WriteString(a)
   366  		content.WriteString("\n")
   367  	}
   368  	fmt.Println("writing AUTHORS")
   369  	if err := ioutil.WriteFile("AUTHORS", content.Bytes(), 0644); err != nil {
   370  		log.Fatalln(err)
   371  	}
   372  }
   373  
   374  func getInfo(files <-chan string, out chan<- *info, wg *sync.WaitGroup) {
   375  	for file := range files {
   376  		stat, err := os.Lstat(file)
   377  		if err != nil {
   378  			fmt.Printf("ERROR %s: %v\n", file, err)
   379  			continue
   380  		}
   381  		if !stat.Mode().IsRegular() {
   382  			continue
   383  		}
   384  		if isGenerated(file) {
   385  			continue
   386  		}
   387  		info, err := fileInfo(file)
   388  		if err != nil {
   389  			fmt.Printf("ERROR %s: %v\n", file, err)
   390  			continue
   391  		}
   392  		out <- info
   393  	}
   394  	wg.Done()
   395  }
   396  
   397  func isGenerated(file string) bool {
   398  	fd, err := os.Open(file)
   399  	if err != nil {
   400  		return false
   401  	}
   402  	defer fd.Close()
   403  	buf := make([]byte, 2048)
   404  	n, _ := fd.Read(buf)
   405  	buf = buf[:n]
   406  	for _, l := range bytes.Split(buf, []byte("\n")) {
   407  		if bytes.HasPrefix(l, []byte("// Code generated")) {
   408  			return true
   409  		}
   410  	}
   411  	return false
   412  }
   413  
   414  // fileInfo finds the lowest year in which the given file was committed.
   415  func fileInfo(file string) (*info, error) {
   416  	info := &info{file: file, Year: int64(time.Now().Year()), IsEnergi: false}
   417  	cmd := exec.Command("git", "log", "--follow", "--find-renames=80", "--find-copies=80", "--pretty=format:%ai", "--", file)
   418  	err := doLines(cmd, func(line string) {
   419  		// Character comparison should be OK for ISO format
   420  		if line[:len(energiForkDate)] >= energiForkDate {
   421  			info.IsEnergi = true
   422  			info.OnlyEnergi = true
   423  		}
   424  		if line[:len(energiForkDate)] < energiForkDate {
   425  			info.OnlyEnergi = false
   426  		}
   427  		y, err := strconv.ParseInt(line[:4], 10, 64)
   428  		if err != nil {
   429  			fmt.Printf("cannot parse year: %q", line[:4])
   430  		}
   431  		if y < info.Year {
   432  			info.Year = y
   433  		}
   434  	})
   435  	if info.Year >= energiStartYear {
   436  		info.YearEnergi = info.Year
   437  		// TODO: revise on cherry-picks
   438  		info.Year = energiStartYear
   439  	} else {
   440  		info.YearEnergi = energiStartYear
   441  	}
   442  	return info, err
   443  }
   444  
   445  func writeLicenses(infos <-chan *info) {
   446  	for i := range infos {
   447  		writeLicense(i)
   448  	}
   449  }
   450  
   451  func writeLicense(info *info) {
   452  	fi, err := os.Stat(info.file)
   453  	if os.IsNotExist(err) {
   454  		fmt.Println("skipping (does not exist)", info.file)
   455  		return
   456  	}
   457  	if err != nil {
   458  		log.Fatalf("error stat'ing %s: %v\n", info.file, err)
   459  	}
   460  	content, err := ioutil.ReadFile(info.file)
   461  	if err != nil {
   462  		log.Fatalf("error reading %s: %v\n", info.file, err)
   463  	}
   464  	// Construct new file content.
   465  	buf := new(bytes.Buffer)
   466  	if info.IsEnergi {
   467  		if info.OnlyEnergi {
   468  			onlyEnergiLicenseT.Execute(buf, info)
   469  		} else {
   470  			energiLicenseT.Execute(buf, info)
   471  		}
   472  	} else {
   473  		licenseT.Execute(buf, info)
   474  	}
   475  	if m := licenseCommentRE.FindIndex(content); m != nil && m[0] == 0 {
   476  		buf.Write(content[:m[0]])
   477  		buf.Write(content[m[1]:])
   478  	} else {
   479  		buf.Write(content)
   480  	}
   481  	// Write it to the file.
   482  	if bytes.Equal(content, buf.Bytes()) {
   483  		fmt.Println("skipping (no changes)", info.file)
   484  		return
   485  	}
   486  	fmt.Println("writing", info.ShortLicense(), info.file)
   487  	if err := ioutil.WriteFile(info.file, buf.Bytes(), fi.Mode()); err != nil {
   488  		log.Fatalf("error writing %s: %v", info.file, err)
   489  	}
   490  }
   491  
   492  func doLines(cmd *exec.Cmd, f func(string)) error {
   493  	stdout, err := cmd.StdoutPipe()
   494  	if err != nil {
   495  		return err
   496  	}
   497  	if err := cmd.Start(); err != nil {
   498  		return err
   499  	}
   500  	s := bufio.NewScanner(stdout)
   501  	for s.Scan() {
   502  		f(s.Text())
   503  	}
   504  	if s.Err() != nil {
   505  		return s.Err()
   506  	}
   507  	if err := cmd.Wait(); err != nil {
   508  		return fmt.Errorf("%v (for %s)", err, strings.Join(cmd.Args, " "))
   509  	}
   510  	return nil
   511  }