Add `excludes` to `license resolve` config (#117)
diff --git a/README.md b/README.md
index 55d81d3..405d1d0 100644
--- a/README.md
+++ b/README.md
@@ -769,6 +769,9 @@
version: dependency-version # <19>
license: Apache-2.0 # <20>
threshold: 75 # <21>
+ excludes: # <22>
+ - name: dependency-name # the same format as <18>
+ version: dependency-version # the same format as <19>
```
1. The `header` section is configurations for source codes license header.
@@ -788,10 +791,11 @@
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 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, preventing license changed between different versions.
+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. You can use file pattern as described in [the doc](https://pkg.go.dev/path/filepath#Match).
+19. The `version` of the dependency, comma seperated string (such as `1.0,2.0,3.0`), if this is empty, it means all versions of the dependency.
20. The [SPDX ID](https://spdx.org/licenses/) of the dependency license.
21. The minimum percentage of the file that must contain license text for identifying a license, default is `75`.
+22. The dependencies that should be excluded when analyzing the licenses, this is useful when you declare the dependencies in `pom.xml` with `compile` scope but don't distribute them in package. (Note that non-`compile` scope dependencies are automatically excluded so you don't need to put them here).
**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/pkg/deps/config.go b/pkg/deps/config.go
index d3a77ed..9993bad 100644
--- a/pkg/deps/config.go
+++ b/pkg/deps/config.go
@@ -20,6 +20,7 @@
import (
"os"
"path/filepath"
+ "strings"
)
// DefaultCoverageThreshold is the minimum percentage of the file
@@ -31,6 +32,7 @@
Threshold int `yaml:"threshold"`
Files []string `yaml:"files"`
Licenses []*ConfigDepLicense `yaml:"licenses"`
+ Excludes []Exclude `yaml:"excludes"`
}
type ConfigDepLicense struct {
@@ -39,6 +41,11 @@
License string `yaml:"license"`
}
+type Exclude struct {
+ Name string `yaml:"name"`
+ Version string `yaml:"version"`
+}
+
func (config *ConfigDeps) Finalize(configFile string) error {
configFileAbsPath, err := filepath.Abs(configFile)
if err != nil {
@@ -58,3 +65,37 @@
return nil
}
+
+func (config *ConfigDeps) GetUserConfiguredLicense(name, version string) (string, bool) {
+ for _, license := range config.Licenses {
+ if matched, _ := filepath.Match(license.Name, name); !matched && license.Name != name {
+ continue
+ }
+ if license.Version == "" {
+ return license.License, true
+ }
+ for _, v := range strings.Split(license.Version, ",") {
+ if v == version {
+ return license.License, true
+ }
+ }
+ }
+ return "", false
+}
+
+func (config *ConfigDeps) IsExcluded(name, version string) bool {
+ for _, license := range config.Excludes {
+ if matched, _ := filepath.Match(license.Name, name); !matched && license.Name != name {
+ continue
+ }
+ if license.Version == "" {
+ return true
+ }
+ for _, v := range strings.Split(license.Version, ",") {
+ if v == version {
+ return true
+ }
+ }
+ }
+ return false
+}
diff --git a/pkg/deps/golang.go b/pkg/deps/golang.go
index 842be06..700329e 100644
--- a/pkg/deps/golang.go
+++ b/pkg/deps/golang.go
@@ -27,7 +27,6 @@
"os/exec"
"path/filepath"
"regexp"
- "strings"
"github.com/apache/skywalking-eyes/internal/logger"
"github.com/apache/skywalking-eyes/pkg/license"
@@ -86,17 +85,16 @@
func (resolver *GoModResolver) ResolvePackages(modules []*packages.Module, config *ConfigDeps, report *Report) error {
for _, module := range modules {
func() {
- for _, l := range config.Licenses {
- for _, version := range strings.Split(l.Version, ",") {
- if l.Name == module.Path && version == module.Version {
- report.Resolve(&Result{
- Dependency: module.Path,
- LicenseSpdxID: l.License,
- Version: module.Version,
- })
- return
- }
- }
+ if config.IsExcluded(module.Path, module.Version) {
+ return
+ }
+ if l, ok := config.GetUserConfiguredLicense(module.Path, module.Version); ok {
+ report.Resolve(&Result{
+ Dependency: module.Path,
+ LicenseSpdxID: l,
+ Version: module.Version,
+ })
+ return
}
err := resolver.ResolvePackageLicense(config, module, report)
if err != nil {
diff --git a/pkg/deps/maven.go b/pkg/deps/maven.go
index 28c5c08..7220823 100644
--- a/pkg/deps/maven.go
+++ b/pkg/deps/maven.go
@@ -58,14 +58,14 @@
return err
}
- deps, err := resolver.LoadDependencies()
+ deps, err := resolver.LoadDependencies(config)
if err != nil {
// attempt to download dependencies
if err = resolver.DownloadDeps(); err != nil {
return fmt.Errorf("dependencies download error")
}
// load again
- deps, err = resolver.LoadDependencies()
+ deps, err = resolver.LoadDependencies(config)
if err != nil {
return err
}
@@ -125,7 +125,7 @@
return install.Run()
}
-func (resolver *MavenPomResolver) LoadDependencies() ([]*Dependency, error) {
+func (resolver *MavenPomResolver) LoadDependencies(config *ConfigDeps) ([]*Dependency, error) {
buf := bytes.NewBuffer(nil)
cmd := exec.Command(resolver.maven, "dependency:tree") // #nosec G204
@@ -138,7 +138,7 @@
return nil, err
}
- deps := LoadDependencies(buf.Bytes())
+ deps := LoadDependencies(buf.Bytes(), config)
return deps, nil
}
@@ -146,24 +146,20 @@
func (resolver *MavenPomResolver) ResolveDependencies(deps []*Dependency, config *ConfigDeps, report *Report) error {
for _, dep := range deps {
func() {
- for _, l := range config.Licenses {
- for _, version := range strings.Split(l.Version, ",") {
- if l.Name == fmt.Sprintf("%s:%s", strings.Join(dep.GroupID, "."), dep.ArtifactID) && version == dep.Version {
- report.Resolve(&Result{
- Dependency: dep.String(),
- LicenseSpdxID: l.License,
- Version: dep.Version,
- })
- return
- }
- }
+ if l, ok := config.GetUserConfiguredLicense(dep.Name(), dep.Version); ok {
+ report.Resolve(&Result{
+ Dependency: dep.Name(),
+ LicenseSpdxID: l,
+ Version: dep.Version,
+ })
+ return
}
state := NotFound
err := resolver.ResolveLicense(config, &state, dep, report)
if err != nil {
- logger.Log.Warnf("Failed to resolve the license of <%s>: %v\n", dep.String(), state.String())
+ logger.Log.Warnf("Failed to resolve the license of <%s>: %v\n", dep.Name(), state.String())
report.Skip(&Result{
- Dependency: dep.String(),
+ Dependency: dep.Name(),
LicenseSpdxID: Unknown,
Version: dep.Version,
})
@@ -177,7 +173,7 @@
func (resolver *MavenPomResolver) ResolveLicense(config *ConfigDeps, state *State, dep *Dependency, report *Report) error {
result1, err1 := resolver.ResolveJar(config, state, filepath.Join(resolver.repo, dep.Path(), dep.Jar()), dep.Version)
if result1 != nil {
- result1.Dependency = dep.String()
+ result1.Dependency = dep.Name()
report.Resolve(result1)
return nil
}
@@ -188,7 +184,7 @@
return nil
}
- return fmt.Errorf("failed to resolve license for <%s> from jar or pom: %+v, %+v", dep.String(), err1, err2)
+ return fmt.Errorf("failed to resolve license for <%s> from jar or pom: %+v, %+v", dep.Name(), err1, err2)
}
// ResolveLicenseFromPom search for license in the pom file, which may appear in the header comments or in license element of xml
@@ -202,7 +198,7 @@
if pom != nil && len(pom.Licenses) != 0 {
return &Result{
- Dependency: dep.String(),
+ Dependency: dep.Name(),
LicenseFilePath: pomFile,
LicenseContent: pom.Raw(),
LicenseSpdxID: pom.AllLicenses(config),
@@ -215,7 +211,7 @@
return nil, err
} else if headerComments != "" {
*state |= FoundLicenseInPomHeader
- return resolver.IdentifyLicense(config, pomFile, dep.String(), headerComments, dep.Version)
+ return resolver.IdentifyLicense(config, pomFile, dep.Name(), headerComments, dep.Version)
}
return nil, fmt.Errorf("not found in pom file")
@@ -287,7 +283,7 @@
return reMaybeLicense.MatchString(content)
}
-func LoadDependencies(data []byte) []*Dependency {
+func LoadDependencies(data []byte, config *ConfigDeps) []*Dependency {
depsTree := LoadDependenciesTree(data)
cnt := 0
@@ -299,6 +295,9 @@
queue := []*Dependency{}
for _, depTree := range depsTree {
+ if config.IsExcluded(depTree.Name(), depTree.Version) {
+ continue
+ }
queue = append(queue, depTree)
for len(queue) > 0 {
dep := queue[0]
@@ -326,9 +325,8 @@
deps := make([]*Dependency, 0, len(rawDeps))
for _, rawDep := range rawDeps {
- gid := strings.Split(string(rawDep[reFind.SubexpIndex("gid")]), ".")
dep := &Dependency{
- GroupID: gid,
+ GroupID: string(rawDep[reFind.SubexpIndex("gid")]),
ArtifactID: string(rawDep[reFind.SubexpIndex("aid")]),
Packaging: string(rawDep[reFind.SubexpIndex("packaging")]),
Version: string(rawDep[reFind.SubexpIndex("version")]),
@@ -406,9 +404,8 @@
}
type Dependency struct {
- GroupID []string
- ArtifactID, Version, Packaging, Scope string
- TransitiveDeps []*Dependency
+ GroupID, ArtifactID, Version, Packaging, Scope string
+ TransitiveDeps []*Dependency
}
func (dep *Dependency) Clone() *Dependency {
@@ -430,7 +427,7 @@
}
func (dep *Dependency) Path() string {
- return fmt.Sprintf("%v/%v/%v", strings.Join(dep.GroupID, "/"), dep.ArtifactID, dep.Version)
+ return fmt.Sprintf("%v/%v/%v", strings.ReplaceAll(dep.GroupID, ".", "/"), dep.ArtifactID, dep.Version)
}
func (dep *Dependency) Pom() string {
@@ -441,24 +438,8 @@
return fmt.Sprintf("%v-%v.jar", dep.ArtifactID, dep.Version)
}
-func (dep *Dependency) String() string {
- buf := bytes.NewBuffer(nil)
- w := bufio.NewWriter(buf)
-
- _, err := w.WriteString(fmt.Sprintf("%v:%v", strings.Join(dep.GroupID, "."), dep.ArtifactID))
- if err != nil {
- logger.Log.Error(err)
- }
-
- for _, tDep := range dep.TransitiveDeps {
- _, err = w.WriteString(fmt.Sprintf("\n\t%v", tDep))
- if err != nil {
- logger.Log.Error(err)
- }
- }
-
- _ = w.Flush()
- return buf.String()
+func (dep *Dependency) Name() string {
+ return fmt.Sprintf("%v:%v", dep.GroupID, dep.ArtifactID)
}
// PomFile is used to extract license from the pom.xml file
diff --git a/pkg/deps/npm.go b/pkg/deps/npm.go
index 4cdb28f..c1d97dd 100644
--- a/pkg/deps/npm.go
+++ b/pkg/deps/npm.go
@@ -190,7 +190,7 @@
Dependency: pkgName,
}
// resolve from the package.json file
- if err := resolver.ResolvePkgFile(result, pkgPath, config.Licenses); err != nil {
+ if err := resolver.ResolvePkgFile(result, pkgPath, config); err != nil {
result.ResolveErrors = append(result.ResolveErrors, err)
}
@@ -203,7 +203,7 @@
}
// ResolvePkgFile tries to find and parse the package.json file to capture the license field
-func (resolver *NpmResolver) ResolvePkgFile(result *Result, pkgPath string, licenses []*ConfigDepLicense) error {
+func (resolver *NpmResolver) ResolvePkgFile(result *Result, pkgPath string, config *ConfigDeps) error {
expectedPkgFile := filepath.Join(pkgPath, PkgFileName)
packageInfo, err := resolver.ParsePkgFile(expectedPkgFile)
if err != nil {
@@ -211,13 +211,9 @@
}
result.Version = packageInfo.Version
- for _, l := range licenses {
- for _, version := range strings.Split(l.Version, ",") {
- if l.Name == packageInfo.Name && version == packageInfo.Version {
- result.LicenseSpdxID = l.License
- return nil
- }
- }
+ if l, ok := config.GetUserConfiguredLicense(packageInfo.Name, packageInfo.Version); ok {
+ result.LicenseSpdxID = l
+ return nil
}
if lcs, ok := resolver.ResolveLicenseField(packageInfo.License); ok {
@@ -287,13 +283,9 @@
if result.LicenseSpdxID != "" {
return nil
}
- for _, l := range config.Licenses {
- for _, version := range strings.Split(l.Version, ",") {
- if l.Name == info.Name() && version == result.Version {
- result.LicenseSpdxID = l.License
- return nil
- }
- }
+ if l, ok := config.GetUserConfiguredLicense(info.Name(), result.Version); ok {
+ result.LicenseSpdxID = l
+ return nil
}
identifier, err := license.Identify(string(content), config.Threshold)
if err != nil {
diff --git a/pkg/deps/result.go b/pkg/deps/result.go
index 95a318b..66f87df 100644
--- a/pkg/deps/result.go
+++ b/pkg/deps/result.go
@@ -20,6 +20,7 @@
import (
"fmt"
"math"
+ "sort"
"strings"
)
@@ -56,6 +57,13 @@
}
func (report *Report) String() string {
+ sort.SliceStable(report.Resolved, func(i, j int) bool {
+ return report.Resolved[i].Dependency < report.Resolved[j].Dependency
+ })
+ sort.SliceStable(report.Skipped, func(i, j int) bool {
+ return report.Skipped[i].Dependency < report.Skipped[j].Dependency
+ })
+
dWidth, lWidth, vWidth := .0, .0, .0
for _, r := range report.Skipped {
dWidth = math.Max(float64(len(r.Dependency)), dWidth)