Skip to content

Commit 78aa037

Browse files
authored
Provide --summary flag to generate the license summary file (#103)
1 parent 985866c commit 78aa037

13 files changed

Lines changed: 553 additions & 50 deletions

File tree

README.md

Lines changed: 307 additions & 3 deletions
Large diffs are not rendered by default.

commands/deps_resolve.go

Lines changed: 46 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ import (
2323
"path/filepath"
2424
"regexp"
2525
"strings"
26+
"text/template"
2627

2728
"github.com/spf13/cobra"
2829

@@ -31,10 +32,15 @@ import (
3132
)
3233

3334
var outDir string
35+
var summaryTplPath string
36+
var summaryTpl *template.Template
3437

3538
func init() {
3639
DepsResolveCommand.PersistentFlags().StringVarP(&outDir, "output", "o", "",
3740
"the directory to output the resolved dependencies' licenses, if not set the dependencies' licenses won't be saved")
41+
DepsResolveCommand.PersistentFlags().StringVarP(&summaryTplPath, "summary", "s", "",
42+
"the template file to write the summary of dependencies' licenses, a new file named \"LICENSE\" will be "+
43+
"created in the same directory as the template file, to save the final summary.")
3844
}
3945

4046
var fileNamePattern = regexp.MustCompile(`[^a-zA-Z0-9\\.\-]`)
@@ -44,16 +50,27 @@ var DepsResolveCommand = &cobra.Command{
4450
Aliases: []string{"r"},
4551
Long: "resolves all dependencies of a module and their transitive dependencies",
4652
PreRunE: func(cmd *cobra.Command, args []string) error {
47-
if outDir == "" {
48-
return nil
49-
}
50-
absPath, err := filepath.Abs(outDir)
51-
if err != nil {
52-
return err
53+
if outDir != "" {
54+
absPath, err := filepath.Abs(outDir)
55+
if err != nil {
56+
return err
57+
}
58+
outDir = absPath
59+
if err := os.MkdirAll(outDir, 0o700); err != nil && !os.IsExist(err) {
60+
return err
61+
}
5362
}
54-
outDir = absPath
55-
if err := os.MkdirAll(outDir, 0o700); err != nil && !os.IsExist(err) {
56-
return err
63+
if summaryTplPath != "" {
64+
absPath, err := filepath.Abs(summaryTplPath)
65+
if err != nil {
66+
return err
67+
}
68+
summaryTplPath = absPath
69+
tpl, err := deps.ParseTemplate(summaryTplPath)
70+
if err != nil {
71+
return err
72+
}
73+
summaryTpl = tpl
5774
}
5875
return nil
5976
},
@@ -64,6 +81,12 @@ var DepsResolveCommand = &cobra.Command{
6481
return err
6582
}
6683

84+
if summaryTpl != nil {
85+
if err := writeSummary(&report); err != nil {
86+
return err
87+
}
88+
}
89+
6790
if outDir != "" {
6891
for _, result := range report.Resolved {
6992
writeLicense(result)
@@ -102,3 +125,17 @@ func writeLicense(result *deps.Result) {
102125
return
103126
}
104127
}
128+
129+
func writeSummary(rep *deps.Report) error {
130+
file, err := os.Create(filepath.Join(filepath.Dir(summaryTplPath), "LICENSE"))
131+
if err != nil {
132+
return err
133+
}
134+
defer file.Close()
135+
summary, err := deps.GenerateSummary(summaryTpl, &Config.Header, rep)
136+
if err != nil {
137+
return err
138+
}
139+
_, err = file.WriteString(summary)
140+
return err
141+
}

pkg/deps/config.go

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,14 @@ import (
2323
)
2424

2525
type ConfigDeps struct {
26-
Files []string `yaml:"files"`
26+
Files []string `yaml:"files"`
27+
License []*ConfigDepLicense `yaml:"licenses"`
28+
}
29+
30+
type ConfigDepLicense struct {
31+
Name string `yaml:"name"`
32+
Version string `yaml:"version"`
33+
License string `yaml:"license"`
2734
}
2835

2936
func (config *ConfigDeps) Finalize(configFile string) error {

pkg/deps/golang.go

Lines changed: 22 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,7 @@ func (resolver *GoModResolver) CanResolve(file string) bool {
4545
}
4646

4747
// Resolve resolves licenses of all dependencies declared in the go.mod file.
48-
func (resolver *GoModResolver) Resolve(goModFile string, report *Report) error {
48+
func (resolver *GoModResolver) Resolve(goModFile string, licenses []*ConfigDepLicense, report *Report) error {
4949
if err := os.Chdir(filepath.Dir(goModFile)); err != nil {
5050
return err
5151
}
@@ -78,13 +78,19 @@ func (resolver *GoModResolver) Resolve(goModFile string, report *Report) error {
7878

7979
logger.Log.Debugln("Module size:", len(modules))
8080

81-
return resolver.ResolvePackages(modules, report)
81+
return resolver.ResolvePackages(modules, licenses, report)
8282
}
8383

8484
// ResolvePackages resolves the licenses of the given packages.
85-
func (resolver *GoModResolver) ResolvePackages(modules []*packages.Module, report *Report) error {
85+
func (resolver *GoModResolver) ResolvePackages(modules []*packages.Module, licenses []*ConfigDepLicense, report *Report) error {
8686
for _, module := range modules {
87-
err := resolver.ResolvePackageLicense(module, report)
87+
var decalreLicense *ConfigDepLicense
88+
for _, l := range licenses {
89+
if l.Name == module.Path && l.Version == module.Version {
90+
decalreLicense = l
91+
}
92+
}
93+
err := resolver.ResolvePackageLicense(module, decalreLicense, report)
8894
if err != nil {
8995
logger.Log.Warnf("Failed to resolve the license of <%s>: %v\n", module.Path, err)
9096
report.Skip(&Result{
@@ -99,7 +105,7 @@ func (resolver *GoModResolver) ResolvePackages(modules []*packages.Module, repor
99105

100106
var possibleLicenseFileName = regexp.MustCompile(`(?i)^LICENSE|LICENCE(\.txt)?|COPYING(\.txt)?$`)
101107

102-
func (resolver *GoModResolver) ResolvePackageLicense(module *packages.Module, report *Report) error {
108+
func (resolver *GoModResolver) ResolvePackageLicense(module *packages.Module, declareLicense *ConfigDepLicense, report *Report) error {
103109
dir := module.Dir
104110

105111
for {
@@ -117,15 +123,22 @@ func (resolver *GoModResolver) ResolvePackageLicense(module *packages.Module, re
117123
if err != nil {
118124
return err
119125
}
120-
identifier, err := license.Identify(module.Path, string(content))
121-
if err != nil {
122-
return err
126+
var licenseID string
127+
if declareLicense != nil {
128+
licenseID = declareLicense.License
129+
} else {
130+
identifier, err := license.Identify(module.Path, string(content))
131+
if err != nil {
132+
return err
133+
}
134+
licenseID = identifier
123135
}
136+
124137
report.Resolve(&Result{
125138
Dependency: module.Path,
126139
LicenseFilePath: licenseFilePath,
127140
LicenseContent: string(content),
128-
LicenseSpdxID: identifier,
141+
LicenseSpdxID: licenseID,
129142
Version: module.Version,
130143
})
131144
return nil

pkg/deps/jar.go

Lines changed: 13 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,7 @@ func (resolver *JarResolver) CanResolve(jarFile string) bool {
3737
return filepath.Ext(jarFile) == ".jar"
3838
}
3939

40-
func (resolver *JarResolver) Resolve(jarFile string, report *Report) error {
40+
func (resolver *JarResolver) Resolve(jarFile string, licenses []*ConfigDepLicense, report *Report) error {
4141
state := NotFound
4242
if err := resolver.ResolveJar(&state, jarFile, Unknown, report); err != nil {
4343
dep := filepath.Base(jarFile)
@@ -76,7 +76,7 @@ func (resolver *JarResolver) ResolveJar(state *State, jarFile, version string, r
7676
return err
7777
}
7878

79-
return resolver.IdentifyLicense(jarFile, dep, buf.String(), version, report)
79+
return resolver.IdentifyLicense(jarFile, dep, buf.String(), version, nil, report)
8080
}
8181
}
8282

@@ -122,17 +122,23 @@ func (resolver *JarResolver) ReadFileFromZip(archiveFile *zip.File) (*bytes.Buff
122122
return buf, nil
123123
}
124124

125-
func (resolver *JarResolver) IdentifyLicense(path, dep, content, version string, report *Report) error {
126-
identifier, err := license.Identify(path, content)
127-
if err != nil {
128-
return err
125+
func (resolver *JarResolver) IdentifyLicense(path, dep, content, version string, declareLicense *ConfigDepLicense, report *Report) error {
126+
var licenseID string
127+
if declareLicense != nil {
128+
licenseID = declareLicense.License
129+
} else {
130+
identifier, err := license.Identify(path, content)
131+
if err != nil {
132+
return err
133+
}
134+
licenseID = identifier
129135
}
130136

131137
report.Resolve(&Result{
132138
Dependency: dep,
133139
LicenseFilePath: path,
134140
LicenseContent: content,
135-
LicenseSpdxID: identifier,
141+
LicenseSpdxID: licenseID,
136142
Version: version,
137143
})
138144
return nil

pkg/deps/jar_test.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -132,7 +132,7 @@ func TestResolveJar(t *testing.T) {
132132
report := deps.Report{}
133133
for _, jar := range jars {
134134
if resolver.CanResolve(jar) {
135-
if err := resolver.Resolve(jar, &report); err != nil {
135+
if err := resolver.Resolve(jar, nil, &report); err != nil {
136136
t.Error(err)
137137
return
138138
}

pkg/deps/maven.go

Lines changed: 14 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,7 @@ func (resolver *MavenPomResolver) CanResolve(mavenPomFile string) bool {
4848
}
4949

5050
// Resolve resolves licenses of all dependencies declared in the pom.xml file.
51-
func (resolver *MavenPomResolver) Resolve(mavenPomFile string, report *Report) error {
51+
func (resolver *MavenPomResolver) Resolve(mavenPomFile string, licenses []*ConfigDepLicense, report *Report) error {
5252
if err := os.Chdir(filepath.Dir(mavenPomFile)); err != nil {
5353
return err
5454
}
@@ -70,7 +70,7 @@ func (resolver *MavenPomResolver) Resolve(mavenPomFile string, report *Report) e
7070
}
7171
}
7272

73-
return resolver.ResolveDependencies(deps, report)
73+
return resolver.ResolveDependencies(deps, licenses, report)
7474
}
7575

7676
// CheckMVN check available maven tools, find local repositories and download all dependencies
@@ -142,10 +142,16 @@ func (resolver *MavenPomResolver) LoadDependencies() ([]*Dependency, error) {
142142
}
143143

144144
// ResolveDependencies resolves the licenses of the given dependencies
145-
func (resolver *MavenPomResolver) ResolveDependencies(deps []*Dependency, report *Report) error {
145+
func (resolver *MavenPomResolver) ResolveDependencies(deps []*Dependency, licenses []*ConfigDepLicense, report *Report) error {
146146
for _, dep := range deps {
147147
state := NotFound
148-
err := resolver.ResolveLicense(&state, dep, report)
148+
var declareLicense *ConfigDepLicense
149+
for _, l := range licenses {
150+
if l.Name == fmt.Sprintf("%s:%s", dep.GroupID, dep.ArtifactID) && l.Version == dep.Version {
151+
declareLicense = l
152+
}
153+
}
154+
err := resolver.ResolveLicense(&state, dep, declareLicense, report)
149155
if err != nil {
150156
logger.Log.Warnf("Failed to resolve the license of <%s>: %v\n", dep.Jar(), state.String())
151157
report.Skip(&Result{
@@ -159,17 +165,17 @@ func (resolver *MavenPomResolver) ResolveDependencies(deps []*Dependency, report
159165
}
160166

161167
// ResolveLicense search all possible locations of the license, such as pom file, jar package
162-
func (resolver *MavenPomResolver) ResolveLicense(state *State, dep *Dependency, report *Report) error {
168+
func (resolver *MavenPomResolver) ResolveLicense(state *State, dep *Dependency, declareLicense *ConfigDepLicense, report *Report) error {
163169
err := resolver.ResolveJar(state, filepath.Join(resolver.repo, dep.Path(), dep.Jar()), dep.Version, report)
164170
if err == nil {
165171
return nil
166172
}
167173

168-
return resolver.ResolveLicenseFromPom(state, dep, report)
174+
return resolver.ResolveLicenseFromPom(state, dep, declareLicense, report)
169175
}
170176

171177
// ResolveLicenseFromPom search for license in the pom file, which may appear in the header comments or in license element of xml
172-
func (resolver *MavenPomResolver) ResolveLicenseFromPom(state *State, dep *Dependency, report *Report) (err error) {
178+
func (resolver *MavenPomResolver) ResolveLicenseFromPom(state *State, dep *Dependency, declareLicense *ConfigDepLicense, report *Report) (err error) {
173179
pomFile := filepath.Join(resolver.repo, dep.Path(), dep.Pom())
174180

175181
pom, err := resolver.ReadLicensesFromPom(pomFile)
@@ -192,7 +198,7 @@ func (resolver *MavenPomResolver) ResolveLicenseFromPom(state *State, dep *Depen
192198
return err
193199
} else if headerComments != "" {
194200
*state |= FoundLicenseInPomHeader
195-
return resolver.IdentifyLicense(pomFile, dep.Jar(), headerComments, dep.Version, report)
201+
return resolver.IdentifyLicense(pomFile, dep.Jar(), headerComments, dep.Version, declareLicense, report)
196202
}
197203

198204
return fmt.Errorf("not found in pom file")

pkg/deps/maven_test.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -113,7 +113,7 @@ func TestResolveMaven(t *testing.T) {
113113

114114
if resolver.CanResolve(pomFile) {
115115
report := deps.Report{}
116-
if err := resolver.Resolve(pomFile, &report); err != nil {
116+
if err := resolver.Resolve(pomFile, nil, &report); err != nil {
117117
t.Error(err)
118118
return
119119
}

pkg/deps/npm.go

Lines changed: 20 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -63,7 +63,7 @@ func (resolver *NpmResolver) CanResolve(file string) bool {
6363
}
6464

6565
// Resolve resolves licenses of all dependencies declared in the package.json file.
66-
func (resolver *NpmResolver) Resolve(pkgFile string, report *Report) error {
66+
func (resolver *NpmResolver) Resolve(pkgFile string, licenses []*ConfigDepLicense, report *Report) error {
6767
workDir := filepath.Dir(pkgFile)
6868
if err := os.Chdir(workDir); err != nil {
6969
return err
@@ -85,7 +85,7 @@ func (resolver *NpmResolver) Resolve(pkgFile string, report *Report) error {
8585
// Walk through each package's root directory to resolve licenses
8686
// Resolve from a package's package.json file or its license file
8787
for _, pkg := range pkgs {
88-
if result := resolver.ResolvePackageLicense(pkg.Name, pkg.Path); result.LicenseSpdxID != "" {
88+
if result := resolver.ResolvePackageLicense(pkg.Name, pkg.Path, licenses); result.LicenseSpdxID != "" {
8989
report.Resolve(result)
9090
} else {
9191
result.LicenseSpdxID = Unknown
@@ -185,32 +185,39 @@ func (resolver *NpmResolver) GetInstalledPkgs(pkgDir string) []*Package {
185185
// First, try to find and parse the package's package.json file to check the license file
186186
// If the previous step fails, then try to identify the package's LICENSE file
187187
// It's a necessary procedure to check the LICENSE file, because the resolver needs to record the license content
188-
func (resolver *NpmResolver) ResolvePackageLicense(pkgName, pkgPath string) *Result {
188+
func (resolver *NpmResolver) ResolvePackageLicense(pkgName, pkgPath string, licenses []*ConfigDepLicense) *Result {
189189
result := &Result{
190190
Dependency: pkgName,
191191
}
192192
// resolve from the package.json file
193-
if err := resolver.ResolvePkgFile(result, pkgPath); err != nil {
193+
if err := resolver.ResolvePkgFile(result, pkgPath, licenses); err != nil {
194194
result.ResolveErrors = append(result.ResolveErrors, err)
195195
}
196196

197197
// resolve from the LICENSE file
198-
if err := resolver.ResolveLcsFile(result, pkgPath); err != nil {
198+
if err := resolver.ResolveLcsFile(result, pkgPath, licenses); err != nil {
199199
result.ResolveErrors = append(result.ResolveErrors, err)
200200
}
201201

202202
return result
203203
}
204204

205205
// ResolvePkgFile tries to find and parse the package.json file to capture the license field
206-
func (resolver *NpmResolver) ResolvePkgFile(result *Result, pkgPath string) error {
206+
func (resolver *NpmResolver) ResolvePkgFile(result *Result, pkgPath string, licenses []*ConfigDepLicense) error {
207207
expectedPkgFile := filepath.Join(pkgPath, PkgFileName)
208208
packageInfo, err := resolver.ParsePkgFile(expectedPkgFile)
209209
if err != nil {
210210
return err
211211
}
212212

213213
result.Version = packageInfo.Version
214+
for _, l := range licenses {
215+
if l.Name == packageInfo.Name && l.Version == packageInfo.Version {
216+
result.LicenseSpdxID = l.License
217+
return nil
218+
}
219+
}
220+
214221
if lcs, ok := resolver.ResolveLicenseField(packageInfo.License); ok {
215222
result.LicenseSpdxID = lcs
216223
return nil
@@ -259,7 +266,7 @@ func (resolver *NpmResolver) ResolveLicensesField(licenses []Lcs) (string, bool)
259266
}
260267

261268
// ResolveLcsFile tries to find the license file to identify the license
262-
func (resolver *NpmResolver) ResolveLcsFile(result *Result, pkgPath string) error {
269+
func (resolver *NpmResolver) ResolveLcsFile(result *Result, pkgPath string, licenses []*ConfigDepLicense) error {
263270
depFiles, err := os.ReadDir(pkgPath)
264271
if err != nil {
265272
return err
@@ -278,6 +285,12 @@ func (resolver *NpmResolver) ResolveLcsFile(result *Result, pkgPath string) erro
278285
if result.LicenseSpdxID != "" {
279286
return nil
280287
}
288+
for _, l := range licenses {
289+
if l.Name == info.Name() && l.Version == result.Version {
290+
result.LicenseSpdxID = l.License
291+
return nil
292+
}
293+
}
281294
identifier, err := license.Identify(result.Dependency, string(content))
282295
if err != nil {
283296
return err

0 commit comments

Comments
 (0)