github.com/cilium/cilium@v1.16.2/tools/spdxconv/main.go (about) 1 // SPDX-License-Identifier: Apache-2.0 2 // Copyright Authors of Cilium 3 4 package main 5 6 import ( 7 "fmt" 8 "go/ast" 9 "go/format" 10 "go/parser" 11 "go/token" 12 "log" 13 "os" 14 "path/filepath" 15 "regexp" 16 "strings" 17 ) 18 19 const ( 20 usage = `spdxconv is a tool for converting Cilium's copyright and license header 21 to a SPDX compliant one 22 23 Usage: 24 spdxconv [package_dir] - provide spdxconv with a absolute path to the go package you'd like to convert. 25 26 ` 27 ) 28 29 var ( 30 // RegexMap maps an identifying regexp expression 31 // with the SPDX string that replaces it. 32 RegexpMap = map[*regexp.Regexp]string{ 33 regexp.MustCompile(`(?m)Apache License, Version 2.0`): "// SPDX-License-Identifier: Apache-2.0", 34 } 35 ) 36 37 type SPDXConverter struct { 38 // the root directory to begin replacing 39 // license clauses to spdx clauses 40 rootDir string 41 } 42 43 // Walk will begin walking the tree rooted at 44 // rootDir. 45 // 46 // For each .go file it encounters it will parse 47 // that file's AST and discover the comments. 48 // 49 // If the comments indicate a non spdx license 50 // it will convert it to one and write the file back 51 // to disk. 52 func (s *SPDXConverter) Walk() error { 53 err := filepath.WalkDir(s.rootDir, func(path string, d os.DirEntry, e error) error { 54 if e != nil { 55 return e 56 } 57 if filepath.Ext(path) != ".go" { 58 return nil 59 } 60 fs := token.NewFileSet() 61 astFd, err := parser.ParseFile(fs, path, nil, parser.ParseComments) 62 if err != nil { 63 return fmt.Errorf("error while parsing %v: %w", path, err) 64 } 65 66 err = s.conv(path, fs, astFd) 67 if err != nil { 68 return err 69 } 70 71 return nil 72 }) 73 return err 74 } 75 76 func (s *SPDXConverter) conv(path string, fset *token.FileSet, f *ast.File) error { 77 if len(f.Comments) == 0 { 78 return nil 79 } 80 cg := f.Comments[0] 81 if !strings.HasPrefix(cg.List[0].Text, "// Copyright") { 82 log.Printf("file %v did not start with a Copyright string. skipping...", path) 83 return nil 84 } 85 var spdx string 86 for exp, tmp := range RegexpMap { 87 if exp.MatchString(cg.Text()) { 88 spdx = tmp 89 } 90 } 91 if spdx == "" { 92 log.Printf("could not determine license for %v", path) 93 return nil 94 } 95 96 copyRight := cg.List[0].Text 97 cg.List[0].Text, cg.List[1].Text = spdx, copyRight 98 cg.List = cg.List[:2] 99 100 fd, err := os.OpenFile(path, os.O_TRUNC|os.O_RDWR, 0660) 101 if err != nil { 102 return fmt.Errorf("failed to open original source file for conversion: %w", err) 103 } 104 defer fd.Close() 105 106 err = format.Node(fd, fset, f) 107 if err != nil { 108 return fmt.Errorf("failed to write converted file %v: %w", path, err) 109 } 110 111 return nil 112 } 113 114 func main() { 115 switch { 116 case len(os.Args) != 2: 117 fmt.Print(usage) 118 os.Exit(1) 119 case os.Args[1] == "help": 120 fmt.Print(usage) 121 os.Exit(0) 122 } 123 124 path := os.Args[1] 125 dir, err := os.Stat(path) 126 if err != nil { 127 fmt.Printf("provided path %v could not be opened: %v", path, err) 128 os.Exit(1) 129 } 130 if !dir.IsDir() { 131 fmt.Println("provided path is not a directory") 132 os.Exit(1) 133 } 134 135 conv := &SPDXConverter{ 136 rootDir: path, 137 } 138 if err := conv.Walk(); err != nil { 139 log.Fatal(err.Error()) 140 } 141 }