Skip to content

Commit 97ae522

Browse files
add support for SPDX license headers
This adds a new "-s" flag that will append an SPDX-License-Identifier line to license headers. If "-s=only" is specified, then only the SPDX identifier will be used. This also changes the "-l" flag to use SPDX identifiers, with mappings to support the legacy "apache", "mit", and "mpl" values. Together with the "-s" flag, this allows SPDX headers for any arbitrary license type to be added to files. Co-authored-by: Bob Callaway <[email protected]>
1 parent c2fdf83 commit 97ae522

File tree

3 files changed

+113
-19
lines changed

3 files changed

+113
-19
lines changed

main.go

+34-2
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,7 @@ Flags:
4949

5050
var (
5151
skipExtensionFlags skipExtensionFlag
52+
spdx spdxFlag
5253

5354
holder = flag.String("c", "Google LLC", "copyright holder")
5455
license = flag.String("l", "apache", "license type: apache, bsd, mit, mpl")
@@ -64,6 +65,7 @@ func init() {
6465
flag.PrintDefaults()
6566
}
6667
flag.Var(&skipExtensionFlags, "skip", "To skip files to check/add the header file, for example: -skip rb -skip go")
68+
flag.Var(&spdx, "s", "Include SPDX identifier in license header. Set -s=only to only include SPDX identifier.")
6769
}
6870

6971
type skipExtensionFlag []string
@@ -77,19 +79,48 @@ func (i *skipExtensionFlag) Set(value string) error {
7779
return nil
7880
}
7981

82+
// spdxFlag defines the line flag behavior for specifying SPDX support.
83+
type spdxFlag string
84+
85+
const (
86+
spdxOff spdxFlag = ""
87+
spdxOn spdxFlag = "true" // value set by flag package on bool flag
88+
spdxOnly spdxFlag = "only"
89+
)
90+
91+
// IsBoolFlag causes a bare '-s' flag to be set as the string 'true'. This
92+
// allows the use of the bare '-s' or setting a string '-s=only'.
93+
func (i *spdxFlag) IsBoolFlag() bool { return true }
94+
func (i *spdxFlag) String() string { return string(*i) }
95+
96+
func (i *spdxFlag) Set(value string) error {
97+
v := spdxFlag(value)
98+
if v != spdxOn && v != spdxOnly {
99+
return fmt.Errorf("error: flag 's' expects '%v' or '%v'", spdxOn, spdxOnly)
100+
}
101+
*i = v
102+
return nil
103+
}
104+
80105
func main() {
81106
flag.Parse()
82107
if flag.NArg() == 0 {
83108
flag.Usage()
84109
os.Exit(1)
85110
}
86111

112+
// map legacy license values
113+
if t, ok := legacyLicenseTypes[*license]; ok {
114+
*license = t
115+
}
116+
87117
data := licenseData{
88118
Year: *year,
89119
Holder: *holder,
120+
SPDXID: *license,
90121
}
91122

92-
tpl, err := fetchTemplate(*license, *licensef)
123+
tpl, err := fetchTemplate(*license, *licensef, spdx)
93124
if err != nil {
94125
log.Fatal(err)
95126
}
@@ -300,5 +331,6 @@ func hasLicense(b []byte) bool {
300331
n = len(b)
301332
}
302333
return bytes.Contains(bytes.ToLower(b[:n]), []byte("copyright")) ||
303-
bytes.Contains(bytes.ToLower(b[:n]), []byte("mozilla public"))
334+
bytes.Contains(bytes.ToLower(b[:n]), []byte("mozilla public")) ||
335+
bytes.Contains(bytes.ToLower(b[:n]), []byte("SPDX-License-Identifier"))
304336
}

tmpl.go

+31-7
Original file line numberDiff line numberDiff line change
@@ -25,25 +25,36 @@ import (
2525
)
2626

2727
var licenseTemplate = map[string]string{
28-
"apache": tmplApache,
29-
"mit": tmplMIT,
30-
"bsd": tmplBSD,
31-
"mpl": tmplMPL,
28+
"Apache-2.0": tmplApache,
29+
"MIT": tmplMIT,
30+
"bsd": tmplBSD,
31+
"MPL-2.0": tmplMPL,
32+
}
33+
34+
// maintain backwards compatibility by mapping legacy license types to their
35+
// SPDX equivalents.
36+
var legacyLicenseTypes = map[string]string{
37+
"apache": "Apache-2.0",
38+
"mit": "MIT",
39+
"mpl": "MPL-2.0",
3240
}
3341

3442
// licenseData specifies the data used to fill out a license template.
3543
type licenseData struct {
3644
Year string // Copyright year(s).
3745
Holder string // Name of the copyright holder.
46+
SPDXID string // SPDX Identifier
3847
}
3948

4049
// fetchTemplate returns the license template for the specified license and
4150
// optional templateFile. If templateFile is provided, the license is read
4251
// from the specified file. Otherwise, a template is loaded for the specified
4352
// license, if recognized.
44-
func fetchTemplate(license string, templateFile string) (string, error) {
53+
func fetchTemplate(license string, templateFile string, spdx spdxFlag) (string, error) {
4554
var t string
46-
if templateFile != "" {
55+
if spdx == spdxOnly {
56+
t = tmplSPDX
57+
} else if templateFile != "" {
4758
d, err := ioutil.ReadFile(templateFile)
4859
if err != nil {
4960
return "", fmt.Errorf("license file: %w", err)
@@ -53,7 +64,15 @@ func fetchTemplate(license string, templateFile string) (string, error) {
5364
} else {
5465
t = licenseTemplate[license]
5566
if t == "" {
56-
return "", fmt.Errorf("unknown license: %q", license)
67+
if spdx == spdxOn {
68+
// unknown license, but SPDX headers requested
69+
t = tmplSPDX
70+
} else {
71+
return "", fmt.Errorf("unknown license: %q. Include the '-s' flag to request SPDX style headers using this license.", license)
72+
}
73+
} else if spdx == spdxOn {
74+
// append spdx headers to recognized license
75+
t = t + spdxSuffix
5776
}
5877
}
5978

@@ -122,3 +141,8 @@ CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.`
122141
const tmplMPL = `This Source Code Form is subject to the terms of the Mozilla Public
123142
License, v. 2.0. If a copy of the MPL was not distributed with this
124143
file, You can obtain one at https://mozilla.org/MPL/2.0/.`
144+
145+
const tmplSPDX = `{{ if and .Year .Holder }}Copyright {{.Year}} {{.Holder}}
146+
{{ end }}SPDX-License-Identifier: {{.SPDXID}}`
147+
148+
const spdxSuffix = "\n\nSPDX-License-Identifier: {{.SPDXID}}"

tmpl_test.go

+48-10
Original file line numberDiff line numberDiff line change
@@ -31,66 +31,104 @@ func init() {
3131

3232
func TestFetchTemplate(t *testing.T) {
3333
tests := []struct {
34-
description string // test case description
35-
license string // license passed to fetchTemplate
36-
templateFile string // templatefile passed to fetchTemplate
37-
wantTemplate string // expected returned template
38-
wantErr error // expected returned error
34+
description string // test case description
35+
license string // license passed to fetchTemplate
36+
templateFile string // templatefile passed to fetchTemplate
37+
spdx spdxFlag // spdx value passed to fetchTemplate
38+
wantTemplate string // expected returned template
39+
wantErr error // expected returned error
3940
}{
41+
// custom template files
4042
{
4143
"non-existant template file",
4244
"",
4345
"/does/not/exist",
46+
spdxOff,
4447
"",
4548
os.ErrNotExist,
4649
},
4750
{
4851
"custom template file",
4952
"",
5053
"testdata/custom.tpl",
54+
spdxOff,
5155
"Copyright {{.Year}} {{.Holder}}\n\nCustom License Template\n",
5256
nil,
5357
},
58+
5459
{
5560
"unknown license",
5661
"unknown",
5762
"",
63+
spdxOff,
5864
"",
59-
errors.New(`unknown license: "unknown"`),
65+
errors.New(`unknown license: "unknown". Include the '-s' flag to request SPDX style headers using this license.`),
6066
},
67+
68+
// pre-defined license templates, no SPDX
6169
{
6270
"apache license template",
63-
"apache",
71+
"Apache-2.0",
6472
"",
73+
spdxOff,
6574
tmplApache,
6675
nil,
6776
},
6877
{
6978
"mit license template",
70-
"mit",
79+
"MIT",
7180
"",
81+
spdxOff,
7282
tmplMIT,
7383
nil,
7484
},
7585
{
7686
"bsd license template",
7787
"bsd",
7888
"",
89+
spdxOff,
7990
tmplBSD,
8091
nil,
8192
},
8293
{
8394
"mpl license template",
84-
"mpl",
95+
"MPL-2.0",
8596
"",
97+
spdxOff,
8698
tmplMPL,
8799
nil,
88100
},
101+
102+
// SPDX variants
103+
{
104+
"apache license template with SPDX added",
105+
"Apache-2.0",
106+
"",
107+
spdxOn,
108+
tmplApache + spdxSuffix,
109+
nil,
110+
},
111+
{
112+
"apache license template with SPDX only",
113+
"Apache-2.0",
114+
"",
115+
spdxOnly,
116+
tmplSPDX,
117+
nil,
118+
},
119+
{
120+
"unknown license with SPDX only",
121+
"unknown",
122+
"",
123+
spdxOnly,
124+
tmplSPDX,
125+
nil,
126+
},
89127
}
90128

91129
for _, tt := range tests {
92130
t.Run(tt.description, func(t *testing.T) {
93-
tpl, err := fetchTemplate(tt.license, tt.templateFile)
131+
tpl, err := fetchTemplate(tt.license, tt.templateFile, tt.spdx)
94132
if tt.wantErr != nil && (err == nil || (!errors.Is(err, tt.wantErr) && err.Error() != tt.wantErr.Error())) {
95133
t.Fatalf("fetchTemplate(%q, %q) returned error: %#v, want %#v", tt.license, tt.templateFile, err, tt.wantErr)
96134
}

0 commit comments

Comments
 (0)