blob: 9c7b7ee30425ad8811071c11047769a3c5460cdd [file] [log] [blame]
// Copyright 2024 The BoringSSL Authors
//
// Permission to use, copy, modify, and/or distribute this software for any
// purpose with or without fee is hereby granted, provided that the above
// copyright notice and this permission notice appear in all copies.
//
// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
// WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
// MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY
// SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
// WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION
// OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN
// CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
//go:build ignore
package main
import (
"bufio"
"cmp"
_ "embed"
"encoding/json"
"flag"
"fmt"
"iter"
"maps"
"os"
"regexp"
"slices"
"strconv"
)
var (
outPath = flag.String("out", "", "The path to write the results in JSON format")
comparePath = flag.String("compare", "", "The path to a JSON file to compare against")
)
func sortedKeyValuePairs[K cmp.Ordered, V any](m map[K]V) iter.Seq2[K, V] {
return func(yield func(K, V) bool) {
for _, k := range slices.Sorted(maps.Keys(m)) {
if !yield(k, m[k]) {
return
}
}
}
}
var copyrightRE = regexp.MustCompile(
`Copyright ` +
// Ignore (c) and (C)
`(?:\([cC]\) )?` +
// Capture the starting copyright year.
`([0-9]+)` +
// Ignore ending copyright year. OpenSSL's "copyright consolidation"
// tool rewrites it anyway. We're just interested in looking for which
// start years changed, to manually double-check.
`(?:[-,][0-9]+)?` +
// Some files have a comma after the years.
`,?` +
// Skip spaces.
` *` +
// Capture the name. Stop at punctuation and don't pick up trailing
// spaces. We don't want to pick up things like "All Rights Reserved".
// This does drop things like ", Inc", but this is good enough for a
// summary to double-check an otherwise mostly automated process.
`([-a-zA-Z ]*[-a-zA-Z])`)
type CopyrightInfo struct {
Name string
StartYear int
}
type FileInfo struct {
CopyrightInfos []CopyrightInfo
}
func (f *FileInfo) MergeFrom(other FileInfo) {
f.CopyrightInfos = append(f.CopyrightInfos, other.CopyrightInfos...)
}
func summarize(info FileInfo) map[string]int {
ret := map[string]int{}
for _, c := range info.CopyrightInfos {
name := c.Name
// Apply the same mapping as OpenSSL's "copyright consolidation" script.
if name == "The OpenSSL Project" || name == "Eric Young" {
name = "The OpenSSL Project Authors"
}
if old, ok := ret[name]; !ok || old > c.StartYear {
ret[name] = c.StartYear
}
}
return ret
}
func process(path string) (info FileInfo, err error) {
f, err := os.Open(path)
if err != nil {
return
}
defer f.Close()
scanner := bufio.NewScanner(f)
for scanner.Scan() {
m := copyrightRE.FindStringSubmatch(scanner.Text())
if m == nil {
continue
}
var year int
year, err = strconv.Atoi(m[1])
if err != nil {
err = fmt.Errorf("error parsing year %q: %s", m[1], err)
return
}
info.CopyrightInfos = append(info.CopyrightInfos, CopyrightInfo{Name: m[2], StartYear: year})
}
err = scanner.Err()
return
}
func main() {
flag.Parse()
infos := map[string]FileInfo{}
for _, path := range flag.Args() {
info, err := process(path)
if err != nil {
fmt.Fprintf(os.Stderr, "Error processing %q: %s\n", path, err)
os.Exit(1)
}
infos[path] = info
}
if len(*outPath) != 0 {
data, err := json.Marshal(infos)
if err != nil {
fmt.Fprintf(os.Stderr, "Error serializing results: %s\n", err)
os.Exit(1)
}
if err := os.WriteFile(*outPath, data, 0666); err != nil {
fmt.Fprintf(os.Stderr, "Error writing results: %s\n", err)
os.Exit(1)
}
}
if len(*comparePath) == 0 {
// Print what we have and return.
for path, info := range sortedKeyValuePairs(infos) {
for _, c := range info.CopyrightInfos {
fmt.Printf("%s: %d %s\n", path, c.StartYear, c.Name)
}
}
return
}
oldData, err := os.ReadFile(*comparePath)
if err != nil {
fmt.Fprintf(os.Stderr, "Error reading file: %s\n", err)
os.Exit(1)
}
var oldInfos map[string]FileInfo
if err := json.Unmarshal(oldData, &oldInfos); err != nil {
fmt.Fprintf(os.Stderr, "Error decoding %q: %s\n", *comparePath, err)
os.Exit(1)
}
// Output in CSV, so it is easy to paste into a spreadsheet.
fmt.Printf("Path,Name,Old Start Year,New Start Year\n")
for path, info := range sortedKeyValuePairs(infos) {
oldInfo, ok := oldInfos[path]
if !ok {
fmt.Printf("%s: file not previously present\n", path)
continue
}
summary := summarize(info)
oldSummary := summarize(oldInfo)
for name, year := range sortedKeyValuePairs(summary) {
oldYear, ok := oldSummary[name]
if !ok {
fmt.Printf("%s,%s,-1,%d\n", path, name, year)
} else if year != oldYear {
fmt.Printf("%s,%s,%d,%d\n", path, name, oldYear, year)
}
}
for oldName, oldYear := range sortedKeyValuePairs(oldSummary) {
if _, ok := summary[oldName]; !ok {
fmt.Printf("%s,%s,%d,-1\n", path, oldName, oldYear)
}
}
}
}