From 5cab81de5f9360d94d0c8fd399c191fe31e63901 Mon Sep 17 00:00:00 2001 From: Nikita Pivkin <100182843+nikpivkin@users.noreply.github.com> Date: Wed, 14 Jun 2023 15:35:59 +0300 Subject: [PATCH] feat(java): capture licenses from pom.xml (#225) --- .gitignore | 2 ++ pkg/java/pom/artifact.go | 4 ++- pkg/java/pom/parse.go | 31 +++++++++---------- pkg/java/pom/parse_test.go | 30 ++++++++++++++++++ pkg/java/pom/pom.go | 19 ++++++++++-- pkg/java/pom/testdata/happy/pom.xml | 4 +-- .../pom/testdata/multiply-licenses/pom.xml | 23 ++++++++++++++ 7 files changed, 92 insertions(+), 21 deletions(-) create mode 100644 pkg/java/pom/testdata/multiply-licenses/pom.xml diff --git a/.gitignore b/.gitignore index 854602ef..5705dbab 100644 --- a/.gitignore +++ b/.gitignore @@ -15,3 +15,5 @@ .idea /vendor + +**/.DS_Store diff --git a/pkg/java/pom/artifact.go b/pkg/java/pom/artifact.go index 41338143..11ee2522 100644 --- a/pkg/java/pom/artifact.go +++ b/pkg/java/pom/artifact.go @@ -18,6 +18,7 @@ type artifact struct { GroupID string ArtifactID string Version version + License string Exclusions map[string]struct{} @@ -25,11 +26,12 @@ type artifact struct { Root bool } -func newArtifact(groupID, artifactID, version string, props map[string]string) artifact { +func newArtifact(groupID, artifactID, version, license string, props map[string]string) artifact { return artifact{ GroupID: evaluateVariable(groupID, props, nil), ArtifactID: evaluateVariable(artifactID, props, nil), Version: newVersion(evaluateVariable(version, props, nil)), + License: license, } } diff --git a/pkg/java/pom/parse.go b/pkg/java/pom/parse.go index 75aa878d..b0df7416 100644 --- a/pkg/java/pom/parse.go +++ b/pkg/java/pom/parse.go @@ -114,7 +114,7 @@ func (p *parser) parseRoot(root artifact) ([]types.Library, []types.Dependency, libs []types.Library deps []types.Dependency rootDepManagement []pomDependency - uniqArtifacts = map[string]version{} + uniqArtifacts = map[string]artifact{} ) // Iterate direct and transitive dependencies @@ -159,9 +159,8 @@ func (p *parser) parseRoot(root artifact) ([]types.Library, []types.Dependency, } // For soft requirements, skip dependency resolution that has already been resolved. - if v, ok := uniqArtifacts[art.Name()]; ok { - - if !v.shouldOverride(art.Version) { + if uniqueArt, ok := uniqArtifacts[art.Name()]; ok { + if !uniqueArt.Version.shouldOverride(art.Version) { continue } } @@ -199,20 +198,20 @@ func (p *parser) parseRoot(root artifact) ([]types.Library, []types.Dependency, // Offline mode may be missing some fields. if !art.IsEmpty() { // Override the version - uniqArtifacts[art.Name()] = art.Version + uniqArtifacts[art.Name()] = artifact{ + Version: art.Version, + License: art.License, + } } } // Convert to []types.Library - for _, lib := range processed { - if !lib.IsEmpty() { // remove the (empty) root - - libs = append(libs, types.Library{ - ID: id(lib), - Name: lib.Name(), - Version: lib.Version.ver, - }) - } + for name, art := range uniqArtifacts { + libs = append(libs, types.Library{ + Name: name, + Version: art.Version.String(), + License: art.License, + }) } return libs, deps, nil @@ -386,7 +385,7 @@ func (p *parser) resolveDepManagement(props map[string]string, depManagement []p // Managed dependencies with a scope of "import" should be processed after other managed dependencies. // cf. https://maven.apache.org/guides/introduction/introduction-to-dependency-mechanism.html#importing-dependencies for _, imp := range imports { - art := newArtifact(imp.GroupID, imp.ArtifactID, imp.Version, props) + art := newArtifact(imp.GroupID, imp.ArtifactID, imp.Version, "", props) result, err := p.resolve(art, nil) if err != nil { continue @@ -437,7 +436,7 @@ func excludeDep(exclusions map[string]struct{}, art artifact) bool { func (p *parser) parseParent(currentPath string, parent pomParent) (analysisResult, error) { // Pass nil properties so that variables in are not evaluated. - target := newArtifact(parent.GroupId, parent.ArtifactId, parent.Version, nil) + target := newArtifact(parent.GroupId, parent.ArtifactId, parent.Version, "", nil) if target.IsEmpty() { return analysisResult{}, nil } diff --git a/pkg/java/pom/parse_test.go b/pkg/java/pom/parse_test.go index 335727d7..09607534 100644 --- a/pkg/java/pom/parse_test.go +++ b/pkg/java/pom/parse_test.go @@ -34,6 +34,7 @@ func TestPom_Parse(t *testing.T) { ID: "com.example.happy:1.0.0", Name: "com.example:happy", Version: "1.0.0", + License: "BSD-3-Clause", }, { ID: "org.example.example-api:1.7.30", @@ -150,6 +151,7 @@ func TestPom_Parse(t *testing.T) { ID: "com.example.happy:1.0.0", Name: "com.example:happy", Version: "1.0.0", + License: "BSD-3-Clause", }, { ID: "org.example.example-api:1.7.30", @@ -206,6 +208,7 @@ func TestPom_Parse(t *testing.T) { ID: "com.example.child:1.0.0", Name: "com.example:child", Version: "1.0.0", + License: "Apache 2.0", }, { ID: "org.example.example-api:1.7.30", @@ -287,6 +290,7 @@ func TestPom_Parse(t *testing.T) { ID: "com.example.child:1.0.0-SNAPSHOT", Name: "com.example:child", Version: "1.0.0-SNAPSHOT", + License: "Apache 2.0", }, { ID: "org.example.example-api:1.7.30", @@ -319,6 +323,7 @@ func TestPom_Parse(t *testing.T) { ID: "com.example.child:3.0.0", Name: "com.example:child", Version: "3.0.0", + License: "Apache 2.0", }, { ID: "org.example.example-api:1.7.30", @@ -351,6 +356,7 @@ func TestPom_Parse(t *testing.T) { ID: "com.example.base:4.0.0", Name: "com.example:base", Version: "4.0.0", + License: "Apache 2.0", }, { ID: "org.example.example-api:1.7.30", @@ -392,6 +398,7 @@ func TestPom_Parse(t *testing.T) { ID: "com.example.child:1.0.0", Name: "com.example:child", Version: "1.0.0", + License: "Apache 2.0", }, { ID: "org.example.example-api:1.7.30", @@ -424,6 +431,7 @@ func TestPom_Parse(t *testing.T) { ID: "org.example.child:1.0.0", Name: "org.example:child", Version: "1.0.0", + License: "Apache 2.0", }, { ID: "org.example.example-api:1.7.30", @@ -456,6 +464,7 @@ func TestPom_Parse(t *testing.T) { ID: "com.example.soft:1.0.0", Name: "com.example:soft", Version: "1.0.0", + License: "Apache 2.0", }, { ID: "org.example.example-api:1.7.30", @@ -550,6 +559,7 @@ func TestPom_Parse(t *testing.T) { ID: "com.example.hard:1.0.0", Name: "com.example:hard", Version: "1.0.0", + License: "Apache 2.0", }, { ID: "org.example.example-api:1.7.30", @@ -596,6 +606,7 @@ func TestPom_Parse(t *testing.T) { ID: "com.example.hard:1.0.0", Name: "com.example:hard", Version: "1.0.0", + License: "Apache 2.0", }, }, wantDeps: []types.Dependency{ @@ -614,6 +625,7 @@ func TestPom_Parse(t *testing.T) { ID: "com.example.import:2.0.0", Name: "com.example:import", Version: "2.0.0", + License: "Apache 2.0", }, { ID: "org.example.example-api:1.7.30", @@ -646,6 +658,7 @@ func TestPom_Parse(t *testing.T) { ID: "com.example.import:2.0.0", Name: "com.example:import", Version: "2.0.0", + License: "Apache 2.0", }, { ID: "org.example.example-api:1.7.30", @@ -733,11 +746,13 @@ func TestPom_Parse(t *testing.T) { ID: "com.example.aggregation:1.0.0", Name: "com.example:aggregation", Version: "1.0.0", + License: "Apache 2.0", }, { ID: "com.example.module:1.1.1", Name: "com.example:module", Version: "1.1.1", + License: "Apache 2.0", }, { ID: "org.example.example-api:1.7.30", @@ -874,6 +889,7 @@ func TestPom_Parse(t *testing.T) { ID: "com.example.no-parent:1.0-SNAPSHOT", Name: "com.example:no-parent", Version: "1.0-SNAPSHOT", + License: "Apache 2.0", }, { ID: "org.example.example-api:1.7.30", @@ -947,6 +963,7 @@ func TestPom_Parse(t *testing.T) { ID: "com.example.not-found-dependency:1.0.0", Name: "com.example:not-found-dependency", Version: "1.0.0", + License: "Apache 2.0", }, { ID: "org.example.example-not-found:999", @@ -969,6 +986,19 @@ func TestPom_Parse(t *testing.T) { { Name: "com.example:aggregation", Version: "1.0.0", + License: "Apache 2.0", + }, + }, + }, + { + name: "multiply licenses", + inputFile: filepath.Join("testdata", "multiply-licenses", "pom.xml"), + local: true, + want: []types.Library{ + { + Name: "com.example:multiply-licenses", + Version: "1.0.0", + License: "MIT, Apache 2.0", }, }, }, diff --git a/pkg/java/pom/pom.go b/pkg/java/pom/pom.go index b5814f9c..0a9a98f6 100644 --- a/pkg/java/pom/pom.go +++ b/pkg/java/pom/pom.go @@ -95,7 +95,17 @@ func (p pom) listProperties(val reflect.Value) map[string]string { } func (p pom) artifact() artifact { - return newArtifact(p.content.GroupId, p.content.ArtifactId, p.content.Version, p.content.Properties) + return newArtifact(p.content.GroupId, p.content.ArtifactId, p.content.Version, p.joinLicenses(), p.content.Properties) +} + +func (p pom) joinLicenses() string { + var licenses []string + for _, license := range p.content.Licenses.License { + if license.Name != "" { + licenses = append(licenses, license.Name) + } + } + return strings.Join(licenses, ", ") } func (p pom) repositories() []string { @@ -113,7 +123,12 @@ type pomXML struct { GroupId string `xml:"groupId"` ArtifactId string `xml:"artifactId"` Version string `xml:"version"` - Modules struct { + Licenses struct { + License []struct { + Name string `xml:"name"` + } `xml:"license"` + } `xml:"licenses"` + Modules struct { Text string `xml:",chardata"` Module []string `xml:"module"` } `xml:"modules"` diff --git a/pkg/java/pom/testdata/happy/pom.xml b/pkg/java/pom/testdata/happy/pom.xml index 36e9f2a3..3da117ee 100644 --- a/pkg/java/pom/testdata/happy/pom.xml +++ b/pkg/java/pom/testdata/happy/pom.xml @@ -11,8 +11,8 @@ - Apache 2.0 - http://www.apache.org/licenses/LICENSE-2.0.html + BSD-3-Clause + https://opensource.org/licenses/BSD-3-Clause repo diff --git a/pkg/java/pom/testdata/multiply-licenses/pom.xml b/pkg/java/pom/testdata/multiply-licenses/pom.xml new file mode 100644 index 00000000..1f9609e6 --- /dev/null +++ b/pkg/java/pom/testdata/multiply-licenses/pom.xml @@ -0,0 +1,23 @@ + + 4.0.0 + + com.example + multiply-licenses + 1.0.0 + + multiply-licenses + Example + + + + MIT + All source code is under the MIT license. + + + Apache 2.0 + http://www.apache.org/licenses/LICENSE-2.0.html + repo + + +