-
Notifications
You must be signed in to change notification settings - Fork 2
/
Copy pathmain.go
226 lines (196 loc) · 5.01 KB
/
main.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
package main
import (
"flag"
"fmt"
"io"
"os"
"runtime/debug"
"strings"
"text/tabwriter"
)
// Version can be used to set the version at link time
var Version string
type options struct {
allowed string
download bool
dump bool
listLicenses bool
listLicenseNames bool
output string
version bool
excluded string
}
func main() {
flag.Usage = func() {
fmt.Fprint(os.Stderr, `Usage: lian [OPTIONS] [PATH]
lian is a license analyzer for Go binaries and modules.
Default is to search for a go.mod file into the current directory.
Options:
-a, --allowed comma separated list of allowed licenses (i.e. MIT, BSD-3-Clause). Default to all
-e, --excluded comma separated list of repository with version excluded from the licenses check. Default to none
-d, --download download dependencies to local cache
--dump dump all licenses
-h, --help show this help message
--list-names list the names of the license file can be detected and exit
--list-licenses list the licenses can be detected and exit
-o, --output <file> write to file instead of stdout
--version show the version number
`)
}
var opts options
flag.StringVar(&opts.allowed, "a", "", "")
flag.StringVar(&opts.allowed, "allowed", "", "")
flag.BoolVar(&opts.download, "d", false, "")
flag.BoolVar(&opts.download, "download", false, "")
flag.BoolVar(&opts.dump, "dump", false, "")
flag.StringVar(&opts.output, "o", "", "")
flag.StringVar(&opts.output, "output", "", "")
flag.BoolVar(&opts.version, "version", false, "")
flag.BoolVar(&opts.listLicenseNames, "list-names", false, "")
flag.BoolVar(&opts.listLicenses, "list-licenses", false, "")
flag.StringVar(&opts.excluded, "e", "", "")
flag.StringVar(&opts.excluded, "excluded", "", "")
flag.Parse()
if opts.version {
fmt.Println("lian", version())
os.Exit(0)
}
if opts.listLicenses {
listLicenses()
os.Exit(0)
}
if opts.listLicenseNames {
listLicenseNames()
os.Exit(0)
}
path := "go.mod"
if len(flag.Args()) > 0 {
path = flag.Arg(0)
}
mi, err := getModuleInfo(path)
if err != nil {
fmt.Fprintln(os.Stderr, err)
os.Exit(1)
}
if opts.download {
err = downloadModules(mi)
if err != nil {
fmt.Fprintln(os.Stderr, err)
os.Exit(1)
}
}
excluded := splitCommaSeparatedFlag(opts.excluded)
excludedExist := map[string]struct{}{}
for _, e := range excluded {
excludedExist[e] = struct{}{}
}
gomodcache := getGoModCache()
licenses, err := getLicenses(gomodcache, mi, licenseNames, excludedExist)
if err != nil {
fmt.Fprintln(os.Stderr, err)
os.Exit(1)
}
var w io.Writer
w = os.Stdout
if opts.output != "" {
f, err := os.Create(opts.output)
if err != nil {
fmt.Fprintln(os.Stderr, err)
os.Exit(1)
}
defer f.Close()
w = f
}
if opts.dump {
dump(w, licenses)
os.Exit(0)
}
err = report(w, licenses, opts)
if err != nil {
os.Exit(1)
}
}
func dump(w io.Writer, licenses []license) {
// add the Go Programming Language license
fmt.Fprintln(w, golangLicense)
for _, l := range licenses {
fmt.Fprintf(w, "* %s - (https://%s)\n", l.Version.String(), l.Version.Path)
fmt.Fprintln(w)
fmt.Fprintf(w, "%s", l.Content)
fmt.Fprintln(w)
}
}
func report(w io.Writer, licenses []license, opts options) error {
tw := tabwriter.NewWriter(w, 0, 0, 2, ' ', tabwriter.Debug)
defer tw.Flush()
// the table header
th := "License\tDependency\tFile\tpkg.go.dev URL"
allowed := splitCommaSeparatedFlag(opts.allowed)
if len(allowed) > 0 {
th = "Allowed\t" + th
}
// print the table header
fmt.Fprintln(tw, th)
merr := map[string]error{}
for _, l := range licenses {
row := fmt.Sprintf("%s\t%s\t%s\thttps://pkg.go.dev/%s?tab=licenses", l.Type, l.Version.String(), l.Name, l.Version.String())
if len(allowed) == 0 {
// no allowed rule, print the data and continue
fmt.Fprintln(tw, row)
continue
}
// check for allowed license and add result to the report row
isAllowedColumn := "Yes\t"
if isAllowedLicense(l, allowed) {
// need to keep track of valid license in case of dual license
merr[l.Path] = nil
} else {
// dual license check
if _, ok := merr[l.Path]; !ok {
merr[l.Path] = fmt.Errorf("license not allowed: license %q - dependency %q", l.Type, l.Path)
}
isAllowedColumn = "No\t"
}
row = isAllowedColumn + row
fmt.Fprintln(tw, row)
}
for _, err := range merr {
if err != nil {
return err
}
}
return nil
}
func isAllowedLicense(l license, allowed []string) bool {
if len(allowed) == 0 {
return true
}
for _, v := range allowed {
if v == l.Type {
return true
}
}
return false
}
func version() string {
if Version != "" {
return Version
}
if info, ok := debug.ReadBuildInfo(); ok {
return info.Main.Version
}
return "(unknown)"
}
func splitCommaSeparatedFlag(s string) []string {
if s == "" {
return []string{}
}
var r []string
for _, v := range strings.Split(s, ",") {
v := strings.TrimSpace(v)
if v != "" {
r = append(r, v)
}
}
return r
}