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 068cc15c7e9753c673be419d3b9c9fae2b3e85c1 Author: Sylwester Lachiewicz <[email protected]> AuthorDate: Wed Oct 28 01:20:58 2026 +0000 Map project-specific licenses to SPDX IDs with licenseMappings (#316) --- .../spdx-license-id/{ => f-mapped}/pom.xml | 18 +++--- src/it/resources/spdx-license-id/pom.xml | 1 + src/it/resources/spdx-license-id/project/pom.xml | 13 +++++ .../remote/AbstractProcessRemoteResourcesMojo.java | 32 ++++++++++- .../plugin/resources/remote/LicenseMapping.java | 67 ++++++++++++++++++++++ .../plugin/resources/remote/SpdxLicenses.java | 31 +++++++++- .../plugin/resources/remote/license-aliases.txt | 3 +- .../plugin/resources/remote/SpdxLicensesTest.java | 24 ++++++++ .../resources/remote/it/ITSpdxLicenseId.java | 1 + 9 files changed, 175 insertions(+), 15 deletions(-) diff --git a/src/it/resources/spdx-license-id/pom.xml b/src/it/resources/spdx-license-id/f-mapped/pom.xml similarity index 73% copy from src/it/resources/spdx-license-id/pom.xml copy to src/it/resources/spdx-license-id/f-mapped/pom.xml index cbf7256..3dd5fa1 100644 --- a/src/it/resources/spdx-license-id/pom.xml +++ b/src/it/resources/spdx-license-id/f-mapped/pom.xml @@ -18,22 +18,20 @@ specific language governing permissions and limitations under the License. --> -<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" - xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd"> +<project> <modelVersion>4.0.0</modelVersion> <groupId>org.apache.maven.plugin.rresource.it.spdx</groupId> - <artifactId>aggregator</artifactId> + <artifactId>f-mapped</artifactId> <version>1.0</version> <packaging>pom</packaging> - <modules> - <module>a-name</module> - <module>b-url</module> - <module>c-expression</module> - <module>d-unknown</module> - <module>e-dual</module> - </modules> + <licenses> + <license> + <name>BSD</name> + <url>http://example.org/widget/LICENSE.txt</url> + </license> + </licenses> <distributionManagement> <repository> diff --git a/src/it/resources/spdx-license-id/pom.xml b/src/it/resources/spdx-license-id/pom.xml index cbf7256..2d340a7 100644 --- a/src/it/resources/spdx-license-id/pom.xml +++ b/src/it/resources/spdx-license-id/pom.xml @@ -33,6 +33,7 @@ under the License. <module>c-expression</module> <module>d-unknown</module> <module>e-dual</module> + <module>f-mapped</module> </modules> <distributionManagement> diff --git a/src/it/resources/spdx-license-id/project/pom.xml b/src/it/resources/spdx-license-id/project/pom.xml index 78227d9..35c3b96 100644 --- a/src/it/resources/spdx-license-id/project/pom.xml +++ b/src/it/resources/spdx-license-id/project/pom.xml @@ -62,6 +62,12 @@ under the License. <version>1.0</version> <type>pom</type> </dependency> + <dependency> + <groupId>org.apache.maven.plugin.rresource.it.spdx</groupId> + <artifactId>f-mapped</artifactId> + <version>1.0</version> + <type>pom</type> + </dependency> </dependencies> <build> @@ -78,6 +84,13 @@ under the License. <resourceBundles> <resourceBundle>org.apache.maven.plugin.rresource.it:resource-bundle-with-template:${project.version}</resourceBundle> </resourceBundles> + <licenseMappings> + <licenseMapping> + <spdxId>BSD-3-Clause</spdxId> + <name>BSD</name> + <url>https://example.org/widget/LICENSE</url> + </licenseMapping> + </licenseMappings> </configuration> </execution> </executions> diff --git a/src/main/java/org/apache/maven/plugin/resources/remote/AbstractProcessRemoteResourcesMojo.java b/src/main/java/org/apache/maven/plugin/resources/remote/AbstractProcessRemoteResourcesMojo.java index c5493c9..c6cecfb 100644 --- a/src/main/java/org/apache/maven/plugin/resources/remote/AbstractProcessRemoteResourcesMojo.java +++ b/src/main/java/org/apache/maven/plugin/resources/remote/AbstractProcessRemoteResourcesMojo.java @@ -191,6 +191,26 @@ public abstract class AbstractProcessRemoteResourcesMojo extends AbstractMojo { @Parameter private List<String> supplementalModelArtifacts; + /** + * Maps licenses that <code>$spdx</code> does not resolve to SPDX license IDs or expressions. A mapping + * applies to a declared license when each of its <code>name</code> and <code>url</code> that is set matches, + * ignoring case, punctuation, the URL scheme, <code>www.</code> and a trailing <code>.txt</code> or + * <code>.html</code>. Mappings are tried in order, before the built-in aliases. + * <pre> + * <licenseMappings> + * <licenseMapping> + * <spdxId>BSD-3-Clause</spdxId> + * <name>BSD</name> + * <url>https://example.org/widget/LICENSE</url> + * </licenseMapping> + * </licenseMappings> + * </pre> + * + * @since 3.4.0 + */ + @Parameter + private List<LicenseMapping> licenseMappings; + /** * The resource bundles that will be retrieved and processed, * expressed with <code>groupId:artifactId:version[:type[:classifier]]</code> format. @@ -725,6 +745,16 @@ public abstract class AbstractProcessRemoteResourcesMojo extends AbstractMojo { } protected void validate() throws MojoExecutionException { + if (licenseMappings != null) { + for (LicenseMapping mapping : licenseMappings) { + if (SpdxLicenses.isBlank(mapping.getSpdxId()) + || (SpdxLicenses.isBlank(mapping.getName()) && SpdxLicenses.isBlank(mapping.getUrl()))) { + throw new MojoExecutionException( + "Each licenseMapping needs an spdxId and a name, a url or both: " + mapping); + } + } + } + int bundleCount = 1; for (String artifactDescriptor : resourceBundles) { @@ -809,7 +839,7 @@ public abstract class AbstractProcessRemoteResourcesMojo extends AbstractMojo { context.put("project", project); context.put("presentYear", year); context.put("locator", locator); - context.put("spdx", new SpdxLicenses()); + context.put("spdx", new SpdxLicenses(licenseMappings == null ? Collections.emptyList() : licenseMappings)); if (inceptionYear.equals(year)) { context.put("projectTimespan", year); diff --git a/src/main/java/org/apache/maven/plugin/resources/remote/LicenseMapping.java b/src/main/java/org/apache/maven/plugin/resources/remote/LicenseMapping.java new file mode 100644 index 0000000..2b42603 --- /dev/null +++ b/src/main/java/org/apache/maven/plugin/resources/remote/LicenseMapping.java @@ -0,0 +1,67 @@ +/* + * 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 org.apache.maven.plugin.resources.remote; + +/** + * Maps a license, as dependencies declare it, to an SPDX license ID or expression for <code>$spdx</code> in + * templates. A mapping applies to a declared license when each of its <code>name</code> and <code>url</code> + * that is set matches, after the same normalisation as the built-in aliases. + * + * @since 3.4.0 + */ +public class LicenseMapping { + + /** The SPDX license ID or expression to return, for example <code>BSD-3-Clause</code>. */ + private String spdxId; + + /** The declared license name to match. */ + private String name; + + /** The declared license URL to match. */ + private String url; + + public String getSpdxId() { + return spdxId; + } + + public void setSpdxId(String spdxId) { + this.spdxId = spdxId; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public String getUrl() { + return url; + } + + public void setUrl(String url) { + this.url = url; + } + + @Override + public String toString() { + return "LicenseMapping{spdxId=" + spdxId + ", name=" + name + ", url=" + url + "}"; + } +} 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 80b6234..6e34ce3 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 @@ -25,6 +25,7 @@ import java.io.InputStreamReader; import java.io.UncheckedIOException; import java.nio.charset.StandardCharsets; import java.util.ArrayList; +import java.util.Collections; import java.util.HashMap; import java.util.LinkedHashSet; import java.util.List; @@ -41,8 +42,8 @@ import org.apache.maven.project.MavenProject; * Resolves a license declared in a POM to an <a href="https://spdx.org/licenses/">SPDX license ID</a>. * Available in templates as <code>$spdx</code>, for example <code>$!spdx.id($license)</code>. * <p> - * The declared name is tried first: as a known SPDX ID, as an SPDX license expression made of known IDs, - * and as a known alias. The declared URL is tried next: as a known alias, and as an <code>opensource.org</code> + * Configured {@link LicenseMapping}s are tried first. The declared name is tried next: as a known SPDX ID, as an SPDX license expression made of known IDs, + * and as a known alias. The declared URL is tried last: as a known alias, and as an <code>opensource.org</code> * or <code>spdx.org</code> URL that ends with a known ID. When the name and the URL resolve to different IDs, * nothing matches. The known IDs and aliases cover the licenses that POMs declare most often; a deprecated * SPDX ID such as <code>GPL-2.0</code> is returned as declared. @@ -72,7 +73,14 @@ public class SpdxLicenses { /** Normalised URL to ID or expression. */ private final Map<String, String> urls = new HashMap<>(); + private final List<LicenseMapping> mappings; + SpdxLicenses() { + this(Collections.emptyList()); + } + + SpdxLicenses(List<LicenseMapping> mappings) { + this.mappings = new ArrayList<>(mappings); InputStream in = SpdxLicenses.class.getResourceAsStream(RESOURCE); if (in == null) { throw new IllegalStateException("Missing resource " + RESOURCE); @@ -163,6 +171,11 @@ public class SpdxLicenses { * @return the SPDX ID or expression, or {@code null} when nothing matches */ public String id(String name, String url) { + for (LicenseMapping mapping : mappings) { + if (matches(mapping.getName(), name, true) && matches(mapping.getUrl(), url, false)) { + return mapping.getSpdxId().trim(); + } + } String byName = isBlank(name) ? null : idOfName(name.trim()); String byUrl = isBlank(url) ? null : idOfUrl(normaliseUrl(url)); if (byName == null) { @@ -281,7 +294,19 @@ public class SpdxLicenses { } } - private static boolean isBlank(String s) { + private static boolean matches(String expected, String declared, boolean isName) { + if (isBlank(expected)) { + return true; + } + if (isBlank(declared)) { + return false; + } + return isName + ? normaliseName(expected).equals(normaliseName(declared)) + : normaliseUrl(expected).equals(normaliseUrl(declared)); + } + + static boolean isBlank(String s) { return s == null || s.trim().isEmpty(); } diff --git a/src/main/resources/org/apache/maven/plugin/resources/remote/license-aliases.txt b/src/main/resources/org/apache/maven/plugin/resources/remote/license-aliases.txt index 34ebde1..01e001e 100644 --- a/src/main/resources/org/apache/maven/plugin/resources/remote/license-aliases.txt +++ b/src/main/resources/org/apache/maven/plugin/resources/remote/license-aliases.txt @@ -26,7 +26,8 @@ # whether "only" or "or later" applies, and a wrong ID is worse than none. So are URLs # of pages that cover two licenses at once, such as GlassFish's CDDL+GPL_1_1.html, and # license pages of individual projects, such as jsoup.org/license: they name no license, -# and keep their address when the project changes its license. +# and keep their address when the project changes its license. Map those with the +# plugin's licenseMappings parameter instead. 0BSD AGPL-3.0-only 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 80f4af7..97c2453 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 @@ -18,6 +18,8 @@ */ package org.apache.maven.plugin.resources.remote; +import java.util.Arrays; + import org.apache.maven.model.License; import org.apache.maven.model.Model; import org.apache.maven.project.MavenProject; @@ -139,6 +141,28 @@ class SpdxLicensesTest { return new MavenProject(model); } + @Test + void mappings() { + SpdxLicenses mapped = new SpdxLicenses(Arrays.asList( + mapping("BSD-3-Clause", "BSD", "https://example.org/widget/LICENSE"), + mapping("LicenseRef-Bouncy-Castle", "Bouncy Castle Licence", null), + mapping("Apache-2.0 OR MIT", null, "https://example.com/LICENSE"))); + assertEquals("BSD-3-Clause", mapped.id("bsd", "http://www.example.org/widget/LICENSE.txt")); + assertNull(mapped.id("BSD", null)); + assertNull(mapped.id(null, "https://example.org/widget/LICENSE")); + assertEquals("LicenseRef-Bouncy-Castle", mapped.id("Bouncy Castle Licence", "https://example.org/")); + assertEquals("Apache-2.0 OR MIT", mapped.id("Apache 2.0", "https://example.com/LICENSE.txt")); + assertEquals("MIT", mapped.id("MIT", null)); + } + + private static LicenseMapping mapping(String spdxId, String name, String url) { + LicenseMapping mapping = new LicenseMapping(); + mapping.setSpdxId(spdxId); + mapping.setName(name); + mapping.setUrl(url); + return mapping; + } + @Test void license() { License license = new License(); 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 7676878..c99e11f 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 @@ -59,5 +59,6 @@ public class ITSpdxLicenseId extends AbstractIT { assertTrue( 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); } }
