main.go
// SPDX-License-Identifier: Apache-2.0
// Copyright Authors of Cilium
package main
import (
"fmt"
"go/ast"
"go/format"
"go/parser"
"go/token"
"log"
"os"
"path/filepath"
"regexp"
"strings"
)
const (
usage = `spdxconv is a tool for converting Cilium's copyright and license header
to a SPDX compliant one
Usage:
spdxconv [package_dir] - provide spdxconv with a absolute path to the go package you'd like to convert.
`
)
var (
// RegexMap maps an identifying regexp expression
// with the SPDX string that replaces it.
RegexpMap = map[*regexp.Regexp]string{
regexp.MustCompile(`(?m)Apache License, Version 2.0`): "// SPDX-License-Identifier: Apache-2.0",
}
)
type SPDXConverter struct {
// the root directory to begin replacing
// license clauses to spdx clauses
rootDir string
}
// Walk will begin walking the tree rooted at
// rootDir.
//
// For each .go file it encounters it will parse
// that file's AST and discover the comments.
//
// If the comments indicate a non spdx license
// it will convert it to one and write the file back
// to disk.
func (s *SPDXConverter) Walk() error {
err := filepath.WalkDir(s.rootDir, func(path string, d os.DirEntry, e error) error {
if e != nil {
return e
}
if filepath.Ext(path) != ".go" {
return nil
}
fs := token.NewFileSet()
astFd, err := parser.ParseFile(fs, path, nil, parser.ParseComments)
if err != nil {
return fmt.Errorf("error while parsing %v: %w", path, err)
}
err = s.conv(path, fs, astFd)
if err != nil {
return err
}
return nil
})
return err
}
func (s *SPDXConverter) conv(path string, fset *token.FileSet, f *ast.File) error {
if len(f.Comments) == 0 {
return nil
}
cg := f.Comments[0]
if !strings.HasPrefix(cg.List[0].Text, "// Copyright") {
log.Printf("file %v did not start with a Copyright string. skipping...", path)
return nil
}
var spdx string
for exp, tmp := range RegexpMap {
if exp.MatchString(cg.Text()) {
spdx = tmp
}
}
if spdx == "" {
log.Printf("could not determine license for %v", path)
return nil
}
copyRight := cg.List[0].Text
cg.List[0].Text, cg.List[1].Text = spdx, copyRight
cg.List = cg.List[:2]
fd, err := os.OpenFile(path, os.O_TRUNC|os.O_RDWR, 0660)
if err != nil {
return fmt.Errorf("failed to open original source file for conversion: %w", err)
}
defer fd.Close()
err = format.Node(fd, fset, f)
if err != nil {
return fmt.Errorf("failed to write converted file %v: %w", path, err)
}
return nil
}
func main() {
switch {
case len(os.Args) != 2:
fmt.Print(usage)
os.Exit(1)
case os.Args[1] == "help":
fmt.Print(usage)
os.Exit(0)
}
path := os.Args[1]
dir, err := os.Stat(path)
if err != nil {
fmt.Printf("provided path %v could not be opened: %v", path, err)
os.Exit(1)
}
if !dir.IsDir() {
fmt.Println("provided path is not a directory")
os.Exit(1)
}
conv := &SPDXConverter{
rootDir: path,
}
if err := conv.Walk(); err != nil {
log.Fatal(err.Error())
}
}