Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
gitpod-io
GitHub Repository: gitpod-io/gitpod
Path: blob/main/dev/addlicense/main.go
2492 views
1
// Copyright 2018 Google LLC
2
//
3
// Licensed under the Apache License, Version 2.0 (the "License");
4
// you may not use this file except in compliance with the License.
5
// You may obtain a copy of the License at
6
//
7
// http://www.apache.org/licenses/LICENSE-2.0
8
//
9
// Unless required by applicable law or agreed to in writing, software
10
// distributed under the License is distributed on an "AS IS" BASIS,
11
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
// See the License for the specific language governing permissions and
13
// limitations under the License.
14
15
// This program ensures source code files have copyright license headers.
16
// See usage with "addlicense -h".
17
package main
18
19
import (
20
"bufio"
21
"bytes"
22
"errors"
23
"flag"
24
"fmt"
25
"html/template"
26
"log"
27
"os"
28
"path/filepath"
29
"strings"
30
"time"
31
32
"golang.org/x/sync/errgroup"
33
)
34
35
const helpText = `Usage: addlicense [flags] pattern [pattern ...]
36
37
The program ensures source code files have copyright license headers
38
by scanning directory patterns recursively.
39
40
It modifies all source files in place and avoids adding a license header
41
to any file that already has one.
42
43
The pattern argument can be provided multiple times, and may also refer
44
to single files.
45
46
Flags:
47
`
48
49
var (
50
holder = flag.String("c", "Gitpod GmbH", "copyright holder")
51
license = flag.String("l", "agpl", "license type: agpl")
52
licensef = flag.String("f", "", "license file")
53
year = flag.String("y", fmt.Sprint(time.Now().Year()), "copyright year(s)")
54
verbose = flag.Bool("v", false, "verbose mode: print the name of the files that are modified")
55
stdin = flag.Bool("s", false, "read paths to file that are modified from stdin")
56
checkonly = flag.Bool("check", false, "check only mode: verify presence of license headers and exit with non-zero code if missing")
57
remove = flag.Bool("remove", false, "remove header mode: if a license header is present, we'll remove it")
58
)
59
60
func main() {
61
flag.Usage = func() {
62
fmt.Fprintln(os.Stderr, helpText)
63
flag.PrintDefaults()
64
}
65
flag.Parse()
66
if flag.NArg() == 0 {
67
flag.Usage()
68
os.Exit(1)
69
}
70
71
data := &copyrightData{
72
Year: *year,
73
Holder: *holder,
74
}
75
76
var t *template.Template
77
if *licensef != "" {
78
d, err := os.ReadFile(*licensef)
79
if err != nil {
80
log.Printf("license file: %v", err)
81
os.Exit(1)
82
}
83
t, err = template.New("").Parse(string(d))
84
if err != nil {
85
log.Printf("license file: %v", err)
86
os.Exit(1)
87
}
88
} else {
89
t = licenseTemplate[*license]
90
if t == nil {
91
log.Printf("unknown license: %s", *license)
92
os.Exit(1)
93
}
94
}
95
96
// process at most 1000 files in parallel
97
ch := make(chan *file, 1000)
98
done := make(chan struct{})
99
go func() {
100
var wg errgroup.Group
101
for f := range ch {
102
f := f // https://golang.org/doc/faq#closures_and_goroutines
103
wg.Go(func() error {
104
if *checkonly {
105
// Check if file extension is known
106
lic, err := licenseHeader(f.path, t, data)
107
if err != nil {
108
log.Printf("%s: %v", f.path, err)
109
return err
110
}
111
if lic == nil { // Unknown fileExtension
112
return nil
113
}
114
// Check if file has a license
115
isMissingLicenseHeader, err := fileHasLicense(f.path)
116
if err != nil {
117
log.Printf("%s: %v", f.path, err)
118
return err
119
}
120
if isMissingLicenseHeader {
121
fmt.Printf("%s\n", f.path)
122
return errors.New("missing license header")
123
}
124
} else if *remove {
125
modified, haslic, err := removeLicense(f.path, f.mode, t, data)
126
if err != nil {
127
log.Printf("%s: %v", f.path, err)
128
return err
129
}
130
if haslic && !modified {
131
log.Printf("%s should have been modified but wasn't", f.path)
132
}
133
} else {
134
modified, err := addLicense(f.path, f.mode, t, data)
135
if err != nil {
136
log.Printf("%s: %v", f.path, err)
137
return err
138
}
139
if *verbose && modified {
140
log.Printf("%s modified", f.path)
141
}
142
}
143
return nil
144
})
145
}
146
err := wg.Wait()
147
close(done)
148
if err != nil {
149
os.Exit(1)
150
}
151
}()
152
153
defer func() {
154
close(ch)
155
<-done
156
}()
157
158
if *stdin {
159
scanner := bufio.NewScanner(os.Stdin)
160
for scanner.Scan() {
161
path := scanner.Text()
162
stat, err := os.Stat(path)
163
if err != nil {
164
log.Printf("file %s does not exist", path)
165
}
166
ch <- &file{path, stat.Mode()}
167
}
168
return
169
}
170
171
for _, d := range flag.Args() {
172
walk(ch, d)
173
}
174
}
175
176
type file struct {
177
path string
178
mode os.FileMode
179
}
180
181
func walk(ch chan<- *file, start string) {
182
filepath.Walk(start, func(path string, fi os.FileInfo, err error) error {
183
if err != nil {
184
log.Printf("%s error: %v", path, err)
185
return nil
186
}
187
if fi.IsDir() {
188
return nil
189
}
190
ch <- &file{path, fi.Mode()}
191
return nil
192
})
193
}
194
195
func addLicense(path string, fmode os.FileMode, tmpl *template.Template, data *copyrightData) (bool, error) {
196
var lic []byte
197
var err error
198
lic, err = licenseHeader(path, tmpl, data)
199
if err != nil || lic == nil {
200
return false, err
201
}
202
203
b, err := os.ReadFile(path)
204
if err != nil || hasLicense(b) {
205
return false, err
206
}
207
208
line := hashBang(b)
209
if len(line) > 0 {
210
b = b[len(line):]
211
if line[len(line)-1] != '\n' {
212
line = append(line, '\n')
213
}
214
lic = append(line, lic...)
215
}
216
b = append(lic, b...)
217
return true, os.WriteFile(path, b, fmode)
218
}
219
220
func removeLicense(path string, fmode os.FileMode, tmpl *template.Template, data *copyrightData) (haslic bool, modified bool, err error) {
221
var lic []byte
222
lic, err = licenseHeader(path, tmpl, data)
223
if err != nil || lic == nil {
224
return false, false, err
225
}
226
227
b, err := os.ReadFile(path)
228
if err != nil {
229
return false, false, err
230
}
231
olen := len(b)
232
if !hasLicense(b) {
233
return false, false, nil
234
}
235
236
lic = bytes.TrimSpace(lic)
237
238
b = bytes.ReplaceAll(b, []byte("Copyright (c) 2021 Gitpod GmbH."), []byte("Copyright (c) 2022 Gitpod GmbH."))
239
b = bytes.ReplaceAll(b, []byte("Copyright (c) 2020 Gitpod GmbH."), []byte("Copyright (c) 2022 Gitpod GmbH."))
240
b = bytes.ReplaceAll(b, bytes.TrimSpace(lic), nil)
241
if len(b) >= olen {
242
fmt.Println(string(lic))
243
fmt.Println("---")
244
fmt.Println(string(b[:len(lic)]))
245
fmt.Println("===")
246
}
247
248
return len(b) < olen, true, os.WriteFile(path, b, fmode)
249
}
250
251
// fileHasLicense reports whether the file at path contains a license header.
252
func fileHasLicense(path string) (bool, error) {
253
b, err := os.ReadFile(path)
254
if err != nil || hasLicense(b) {
255
return false, err
256
}
257
return true, nil
258
}
259
260
func licenseHeader(path string, tmpl *template.Template, data *copyrightData) ([]byte, error) {
261
var lic []byte
262
var err error
263
switch fileExtension(path) {
264
default:
265
return nil, nil
266
case ".c", ".h":
267
lic, err = prefix(tmpl, data, "/*", " * ", " */")
268
case ".js", ".mjs", ".cjs", ".jsx", ".tsx", ".css", ".tf", ".ts", ".jsonnet", ".libsonnet":
269
lic, err = prefix(tmpl, data, "/**", " * ", " */")
270
case ".cc", ".cpp", ".cs", ".go", ".hh", ".hpp", ".java", ".m", ".mm", ".proto", ".rs", ".scala", ".swift", ".dart", ".groovy", ".kt", ".kts":
271
lic, err = prefix(tmpl, data, "", "// ", "")
272
case ".py", ".sh", ".yaml", ".yml", ".dockerfile", "dockerfile", ".rb", "gemfile":
273
lic, err = prefix(tmpl, data, "", "# ", "")
274
case ".el", ".lisp":
275
lic, err = prefix(tmpl, data, "", ";; ", "")
276
case ".erl":
277
lic, err = prefix(tmpl, data, "", "% ", "")
278
case ".hs", ".sql":
279
lic, err = prefix(tmpl, data, "", "-- ", "")
280
case ".html", ".xml", ".vue":
281
lic, err = prefix(tmpl, data, "<!--", " ", "-->")
282
case ".php":
283
lic, err = prefix(tmpl, data, "", "// ", "")
284
case ".ml", ".mli", ".mll", ".mly":
285
lic, err = prefix(tmpl, data, "(**", " ", "*)")
286
}
287
return lic, err
288
}
289
290
func fileExtension(name string) string {
291
if v := filepath.Ext(name); v != "" {
292
return strings.ToLower(v)
293
}
294
return strings.ToLower(filepath.Base(name))
295
}
296
297
var head = []string{
298
"#!", // shell script
299
"<?xml", // XML declaratioon
300
"<!doctype", // HTML doctype
301
"# encoding:", // Ruby encoding
302
"# frozen_string_literal:", // Ruby interpreter instruction
303
"<?php", // PHP opening tag
304
}
305
306
func hashBang(b []byte) []byte {
307
var line []byte
308
for _, c := range b {
309
line = append(line, c)
310
if c == '\n' {
311
break
312
}
313
}
314
first := strings.ToLower(string(line))
315
for _, h := range head {
316
if strings.HasPrefix(first, h) {
317
return line
318
}
319
}
320
return nil
321
}
322
323
func hasLicense(b []byte) bool {
324
n := 1000
325
if len(b) < 1000 {
326
n = len(b)
327
}
328
return bytes.Contains(bytes.ToLower(b[:n]), []byte("copyright")) ||
329
bytes.Contains(bytes.ToLower(b[:n]), []byte("mozilla public"))
330
}
331
332