diff --git a/internal/copyright/header.txt b/internal/copyright/header.txt new file mode 100644 index 00000000..1c5724d1 --- /dev/null +++ b/internal/copyright/header.txt @@ -0,0 +1,3 @@ +Unless explicitly stated otherwise all files in this repository are licensed under the MIT License. + +This product includes software developed at Datadog (https://www.datadoghq.com/). Copyright 2021 Datadog, Inc. diff --git a/internal/copyright/main.go b/internal/copyright/main.go new file mode 100644 index 00000000..23d8b259 --- /dev/null +++ b/internal/copyright/main.go @@ -0,0 +1,167 @@ +package main + +import ( + "bufio" + _ "embed" + "flag" + "fmt" + "io/ioutil" + "os" + "path/filepath" + "strings" +) + +type ( + // task that adds license header to source + // files, if they don't already exist + addLicenseHeaderTask struct { + license string // license header string to add + config *config // root directory of the project source + } + + // command line config params + config struct { + licenseFile string + scanDir string + verifyOnly bool + } +) + +var ( + //go:embed header.txt + headerText string + headerPrefixes = []string{"The MIT License", "Unless explicitly stated"} +) + +var ( + // directories to be excluded + dirBlocklist = []string{".gen/", ".git/", ".vscode/", ".idea/"} + // default perms for the newly created files + defaultFilePerms = os.FileMode(0644) +) + +// command line utility that adds license header +// to the source files. Usage as follows: +// +// ./cmd/tools/copyright/licensegen.go +func main() { + var cfg config + flag.StringVar(&cfg.scanDir, "scanDir", ".", "directory to scan") + flag.BoolVar(&cfg.verifyOnly, "verifyOnly", false, "don't automatically add headers, just verify all files") + flag.Parse() + + task := newAddLicenseHeaderTask(&cfg) + if err := task.run(); err != nil { + fmt.Println(err) + os.Exit(-1) + } +} + +func newAddLicenseHeaderTask(cfg *config) *addLicenseHeaderTask { + return &addLicenseHeaderTask{ + config: cfg, + } +} + +func (task *addLicenseHeaderTask) run() error { + license, err := commentOutLines(headerText) + if err != nil { + return fmt.Errorf("copyright header failed to comment out lines, err=%v", err.Error()) + } + task.license = license + + if err := filepath.Walk(task.config.scanDir, task.handleFile); err != nil { + return fmt.Errorf("copyright header check failed, err=%v", err.Error()) + } + return nil +} + +func (task *addLicenseHeaderTask) handleFile(path string, fileInfo os.FileInfo, err error) error { + if err != nil { + return err + } + + if fileInfo.IsDir() { + return nil + } + + if !mustProcessPath(path) { + return nil + } + + if !strings.HasSuffix(fileInfo.Name(), ".go") { + return nil + } + + // Used as part of the cli to write licence headers on files, does not use user supplied input so marked as nosec + // #nosec + f, err := os.Open(path) + if err != nil { + return err + } + + scanner := bufio.NewScanner(f) + readLineSucc := scanner.Scan() + if !readLineSucc { + return fmt.Errorf("fail to read first line of file %v", path) + } + firstLine := strings.TrimSpace(scanner.Text()) + if err := scanner.Err(); err != nil { + return err + } + f.Close() + + for _, prefix := range headerPrefixes { + if strings.Contains(firstLine, prefix) { + return nil // file already has the copyright header + } + } + + // at this point, src file is missing the header + if task.config.verifyOnly { + if !isFileAutogenerated(path) { + return fmt.Errorf("%v missing license header", path) + } + } + + // Used as part of the cli to write licence headers on files, does not use user supplied input so marked as nosec + // #nosec + data, err := ioutil.ReadFile(path) + if err != nil { + return err + } + + return ioutil.WriteFile(path, []byte(task.license+string(data)), defaultFilePerms) +} + +func isFileAutogenerated(path string) bool { + return false +} + +func mustProcessPath(path string) bool { + for _, d := range dirBlocklist { + if strings.HasPrefix(path, d) { + return false + } + } + return true +} + +func commentOutLines(str string) (string, error) { + var lines []string + scanner := bufio.NewScanner(strings.NewReader(str)) + for scanner.Scan() { + line := scanner.Text() + if line == "" { + lines = append(lines, "//\n") + } else { + lines = append(lines, fmt.Sprintf("// %s\n", line)) + } + } + lines = append(lines, "\n") + + if err := scanner.Err(); err != nil { + return "", err + } + return strings.Join(lines, ""), nil +}