diff --git a/tycho-extras/pom.xml b/tycho-extras/pom.xml index 0123fcf5e4..e258f59607 100644 --- a/tycho-extras/pom.xml +++ b/tycho-extras/pom.xml @@ -42,6 +42,7 @@ <module>target-platform-validation-plugin</module> <module>tycho-pomless</module> <module>tycho-dependency-tools-plugin</module> + <module>tycho-sbom</module> </modules> <dependencyManagement> diff --git a/tycho-extras/tycho-sbom/.settings/org.eclipse.jdt.core.prefs b/tycho-extras/tycho-sbom/.settings/org.eclipse.jdt.core.prefs new file mode 100644 index 0000000000..cf2cd4590a --- /dev/null +++ b/tycho-extras/tycho-sbom/.settings/org.eclipse.jdt.core.prefs @@ -0,0 +1,8 @@ +eclipse.preferences.version=1 +org.eclipse.jdt.core.compiler.codegen.targetPlatform=17 +org.eclipse.jdt.core.compiler.compliance=17 +org.eclipse.jdt.core.compiler.problem.enablePreviewFeatures=disabled +org.eclipse.jdt.core.compiler.problem.forbiddenReference=warning +org.eclipse.jdt.core.compiler.problem.reportPreviewFeatures=ignore +org.eclipse.jdt.core.compiler.release=disabled +org.eclipse.jdt.core.compiler.source=17 diff --git a/tycho-extras/tycho-sbom/pom.xml b/tycho-extras/tycho-sbom/pom.xml new file mode 100644 index 0000000000..caac2ab4de --- /dev/null +++ b/tycho-extras/tycho-sbom/pom.xml @@ -0,0 +1,55 @@ +<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 https://maven.apache.org/xsd/maven-4.0.0.xsd"> + <modelVersion>4.0.0</modelVersion> + <parent> + <groupId>org.eclipse.tycho.extras</groupId> + <artifactId>tycho-extras</artifactId> + <version>5.0.0-SNAPSHOT</version> + </parent> + <artifactId>tycho-sbom</artifactId> + <name>Tycho SBOM model extension</name> + + <dependencies> + <dependency> + <groupId>org.eclipse.tycho</groupId> + <artifactId>tycho-core</artifactId> + </dependency> + <dependency> + <groupId>org.eclipse.tycho</groupId> + <artifactId>tycho-api</artifactId> + <version>${project.version}</version> + </dependency> + <dependency> + <groupId>org.eclipse.tycho</groupId> + <artifactId>p2-maven-plugin</artifactId> + <version>${project.version}</version> + <type>maven-plugin</type> + </dependency> + <dependency> + <groupId>org.eclipse.tycho</groupId> + <artifactId>tycho-versions-plugin</artifactId> + <version>${project.version}</version> + <type>maven-plugin</type> + </dependency> + + <dependency> + <groupId>org.apache.maven</groupId> + <artifactId>maven-plugin-api</artifactId> + </dependency> + <dependency> + <groupId>org.codehaus.plexus</groupId> + <artifactId>plexus-component-annotations</artifactId> + </dependency> + + <dependency> + <groupId>org.cyclonedx</groupId> + <artifactId>cyclonedx-maven-plugin</artifactId> + <version>2.7.10</version> + </dependency> + + <dependency> + <groupId>junit</groupId> + <artifactId>junit</artifactId> + <scope>test</scope> + </dependency> + </dependencies> +</project> \ No newline at end of file diff --git a/tycho-extras/tycho-sbom/src/main/java/org/eclipse/tycho/sbom/P2ModelConverter.java b/tycho-extras/tycho-sbom/src/main/java/org/eclipse/tycho/sbom/P2ModelConverter.java new file mode 100644 index 0000000000..ba31336985 --- /dev/null +++ b/tycho-extras/tycho-sbom/src/main/java/org/eclipse/tycho/sbom/P2ModelConverter.java @@ -0,0 +1,127 @@ +/******************************************************************************* + * Copyright (c) 2023 Patrick Ziegler and others. + * This program and the accompanying materials + * are made available under the terms of the Eclipse Public License 2.0 + * which accompanies this distribution, and is available at + * https://www.eclipse.org/legal/epl-2.0/ + * + * SPDX-License-Identifier: EPL-2.0 + * + * Contributors: + * Patrick Ziegler - initial API and implementation + *******************************************************************************/ +package org.eclipse.tycho.sbom; + +import java.net.URLEncoder; +import java.nio.charset.StandardCharsets; +import java.util.Set; + +import org.apache.maven.RepositoryUtils; +import org.apache.maven.artifact.Artifact; +import org.codehaus.plexus.component.annotations.Component; +import org.cyclonedx.maven.DefaultModelConverter; +import org.cyclonedx.maven.ModelConverter; +import org.eclipse.equinox.p2.metadata.IArtifactKey; +import org.eclipse.tycho.ArtifactKey; +import org.eclipse.tycho.ArtifactType; +import org.eclipse.tycho.DefaultArtifactKey; +import org.eclipse.tycho.core.resolver.target.ArtifactTypeHelper; +import org.eclipse.tycho.versions.engine.Versions; + +/** + * Custom implementation of the CycloneDX model converter with support for both + * Maven and p2 artifacts. The generated PURL is usually of the form: + * + * <pre> + * pkg:/p2/<id>@<version>?classifier=<classifier>&location=<download-url> + * </pre> + * + * This converter can be used with the {@code cyclonedx-maven-plugin} by adding + * it as a dependency as follows: + * + * <pre> + * <plugin> + * <groupId>org.cyclonedx</groupId> + * <artifactId>cyclonedx-maven-plugin</artifactId> + * <dependencies> + * <dependency> + * <groupId>org.eclipse.tycho.extras</groupId> + * <artifactId>tycho-sbom</artifactId> + * </dependency> + * </dependencies> + * </plugin> + * </pre> + */ +@Component(role = ModelConverter.class, hint = "p2") +public class P2ModelConverter extends DefaultModelConverter { + private static final Set<String> SUPPORTED_TYPES = Set.of(ArtifactType.TYPE_BUNDLE_FRAGMENT, + ArtifactType.TYPE_ECLIPSE_PLUGIN, ArtifactType.TYPE_ECLIPSE_FEATURE); + + @Override + public String generatePackageUrl(Artifact artifact) { + if (SUPPORTED_TYPES.contains(artifact.getType())) { + return generateP2PackageUrl(artifact, true, true); + } + return super.generatePackageUrl(artifact); + } + + @Override + public String generatePackageUrl(org.eclipse.aether.artifact.Artifact artifact) { + return generatePackageUrl(RepositoryUtils.toArtifact(artifact)); + } + + @Override + public String generateVersionlessPackageUrl(Artifact artifact) { + if (SUPPORTED_TYPES.contains(artifact.getType())) { + return generateP2PackageUrl(artifact, false, true); + } + return super.generateVersionlessPackageUrl(artifact); + } + + @Override + public String generateVersionlessPackageUrl(org.eclipse.aether.artifact.Artifact artifact) { + return generateVersionlessPackageUrl(RepositoryUtils.toArtifact(artifact)); + } + + @Override + public String generateClassifierlessPackageUrl(org.eclipse.aether.artifact.Artifact artifact) { + Artifact mavenArtifact = RepositoryUtils.toArtifact(artifact); + if (SUPPORTED_TYPES.contains(mavenArtifact.getType())) { + return generateP2PackageUrl(mavenArtifact, true, false); + } + return super.generateClassifierlessPackageUrl(artifact); + } + + private String generateP2PackageUrl(Artifact artifact, boolean withVersion, boolean withClassifier) { + // TODO Resolve "artifact" and use e.g. TychoProjectManager w/ proper qualifier + ArtifactKey artifactKey = new DefaultArtifactKey(artifact.getType(), artifact.getArtifactId(), + Versions.toCanonicalVersion(artifact.getVersion())); + IArtifactKey p2artifactKey = ArtifactTypeHelper.toP2ArtifactKey(artifactKey); + // TODO Try to find p2 location + String location = "unknown"; + if (artifact.getFile() != null) { + location = artifact.getFile().toURI().toString(); + } + if (artifact.getDownloadUrl() != null) { + location = artifact.getDownloadUrl(); + } + String encodedLocation = URLEncoder.encode(location, StandardCharsets.UTF_8); + // + StringBuilder builder = new StringBuilder(); + builder.append("pkg:p2/"); + builder.append(p2artifactKey.getId()); + if (withVersion) { + builder.append('@'); + builder.append(p2artifactKey.getVersion()); + } + builder.append('?'); + if (withClassifier) { + builder.append("classifier="); + builder.append(p2artifactKey.getClassifier()); + builder.append('&'); + } + builder.append("location="); + builder.append(encodedLocation); + return builder.toString(); + } +} diff --git a/tycho-extras/tycho-sbom/src/main/resources/META-INF/plexus/components.xml b/tycho-extras/tycho-sbom/src/main/resources/META-INF/plexus/components.xml new file mode 100644 index 0000000000..9e4c319a81 --- /dev/null +++ b/tycho-extras/tycho-sbom/src/main/resources/META-INF/plexus/components.xml @@ -0,0 +1,9 @@ +<component-set> + <components> + <component> + <role>org.cyclonedx.maven.ModelConverter</role> + <hint>p2</hint> + <implementation>org.eclipse.tycho.sbom.P2ModelConverter</implementation> + </component> + </components> +</component-set> \ No newline at end of file diff --git a/tycho-extras/tycho-sbom/src/test/java/org/eclipse/tycho/sbom/P2ModelConverterTest.java b/tycho-extras/tycho-sbom/src/test/java/org/eclipse/tycho/sbom/P2ModelConverterTest.java new file mode 100644 index 0000000000..540fda3255 --- /dev/null +++ b/tycho-extras/tycho-sbom/src/test/java/org/eclipse/tycho/sbom/P2ModelConverterTest.java @@ -0,0 +1,55 @@ +/******************************************************************************* + * Copyright (c) 2023 Patrick Ziegler and others. + * This program and the accompanying materials + * are made available under the terms of the Eclipse Public License 2.0 + * which accompanies this distribution, and is available at + * https://www.eclipse.org/legal/epl-2.0/ + * + * SPDX-License-Identifier: EPL-2.0 + * + * Contributors: + * Patrick Ziegler - initial API and implementation + *******************************************************************************/ +package org.eclipse.tycho.sbom; + +import static org.junit.Assert.assertEquals; + +import java.net.URLEncoder; +import java.nio.charset.StandardCharsets; + +import org.apache.maven.artifact.Artifact; +import org.apache.maven.artifact.DefaultArtifact; +import org.cyclonedx.maven.ModelConverter; +import org.eclipse.tycho.ArtifactType; +import org.eclipse.tycho.p2maven.repository.EclipsePluginArtifactHandler; +import org.junit.Before; +import org.junit.Test; + +public class P2ModelConverterTest { + private Artifact artifact; + private ModelConverter modelConverter; + + @Before + public void setUp() { + artifact = new DefaultArtifact("p2.eclipse.plugin", "org.eclipse.platform", "4.30.0.v20231201-0110", "compile", + ArtifactType.TYPE_ECLIPSE_PLUGIN, null, new EclipsePluginArtifactHandler()); + artifact.setDownloadUrl( + "https://download.eclipse.org/releases/2023-12/202312061001/plugins/org.eclipse.platform_4.30.0.v20231201-0110.jar"); + modelConverter = new P2ModelConverter(); + } + + @Test + public void testGeneratePackageUrl() { + String purl = modelConverter.generatePackageUrl(artifact); + String location = URLEncoder.encode(artifact.getDownloadUrl(), StandardCharsets.UTF_8); + assertEquals(purl, + "pkg:p2/org.eclipse.platform@4.30.0.v20231201-0110?classifier=osgi.bundle&location=" + location); + } + + @Test + public void testGeneratePackageUrlWithoutVersion() { + String purl = modelConverter.generateVersionlessPackageUrl(artifact); + String location = URLEncoder.encode(artifact.getDownloadUrl(), StandardCharsets.UTF_8); + assertEquals(purl, "pkg:p2/org.eclipse.platform?classifier=osgi.bundle&location=" + location); + } +}