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 c87a5123dc8c42c0e35757873fc66bb28d117b51 Author: Sylwester Lachiewicz <[email protected]> AuthorDate: Wed Oct 28 01:12:34 2026 +0000 Expose SPDX license IDs to resource templates (#316) The alias table is our own, not a copy of the SPDX License List, which is published without a licence that clears it for an ASF source release. GNU URLs stay unmapped: they do not say "only" or "or later". --- src/it/resources/spdx-license-id/a-name/pom.xml | 41 ++++ src/it/resources/spdx-license-id/b-url/pom.xml | 42 ++++ .../resources/spdx-license-id/c-expression/pom.xml | 41 ++++ src/it/resources/spdx-license-id/d-unknown/pom.xml | 41 ++++ src/it/resources/spdx-license-id/e-dual/pom.xml | 46 ++++ src/it/resources/spdx-license-id/pom.xml | 44 ++++ src/it/resources/spdx-license-id/project/pom.xml | 87 ++++++++ .../project/src/main/resources/DEPENDENCIES.vm | 24 +++ .../remote/AbstractProcessRemoteResourcesMojo.java | 4 + .../plugin/resources/remote/SpdxLicenses.java | 239 +++++++++++++++++++++ .../plugin/resources/remote/license-aliases.txt | 167 ++++++++++++++ src/site/markdown/usage.md.vm | 18 ++ .../plugin/resources/remote/SpdxLicensesTest.java | 134 ++++++++++++ .../resources/remote/it/ITSpdxLicenseId.java | 63 ++++++ 14 files changed, 991 insertions(+) diff --git a/src/it/resources/spdx-license-id/a-name/pom.xml b/src/it/resources/spdx-license-id/a-name/pom.xml new file mode 100644 index 0000000..61bfedf --- /dev/null +++ b/src/it/resources/spdx-license-id/a-name/pom.xml @@ -0,0 +1,41 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- +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. +--> + +<project> + <modelVersion>4.0.0</modelVersion> + + <groupId>org.apache.maven.plugin.rresource.it.spdx</groupId> + <artifactId>a-name</artifactId> + <version>1.0</version> + <packaging>pom</packaging> + + <licenses> + <license> + <name>The Apache Software License, Version 2.0</name> + </license> + </licenses> + + <distributionManagement> + <repository> + <id>it-remote</id> + <url>file:${remoteRepository}</url> + </repository> + </distributionManagement> +</project> diff --git a/src/it/resources/spdx-license-id/b-url/pom.xml b/src/it/resources/spdx-license-id/b-url/pom.xml new file mode 100644 index 0000000..c460412 --- /dev/null +++ b/src/it/resources/spdx-license-id/b-url/pom.xml @@ -0,0 +1,42 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- +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. +--> + +<project> + <modelVersion>4.0.0</modelVersion> + + <groupId>org.apache.maven.plugin.rresource.it.spdx</groupId> + <artifactId>b-url</artifactId> + <version>1.0</version> + <packaging>pom</packaging> + + <licenses> + <license> + <name>Apache License</name> + <url>http://www.apache.org/licenses/LICENSE-2.0.txt</url> + </license> + </licenses> + + <distributionManagement> + <repository> + <id>it-remote</id> + <url>file:${remoteRepository}</url> + </repository> + </distributionManagement> +</project> diff --git a/src/it/resources/spdx-license-id/c-expression/pom.xml b/src/it/resources/spdx-license-id/c-expression/pom.xml new file mode 100644 index 0000000..6b8ec0b --- /dev/null +++ b/src/it/resources/spdx-license-id/c-expression/pom.xml @@ -0,0 +1,41 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- +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. +--> + +<project> + <modelVersion>4.0.0</modelVersion> + + <groupId>org.apache.maven.plugin.rresource.it.spdx</groupId> + <artifactId>c-expression</artifactId> + <version>1.0</version> + <packaging>pom</packaging> + + <licenses> + <license> + <name>(Apache-2.0 OR EPL-2.0)</name> + </license> + </licenses> + + <distributionManagement> + <repository> + <id>it-remote</id> + <url>file:${remoteRepository}</url> + </repository> + </distributionManagement> +</project> diff --git a/src/it/resources/spdx-license-id/d-unknown/pom.xml b/src/it/resources/spdx-license-id/d-unknown/pom.xml new file mode 100644 index 0000000..ca07cad --- /dev/null +++ b/src/it/resources/spdx-license-id/d-unknown/pom.xml @@ -0,0 +1,41 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- +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. +--> + +<project> + <modelVersion>4.0.0</modelVersion> + + <groupId>org.apache.maven.plugin.rresource.it.spdx</groupId> + <artifactId>d-unknown</artifactId> + <version>1.0</version> + <packaging>pom</packaging> + + <licenses> + <license> + <name>Public Domain</name> + </license> + </licenses> + + <distributionManagement> + <repository> + <id>it-remote</id> + <url>file:${remoteRepository}</url> + </repository> + </distributionManagement> +</project> diff --git a/src/it/resources/spdx-license-id/e-dual/pom.xml b/src/it/resources/spdx-license-id/e-dual/pom.xml new file mode 100644 index 0000000..38f522c --- /dev/null +++ b/src/it/resources/spdx-license-id/e-dual/pom.xml @@ -0,0 +1,46 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- +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. +--> + +<project> + <modelVersion>4.0.0</modelVersion> + + <groupId>org.apache.maven.plugin.rresource.it.spdx</groupId> + <artifactId>e-dual</artifactId> + <version>1.0</version> + <packaging>pom</packaging> + + <licenses> + <license> + <name>CDDL 1.1</name> + <url>https://glassfish.java.net/public/CDDL+GPL_1_1.html</url> + </license> + <license> + <name>GPL2 w/ CPE</name> + <url>https://glassfish.java.net/public/CDDL+GPL_1_1.html</url> + </license> + </licenses> + + <distributionManagement> + <repository> + <id>it-remote</id> + <url>file:${remoteRepository}</url> + </repository> + </distributionManagement> +</project> diff --git a/src/it/resources/spdx-license-id/pom.xml b/src/it/resources/spdx-license-id/pom.xml new file mode 100644 index 0000000..cbf7256 --- /dev/null +++ b/src/it/resources/spdx-license-id/pom.xml @@ -0,0 +1,44 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- +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. +--> + +<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"> + <modelVersion>4.0.0</modelVersion> + + <groupId>org.apache.maven.plugin.rresource.it.spdx</groupId> + <artifactId>aggregator</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> + + <distributionManagement> + <repository> + <id>it-remote</id> + <url>file:${remoteRepository}</url> + </repository> + </distributionManagement> +</project> diff --git a/src/it/resources/spdx-license-id/project/pom.xml b/src/it/resources/spdx-license-id/project/pom.xml new file mode 100644 index 0000000..78227d9 --- /dev/null +++ b/src/it/resources/spdx-license-id/project/pom.xml @@ -0,0 +1,87 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- +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. +--> + +<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"> + <modelVersion>4.0.0</modelVersion> + + <parent> + <groupId>org.apache.maven.plugin.rresource.it</groupId> + <artifactId>it-parent</artifactId> + <version>${project.version}</version> + <relativePath>../../it-parent/pom.xml</relativePath> + </parent> + + <artifactId>spdx-license-id</artifactId> + + <dependencies> + <dependency> + <groupId>org.apache.maven.plugin.rresource.it.spdx</groupId> + <artifactId>a-name</artifactId> + <version>1.0</version> + <type>pom</type> + </dependency> + <dependency> + <groupId>org.apache.maven.plugin.rresource.it.spdx</groupId> + <artifactId>b-url</artifactId> + <version>1.0</version> + <type>pom</type> + </dependency> + <dependency> + <groupId>org.apache.maven.plugin.rresource.it.spdx</groupId> + <artifactId>c-expression</artifactId> + <version>1.0</version> + <type>pom</type> + </dependency> + <dependency> + <groupId>org.apache.maven.plugin.rresource.it.spdx</groupId> + <artifactId>d-unknown</artifactId> + <version>1.0</version> + <type>pom</type> + </dependency> + <dependency> + <groupId>org.apache.maven.plugin.rresource.it.spdx</groupId> + <artifactId>e-dual</artifactId> + <version>1.0</version> + <type>pom</type> + </dependency> + </dependencies> + + <build> + <plugins> + <plugin> + <artifactId>maven-remote-resources-plugin</artifactId> + <executions> + <execution> + <id>remote-resources</id> + <goals> + <goal>process</goal> + </goals> + <configuration> + <resourceBundles> + <resourceBundle>org.apache.maven.plugin.rresource.it:resource-bundle-with-template:${project.version}</resourceBundle> + </resourceBundles> + </configuration> + </execution> + </executions> + </plugin> + </plugins> + </build> +</project> 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 new file mode 100644 index 0000000..3ab3930 --- /dev/null +++ b/src/it/resources/spdx-license-id/project/src/main/resources/DEPENDENCIES.vm @@ -0,0 +1,24 @@ +## 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. +Override: #if ($projectName)$projectName#else${project.name}#end +#foreach( $dependency in $projects ) +$dependency.artifactId expression: $!spdx.expression($dependency) +#foreach( $license in $dependency.licenses ) +#set( $id = $spdx.id($license) ) +$dependency.artifactId: #if( $id )$id#else$license.name#end +#end +#end 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 755189a..1e4caf3 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 @@ -230,6 +230,9 @@ public abstract class AbstractProcessRemoteResourcesMojo extends AbstractMojo { * <li><code>projectsSortedByOrganization</code> - the list of dependency projects sorted by organization</li> * <li><code>projectTimespan</code> - the timespan of the current project (requires inceptionYear in pom)</li> * <li><code>locator</code> - the ResourceManager that can be used to retrieve additional resources</li> + * <li><code>spdx</code> - resolves a dependency license to an SPDX license ID, for example + * <code>$!spdx.id($license)</code>, or a dependency to an SPDX expression, for example + * <code>$!spdx.expression($dependency)</code> (since 3.4.0)</li> * </ul> * See <a * href="https://maven.apache.org/ref/current/maven-project/apidocs/org/apache/maven/project/MavenProject.html"> the @@ -806,6 +809,7 @@ public abstract class AbstractProcessRemoteResourcesMojo extends AbstractMojo { context.put("project", project); context.put("presentYear", year); context.put("locator", locator); + context.put("spdx", new SpdxLicenses()); if (inceptionYear.equals(year)) { context.put("projectTimespan", year); 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 new file mode 100644 index 0000000..c3d9a96 --- /dev/null +++ b/src/main/java/org/apache/maven/plugin/resources/remote/SpdxLicenses.java @@ -0,0 +1,239 @@ +/* + * 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; + +import java.io.BufferedReader; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.io.UncheckedIOException; +import java.nio.charset.StandardCharsets; +import java.util.HashMap; +import java.util.LinkedHashSet; +import java.util.List; +import java.util.Locale; +import java.util.Map; +import java.util.Set; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +import org.apache.maven.model.License; +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> + * 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. + * <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>. + * + * @since 3.4.0 + */ +public class SpdxLicenses { + + private static final String RESOURCE = "license-aliases.txt"; + + private static final Pattern EXPRESSION_TOKEN = Pattern.compile("[^\\s()]+"); + + private static final Pattern ID_URL = + Pattern.compile("(?:opensource\\.org/licenses?|spdx\\.org/licenses)/([^/?]+)"); + + /** Lower-case ID to ID. */ + private final Map<String, String> ids = new HashMap<>(); + + /** Normalised name to ID or expression. */ + private final Map<String, String> names = new HashMap<>(); + + /** Normalised URL to ID or expression. */ + private final Map<String, String> urls = new HashMap<>(); + + SpdxLicenses() { + InputStream in = SpdxLicenses.class.getResourceAsStream(RESOURCE); + if (in == null) { + throw new IllegalStateException("Missing resource " + RESOURCE); + } + try (BufferedReader reader = new BufferedReader(new InputStreamReader(in, StandardCharsets.UTF_8))) { + String line; + while ((line = reader.readLine()) != null) { + if (line.trim().isEmpty() || line.startsWith("#")) { + continue; + } + String[] fields = line.split("\t", 2); + String id = fields[0].trim(); + if (id.indexOf(' ') < 0) { + ids.put(id.toLowerCase(Locale.ROOT), id); + } + if (fields.length > 1) { + String alias = fields[1].trim(); + if (alias.startsWith("http://") || alias.startsWith("https://")) { + put(urls, normaliseUrl(alias), id); + } else { + put(names, normaliseName(alias), id); + } + } + } + } catch (IOException e) { + throw new UncheckedIOException(e); + } + } + + private static void put(Map<String, String> map, String key, String id) { + String previous = map.put(key, id); + if (previous != null && !previous.equals(id)) { + throw new IllegalStateException(RESOURCE + " maps '" + key + "' to both " + previous + " and " + id); + } + } + + /** + * Returns the SPDX license ID, or SPDX license expression, of a declared license. + * + * @param license a license from a project model + * @return the SPDX ID or expression, or {@code null} when nothing matches + */ + public String id(License license) { + return license == null ? null : id(license.getName(), license.getUrl()); + } + + /** + * Returns the SPDX license expression of a project: the ID of its license, or the IDs of its licenses + * joined with <code>OR</code>, since a project that declares several licenses may be used under any one + * of them. + * + * @param project a project, typically a dependency + * @return the SPDX expression, or {@code null} when the project declares no license or one of its + * licenses matches nothing + */ + public String expression(MavenProject project) { + List<License> licenses = project == null ? null : project.getLicenses(); + if (licenses == null || licenses.isEmpty()) { + return null; + } + Set<String> choices = new LinkedHashSet<>(); + for (License license : licenses) { + String id = id(license); + if (id == null) { + return null; + } + choices.add(id); + } + if (choices.size() == 1) { + return choices.iterator().next(); + } + StringBuilder result = new StringBuilder(); + for (String id : choices) { + if (result.length() > 0) { + result.append(" OR "); + } + boolean compound = id.indexOf(' ') >= 0 && !id.startsWith("("); + result.append(compound ? "(" + id + ")" : id); + } + return result.toString(); + } + + /** + * Returns the SPDX license ID, or SPDX license expression, for a declared license name and URL. + * + * @param name the declared license name, may be {@code null} + * @param url the declared license URL, may be {@code null} + * @return the SPDX ID or expression, or {@code null} when nothing matches + */ + public String id(String name, String url) { + String byName = isBlank(name) ? null : idOfName(name.trim()); + String byUrl = isBlank(url) ? null : idOfUrl(normaliseUrl(url)); + if (byName == null) { + return byUrl; + } + return byUrl == null || byUrl.equals(byName) ? byName : null; + } + + private String idOfName(String name) { + String id = ids.get(name.toLowerCase(Locale.ROOT)); + if (id == null) { + id = parseExpression(name); + } + if (id == null) { + id = names.get(normaliseName(name)); + } + return id; + } + + private String idOfUrl(String url) { + String id = urls.get(url); + if (id == null) { + Matcher m = ID_URL.matcher(url); + if (m.matches()) { + id = ids.get(m.group(1)); + } + } + return id; + } + + /** + * Returns the expression with each ID in its SPDX spelling, or {@code null} unless it combines at least + * two known IDs with <code>AND</code>, <code>OR</code> or <code>WITH</code>. + */ + private String parseExpression(String declared) { + Matcher m = EXPRESSION_TOKEN.matcher(declared); + StringBuffer result = new StringBuffer(); + int operators = 0; + while (m.find()) { + String token = m.group(); + String upper = token.toUpperCase(Locale.ROOT); + if (upper.equals("AND") || upper.equals("OR") || upper.equals("WITH")) { + operators++; + m.appendReplacement(result, upper); + continue; + } + String id = ids.get(token.toLowerCase(Locale.ROOT)); + if (id == null) { + return null; + } + m.appendReplacement(result, Matcher.quoteReplacement(id)); + } + m.appendTail(result); + return operators > 0 ? result.toString() : null; + } + + private static boolean isBlank(String s) { + return s == null || s.trim().isEmpty(); + } + + /** Lower case, with every run of other characters than letters and digits turned into one space. */ + static String normaliseName(String name) { + String n = name.toLowerCase(Locale.ROOT).replaceAll("[^a-z0-9]+", " ").trim(); + return n.startsWith("the ") ? n.substring(4) : n; + } + + /** Lower case, without scheme, <code>www.</code>, fragment, trailing slashes and document extension. */ + static String normaliseUrl(String url) { + String u = url.trim().toLowerCase(Locale.ROOT); + int fragment = u.indexOf('#'); + if (fragment >= 0) { + u = u.substring(0, fragment); + } + u = u.replaceFirst("^https?://", "").replaceFirst("^www\\.", "").replaceFirst("/+$", ""); + return u.replaceFirst("\\.(txt|html?|php|md)$", ""); + } +} 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 new file mode 100644 index 0000000..7f4a4e1 --- /dev/null +++ b/src/main/resources/org/apache/maven/plugin/resources/remote/license-aliases.txt @@ -0,0 +1,167 @@ +# 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. + +# License names and URLs, as POMs declare them, mapped to SPDX license IDs. +# +# Each line holds an SPDX ID or expression, a tab, and one declared name or URL. +# An alias that starts with http:// or https:// is a URL. A line with an ID alone makes the ID known +# without adding an alias. Names and URLs are normalised before matching, so case, +# punctuation, the URL scheme, "www." and a trailing ".txt" or ".html" do not matter. +# +# GNU license URLs and unversioned GNU names are left out on purpose: they do not say +# 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. + +0BSD +AGPL-3.0-only +AGPL-3.0-or-later +BSL-1.0 Boost Software License 1.0 +BSL-1.0 Boost Software License, Version 1.0 +BSL-1.0 https://www.boost.org/LICENSE_1_0.txt +CC0-1.0 CC0 +CC0-1.0 CC0 1.0 Universal +CC0-1.0 Creative Commons Zero v1.0 Universal +CC0-1.0 https://creativecommons.org/publicdomain/zero/1.0/ +Classpath-exception-2.0 +GPL-2.0 +GPL-2.0+ +GPL-2.0-only +GPL-2.0-or-later +GPL-3.0 +GPL-3.0+ +GPL-3.0-only +GPL-3.0-or-later +ISC ISC License +LGPL-2.1 +LGPL-2.1+ +LGPL-2.1-only +LGPL-2.1-or-later +LGPL-3.0 +LGPL-3.0+ +LGPL-3.0-only +LGPL-3.0-or-later +MIT-0 +Unlicense The Unlicense +Unlicense https://unlicense.org/ + +Apache-1.1 Apache License 1.1 +Apache-1.1 Apache License, Version 1.1 +Apache-1.1 Apache Software License, Version 1.1 +Apache-1.1 The Apache Software License, Version 1.1 +Apache-1.1 https://www.apache.org/licenses/LICENSE-1.1 + +Apache-2.0 ASL 2.0 +Apache-2.0 ASL2 +Apache-2.0 Apache 2 +Apache-2.0 Apache 2.0 +Apache-2.0 Apache 2.0 License +Apache-2.0 Apache License 2.0 +Apache-2.0 Apache License Version 2.0 +Apache-2.0 Apache License, Version 2 +Apache-2.0 Apache License, Version 2.0 +Apache-2.0 Apache Software License - Version 2.0 +Apache-2.0 Apache Software License 2.0 +Apache-2.0 Apache Software License, Version 2.0 +Apache-2.0 The Apache License, Version 2.0 +Apache-2.0 The Apache Software License, Version 2.0 +Apache-2.0 https://www.apache.org/licenses/LICENSE-2.0 +Apache-2.0 https://opensource.org/licenses/Apache-2.0 +Apache-2.0 https://opensource.org/licenses/apache2.0.php + +BSD-2-Clause BSD 2-Clause +BSD-2-Clause BSD 2-Clause License +BSD-2-Clause BSD 2-Clause "Simplified" License +BSD-2-Clause Simplified BSD License +BSD-2-Clause https://opensource.org/licenses/BSD-2-Clause + +# The Eclipse Distribution License 1.0 is the 3-clause BSD license. +BSD-3-Clause 3-Clause BSD License +BSD-3-Clause BSD 3-Clause +BSD-3-Clause BSD 3-Clause License +BSD-3-Clause BSD-3-Clause License +BSD-3-Clause New BSD License +BSD-3-Clause Revised BSD +BSD-3-Clause Revised BSD License +BSD-3-Clause The BSD 3-Clause License +BSD-3-Clause EDL 1.0 +BSD-3-Clause Eclipse Distribution License - v 1.0 +BSD-3-Clause Eclipse Distribution License v. 1.0 +BSD-3-Clause https://www.eclipse.org/org/documents/edl-v10.php +BSD-3-Clause https://opensource.org/licenses/BSD-3-Clause + +CDDL-1.0 CDDL 1.0 +CDDL-1.0 Common Development and Distribution License 1.0 +CDDL-1.0 Common Development and Distribution License (CDDL) v1.0 +CDDL-1.0 https://opensource.org/licenses/CDDL-1.0 +CDDL-1.1 CDDL 1.1 +CDDL-1.1 Common Development and Distribution License 1.1 + +CPL-1.0 Common Public License 1.0 +CPL-1.0 Common Public License Version 1.0 +CPL-1.0 https://www.opensource.org/licenses/cpl1.0.txt + +EPL-1.0 EPL 1.0 +EPL-1.0 Eclipse Public License - v 1.0 +EPL-1.0 Eclipse Public License - Version 1.0 +EPL-1.0 Eclipse Public License 1.0 +EPL-1.0 Eclipse Public License v1.0 +EPL-1.0 Eclipse Public License version 1.0 +EPL-1.0 Eclipse Public License, Version 1.0 +EPL-1.0 The Eclipse Public License, Version 1.0 +EPL-1.0 https://www.eclipse.org/legal/epl-v10.html +EPL-1.0 https://www.eclipse.org/org/documents/epl-v10.php + +EPL-2.0 EPL 2.0 +EPL-2.0 EPLv2 +EPL-2.0 Eclipse Public License - v 2.0 +EPL-2.0 Eclipse Public License - Version 2.0 +EPL-2.0 Eclipse Public License 2.0 +EPL-2.0 Eclipse Public License v. 2.0 +EPL-2.0 Eclipse Public License v2.0 +EPL-2.0 Eclipse Public License, Version 2.0 +EPL-2.0 https://www.eclipse.org/legal/epl-2.0/ +EPL-2.0 https://www.eclipse.org/legal/epl-v20.html +EPL-2.0 https://www.eclipse.org/org/documents/epl-2.0/EPL-2.0.txt +EPL-2.0 https://projects.eclipse.org/license/epl-2.0 + +GPL-2.0-only WITH Classpath-exception-2.0 GPL2 w/ CPE +GPL-2.0-only WITH Classpath-exception-2.0 GNU General Public License, version 2, with the Classpath Exception +GPL-2.0-only WITH Classpath-exception-2.0 GNU General Public License, version 2 with the GNU Classpath Exception +GPL-2.0-only WITH Classpath-exception-2.0 https://openjdk.java.net/legal/gplv2+ce.html +GPL-2.0-only WITH Classpath-exception-2.0 https://openjdk.org/legal/gplv2+ce.html + +MIT MIT License +MIT The MIT License +MIT The MIT License (MIT) +MIT https://opensource.org/licenses/MIT +MIT https://opensource.org/licenses/mit-license.php + +MPL-1.0 Mozilla Public License Version 1.0 +MPL-1.0 https://www.mozilla.org/MPL/MPL-1.0.html +MPL-1.1 MPL 1.1 +MPL-1.1 Mozilla Public License, Version 1.1 +MPL-1.1 Mozilla Public License Version 1.1 +MPL-1.1 https://www.mozilla.org/MPL/MPL-1.1.html +MPL-2.0 MPL 2.0 +MPL-2.0 Mozilla Public License, Version 2.0 +MPL-2.0 Mozilla Public License Version 2.0 +MPL-2.0 https://www.mozilla.org/MPL/2.0/ + +UPL-1.0 Universal Permissive License, Version 1.0 +UPL-1.0 Universal Permissive License v1.0 +UPL-1.0 https://opensource.org/licenses/UPL +UPL-1.0 https://oss.oracle.com/licenses/upl diff --git a/src/site/markdown/usage.md.vm b/src/site/markdown/usage.md.vm index fdbcbc2..ca0a731 100644 --- a/src/site/markdown/usage.md.vm +++ b/src/site/markdown/usage.md.vm @@ -97,6 +97,24 @@ To use remote resource bundles you need to configure the plugin as follows: This will retrieve the `apache-jar-resource-bundle-1.0.jar` from the remote repositories specified in your POM, process each resource in the bundle and deposit them in your projects `${esc.d}{basedir}/target/classes` directory. +Using SPDX License IDs in Templates +---------------------------------- + +Dependencies declare the same license under many names, such as "Apache License, Version 2.0" and "The Apache Software License, Version 2.0". Since version 3.4.0, templates can use `${esc.d}spdx.id(${esc.d}license)` to get the [SPDX license ID](https://spdx.org/licenses/) of a declared license, for example `Apache-2.0`. The plugin tries the declared name first, then the URL, against a table of the licenses that POMs declare most often. A declaration that is already an SPDX ID or SPDX lic [...] + +When nothing matches, or the name and URL resolve to different IDs, `${esc.d}spdx.id` returns null. Write `${esc.d}!spdx.id(${esc.d}license)` to print nothing in that case, or fall back to the declared name: + +``` +#[[#foreach( $dependency in $projects ) +#foreach( $license in $dependency.licenses ) +#set( $id = $spdx.id($license) ) +$dependency.groupId:$dependency.artifactId #if( $id )$id#else$license.name#end +#end +#end]]# +``` + +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. + 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 new file mode 100644 index 0000000..81493ec --- /dev/null +++ b/src/test/java/org/apache/maven/plugin/resources/remote/SpdxLicensesTest.java @@ -0,0 +1,134 @@ +/* + * 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; + +import org.apache.maven.model.License; +import org.apache.maven.model.Model; +import org.apache.maven.project.MavenProject; +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNull; + +class SpdxLicensesTest { + + private final SpdxLicenses spdx = new SpdxLicenses(); + + @Test + void declaredId() { + assertEquals("Apache-2.0", spdx.id("Apache-2.0", null)); + assertEquals("Apache-2.0", spdx.id("apache-2.0", null)); + assertEquals("GPL-2.0", spdx.id("GPL-2.0", null)); + } + + @Test + void expression() { + assertEquals("(Apache-2.0 OR EPL-2.0)", spdx.id("(Apache-2.0 or epl-2.0)", null)); + assertEquals( + "GPL-2.0-only WITH Classpath-exception-2.0", + spdx.id("GPL-2.0-only with Classpath-exception-2.0", null)); + assertNull(spdx.id("Apache-2.0 OR Unknown-1.0", null)); + assertNull(spdx.id("Apache-2.0 MIT", null)); + } + + @Test + void name() { + assertEquals("Apache-2.0", spdx.id("The Apache Software License, Version 2.0", null)); + assertEquals("Apache-2.0", spdx.id(" apache license version 2.0 ", null)); + assertEquals("MIT", spdx.id("The MIT License (MIT)", null)); + assertEquals("BSD-3-Clause", spdx.id("Eclipse Distribution License - v 1.0", null)); + assertEquals("GPL-2.0-only WITH Classpath-exception-2.0", spdx.id("GPL2 w/ CPE", null)); + } + + @Test + void url() { + assertEquals("Apache-2.0", spdx.id(null, "http://www.apache.org/licenses/LICENSE-2.0.txt")); + assertEquals("Apache-2.0", spdx.id("", "https://apache.org/licenses/LICENSE-2.0/")); + assertEquals("EPL-2.0", spdx.id("Some name", "https://www.eclipse.org/legal/epl-v20.html")); + } + + @Test + void idAtEndOfUrl() { + assertEquals("MIT", spdx.id(null, "https://spdx.org/licenses/MIT.txt")); + assertEquals("BSD-2-Clause", spdx.id(null, "https://opensource.org/license/bsd-2-clause")); + assertEquals("MIT-0", spdx.id(null, "https://spdx.org/licenses/MIT-0.html")); + assertNull(spdx.id(null, "https://opensource.org/licenses/bsd-license.php")); + } + + @Test + void nameAndUrlDisagree() { + assertNull(spdx.id("MIT License", "https://www.apache.org/licenses/LICENSE-2.0")); + } + + @Test + void ambiguousGnuDeclarationsMatchNothing() { + assertNull(spdx.id("GNU Lesser General Public License", "https://www.gnu.org/licenses/lgpl.html")); + assertNull(spdx.id(null, "https://www.gnu.org/licenses/old-licenses/gpl-2.0.html")); + } + + @Test + void unknown() { + assertNull(spdx.id("Public Domain", null)); + assertNull(spdx.id(null, null)); + assertNull(spdx.id((License) null)); + } + + @Test + void severalLicensesAreAChoice() { + assertEquals( + "CDDL-1.1 OR (GPL-2.0-only WITH Classpath-exception-2.0)", + spdx.expression(project( + license("CDDL 1.1", "https://glassfish.java.net/public/CDDL+GPL_1_1.html"), + license("GPL2 w/ CPE", "https://glassfish.java.net/public/CDDL+GPL_1_1.html")))); + assertEquals( + "Apache-2.0 OR (MIT OR EPL-2.0)", + spdx.expression(project(license("Apache 2.0", null), license("MIT OR EPL-2.0", null)))); + } + + @Test + void projectExpression() { + assertEquals("MIT", spdx.expression(project(license("MIT", null), license("The MIT License", null)))); + assertNull(spdx.expression(project(license("MIT", null), license("Public Domain", null)))); + assertNull(spdx.expression(project())); + assertNull(spdx.expression(null)); + } + + private static License license(String name, String url) { + License license = new License(); + license.setName(name); + license.setUrl(url); + return license; + } + + private static MavenProject project(License... licenses) { + Model model = new Model(); + for (License license : licenses) { + model.addLicense(license); + } + return new MavenProject(model); + } + + @Test + void license() { + License license = new License(); + license.setName("Apache License, Version 2.0"); + license.setUrl("https://www.apache.org/licenses/LICENSE-2.0"); + assertEquals("Apache-2.0", spdx.id(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 new file mode 100644 index 0000000..cec9cdf --- /dev/null +++ b/src/test/java/org/apache/maven/plugin/resources/remote/it/ITSpdxLicenseId.java @@ -0,0 +1,63 @@ +/* + * 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.it; + +import java.io.File; +import java.io.IOException; +import java.net.URISyntaxException; + +import org.apache.maven.plugin.resources.remote.it.support.MavenRunner; +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.assertTrue; + +public class ITSpdxLicenseId extends AbstractIT { + @Test + public void test() throws IOException, URISyntaxException { + File dir = TestUtils.getTestDir("spdx-license-id"); + + MavenRunner verifier = TestUtils.newVerifier(dir); + verifier.addCliArgument("deploy"); + verifier.execute(); + verifier.verifyErrorFreeLog(); + + File projectDir = new File(dir, "project"); + verifier = TestUtils.newVerifier(projectDir); + verifier.deleteArtifacts("org.apache.maven.plugin.rresource.it.spdx"); + verifier.addCliArgument("generate-resources"); + verifier.execute(); + verifier.verifyErrorFreeLog(); + + File output = new File(projectDir, "target/maven-shared-archive-resources/DEPENDENCIES"); + String content = FileUtils.fileRead(output); + + assertTrue(content.contains("a-name: Apache-2.0\n"), content); + assertTrue(content.contains("b-url: Apache-2.0\n"), content); + assertTrue(content.contains("c-expression: (Apache-2.0 OR EPL-2.0)\n"), content); + assertTrue(content.contains("d-unknown: Public Domain\n"), content); + assertTrue(content.contains("d-unknown expression: \n"), content); + assertTrue(content.contains("e-dual: CDDL-1.1\n"), content); + assertTrue(content.contains("e-dual: GPL-2.0-only WITH Classpath-exception-2.0\n"), content); + assertTrue( + content.contains("e-dual expression: CDDL-1.1 OR (GPL-2.0-only WITH Classpath-exception-2.0)\n"), + content); + } +}
