Skip to content

Commit 54e4b0b

Browse files
authored
Use SPDX license identifiers in go.mod and add test to enforce them (#4940)
## Summary - Normalize all license comments in `go.mod` to use standard SPDX identifiers (e.g. `Apache 2.0` → `Apache-2.0`, `BSD 3-Clause` → `BSD-3-Clause`) - Use `MIT AND Apache-2.0` for `go.yaml.in/yaml/v3` (different files under different licenses) - Add `internal/build/license_test.go` that parses `go.mod` with `x/mod/modfile` and validates every direct dependency has a valid SPDX license comment ## Test plan - [x] `go test ./internal/build/ -run TestRequireSPDXLicenseComment` passes - [x] Cross-checked all 38 license comments against upstream LICENSE files This pull request was AI-assisted by Isaac.
1 parent fde66e1 commit 54e4b0b

2 files changed

Lines changed: 110 additions & 13 deletions

File tree

go.mod

Lines changed: 13 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -5,44 +5,44 @@ go 1.25.0
55
toolchain go1.25.7
66

77
require (
8-
dario.cat/mergo v1.0.2 // BSD 3-Clause
8+
dario.cat/mergo v1.0.2 // BSD-3-Clause
99
github.com/BurntSushi/toml v1.6.0 // MIT
1010
github.com/Masterminds/semver/v3 v3.4.0 // MIT
1111
github.com/charmbracelet/bubbles v1.0.0 // MIT
1212
github.com/charmbracelet/bubbletea v1.3.10 // MIT
1313
github.com/charmbracelet/huh v1.0.0 // MIT
1414
github.com/charmbracelet/lipgloss v1.1.0 // MIT
15-
github.com/databricks/databricks-sdk-go v0.126.0 // Apache 2.0
15+
github.com/databricks/databricks-sdk-go v0.126.0 // Apache-2.0
1616
github.com/fatih/color v1.19.0 // MIT
1717
github.com/google/jsonschema-go v0.4.2 // MIT
1818
github.com/google/uuid v1.6.0 // BSD-3-Clause
19-
github.com/gorilla/mux v1.8.1 // BSD 3-Clause
20-
github.com/gorilla/websocket v1.5.3 // BSD 2-Clause
21-
github.com/hashicorp/go-version v1.8.0 // MPL 2.0
22-
github.com/hashicorp/hc-install v0.9.3 // MPL 2.0
23-
github.com/hashicorp/terraform-exec v0.25.0 // MPL 2.0
24-
github.com/hashicorp/terraform-json v0.27.2 // MPL 2.0
25-
github.com/hexops/gotextdiff v1.0.3 // BSD 3-Clause "New" or "Revised" License
19+
github.com/gorilla/mux v1.8.1 // BSD-3-Clause
20+
github.com/gorilla/websocket v1.5.3 // BSD-2-Clause
21+
github.com/hashicorp/go-version v1.8.0 // MPL-2.0
22+
github.com/hashicorp/hc-install v0.9.3 // MPL-2.0
23+
github.com/hashicorp/terraform-exec v0.25.0 // MPL-2.0
24+
github.com/hashicorp/terraform-json v0.27.2 // MPL-2.0
25+
github.com/hexops/gotextdiff v1.0.3 // BSD-3-Clause
2626
github.com/manifoldco/promptui v0.9.0 // BSD-3-Clause
2727
github.com/mattn/go-isatty v0.0.20 // MIT
2828
github.com/nwidger/jsoncolor v0.3.2 // MIT
2929
github.com/palantir/pkg/yamlpatch v1.5.0 // BSD-3-Clause
3030
github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c // BSD-2-Clause
31-
github.com/quasilyte/go-ruleguard/dsl v0.3.22 // BSD 3-Clause
31+
github.com/quasilyte/go-ruleguard/dsl v0.3.22 // BSD-3-Clause
3232
github.com/sabhiram/go-gitignore v0.0.0-20210923224102-525f6e181f06 // MIT
33-
github.com/spf13/cobra v1.10.2 // Apache 2.0
33+
github.com/spf13/cobra v1.10.2 // Apache-2.0
3434
github.com/spf13/pflag v1.0.10 // BSD-3-Clause
3535
github.com/stretchr/testify v1.11.1 // MIT
3636
github.com/tailscale/hujson v0.0.0-20250605163823-992244df8c5a // BSD-3-Clause
37-
go.yaml.in/yaml/v3 v3.0.4 // MIT, Apache 2.0
37+
go.yaml.in/yaml/v3 v3.0.4 // MIT AND Apache-2.0
3838
golang.org/x/crypto v0.49.0 // BSD-3-Clause
3939
golang.org/x/exp v0.0.0-20260112195511-716be5621a96 // BSD-3-Clause
4040
golang.org/x/mod v0.34.0 // BSD-3-Clause
4141
golang.org/x/oauth2 v0.36.0 // BSD-3-Clause
4242
golang.org/x/sync v0.20.0 // BSD-3-Clause
4343
golang.org/x/sys v0.43.0 // BSD-3-Clause
4444
golang.org/x/text v0.35.0 // BSD-3-Clause
45-
gopkg.in/ini.v1 v1.67.1 // Apache 2.0
45+
gopkg.in/ini.v1 v1.67.1 // Apache-2.0
4646
)
4747

4848
require (

internal/build/license_test.go

Lines changed: 97 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,97 @@
1+
package build
2+
3+
import (
4+
"os"
5+
"strings"
6+
"testing"
7+
8+
"github.com/stretchr/testify/assert"
9+
"github.com/stretchr/testify/require"
10+
"golang.org/x/mod/modfile"
11+
)
12+
13+
// Allowlist of SPDX license identifiers we accept for direct dependencies.
14+
// See https://spdx.org/licenses/ for the full list.
15+
var spdxLicenses = map[string]bool{
16+
"Apache-2.0": true,
17+
"BSD-2-Clause": true,
18+
"BSD-3-Clause": true,
19+
"MIT": true,
20+
"MPL-2.0": true,
21+
}
22+
23+
// parseSPDXExpression validates that expr is a valid SPDX license expression
24+
// composed of allowed identifiers joined by AND/OR operators.
25+
// Returns the list of license identifiers found, or an error string.
26+
func parseSPDXExpression(expr string) ([]string, string) {
27+
tokens := strings.Fields(expr)
28+
if len(tokens) == 0 {
29+
return nil, "empty expression"
30+
}
31+
32+
var ids []string
33+
expectID := true
34+
for _, tok := range tokens {
35+
if expectID {
36+
if !spdxLicenses[tok] {
37+
return nil, tok + " is not an allowed SPDX license identifier; allowed: " + allowedList()
38+
}
39+
ids = append(ids, tok)
40+
expectID = false
41+
} else {
42+
if tok != "AND" && tok != "OR" {
43+
return nil, tok + " unexpected; expected AND or OR operator"
44+
}
45+
expectID = true
46+
}
47+
}
48+
49+
if expectID {
50+
return nil, "expression ends with an operator"
51+
}
52+
53+
return ids, ""
54+
}
55+
56+
func allowedList() string {
57+
var out []string
58+
for k := range spdxLicenses {
59+
out = append(out, k)
60+
}
61+
return strings.Join(out, ", ")
62+
}
63+
64+
func TestRequireSPDXLicenseComment(t *testing.T) {
65+
b, err := os.ReadFile("../../go.mod")
66+
require.NoError(t, err)
67+
68+
modFile, err := modfile.Parse("../../go.mod", b, nil)
69+
require.NoError(t, err)
70+
71+
for _, r := range modFile.Require {
72+
if r.Indirect {
73+
continue
74+
}
75+
76+
// Find the license comment in suffix comments (excluding "indirect").
77+
var license string
78+
for _, c := range r.Syntax.Suffix {
79+
text := strings.TrimPrefix(c.Token, "//")
80+
text = strings.TrimSpace(text)
81+
if text == "indirect" {
82+
continue
83+
}
84+
license = text
85+
}
86+
87+
if license == "" {
88+
assert.Failf(t, r.Mod.Path, "missing SPDX license comment; add one like: // MIT")
89+
continue
90+
}
91+
92+
_, errMsg := parseSPDXExpression(license)
93+
if errMsg != "" {
94+
assert.Failf(t, r.Mod.Path, "license comment %q: %s", license, errMsg)
95+
}
96+
}
97+
}

0 commit comments

Comments
 (0)