From 5de260e73b1608e62de7656781286279549d9ef8 Mon Sep 17 00:00:00 2001 From: Mrproliu <741550557@qq.com> Date: Fri, 6 May 2022 17:17:53 +0800 Subject: [PATCH 1/7] Provide summary flag to generate the license summary file --- README.md | 18 +++++- commands/deps_resolve.go | 60 +++++++++++++++----- pkg/deps/config.go | 9 ++- pkg/deps/golang.go | 21 +++++-- pkg/deps/jar.go | 11 ++-- pkg/deps/jar_test.go | 2 +- pkg/deps/maven.go | 22 +++++--- pkg/deps/maven_test.go | 2 +- pkg/deps/npm.go | 17 +++++- pkg/deps/resolve.go | 4 +- pkg/deps/summary.go | 114 ++++++++++++++++++++++++++++++++++++++ pkg/license/identifier.go | 22 ++++++++ 12 files changed, 261 insertions(+), 41 deletions(-) create mode 100644 pkg/deps/summary.go diff --git a/README.md b/README.md index f2bb97ce..fde8e655 100644 --- a/README.md +++ b/README.md @@ -157,11 +157,15 @@ INFO Totally checked 20 files, valid: 10, invalid: 10, ignored: 0, fixed: 10 This command serves as assistance for human beings to audit the dependencies license, it's exit code is always 0. -You can also use the `--output` or `-o` to save the dependencies' `LICENSE` files to a specified directory so that -you can put them in distribution package if needed. +We also support two flag: + +|Flag name|Short name|Description| +|---------|----------|-----------| +|--output|-o|Save the dependencies' `LICENSE` files to a specified directory so that you can put them in distribution package if needed.| +|--summary|-s|Based on the template, aggregate all dependency information and generate a `LICENSE` file.| ```bash -license-eye -c test/testdata/.licenserc_for_test_check.yaml dep resolve -o ./dependencies/licenses +license-eye -c test/testdata/.licenserc_for_test_check.yaml dep resolve -o ./dependencies/licenses -s LICENSE.tpl ```
@@ -468,6 +472,10 @@ header: # <1> dependency: # <15> files: # <16> - go.mod + license: # <17> + - name: dependency-name # <18> + version: dependency-version # <19> + license: Apache-2.0 # <20> ``` 1. The `header` section is configurations for source codes license header. @@ -486,6 +494,10 @@ dependency: # <15> 14. The `comment_style_id` set the license header comment style, it's the `id` at the `styles.yaml`. 15. The `dependency` section is configurations for resolving dependencies' licenses. 16. The `files` are the files that declare the dependencies of a project, typically, `go.mod` in Go project, `pom.xml` in maven project, and `package.json` in NodeJS project. If it's a relative path, it's relative to the `.licenserc.yaml`. +17. Declare the license which cannot be identified as a dependency. +18. The `name` of the dependency, The name is different for different projects, `PackagePath` in Go project, `GroupID:ArtifactID` in maven project, `PackageName` in NodeJS project. +19. The `version` of the dependency, It's locked, prevent version update and change the License. +20. The [SPDX ID](https://spdx.org/licenses/) of the dependency license. **NOTE**: When the `SPDX-ID` is Apache-2.0 and the owner is Apache Software foundation, the content would be [a dedicated license](https://www.apache.org/legal/src-headers.html#headers) specified by the ASF, otherwise, the license would be [the standard one](https://www.apache.org/foundation/license-faq.html#Apply-My-Software). diff --git a/commands/deps_resolve.go b/commands/deps_resolve.go index d242eeb6..7ad20745 100644 --- a/commands/deps_resolve.go +++ b/commands/deps_resolve.go @@ -21,8 +21,8 @@ import ( "fmt" "os" "path/filepath" - "regexp" "strings" + "text/template" "github.com/spf13/cobra" @@ -31,29 +31,40 @@ import ( ) var outDir string +var summaryTplPath string +var summaryTpl *template.Template func init() { DepsResolveCommand.PersistentFlags().StringVarP(&outDir, "output", "o", "", "the directory to output the resolved dependencies' licenses, if not set the dependencies' licenses won't be saved") + DepsResolveCommand.PersistentFlags().StringVarP(&summaryTplPath, "summary", "s", "", + "the template file to write the summary the dependencies' licenses, if not set the licenses just print as the table") } -var fileNamePattern = regexp.MustCompile(`[^a-zA-Z0-9\\.\-]`) - var DepsResolveCommand = &cobra.Command{ Use: "resolve", Aliases: []string{"r"}, Long: "resolves all dependencies of a module and their transitive dependencies", PreRunE: func(cmd *cobra.Command, args []string) error { - if outDir == "" { - return nil - } - absPath, err := filepath.Abs(outDir) - if err != nil { - return err + if outDir != "" { + absPath, err := filepath.Abs(outDir) + if err != nil { + return err + } + outDir = absPath + if err := os.MkdirAll(outDir, 0o700); err != nil && !os.IsExist(err) { + return err + } } - outDir = absPath - if err := os.MkdirAll(outDir, 0o700); err != nil && !os.IsExist(err) { - return err + if summaryTplPath != "" { + if outDir == "" { + return fmt.Errorf("please provide the output directory to write the license summary file") + } + tpl, err := deps.ParseTemplate(summaryTplPath) + if err != nil { + return err + } + summaryTpl = tpl } return nil }, @@ -70,6 +81,10 @@ var DepsResolveCommand = &cobra.Command{ } } + if summaryTpl != nil { + writeSummary(&report) + } + fmt.Println(report.String()) if skipped := len(report.Skipped); skipped > 0 { @@ -88,8 +103,7 @@ var DepsResolveCommand = &cobra.Command{ } func writeLicense(result *deps.Result) { - filename := string(fileNamePattern.ReplaceAll([]byte(result.Dependency), []byte("-"))) - filename = filepath.Join(outDir, "license-"+filename+".txt") + filename := filepath.Join(outDir, deps.GenerateDependencyLicenseFilename(result)) file, err := os.Create(filename) if err != nil { logger.Log.Errorf("failed to create license file %v: %v", filename, err) @@ -102,3 +116,21 @@ func writeLicense(result *deps.Result) { return } } + +func writeSummary(rep *deps.Report) { + file, err := os.Create(filepath.Join(outDir, "LICENSE")) + if err != nil { + logger.Log.Errorf("failed to create summary license file %s: %v", "LICENSE", err) + return + } + defer file.Close() + summary, err := deps.GenerateSummary(summaryTpl, &Config.Header, rep) + if err != nil { + logger.Log.Errorf("failed to generate summary content: %v", err) + return + } + _, err = file.WriteString(summary) + if err != nil { + logger.Log.Errorf("failed to write summary file, %v", err) + } +} diff --git a/pkg/deps/config.go b/pkg/deps/config.go index 0633b96c..64c0a53b 100644 --- a/pkg/deps/config.go +++ b/pkg/deps/config.go @@ -23,7 +23,14 @@ import ( ) type ConfigDeps struct { - Files []string `yaml:"files"` + Files []string `yaml:"files"` + License []*ConfigDepLicense `yaml:"licenses"` +} + +type ConfigDepLicense struct { + Name string `yaml:"name"` + Version string `yaml:"version"` + License string `yaml:"license"` } func (config *ConfigDeps) Finalize(configFile string) error { diff --git a/pkg/deps/golang.go b/pkg/deps/golang.go index 063fbf32..7e3af85d 100644 --- a/pkg/deps/golang.go +++ b/pkg/deps/golang.go @@ -45,7 +45,7 @@ func (resolver *GoModResolver) CanResolve(file string) bool { } // Resolve resolves licenses of all dependencies declared in the go.mod file. -func (resolver *GoModResolver) Resolve(goModFile string, report *Report) error { +func (resolver *GoModResolver) Resolve(goModFile string, licenses []*ConfigDepLicense, report *Report) error { if err := os.Chdir(filepath.Dir(goModFile)); err != nil { return err } @@ -78,13 +78,19 @@ func (resolver *GoModResolver) Resolve(goModFile string, report *Report) error { logger.Log.Debugln("Module size:", len(modules)) - return resolver.ResolvePackages(modules, report) + return resolver.ResolvePackages(modules, licenses, report) } // ResolvePackages resolves the licenses of the given packages. -func (resolver *GoModResolver) ResolvePackages(modules []*packages.Module, report *Report) error { +func (resolver *GoModResolver) ResolvePackages(modules []*packages.Module, licenses []*ConfigDepLicense, report *Report) error { for _, module := range modules { - err := resolver.ResolvePackageLicense(module, report) + var decalreLicense *ConfigDepLicense + for _, l := range licenses { + if l.Name == module.Path && l.Version == module.Version { + decalreLicense = l + } + } + err := resolver.ResolvePackageLicense(module, decalreLicense, report) if err != nil { logger.Log.Warnf("Failed to resolve the license of <%s>: %v\n", module.Path, err) report.Skip(&Result{ @@ -99,7 +105,7 @@ func (resolver *GoModResolver) ResolvePackages(modules []*packages.Module, repor var possibleLicenseFileName = regexp.MustCompile(`(?i)^LICENSE|LICENCE(\.txt)?|COPYING(\.txt)?$`) -func (resolver *GoModResolver) ResolvePackageLicense(module *packages.Module, report *Report) error { +func (resolver *GoModResolver) ResolvePackageLicense(module *packages.Module, declareLicense *ConfigDepLicense, report *Report) error { dir := module.Dir for { @@ -119,7 +125,10 @@ func (resolver *GoModResolver) ResolvePackageLicense(module *packages.Module, re } identifier, err := license.Identify(module.Path, string(content)) if err != nil { - return err + if declareLicense == nil { + return err + } + identifier = declareLicense.License } report.Resolve(&Result{ Dependency: module.Path, diff --git a/pkg/deps/jar.go b/pkg/deps/jar.go index fada99c4..09868bc0 100644 --- a/pkg/deps/jar.go +++ b/pkg/deps/jar.go @@ -37,7 +37,7 @@ func (resolver *JarResolver) CanResolve(jarFile string) bool { return filepath.Ext(jarFile) == ".jar" } -func (resolver *JarResolver) Resolve(jarFile string, report *Report) error { +func (resolver *JarResolver) Resolve(jarFile string, licenses []*ConfigDepLicense, report *Report) error { state := NotFound if err := resolver.ResolveJar(&state, jarFile, Unknown, report); err != nil { dep := filepath.Base(jarFile) @@ -76,7 +76,7 @@ func (resolver *JarResolver) ResolveJar(state *State, jarFile, version string, r return err } - return resolver.IdentifyLicense(jarFile, dep, buf.String(), version, report) + return resolver.IdentifyLicense(jarFile, dep, buf.String(), version, nil, report) } } @@ -122,10 +122,13 @@ func (resolver *JarResolver) ReadFileFromZip(archiveFile *zip.File) (*bytes.Buff return buf, nil } -func (resolver *JarResolver) IdentifyLicense(path, dep, content, version string, report *Report) error { +func (resolver *JarResolver) IdentifyLicense(path, dep, content, version string, declareLicense *ConfigDepLicense, report *Report) error { identifier, err := license.Identify(path, content) if err != nil { - return err + if declareLicense == nil { + return err + } + identifier = declareLicense.License } report.Resolve(&Result{ diff --git a/pkg/deps/jar_test.go b/pkg/deps/jar_test.go index de31b0b1..62156859 100644 --- a/pkg/deps/jar_test.go +++ b/pkg/deps/jar_test.go @@ -132,7 +132,7 @@ func TestResolveJar(t *testing.T) { report := deps.Report{} for _, jar := range jars { if resolver.CanResolve(jar) { - if err := resolver.Resolve(jar, &report); err != nil { + if err := resolver.Resolve(jar, nil, &report); err != nil { t.Error(err) return } diff --git a/pkg/deps/maven.go b/pkg/deps/maven.go index 32740746..28d1ce33 100644 --- a/pkg/deps/maven.go +++ b/pkg/deps/maven.go @@ -48,7 +48,7 @@ func (resolver *MavenPomResolver) CanResolve(mavenPomFile string) bool { } // Resolve resolves licenses of all dependencies declared in the pom.xml file. -func (resolver *MavenPomResolver) Resolve(mavenPomFile string, report *Report) error { +func (resolver *MavenPomResolver) Resolve(mavenPomFile string, licenses []*ConfigDepLicense, report *Report) error { if err := os.Chdir(filepath.Dir(mavenPomFile)); err != nil { return err } @@ -70,7 +70,7 @@ func (resolver *MavenPomResolver) Resolve(mavenPomFile string, report *Report) e } } - return resolver.ResolveDependencies(deps, report) + return resolver.ResolveDependencies(deps, licenses, report) } // CheckMVN check available maven tools, find local repositories and download all dependencies @@ -142,10 +142,16 @@ func (resolver *MavenPomResolver) LoadDependencies() ([]*Dependency, error) { } // ResolveDependencies resolves the licenses of the given dependencies -func (resolver *MavenPomResolver) ResolveDependencies(deps []*Dependency, report *Report) error { +func (resolver *MavenPomResolver) ResolveDependencies(deps []*Dependency, licenses []*ConfigDepLicense, report *Report) error { for _, dep := range deps { state := NotFound - err := resolver.ResolveLicense(&state, dep, report) + var declareLicense *ConfigDepLicense + for _, l := range licenses { + if l.Name == fmt.Sprintf("%s:%s", dep.GroupID, dep.ArtifactID) && l.Version == dep.Version { + declareLicense = l + } + } + err := resolver.ResolveLicense(&state, dep, declareLicense, report) if err != nil { logger.Log.Warnf("Failed to resolve the license of <%s>: %v\n", dep.Jar(), state.String()) report.Skip(&Result{ @@ -159,17 +165,17 @@ func (resolver *MavenPomResolver) ResolveDependencies(deps []*Dependency, report } // ResolveLicense search all possible locations of the license, such as pom file, jar package -func (resolver *MavenPomResolver) ResolveLicense(state *State, dep *Dependency, report *Report) error { +func (resolver *MavenPomResolver) ResolveLicense(state *State, dep *Dependency, declareLicense *ConfigDepLicense, report *Report) error { err := resolver.ResolveJar(state, filepath.Join(resolver.repo, dep.Path(), dep.Jar()), dep.Version, report) if err == nil { return nil } - return resolver.ResolveLicenseFromPom(state, dep, report) + return resolver.ResolveLicenseFromPom(state, dep, declareLicense, report) } // ResolveLicenseFromPom search for license in the pom file, which may appear in the header comments or in license element of xml -func (resolver *MavenPomResolver) ResolveLicenseFromPom(state *State, dep *Dependency, report *Report) (err error) { +func (resolver *MavenPomResolver) ResolveLicenseFromPom(state *State, dep *Dependency, declareLicense *ConfigDepLicense, report *Report) (err error) { pomFile := filepath.Join(resolver.repo, dep.Path(), dep.Pom()) pom, err := resolver.ReadLicensesFromPom(pomFile) @@ -192,7 +198,7 @@ func (resolver *MavenPomResolver) ResolveLicenseFromPom(state *State, dep *Depen return err } else if headerComments != "" { *state |= FoundLicenseInPomHeader - return resolver.IdentifyLicense(pomFile, dep.Jar(), headerComments, dep.Version, report) + return resolver.IdentifyLicense(pomFile, dep.Jar(), headerComments, dep.Version, declareLicense, report) } return fmt.Errorf("not found in pom file") diff --git a/pkg/deps/maven_test.go b/pkg/deps/maven_test.go index 5c4b7976..75c6ae05 100644 --- a/pkg/deps/maven_test.go +++ b/pkg/deps/maven_test.go @@ -113,7 +113,7 @@ func TestResolveMaven(t *testing.T) { if resolver.CanResolve(pomFile) { report := deps.Report{} - if err := resolver.Resolve(pomFile, &report); err != nil { + if err := resolver.Resolve(pomFile, nil, &report); err != nil { t.Error(err) return } diff --git a/pkg/deps/npm.go b/pkg/deps/npm.go index 77e814ea..d1d19274 100644 --- a/pkg/deps/npm.go +++ b/pkg/deps/npm.go @@ -63,7 +63,7 @@ func (resolver *NpmResolver) CanResolve(file string) bool { } // Resolve resolves licenses of all dependencies declared in the package.json file. -func (resolver *NpmResolver) Resolve(pkgFile string, report *Report) error { +func (resolver *NpmResolver) Resolve(pkgFile string, licenses []*ConfigDepLicense, report *Report) error { workDir := filepath.Dir(pkgFile) if err := os.Chdir(workDir); err != nil { return err @@ -88,6 +88,10 @@ func (resolver *NpmResolver) Resolve(pkgFile string, report *Report) error { if result := resolver.ResolvePackageLicense(pkg.Name, pkg.Path); result.LicenseSpdxID != "" { report.Resolve(result) } else { + if resolver.tryToResolve(pkg, licenses, result) { + report.Resolve(result) + continue + } result.LicenseSpdxID = Unknown report.Skip(result) logger.Log.Warnln("Failed to resolve the license of dependency:", pkg.Name, result.ResolveErrors) @@ -96,6 +100,17 @@ func (resolver *NpmResolver) Resolve(pkgFile string, report *Report) error { return nil } +// TryToResolve trying to resolve the license by declaration +func (resolver *NpmResolver) tryToResolve(p *Package, licenses []*ConfigDepLicense, r *Result) bool { + for _, l := range licenses { + if l.Name == p.Name && l.Version == p.Version { + r.LicenseSpdxID = l.License + return true + } + } + return false +} + // NeedSkipInstallPkgs queries whether to skip the procedure of installing or updating packages func (resolver *NpmResolver) NeedSkipInstallPkgs() bool { const countdown = 5 diff --git a/pkg/deps/resolve.go b/pkg/deps/resolve.go index 3c0d3b48..325b9d0e 100644 --- a/pkg/deps/resolve.go +++ b/pkg/deps/resolve.go @@ -23,7 +23,7 @@ import ( type Resolver interface { CanResolve(string) bool - Resolve(string, *Report) error + Resolve(string, []*ConfigDepLicense, *Report) error } var Resolvers = []Resolver{ @@ -39,7 +39,7 @@ resolveFile: if !resolver.CanResolve(file) { continue } - if err := resolver.Resolve(file, report); err != nil { + if err := resolver.Resolve(file, config.License, report); err != nil { return err } continue resolveFile diff --git a/pkg/deps/summary.go b/pkg/deps/summary.go new file mode 100644 index 00000000..e4195805 --- /dev/null +++ b/pkg/deps/summary.go @@ -0,0 +1,114 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +package deps + +import ( + "bytes" + "io/ioutil" + "path/filepath" + "regexp" + "text/template" + + "github.com/apache/skywalking-eyes/pkg/header" + "github.com/apache/skywalking-eyes/pkg/license" +) + +var fileNamePattern = regexp.MustCompile(`[^a-zA-Z0-9\\.\-]`) + +type SummaryRenderContext struct { + LicenseContent string // Current project license content + Groups []*SummaryRenderLicenseGroup // All dependency license groups +} + +type SummaryRenderLicenseGroup struct { + Name string // Aggregate all same license ID dependencies + Deps []*SummaryRenderLicense // Same license ID dependencies +} + +type SummaryRenderLicense struct { + Name string // Dependency name + Version string // Dependency version + Location string // The filename of generated license file + LicenseID string // License ID +} + +func GenerateDependencyLicenseFilename(result *Result) string { + filename := string(fileNamePattern.ReplaceAll([]byte(result.Dependency), []byte("-"))) + return "license-" + filename + ".txt" +} + +func ParseTemplate(path string) (*template.Template, error) { + absPath, err := filepath.Abs(path) + if err != nil { + return nil, err + } + tpl, err := ioutil.ReadFile(absPath) + if err != nil { + return nil, err + } + return template.New("summary").Parse(string(tpl)) +} + +// GenerateSummary generate the summary content by template, license config and dependency report +func GenerateSummary(tpl *template.Template, head *header.ConfigHeader, rep *Report) (string, error) { + var r bytes.Buffer + context, err := generateSummaryRenderContext(head, rep) + if err != nil { + return "", err + } + if err := tpl.Execute(&r, context); err != nil { + return "", err + } + return r.String(), nil +} + +func generateSummaryRenderContext(head *header.ConfigHeader, rep *Report) (*SummaryRenderContext, error) { + // the license id of the project + licenseContent, err := license.GetLicenseContent(head.License.SpdxID) + if err != nil { + return nil, err + } + + groups := make(map[string]*SummaryRenderLicenseGroup) + for _, r := range rep.Resolved { + group := groups[r.LicenseSpdxID] + if group == nil { + group = &SummaryRenderLicenseGroup{ + Name: r.LicenseSpdxID, + Deps: make([]*SummaryRenderLicense, 0), + } + groups[r.LicenseSpdxID] = group + } + + group.Deps = append(group.Deps, &SummaryRenderLicense{ + Name: r.Dependency, + Version: r.Version, + Location: GenerateDependencyLicenseFilename(r), + LicenseID: r.LicenseSpdxID, + }) + } + + groupArray := make([]*SummaryRenderLicenseGroup, 0) + for _, g := range groups { + groupArray = append(groupArray, g) + } + return &SummaryRenderContext{ + LicenseContent: licenseContent, + Groups: groupArray, + }, nil +} diff --git a/pkg/license/identifier.go b/pkg/license/identifier.go index 7a09a154..167b8c4c 100644 --- a/pkg/license/identifier.go +++ b/pkg/license/identifier.go @@ -102,3 +102,25 @@ func Identify(pkgPath, content string) (string, error) { return "", fmt.Errorf("cannot identify license content") } } + +// GetLicenseContent from license id +func GetLicenseContent(spdxID string) (string, error) { + for _, dir := range templatesDirs { + files, err := assets.AssetDir(dir) + if err != nil { + continue + } + for _, f := range files { + if spdxID != strings.TrimSuffix(f.Name(), filepath.Ext(f.Name())) { + continue + } + + res, err := assets.Asset(filepath.Join(dir, f.Name())) + if err != nil { + return "", err + } + return string(res), nil + } + } + return "", fmt.Errorf("could not found license: %s", spdxID) +} From 6c21a03c5959ca725f48ca6f513977218ff4ab41 Mon Sep 17 00:00:00 2001 From: Mrproliu <741550557@qq.com> Date: Fri, 6 May 2022 17:30:36 +0800 Subject: [PATCH 2/7] fix CI --- pkg/deps/summary.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pkg/deps/summary.go b/pkg/deps/summary.go index e4195805..1907ffe1 100644 --- a/pkg/deps/summary.go +++ b/pkg/deps/summary.go @@ -19,7 +19,7 @@ package deps import ( "bytes" - "io/ioutil" + "os" "path/filepath" "regexp" "text/template" @@ -57,7 +57,7 @@ func ParseTemplate(path string) (*template.Template, error) { if err != nil { return nil, err } - tpl, err := ioutil.ReadFile(absPath) + tpl, err := os.ReadFile(absPath) if err != nil { return nil, err } From f7f978d6e1c38a014c55b575d8a7ef733aac758c Mon Sep 17 00:00:00 2001 From: Mrproliu <741550557@qq.com> Date: Fri, 6 May 2022 21:59:29 +0800 Subject: [PATCH 3/7] fix issue --- README.md | 10 +++++----- commands/deps_resolve.go | 32 ++++++++++++++++++-------------- pkg/deps/golang.go | 14 +++++++++----- pkg/deps/jar.go | 13 ++++++++----- pkg/deps/npm.go | 25 +++++++++++++++++++------ pkg/deps/npm_test.go | 2 +- pkg/deps/summary.go | 37 +++++++++++-------------------------- 7 files changed, 71 insertions(+), 62 deletions(-) diff --git a/README.md b/README.md index fde8e655..4a5bda0d 100644 --- a/README.md +++ b/README.md @@ -157,12 +157,12 @@ INFO Totally checked 20 files, valid: 10, invalid: 10, ignored: 0, fixed: 10 This command serves as assistance for human beings to audit the dependencies license, it's exit code is always 0. -We also support two flag: +We also support two flags: |Flag name|Short name|Description| |---------|----------|-----------| -|--output|-o|Save the dependencies' `LICENSE` files to a specified directory so that you can put them in distribution package if needed.| -|--summary|-s|Based on the template, aggregate all dependency information and generate a `LICENSE` file.| +|`--output`|`-o`|Save the dependencies' `LICENSE` files to a specified directory so that you can put them in distribution package if needed.| +|`--summary`|`-s`|Based on the template, aggregate all dependency information and generate a `LICENSE` file.| ```bash license-eye -c test/testdata/.licenserc_for_test_check.yaml dep resolve -o ./dependencies/licenses -s LICENSE.tpl @@ -494,9 +494,9 @@ dependency: # <15> 14. The `comment_style_id` set the license header comment style, it's the `id` at the `styles.yaml`. 15. The `dependency` section is configurations for resolving dependencies' licenses. 16. The `files` are the files that declare the dependencies of a project, typically, `go.mod` in Go project, `pom.xml` in maven project, and `package.json` in NodeJS project. If it's a relative path, it's relative to the `.licenserc.yaml`. -17. Declare the license which cannot be identified as a dependency. +17. Declare the licenses which cannot be identified by this tool. 18. The `name` of the dependency, The name is different for different projects, `PackagePath` in Go project, `GroupID:ArtifactID` in maven project, `PackageName` in NodeJS project. -19. The `version` of the dependency, It's locked, prevent version update and change the License. +19. The `version` of the dependency, it's locked, preventing license changed between different versions. 20. The [SPDX ID](https://spdx.org/licenses/) of the dependency license. **NOTE**: When the `SPDX-ID` is Apache-2.0 and the owner is Apache Software foundation, the content would be [a dedicated license](https://www.apache.org/legal/src-headers.html#headers) specified by the ASF, otherwise, the license would be [the standard one](https://www.apache.org/foundation/license-faq.html#Apply-My-Software). diff --git a/commands/deps_resolve.go b/commands/deps_resolve.go index 7ad20745..3e57bc14 100644 --- a/commands/deps_resolve.go +++ b/commands/deps_resolve.go @@ -21,6 +21,7 @@ import ( "fmt" "os" "path/filepath" + "regexp" "strings" "text/template" @@ -38,9 +39,11 @@ func init() { DepsResolveCommand.PersistentFlags().StringVarP(&outDir, "output", "o", "", "the directory to output the resolved dependencies' licenses, if not set the dependencies' licenses won't be saved") DepsResolveCommand.PersistentFlags().StringVarP(&summaryTplPath, "summary", "s", "", - "the template file to write the summary the dependencies' licenses, if not set the licenses just print as the table") + "the template file to write the summary the dependencies' licenses into \"LICENSE\" file into directory which same with template file.") } +var fileNamePattern = regexp.MustCompile(`[^a-zA-Z0-9\\.\-]`) + var DepsResolveCommand = &cobra.Command{ Use: "resolve", Aliases: []string{"r"}, @@ -57,9 +60,11 @@ var DepsResolveCommand = &cobra.Command{ } } if summaryTplPath != "" { - if outDir == "" { - return fmt.Errorf("please provide the output directory to write the license summary file") + absPath, err := filepath.Abs(summaryTplPath) + if err != nil { + return err } + summaryTplPath = absPath tpl, err := deps.ParseTemplate(summaryTplPath) if err != nil { return err @@ -82,7 +87,9 @@ var DepsResolveCommand = &cobra.Command{ } if summaryTpl != nil { - writeSummary(&report) + if err := writeSummary(&report); err != nil { + logger.Log.Warnf("write summary file error: %v", err) + } } fmt.Println(report.String()) @@ -103,7 +110,8 @@ var DepsResolveCommand = &cobra.Command{ } func writeLicense(result *deps.Result) { - filename := filepath.Join(outDir, deps.GenerateDependencyLicenseFilename(result)) + filename := string(fileNamePattern.ReplaceAll([]byte(result.Dependency), []byte("-"))) + filename = filepath.Join(outDir, "license-"+filename+".txt") file, err := os.Create(filename) if err != nil { logger.Log.Errorf("failed to create license file %v: %v", filename, err) @@ -117,20 +125,16 @@ func writeLicense(result *deps.Result) { } } -func writeSummary(rep *deps.Report) { - file, err := os.Create(filepath.Join(outDir, "LICENSE")) +func writeSummary(rep *deps.Report) error { + file, err := os.Create(filepath.Join(filepath.Dir(summaryTplPath), "LICENSE")) if err != nil { - logger.Log.Errorf("failed to create summary license file %s: %v", "LICENSE", err) - return + return err } defer file.Close() summary, err := deps.GenerateSummary(summaryTpl, &Config.Header, rep) if err != nil { - logger.Log.Errorf("failed to generate summary content: %v", err) - return + return err } _, err = file.WriteString(summary) - if err != nil { - logger.Log.Errorf("failed to write summary file, %v", err) - } + return err } diff --git a/pkg/deps/golang.go b/pkg/deps/golang.go index 7e3af85d..a65c20cf 100644 --- a/pkg/deps/golang.go +++ b/pkg/deps/golang.go @@ -123,18 +123,22 @@ func (resolver *GoModResolver) ResolvePackageLicense(module *packages.Module, de if err != nil { return err } - identifier, err := license.Identify(module.Path, string(content)) - if err != nil { - if declareLicense == nil { + var licenseID string + if declareLicense != nil { + licenseID = declareLicense.License + } else { + identifier, err := license.Identify(module.Path, string(content)) + if err != nil { return err } - identifier = declareLicense.License + licenseID = identifier } + report.Resolve(&Result{ Dependency: module.Path, LicenseFilePath: licenseFilePath, LicenseContent: string(content), - LicenseSpdxID: identifier, + LicenseSpdxID: licenseID, Version: module.Version, }) return nil diff --git a/pkg/deps/jar.go b/pkg/deps/jar.go index 09868bc0..0121da7b 100644 --- a/pkg/deps/jar.go +++ b/pkg/deps/jar.go @@ -123,19 +123,22 @@ func (resolver *JarResolver) ReadFileFromZip(archiveFile *zip.File) (*bytes.Buff } func (resolver *JarResolver) IdentifyLicense(path, dep, content, version string, declareLicense *ConfigDepLicense, report *Report) error { - identifier, err := license.Identify(path, content) - if err != nil { - if declareLicense == nil { + var licenseID string + if declareLicense != nil { + licenseID = declareLicense.License + } else { + identifier, err := license.Identify(path, content) + if err != nil { return err } - identifier = declareLicense.License + licenseID = identifier } report.Resolve(&Result{ Dependency: dep, LicenseFilePath: path, LicenseContent: content, - LicenseSpdxID: identifier, + LicenseSpdxID: licenseID, Version: version, }) return nil diff --git a/pkg/deps/npm.go b/pkg/deps/npm.go index d1d19274..71bde355 100644 --- a/pkg/deps/npm.go +++ b/pkg/deps/npm.go @@ -85,7 +85,7 @@ func (resolver *NpmResolver) Resolve(pkgFile string, licenses []*ConfigDepLicens // Walk through each package's root directory to resolve licenses // Resolve from a package's package.json file or its license file for _, pkg := range pkgs { - if result := resolver.ResolvePackageLicense(pkg.Name, pkg.Path); result.LicenseSpdxID != "" { + if result := resolver.ResolvePackageLicense(pkg.Name, pkg.Path, licenses); result.LicenseSpdxID != "" { report.Resolve(result) } else { if resolver.tryToResolve(pkg, licenses, result) { @@ -200,17 +200,17 @@ func (resolver *NpmResolver) GetInstalledPkgs(pkgDir string) []*Package { // First, try to find and parse the package's package.json file to check the license file // If the previous step fails, then try to identify the package's LICENSE file // It's a necessary procedure to check the LICENSE file, because the resolver needs to record the license content -func (resolver *NpmResolver) ResolvePackageLicense(pkgName, pkgPath string) *Result { +func (resolver *NpmResolver) ResolvePackageLicense(pkgName, pkgPath string, licenses []*ConfigDepLicense) *Result { result := &Result{ Dependency: pkgName, } // resolve from the package.json file - if err := resolver.ResolvePkgFile(result, pkgPath); err != nil { + if err := resolver.ResolvePkgFile(result, pkgPath, licenses); err != nil { result.ResolveErrors = append(result.ResolveErrors, err) } // resolve from the LICENSE file - if err := resolver.ResolveLcsFile(result, pkgPath); err != nil { + if err := resolver.ResolveLcsFile(result, pkgPath, licenses); err != nil { result.ResolveErrors = append(result.ResolveErrors, err) } @@ -218,7 +218,7 @@ func (resolver *NpmResolver) ResolvePackageLicense(pkgName, pkgPath string) *Res } // ResolvePkgFile tries to find and parse the package.json file to capture the license field -func (resolver *NpmResolver) ResolvePkgFile(result *Result, pkgPath string) error { +func (resolver *NpmResolver) ResolvePkgFile(result *Result, pkgPath string, licenses []*ConfigDepLicense) error { expectedPkgFile := filepath.Join(pkgPath, PkgFileName) packageInfo, err := resolver.ParsePkgFile(expectedPkgFile) if err != nil { @@ -226,6 +226,13 @@ func (resolver *NpmResolver) ResolvePkgFile(result *Result, pkgPath string) erro } result.Version = packageInfo.Version + for _, l := range licenses { + if l.Name == packageInfo.Name && l.Version == packageInfo.Version { + result.LicenseSpdxID = l.License + return nil + } + } + if lcs, ok := resolver.ResolveLicenseField(packageInfo.License); ok { result.LicenseSpdxID = lcs return nil @@ -274,7 +281,7 @@ func (resolver *NpmResolver) ResolveLicensesField(licenses []Lcs) (string, bool) } // ResolveLcsFile tries to find the license file to identify the license -func (resolver *NpmResolver) ResolveLcsFile(result *Result, pkgPath string) error { +func (resolver *NpmResolver) ResolveLcsFile(result *Result, pkgPath string, licenses []*ConfigDepLicense) error { depFiles, err := os.ReadDir(pkgPath) if err != nil { return err @@ -293,6 +300,12 @@ func (resolver *NpmResolver) ResolveLcsFile(result *Result, pkgPath string) erro if result.LicenseSpdxID != "" { return nil } + for _, l := range licenses { + if l.Name == info.Name() && l.Version == result.Version { + result.LicenseSpdxID = l.License + return nil + } + } identifier, err := license.Identify(result.Dependency, string(content)) if err != nil { return err diff --git a/pkg/deps/npm_test.go b/pkg/deps/npm_test.go index f30cba57..5d761107 100644 --- a/pkg/deps/npm_test.go +++ b/pkg/deps/npm_test.go @@ -95,7 +95,7 @@ func TestResolvePkgFile(t *testing.T) { if err != nil { t.Fatal(err) } - err = resolver.ResolvePkgFile(result, f.Name()) + err = resolver.ResolvePkgFile(result, f.Name(), nil) if result.LicenseSpdxID != data.result && (err != nil) == data.hasErr { t.Fail() } diff --git a/pkg/deps/summary.go b/pkg/deps/summary.go index 1907ffe1..8e49ea6f 100644 --- a/pkg/deps/summary.go +++ b/pkg/deps/summary.go @@ -20,16 +20,12 @@ package deps import ( "bytes" "os" - "path/filepath" - "regexp" "text/template" "github.com/apache/skywalking-eyes/pkg/header" "github.com/apache/skywalking-eyes/pkg/license" ) -var fileNamePattern = regexp.MustCompile(`[^a-zA-Z0-9\\.\-]`) - type SummaryRenderContext struct { LicenseContent string // Current project license content Groups []*SummaryRenderLicenseGroup // All dependency license groups @@ -43,21 +39,11 @@ type SummaryRenderLicenseGroup struct { type SummaryRenderLicense struct { Name string // Dependency name Version string // Dependency version - Location string // The filename of generated license file LicenseID string // License ID } -func GenerateDependencyLicenseFilename(result *Result) string { - filename := string(fileNamePattern.ReplaceAll([]byte(result.Dependency), []byte("-"))) - return "license-" + filename + ".txt" -} - func ParseTemplate(path string) (*template.Template, error) { - absPath, err := filepath.Abs(path) - if err != nil { - return nil, err - } - tpl, err := os.ReadFile(absPath) + tpl, err := os.ReadFile(path) if err != nil { return nil, err } @@ -67,21 +53,21 @@ func ParseTemplate(path string) (*template.Template, error) { // GenerateSummary generate the summary content by template, license config and dependency report func GenerateSummary(tpl *template.Template, head *header.ConfigHeader, rep *Report) (string, error) { var r bytes.Buffer - context, err := generateSummaryRenderContext(head, rep) - if err != nil { - return "", err - } + context := generateSummaryRenderContext(head, rep) if err := tpl.Execute(&r, context); err != nil { return "", err } return r.String(), nil } -func generateSummaryRenderContext(head *header.ConfigHeader, rep *Report) (*SummaryRenderContext, error) { +func generateSummaryRenderContext(head *header.ConfigHeader, rep *Report) *SummaryRenderContext { // the license id of the project - licenseContent, err := license.GetLicenseContent(head.License.SpdxID) - if err != nil { - return nil, err + var headerContent string + if head.License.SpdxID != "" { + headerContent, _ = license.GetLicenseContent(head.License.SpdxID) + } + if headerContent == "" { + headerContent = head.GetLicenseContent() } groups := make(map[string]*SummaryRenderLicenseGroup) @@ -98,7 +84,6 @@ func generateSummaryRenderContext(head *header.ConfigHeader, rep *Report) (*Summ group.Deps = append(group.Deps, &SummaryRenderLicense{ Name: r.Dependency, Version: r.Version, - Location: GenerateDependencyLicenseFilename(r), LicenseID: r.LicenseSpdxID, }) } @@ -108,7 +93,7 @@ func generateSummaryRenderContext(head *header.ConfigHeader, rep *Report) (*Summ groupArray = append(groupArray, g) } return &SummaryRenderContext{ - LicenseContent: licenseContent, + LicenseContent: headerContent, Groups: groupArray, - }, nil + } } From 40abd32b0fae99f287bc9821b16536ee2453d833 Mon Sep 17 00:00:00 2001 From: Mrproliu <741550557@qq.com> Date: Fri, 6 May 2022 22:09:11 +0800 Subject: [PATCH 4/7] remove unnecessary code --- pkg/deps/npm.go | 15 --------------- 1 file changed, 15 deletions(-) diff --git a/pkg/deps/npm.go b/pkg/deps/npm.go index 71bde355..cbecd4e0 100644 --- a/pkg/deps/npm.go +++ b/pkg/deps/npm.go @@ -88,10 +88,6 @@ func (resolver *NpmResolver) Resolve(pkgFile string, licenses []*ConfigDepLicens if result := resolver.ResolvePackageLicense(pkg.Name, pkg.Path, licenses); result.LicenseSpdxID != "" { report.Resolve(result) } else { - if resolver.tryToResolve(pkg, licenses, result) { - report.Resolve(result) - continue - } result.LicenseSpdxID = Unknown report.Skip(result) logger.Log.Warnln("Failed to resolve the license of dependency:", pkg.Name, result.ResolveErrors) @@ -100,17 +96,6 @@ func (resolver *NpmResolver) Resolve(pkgFile string, licenses []*ConfigDepLicens return nil } -// TryToResolve trying to resolve the license by declaration -func (resolver *NpmResolver) tryToResolve(p *Package, licenses []*ConfigDepLicense, r *Result) bool { - for _, l := range licenses { - if l.Name == p.Name && l.Version == p.Version { - r.LicenseSpdxID = l.License - return true - } - } - return false -} - // NeedSkipInstallPkgs queries whether to skip the procedure of installing or updating packages func (resolver *NpmResolver) NeedSkipInstallPkgs() bool { const countdown = 5 From dca36b428b37778aa27a4c7b0290fc21aa01977c Mon Sep 17 00:00:00 2001 From: Mrproliu <741550557@qq.com> Date: Sat, 7 May 2022 11:25:53 +0800 Subject: [PATCH 5/7] fix issue --- README.md | 292 ++++++++++++++++++++++++++++++++++++++ commands/deps_resolve.go | 14 +- pkg/license/identifier.go | 26 ++-- 3 files changed, 312 insertions(+), 20 deletions(-) diff --git a/README.md b/README.md index 4a5bda0d..64258426 100644 --- a/README.md +++ b/README.md @@ -363,6 +363,298 @@ gopkg.in/check.v1
+##### Summary Template + +The summary is a template to generate the summary of dependencies' licenses based on the [Golang Template](https://pkg.go.dev/text/template). It includes these variables: + +|Name|Type|Example|Description| +|----|----|-------|-----------| +|LicenseContent|string|`{{.LicenseContent}}`|The project license content, it's relate to the `header.license.spdx-id` config. | +|Groups|list structure|`{{ range .Groups }}`|The dependency group, all license is aggregate by the same license [SPDX ID](https://spdx.org/licenses/). | +|Groups.Name|string|`{{.Name}}`|The [SPDX ID](https://spdx.org/licenses/) of dependency. | +|Groups.Deps|list structure|`{{ range .Deps }}`|All dependencies, it including all of same [SPDX ID](https://spdx.org/licenses/) dependencies under `Groups.Name`. | +|Groups.Deps.Name|string|`{{.Name}}`|The name of the dependency. | +|Groups.Deps.Version|string|`{{.Version}}`|The version of the dependency. | +|Groups.Deps.LicenseID|string|`{{.LicenseID}}`|The [SPDX ID](https://spdx.org/licenses/) of the dependency license. | + +
+Summary Template Generate + +Summary template content: +``` +{{.LicenseContent }} +{{ range .Groups }} +======================================================================== +{{.Name}} licenses +======================================================================== +{{range .Deps}} + {{.Name}} {{.Version}} {{.LicenseID}} +{{- end }} +{{ end }} +``` + +Generate LICENSE file content: +``` + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + +======================================================================== +MIT licenses +======================================================================== + + github.com/BurntSushi/toml v0.3.1 MIT + github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf MIT + github.com/armon/circbuf v0.0.0-20150827004946-bbbad097214e MIT + github.com/armon/go-metrics v0.0.0-20180917152333-f0300d1749da MIT + github.com/armon/go-radix v0.0.0-20180808171621-7fddfc383310 MIT + github.com/beorn7/perks v1.0.0 MIT + github.com/bgentry/speakeasy v0.1.0 MIT + github.com/bketelsen/crypt v0.0.3-0.20200106085610-5cbc8cc4026c MIT + github.com/bmatcuk/doublestar/v2 v2.0.4 MIT + +======================================================================== +ISC licenses +======================================================================== + + github.com/davecgh/go-spew v1.1.1 ISC + +======================================================================== +BSD-2-Clause licenses +======================================================================== + + github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1 BSD-2-Clause + github.com/gorilla/websocket v1.4.2 BSD-2-Clause + github.com/pkg/errors v0.8.1 BSD-2-Clause + github.com/russross/blackfriday/v2 v2.0.1 BSD-2-Clause + +======================================================================== +MPL-2.0-no-copyleft-exception licenses +======================================================================== + + github.com/hashicorp/consul/api v1.1.0 MPL-2.0-no-copyleft-exception + github.com/hashicorp/consul/sdk v0.1.1 MPL-2.0-no-copyleft-exception + github.com/hashicorp/go-cleanhttp v0.5.1 MPL-2.0-no-copyleft-exception + github.com/hashicorp/go-immutable-radix v1.0.0 MPL-2.0-no-copyleft-exception + github.com/hashicorp/go-multierror v1.0.0 MPL-2.0-no-copyleft-exception + github.com/hashicorp/go-rootcerts v1.0.0 MPL-2.0-no-copyleft-exception + github.com/hashicorp/go-sockaddr v1.0.0 MPL-2.0-no-copyleft-exception + github.com/hashicorp/go-uuid v1.0.1 MPL-2.0-no-copyleft-exception + github.com/hashicorp/golang-lru v0.5.1 MPL-2.0-no-copyleft-exception + github.com/hashicorp/logutils v1.0.0 MPL-2.0-no-copyleft-exception + github.com/hashicorp/memberlist v0.1.3 MPL-2.0-no-copyleft-exception + +======================================================================== +MPL-2.0 licenses +======================================================================== + + github.com/hashicorp/errwrap v1.0.0 MPL-2.0 + github.com/hashicorp/hcl v1.0.0 MPL-2.0 + github.com/hashicorp/serf v0.8.2 MPL-2.0 + github.com/mitchellh/cli v1.0.0 MPL-2.0 + github.com/mitchellh/gox v0.4.0 MPL-2.0 + +======================================================================== +MIT and Apache licenses +======================================================================== + + gopkg.in/yaml.v3 v3.0.0-20200615113413-eeeca48fe776 MIT and Apache + +======================================================================== +Apache-2.0 licenses +======================================================================== + + cloud.google.com/go v0.46.3 Apache-2.0 + cloud.google.com/go/bigquery v1.0.1 Apache-2.0 + cloud.google.com/go/datastore v1.0.0 Apache-2.0 + cloud.google.com/go/firestore v1.1.0 Apache-2.0 + cloud.google.com/go/pubsub v1.0.1 Apache-2.0 + cloud.google.com/go/storage v1.0.0 Apache-2.0 + +======================================================================== +BSD-3-Clause licenses +======================================================================== + + dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9 BSD-3-Clause + github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802 BSD-3-Clause + github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc BSD-3-Clause + github.com/fsnotify/fsnotify v1.4.7 BSD-3-Clause +``` + +
+ #### Check Dependencies' licenses This command can be used to perform automatic license compatibility check, when there is incompatible licenses found, diff --git a/commands/deps_resolve.go b/commands/deps_resolve.go index 3e57bc14..ce889186 100644 --- a/commands/deps_resolve.go +++ b/commands/deps_resolve.go @@ -39,7 +39,7 @@ func init() { DepsResolveCommand.PersistentFlags().StringVarP(&outDir, "output", "o", "", "the directory to output the resolved dependencies' licenses, if not set the dependencies' licenses won't be saved") DepsResolveCommand.PersistentFlags().StringVarP(&summaryTplPath, "summary", "s", "", - "the template file to write the summary the dependencies' licenses into \"LICENSE\" file into directory which same with template file.") + "the template file to write the summary of dependencies' licenses, a new file named \"LICENSE\" will be created in the same directory as the template file, to save the final summary.") } var fileNamePattern = regexp.MustCompile(`[^a-zA-Z0-9\\.\-]`) @@ -80,15 +80,15 @@ var DepsResolveCommand = &cobra.Command{ return err } - if outDir != "" { - for _, result := range report.Resolved { - writeLicense(result) + if summaryTpl != nil { + if err := writeSummary(&report); err != nil { + return err } } - if summaryTpl != nil { - if err := writeSummary(&report); err != nil { - logger.Log.Warnf("write summary file error: %v", err) + if outDir != "" { + for _, result := range report.Resolved { + writeLicense(result) } } diff --git a/pkg/license/identifier.go b/pkg/license/identifier.go index 167b8c4c..92372b0a 100644 --- a/pkg/license/identifier.go +++ b/pkg/license/identifier.go @@ -29,8 +29,10 @@ import ( "github.com/apache/skywalking-eyes/internal/logger" ) +var licenseTemplatesDir = "lcs-templates" + var templatesDirs = []string{ - "lcs-templates", + licenseTemplatesDir, // Some projects simply use the header text as their LICENSE content... "header-templates", } @@ -105,22 +107,20 @@ func Identify(pkgPath, content string) (string, error) { // GetLicenseContent from license id func GetLicenseContent(spdxID string) (string, error) { - for _, dir := range templatesDirs { - files, err := assets.AssetDir(dir) - if err != nil { + files, err := assets.AssetDir(licenseTemplatesDir) + if err != nil { + return "", err + } + for _, f := range files { + if spdxID != strings.TrimSuffix(f.Name(), filepath.Ext(f.Name())) { continue } - for _, f := range files { - if spdxID != strings.TrimSuffix(f.Name(), filepath.Ext(f.Name())) { - continue - } - res, err := assets.Asset(filepath.Join(dir, f.Name())) - if err != nil { - return "", err - } - return string(res), nil + res, err := assets.Asset(filepath.Join(licenseTemplatesDir, f.Name())) + if err != nil { + return "", err } + return string(res), nil } return "", fmt.Errorf("could not found license: %s", spdxID) } From 1b048a6afab66f7386ff940b2297a37a5b6d019d Mon Sep 17 00:00:00 2001 From: Mrproliu <741550557@qq.com> Date: Sat, 7 May 2022 11:27:50 +0800 Subject: [PATCH 6/7] fix lint --- commands/deps_resolve.go | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/commands/deps_resolve.go b/commands/deps_resolve.go index ce889186..4c8e197e 100644 --- a/commands/deps_resolve.go +++ b/commands/deps_resolve.go @@ -39,7 +39,8 @@ func init() { DepsResolveCommand.PersistentFlags().StringVarP(&outDir, "output", "o", "", "the directory to output the resolved dependencies' licenses, if not set the dependencies' licenses won't be saved") DepsResolveCommand.PersistentFlags().StringVarP(&summaryTplPath, "summary", "s", "", - "the template file to write the summary of dependencies' licenses, a new file named \"LICENSE\" will be created in the same directory as the template file, to save the final summary.") + "the template file to write the summary of dependencies' licenses, a new file named \"LICENSE\" will be "+ + "created in the same directory as the template file, to save the final summary.") } var fileNamePattern = regexp.MustCompile(`[^a-zA-Z0-9\\.\-]`) From 8ec00b43ad90f94175055933f1e4cc33d74d5129 Mon Sep 17 00:00:00 2001 From: Mrproliu <741550557@qq.com> Date: Sat, 7 May 2022 13:37:04 +0800 Subject: [PATCH 7/7] fix issue --- README.md | 10 +++++----- pkg/deps/summary.go | 23 +++++++++++++++-------- pkg/license/identifier.go | 15 ++------------- 3 files changed, 22 insertions(+), 26 deletions(-) diff --git a/README.md b/README.md index 64258426..44da58da 100644 --- a/README.md +++ b/README.md @@ -369,10 +369,10 @@ The summary is a template to generate the summary of dependencies' licenses base |Name|Type|Example|Description| |----|----|-------|-----------| -|LicenseContent|string|`{{.LicenseContent}}`|The project license content, it's relate to the `header.license.spdx-id` config. | -|Groups|list structure|`{{ range .Groups }}`|The dependency group, all license is aggregate by the same license [SPDX ID](https://spdx.org/licenses/). | -|Groups.Name|string|`{{.Name}}`|The [SPDX ID](https://spdx.org/licenses/) of dependency. | -|Groups.Deps|list structure|`{{ range .Deps }}`|All dependencies, it including all of same [SPDX ID](https://spdx.org/licenses/) dependencies under `Groups.Name`. | +|LicenseContent|string|`{{.LicenseContent}}`|The project license content, it's the license of `header.license.spdx-id` (if set), otherwise it's the `header.license.content`. | +|Groups|list structure|`{{ range .Groups }}`|The dependency groups, all licenses are grouped by the same license [SPDX ID](https://spdx.org/licenses/). | +|Groups.LicenseID|string|`{{.LicenseID}}`|The [SPDX ID](https://spdx.org/licenses/) of dependency. | +|Groups.Deps|list structure|`{{ range .Deps }}`|All dependencies with the same [SPDX ID](https://spdx.org/licenses/). | |Groups.Deps.Name|string|`{{.Name}}`|The name of the dependency. | |Groups.Deps.Version|string|`{{.Version}}`|The version of the dependency. | |Groups.Deps.LicenseID|string|`{{.LicenseID}}`|The [SPDX ID](https://spdx.org/licenses/) of the dependency license. | @@ -385,7 +385,7 @@ Summary template content: {{.LicenseContent }} {{ range .Groups }} ======================================================================== -{{.Name}} licenses +{{.LicenseID}} licenses ======================================================================== {{range .Deps}} {{.Name}} {{.Version}} {{.LicenseID}} diff --git a/pkg/deps/summary.go b/pkg/deps/summary.go index 8e49ea6f..d14acb75 100644 --- a/pkg/deps/summary.go +++ b/pkg/deps/summary.go @@ -32,8 +32,8 @@ type SummaryRenderContext struct { } type SummaryRenderLicenseGroup struct { - Name string // Aggregate all same license ID dependencies - Deps []*SummaryRenderLicense // Same license ID dependencies + LicenseID string // Aggregate all same license ID dependencies + Deps []*SummaryRenderLicense // Same license ID dependencies } type SummaryRenderLicense struct { @@ -53,18 +53,25 @@ func ParseTemplate(path string) (*template.Template, error) { // GenerateSummary generate the summary content by template, license config and dependency report func GenerateSummary(tpl *template.Template, head *header.ConfigHeader, rep *Report) (string, error) { var r bytes.Buffer - context := generateSummaryRenderContext(head, rep) + context, err := generateSummaryRenderContext(head, rep) + if err != nil { + return "", err + } if err := tpl.Execute(&r, context); err != nil { return "", err } return r.String(), nil } -func generateSummaryRenderContext(head *header.ConfigHeader, rep *Report) *SummaryRenderContext { +func generateSummaryRenderContext(head *header.ConfigHeader, rep *Report) (*SummaryRenderContext, error) { // the license id of the project var headerContent string if head.License.SpdxID != "" { - headerContent, _ = license.GetLicenseContent(head.License.SpdxID) + c, err := license.GetLicenseContent(head.License.SpdxID) + if err != nil { + return nil, err + } + headerContent = c } if headerContent == "" { headerContent = head.GetLicenseContent() @@ -75,8 +82,8 @@ func generateSummaryRenderContext(head *header.ConfigHeader, rep *Report) *Summa group := groups[r.LicenseSpdxID] if group == nil { group = &SummaryRenderLicenseGroup{ - Name: r.LicenseSpdxID, - Deps: make([]*SummaryRenderLicense, 0), + LicenseID: r.LicenseSpdxID, + Deps: make([]*SummaryRenderLicense, 0), } groups[r.LicenseSpdxID] = group } @@ -95,5 +102,5 @@ func generateSummaryRenderContext(head *header.ConfigHeader, rep *Report) *Summa return &SummaryRenderContext{ LicenseContent: headerContent, Groups: groupArray, - } + }, nil } diff --git a/pkg/license/identifier.go b/pkg/license/identifier.go index 92372b0a..c23ea758 100644 --- a/pkg/license/identifier.go +++ b/pkg/license/identifier.go @@ -107,20 +107,9 @@ func Identify(pkgPath, content string) (string, error) { // GetLicenseContent from license id func GetLicenseContent(spdxID string) (string, error) { - files, err := assets.AssetDir(licenseTemplatesDir) + res, err := assets.Asset(filepath.Join(licenseTemplatesDir, spdxID+".txt")) if err != nil { return "", err } - for _, f := range files { - if spdxID != strings.TrimSuffix(f.Name(), filepath.Ext(f.Name())) { - continue - } - - res, err := assets.Asset(filepath.Join(licenseTemplatesDir, f.Name())) - if err != nil { - return "", err - } - return string(res), nil - } - return "", fmt.Errorf("could not found license: %s", spdxID) + return string(res), nil }