Skip to content

Commit 1a83a5f

Browse files
committed
check copyright year and license type
1 parent bcc603c commit 1a83a5f

File tree

4 files changed

+921
-8
lines changed

4 files changed

+921
-8
lines changed

tools/license-check/cmd/root.go

Lines changed: 38 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -5,11 +5,14 @@ import (
55
"os"
66
"path/filepath"
77
"strings"
8+
"time"
89

10+
"github.com/google/go-licenses/licenses"
911
"github.com/spf13/cobra"
1012
)
1113

1214
const rootCmdDesc = "Utilities for license check."
15+
const licenseConfidence = 0.9
1316

1417
type rootOptions struct {
1518
fileList []string
@@ -37,21 +40,22 @@ func (o *rootOptions) run() error {
3740
}
3841
foundErr := false
3942
for _, file := range o.fileList {
40-
b, err := os.ReadFile(file)
41-
if err != nil {
42-
return err
43-
}
4443
ext := filepath.Ext(file)
4544
if ext != ".tmpl" && ext != ".go" && ext != ".yaml" {
4645
continue
4746
}
48-
if !strings.Contains(string(b), `Licensed under the Apache License, Version 2.0 (the "License");`) {
49-
fmt.Fprintf(os.Stderr, "File %s does not contain Apache License.\n", file)
47+
48+
if err := checkLicenseType(file); err != nil {
49+
fmt.Fprintf(os.Stderr, "File %s failed to pass license check: %s.\n", file, err)
50+
foundErr = true
51+
}
52+
if err := checkCopyright(file, time.Now().Year()); err != nil {
53+
fmt.Fprintf(os.Stderr, "File %s failed to pass license check: %s.\n", file, err)
5054
foundErr = true
5155
}
5256
}
5357
if foundErr {
54-
return fmt.Errorf("found file missing license")
58+
return fmt.Errorf("found file failing license license")
5559
}
5660
return nil
5761
}
@@ -72,3 +76,30 @@ func Execute() {
7276
os.Exit(1)
7377
}
7478
}
79+
80+
func checkLicenseType(filePath string) error {
81+
classifier, err := licenses.NewClassifier(licenseConfidence)
82+
if err != nil {
83+
return fmt.Errorf("failed to create license classifier: %w", err)
84+
}
85+
licenseName, _, err := classifier.Identify(filePath)
86+
if err != nil {
87+
return fmt.Errorf("failed to identify license for %s: %w", filePath, err)
88+
}
89+
if !strings.Contains(licenseName, "Apache") {
90+
return fmt.Errorf("found license type %s, expect Apache", licenseName)
91+
}
92+
return nil
93+
}
94+
95+
func checkCopyright(filePath string, year int) error {
96+
b, err := os.ReadFile(filePath)
97+
if err != nil {
98+
return err
99+
}
100+
expectStr := fmt.Sprintf("Copyright %d Google", year)
101+
if !strings.Contains(strings.ToLower(string(b)), strings.ToLower(expectStr)) {
102+
return fmt.Errorf("expected copyright string %q not found", expectStr)
103+
}
104+
return nil
105+
}

tools/license-check/cmd/root_test.go

Lines changed: 95 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,95 @@
1+
package cmd
2+
3+
import (
4+
"fmt"
5+
"os"
6+
"testing"
7+
"time"
8+
)
9+
10+
const validApache2LicenseFormat string = `
11+
Licensed under the Apache License, Version 2.0 (the "License");
12+
you may not use this file except in compliance with the License.
13+
You may obtain a copy of the License at
14+
15+
http://www.apache.org/licenses/LICENSE-2.0
16+
17+
Unless required by applicable law or agreed to in writing, software
18+
distributed under the License is distributed on an "AS IS" BASIS,
19+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
20+
See the License for the specific language governing permissions and
21+
limitations under the License.
22+
`
23+
const validCopyright string = "Copyright %d Google Inc\n"
24+
const invalidCopyright string = "Copyright 1900 Google Inc\n"
25+
const invalidLicense string = "not a license"
26+
27+
func TestCheckLicenseType(t *testing.T) {
28+
dir := t.TempDir()
29+
tests := []struct {
30+
name string
31+
content string
32+
wantErr bool
33+
}{
34+
{
35+
name: "success",
36+
content: fmt.Sprintf(validCopyright, time.Now().Year()) + validApache2LicenseFormat,
37+
},
38+
{
39+
name: "invalid license",
40+
content: fmt.Sprintf(validCopyright, time.Now().Year()) + invalidLicense,
41+
wantErr: true,
42+
},
43+
}
44+
for _, tc := range tests {
45+
f, err := os.CreateTemp(dir, "testfile")
46+
if err != nil {
47+
t.Fatal(err)
48+
}
49+
if _, err := f.WriteString(tc.content); err != nil {
50+
t.Fatal(err)
51+
}
52+
err = checkLicenseType(f.Name())
53+
if err == nil && tc.wantErr {
54+
t.Errorf("checkLicenseType() want err, but got nil")
55+
}
56+
if err != nil && !tc.wantErr {
57+
t.Errorf("checkLicenseType() got error %s", err)
58+
}
59+
}
60+
}
61+
62+
func TestCheckCopyright(t *testing.T) {
63+
dir := t.TempDir()
64+
tests := []struct {
65+
name string
66+
content string
67+
wantErr bool
68+
}{
69+
{
70+
name: "success",
71+
content: fmt.Sprintf(validCopyright, time.Now().Year()) + validApache2LicenseFormat,
72+
},
73+
{
74+
name: "wrong year copyright",
75+
content: invalidCopyright + validApache2LicenseFormat,
76+
wantErr: true,
77+
},
78+
}
79+
for _, tc := range tests {
80+
f, err := os.CreateTemp(dir, "testfile")
81+
if err != nil {
82+
t.Fatal(err)
83+
}
84+
if _, err := f.WriteString(tc.content); err != nil {
85+
t.Fatal(err)
86+
}
87+
err = checkCopyright(f.Name(), time.Now().Year())
88+
if err == nil && tc.wantErr {
89+
t.Errorf("checkCopyright() want err, but got nil")
90+
}
91+
if err != nil && !tc.wantErr {
92+
t.Errorf("checkCopyright() got error %s", err)
93+
}
94+
}
95+
}

tools/license-check/go.mod

Lines changed: 24 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,9 +4,32 @@ go 1.23.0
44

55
toolchain go1.23.1
66

7-
require github.com/spf13/cobra v1.9.1
7+
require (
8+
github.com/google/go-licenses v1.6.0
9+
github.com/spf13/cobra v1.9.1
10+
)
811

912
require (
13+
github.com/emirpasic/gods v1.12.0 // indirect
14+
github.com/go-logr/logr v1.2.0 // indirect
15+
github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect
16+
github.com/google/licenseclassifier v0.0.0-20210722185704-3043a050f148 // indirect
1017
github.com/inconshreveable/mousetrap v1.1.0 // indirect
18+
github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 // indirect
19+
github.com/kevinburke/ssh_config v0.0.0-20190725054713-01f96b0aa0cd // indirect
20+
github.com/mitchellh/go-homedir v1.1.0 // indirect
21+
github.com/sergi/go-diff v1.2.0 // indirect
1122
github.com/spf13/pflag v1.0.6 // indirect
23+
github.com/src-d/gcfg v1.4.0 // indirect
24+
github.com/xanzy/ssh-agent v0.2.1 // indirect
25+
go.opencensus.io v0.23.0 // indirect
26+
golang.org/x/crypto v0.1.0 // indirect
27+
golang.org/x/mod v0.7.0 // indirect
28+
golang.org/x/net v0.5.0 // indirect
29+
golang.org/x/sys v0.4.0 // indirect
30+
golang.org/x/tools v0.5.0 // indirect
31+
gopkg.in/src-d/go-billy.v4 v4.3.2 // indirect
32+
gopkg.in/src-d/go-git.v4 v4.13.1 // indirect
33+
gopkg.in/warnings.v0 v0.1.2 // indirect
34+
k8s.io/klog/v2 v2.80.1 // indirect
1235
)

0 commit comments

Comments
 (0)