// 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) } } } }