This is an automated email from the ASF dual-hosted git repository. slachiewicz pushed a commit to branch master in repository https://gitbox.apache.org/repos/asf/maven-remote-resources-plugin.git
commit b6b5a0da8a2372b82f2861b35e354234c02de2d6 Author: Sylwester Lachiewicz <[email protected]> AuthorDate: Wed Oct 28 01:27:15 2026 +0000 Group dependencies by SPDX license so templates list each license once (#316) --- .../project/src/main/resources/DEPENDENCIES.vm | 10 ++++ .../plugin/resources/remote/SpdxLicenses.java | 66 ++++++++++++++++++++++ src/site/markdown/usage.md.vm | 15 +++++ .../plugin/resources/remote/SpdxLicensesTest.java | 23 ++++++++ .../resources/remote/it/ITSpdxLicenseId.java | 7 +++ 5 files changed, 121 insertions(+) diff --git a/src/it/resources/spdx-license-id/project/src/main/resources/DEPENDENCIES.vm b/src/it/resources/spdx-license-id/project/src/main/resources/DEPENDENCIES.vm index 3ab3930..3d3323b 100644 --- a/src/it/resources/spdx-license-id/project/src/main/resources/DEPENDENCIES.vm +++ b/src/it/resources/spdx-license-id/project/src/main/resources/DEPENDENCIES.vm @@ -22,3 +22,13 @@ $dependency.artifactId expression: $!spdx.expression($dependency) $dependency.artifactId: #if( $id )$id#else$license.name#end #end #end +#foreach( $entry in $spdx.group($projects).entrySet() ) +Group $entry.key:#foreach( $dependency in $entry.value ) $dependency.artifactId#end + +#end +#foreach( $id in $spdx.ids($projects) ) +License text: $id +#end +#foreach( $dependency in $spdx.unresolved($projects) ) +Unresolved: $dependency.artifactId +#end diff --git a/src/main/java/org/apache/maven/plugin/resources/remote/SpdxLicenses.java b/src/main/java/org/apache/maven/plugin/resources/remote/SpdxLicenses.java index a6e980a..9dfbc2b 100644 --- a/src/main/java/org/apache/maven/plugin/resources/remote/SpdxLicenses.java +++ b/src/main/java/org/apache/maven/plugin/resources/remote/SpdxLicenses.java @@ -32,6 +32,8 @@ import java.util.List; import java.util.Locale; import java.util.Map; import java.util.Set; +import java.util.TreeMap; +import java.util.TreeSet; import java.util.regex.Matcher; import java.util.regex.Pattern; @@ -50,6 +52,10 @@ import org.apache.maven.project.MavenProject; * <p> * A project that declares several licenses may be used under any one of them, so * <code>$!spdx.expression($project)</code> joins their IDs with <code>OR</code>. + * <p> + * To list each license once, <code>$spdx.group($projects)</code> groups dependencies by SPDX expression, + * <code>$spdx.ids($projects)</code> returns every ID they use, and <code>$spdx.unresolved($projects)</code> + * returns the dependencies that match nothing. * * @since 3.4.0 */ @@ -165,6 +171,66 @@ public class SpdxLicenses { return result.toString(); } + /** + * Groups projects by their SPDX license expression, leaving out the projects whose expression is unknown. + * + * @param projects projects, typically <code>$projects</code> + * @return the projects for each expression, sorted by expression + */ + public Map<String, List<MavenProject>> group(List<MavenProject> projects) { + Map<String, List<MavenProject>> groups = new TreeMap<>(String.CASE_INSENSITIVE_ORDER); + if (projects != null) { + for (MavenProject project : projects) { + String expression = expression(project); + if (expression != null) { + groups.computeIfAbsent(expression, k -> new ArrayList<>()).add(project); + } + } + } + return groups; + } + + /** + * Returns every SPDX license and exception ID that the projects' expressions use, each one once, so that a + * template can add each license text once. + * + * @param projects projects, typically <code>$projects</code> + * @return the IDs, sorted + */ + public Set<String> ids(List<MavenProject> projects) { + Set<String> result = new TreeSet<>(String.CASE_INSENSITIVE_ORDER); + for (String expression : group(projects).keySet()) { + Matcher m = EXPRESSION_TOKEN.matcher(expression); + while (m.find()) { + String token = m.group(); + if (!token.equalsIgnoreCase("AND") + && !token.equalsIgnoreCase("OR") + && !token.equalsIgnoreCase("WITH")) { + result.add(token); + } + } + } + return result; + } + + /** + * Returns the projects whose SPDX license expression is unknown. + * + * @param projects projects, typically <code>$projects</code> + * @return the projects that declare no license or a license that matches nothing + */ + public List<MavenProject> unresolved(List<MavenProject> projects) { + List<MavenProject> result = new ArrayList<>(); + if (projects != null) { + for (MavenProject project : projects) { + if (expression(project) == null) { + result.add(project); + } + } + } + return result; + } + /** * Returns the SPDX license ID, or SPDX license expression, for a declared license name and URL. * diff --git a/src/site/markdown/usage.md.vm b/src/site/markdown/usage.md.vm index ded24d3..20315aa 100644 --- a/src/site/markdown/usage.md.vm +++ b/src/site/markdown/usage.md.vm @@ -131,6 +131,21 @@ A mapping matches a spelling wherever it appears, like `licenseMerges` in the [L A dependency that declares several licenses may be used under any one of them. `${esc.d}spdx.expression(${esc.d}dependency)` returns one SPDX license expression for the whole dependency, joining the IDs with `OR`, for example `CDDL-1.1 OR (GPL-2.0-only WITH Classpath-exception-2.0)`. It returns null when the dependency declares no license or when one of its licenses matches nothing, because a partial expression would leave out a choice. +To list each license once rather than once per dependency, group the dependencies by SPDX expression. `${esc.d}spdx.ids(${esc.d}projects)` returns every license and exception ID in use, each one once, so a template can add each license text once. `${esc.d}spdx.unresolved(${esc.d}projects)` returns the dependencies that `${esc.d}spdx` could not resolve, which still need their declared licenses listed: + +``` +#[[#foreach( $group in $spdx.group($projects).entrySet() ) +$group.key: +#foreach( $dependency in $group.value ) + - $dependency.name ($dependency.groupId:$dependency.artifactId) +#end +#end +#foreach( $dependency in $spdx.unresolved($projects) ) +$dependency.name: #foreach( $license in $dependency.licenses )$license.name #end + +#end]]# +``` + Running Once in a Multi-Module Build ------------------------------------ diff --git a/src/test/java/org/apache/maven/plugin/resources/remote/SpdxLicensesTest.java b/src/test/java/org/apache/maven/plugin/resources/remote/SpdxLicensesTest.java index f8296d6..47fcb51 100644 --- a/src/test/java/org/apache/maven/plugin/resources/remote/SpdxLicensesTest.java +++ b/src/test/java/org/apache/maven/plugin/resources/remote/SpdxLicensesTest.java @@ -19,6 +19,8 @@ package org.apache.maven.plugin.resources.remote; import java.util.Arrays; +import java.util.List; +import java.util.Map; import org.apache.maven.model.License; import org.apache.maven.model.Model; @@ -126,6 +128,27 @@ class SpdxLicensesTest { assertNull(spdx.expression(null)); } + @Test + void groupsListEachLicenseOnce() { + MavenProject a = project(license("Apache License, Version 2.0", null)); + MavenProject b = project(license("The Apache Software License, Version 2.0", null)); + MavenProject c = project(license("CDDL 1.1", null), license("GPL2 w/ CPE", null)); + MavenProject d = project(license("Public Domain", null)); + MavenProject e = project(license("Apache-2.0 OR MIT", null)); + List<MavenProject> projects = Arrays.asList(a, b, c, d, e); + + Map<String, List<MavenProject>> groups = spdx.group(projects); + assertEquals( + Arrays.asList( + "Apache-2.0", "Apache-2.0 OR MIT", "CDDL-1.1 OR (GPL-2.0-only WITH Classpath-exception-2.0)"), + Arrays.asList(groups.keySet().toArray())); + assertEquals(Arrays.asList(a, b), groups.get("Apache-2.0")); + assertEquals( + Arrays.asList("Apache-2.0", "CDDL-1.1", "Classpath-exception-2.0", "GPL-2.0-only", "MIT"), + Arrays.asList(spdx.ids(projects).toArray())); + assertEquals(Arrays.asList(d), spdx.unresolved(projects)); + } + private static License license(String name, String url) { License license = new License(); license.setName(name); diff --git a/src/test/java/org/apache/maven/plugin/resources/remote/it/ITSpdxLicenseId.java b/src/test/java/org/apache/maven/plugin/resources/remote/it/ITSpdxLicenseId.java index c99e11f..7b03e2c 100644 --- a/src/test/java/org/apache/maven/plugin/resources/remote/it/ITSpdxLicenseId.java +++ b/src/test/java/org/apache/maven/plugin/resources/remote/it/ITSpdxLicenseId.java @@ -27,6 +27,7 @@ import org.apache.maven.plugin.resources.remote.it.support.TestUtils; import org.codehaus.plexus.util.FileUtils; import org.junit.jupiter.api.Test; +import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertTrue; public class ITSpdxLicenseId extends AbstractIT { @@ -60,5 +61,11 @@ public class ITSpdxLicenseId extends AbstractIT { content.contains("e-dual expression: CDDL-1.1 OR (GPL-2.0-only WITH Classpath-exception-2.0)\n"), content); assertTrue(content.contains("f-mapped: BSD-3-Clause\n"), content); + + assertTrue(content.contains("Group Apache-2.0: a-name b-url\n"), content); + assertTrue(content.contains("Group (Apache-2.0 OR EPL-2.0): c-expression\n"), content); + assertTrue(content.contains("Unresolved: d-unknown\n"), content); + assertEquals(1, content.split("License text: Apache-2.0\n", -1).length - 1, content); + assertTrue(content.contains("License text: Classpath-exception-2.0\n"), content); } }
