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  }