From 486f6cf70b831fff575e42f1f6465f4ebbd93c8e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Christoph=20L=C3=A4ubrich?= Date: Wed, 19 Jul 2023 18:10:45 +0200 Subject: [PATCH] Make the repository format pluggable Currently there needs to be a mojo for each format where the basics are quite common and just the serialization is special so it seems quite useful to reuse the basics. (cherry picked from commit 374e2b2542f98f2e110747d15a27310e7dbecf1c) --- demo/osgi-repository/repository/pom.xml | 22 ++- tycho-repository-plugin/pom.xml | 5 + .../plugin/OSGiRepositoryGenerator.java | 99 +++++++++++ .../plugin/PackageRepositoryMojo.java | 168 ++++++++++-------- .../repository/plugin/RepositoryLayout.java | 24 --- ...sitoryPluginMavenLifecycleParticipant.java | 53 +++++- .../tycho/packaging/RepositoryGenerator.java | 107 +++++++++++ 7 files changed, 368 insertions(+), 110 deletions(-) create mode 100644 tycho-repository-plugin/src/main/java/org/eclipse/tycho/repository/plugin/OSGiRepositoryGenerator.java delete mode 100644 tycho-repository-plugin/src/main/java/org/eclipse/tycho/repository/plugin/RepositoryLayout.java create mode 100644 tycho-spi/src/main/java/org/eclipse/tycho/packaging/RepositoryGenerator.java diff --git a/demo/osgi-repository/repository/pom.xml b/demo/osgi-repository/repository/pom.xml index 9e8154b58a..34b014d41b 100644 --- a/demo/osgi-repository/repository/pom.xml +++ b/demo/osgi-repository/repository/pom.xml @@ -17,10 +17,24 @@ ${tycho-version} true - - - maven - + + + + + + local + package + + package-repository + + + local + + deploy + + + + diff --git a/tycho-repository-plugin/pom.xml b/tycho-repository-plugin/pom.xml index 5f41df677a..d740ca38cc 100644 --- a/tycho-repository-plugin/pom.xml +++ b/tycho-repository-plugin/pom.xml @@ -57,6 +57,11 @@ org.codehaus.plexus plexus-archiver + + org.eclipse.tycho + tycho-spi + ${project.version} + diff --git a/tycho-repository-plugin/src/main/java/org/eclipse/tycho/repository/plugin/OSGiRepositoryGenerator.java b/tycho-repository-plugin/src/main/java/org/eclipse/tycho/repository/plugin/OSGiRepositoryGenerator.java new file mode 100644 index 0000000000..557403dc90 --- /dev/null +++ b/tycho-repository-plugin/src/main/java/org/eclipse/tycho/repository/plugin/OSGiRepositoryGenerator.java @@ -0,0 +1,99 @@ +/******************************************************************************* + * Copyright (c) 2023 Christoph Läubrich 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: + * Christoph Läubrich - initial API and implementation + *******************************************************************************/ +package org.eclipse.tycho.repository.plugin; + +import java.io.File; +import java.io.IOException; +import java.net.URI; +import java.util.List; + +import org.apache.commons.io.FileUtils; +import org.apache.commons.io.FilenameUtils; +import org.apache.maven.plugin.MojoExecutionException; +import org.apache.maven.plugin.MojoFailureException; +import org.apache.maven.plugin.logging.Log; +import org.apache.maven.project.MavenProject; +import org.codehaus.plexus.component.annotations.Component; +import org.codehaus.plexus.configuration.PlexusConfiguration; +import org.eclipse.tycho.MavenArtifactNamespace; +import org.eclipse.tycho.packaging.RepositoryGenerator; + +import aQute.bnd.osgi.repository.XMLResourceGenerator; +import aQute.bnd.osgi.resource.CapReqBuilder; +import aQute.bnd.osgi.resource.ResourceBuilder; + +@Component(role = RepositoryGenerator.class, hint = OSGiRepositoryGenerator.HINT) +public class OSGiRepositoryGenerator implements RepositoryGenerator { + + static final String HINT = "osgi"; + + @Override + public File createRepository(List projects, RepositoryConfiguration repoConfig) + throws IOException, MojoExecutionException, MojoFailureException { + XMLResourceGenerator resourceGenerator = new XMLResourceGenerator(); + resourceGenerator.name(repoConfig.getRepositoryName()); + File folder; + PlexusConfiguration generatorConfig = repoConfig.getConfiguration(); + String repositoryFileName = generatorConfig.getChild("repositoryFileName") + .getValue("repository.xml"); + if (repoConfig.getLayout() == RepositoryLayout.local) { + String folderName = generatorConfig.getChild("repositoryFolderName") + .getValue(FilenameUtils.getBaseName(repositoryFileName)); + folder = new File(repoConfig.getDestination(), folderName); + folder.mkdirs(); + resourceGenerator.base(folder.toURI()); + } else { + folder = null; + } + Log log = repoConfig.getLog(); + for (MavenProject project : projects) { + ResourceBuilder rb = new ResourceBuilder(); + try { + URI uri; + File file = project.getArtifact().getFile(); + if (folder == null) { + uri = new URI( + "mvn:" + project.getGroupId() + ":" + project.getArtifactId() + ":" + project.getVersion()); + } else { + uri = new File(folder, file.getName()).toURI(); + } + if (rb.addFile(project.getArtifact().getFile(), uri)) { + CapReqBuilder identity = new CapReqBuilder(MavenArtifactNamespace.MAVEN_ARTIFACT_NAMESPACE) + .addAttribute(MavenArtifactNamespace.CAPABILITY_GROUP_ATTRIBUTE, project.getGroupId()) + .addAttribute(MavenArtifactNamespace.MAVEN_ARTIFACT_NAMESPACE, project.getArtifactId()) + .addAttribute(MavenArtifactNamespace.CAPABILITY_VERSION_ATTRIBUTE, project.getVersion()); + rb.addCapability(identity); + resourceGenerator.resource(rb.build()); + log.info("Adding " + project.getId()); + if (folder != null) { + FileUtils.copyFileToDirectory(file, folder); + } + } else { + log.info("Skip " + project.getId() + ": Not a bundle"); + } + } catch (Exception e) { + log.warn("Ignoring " + project.getId() + ": " + e, log.isDebugEnabled() ? e : null); + } + } + if (folder != null) { + File location = new File(folder, repositoryFileName); + resourceGenerator.save(location); + return folder; + } else { + File location = new File(repoConfig.getDestination(), repositoryFileName); + resourceGenerator.save(location); + return location; + } + } + +} diff --git a/tycho-repository-plugin/src/main/java/org/eclipse/tycho/repository/plugin/PackageRepositoryMojo.java b/tycho-repository-plugin/src/main/java/org/eclipse/tycho/repository/plugin/PackageRepositoryMojo.java index 1865f6cc33..e80894f8ef 100644 --- a/tycho-repository-plugin/src/main/java/org/eclipse/tycho/repository/plugin/PackageRepositoryMojo.java +++ b/tycho-repository-plugin/src/main/java/org/eclipse/tycho/repository/plugin/PackageRepositoryMojo.java @@ -14,14 +14,16 @@ import java.io.File; import java.io.IOException; -import java.net.URI; +import java.util.List; +import java.util.Map; +import java.util.stream.Collectors; -import org.apache.commons.io.FileUtils; import org.apache.commons.io.FilenameUtils; import org.apache.maven.artifact.Artifact; import org.apache.maven.artifact.handler.DefaultArtifactHandler; import org.apache.maven.execution.MavenSession; import org.apache.maven.plugin.AbstractMojo; +import org.apache.maven.plugin.MojoExecution; import org.apache.maven.plugin.MojoExecutionException; import org.apache.maven.plugin.MojoFailureException; import org.apache.maven.plugin.logging.Log; @@ -29,21 +31,27 @@ import org.apache.maven.plugins.annotations.Mojo; import org.apache.maven.plugins.annotations.Parameter; import org.apache.maven.project.MavenProject; +import org.apache.maven.project.MavenProjectHelper; import org.codehaus.plexus.archiver.Archiver; import org.codehaus.plexus.archiver.zip.ZipArchiver; -import org.eclipse.tycho.ArtifactType; -import org.eclipse.tycho.MavenArtifactNamespace; - -import aQute.bnd.osgi.repository.XMLResourceGenerator; -import aQute.bnd.osgi.resource.CapReqBuilder; -import aQute.bnd.osgi.resource.ResourceBuilder; +import org.codehaus.plexus.configuration.PlexusConfiguration; +import org.codehaus.plexus.configuration.xml.XmlPlexusConfiguration; +import org.eclipse.tycho.packaging.RepositoryGenerator; +import org.eclipse.tycho.packaging.RepositoryGenerator.RepositoryConfiguration; +import org.eclipse.tycho.packaging.RepositoryGenerator.RepositoryLayout; /** * Generates an OSGi repository from the current reactor projects * */ @Mojo(name = "package-repository") -public class PackageRepositoryMojo extends AbstractMojo { +public class PackageRepositoryMojo extends AbstractMojo implements RepositoryConfiguration { + + private static final XmlPlexusConfiguration NO_SETTINGS = new XmlPlexusConfiguration("settings"); + + static final String DEFAULT_REPOSITORY_TYPE = OSGiRepositoryGenerator.HINT; + + static final String PARAMETER_REPOSITORY_TYPE = "repositoryType"; @Parameter(property = "session", readonly = true) protected MavenSession session; @@ -56,13 +64,6 @@ public class PackageRepositoryMojo extends AbstractMojo { @Parameter(defaultValue = "${project.name}") private String repositoryName; - /** - * Specify the filename of the additionally generated OSGi Repository (if - * enabled) - */ - @Parameter(defaultValue = "repository.xml") - private String repositoryFileName; - @Parameter(defaultValue = "${project.build.directory}") private File destination; @@ -82,82 +83,95 @@ public class PackageRepositoryMojo extends AbstractMojo { @Parameter(defaultValue = "maven") private RepositoryLayout repositoryLayout; + /** + * Configures the used repository type + */ + @Parameter(defaultValue = DEFAULT_REPOSITORY_TYPE, name = PARAMETER_REPOSITORY_TYPE) + private String repositoryType; + + @Parameter(property = "mojoExecution", readonly = true) + MojoExecution execution; + + /** + * Configures the repository type specific settings. + */ + @Parameter + private PlexusConfiguration settings; + @Component(role = Archiver.class, hint = "zip") private ZipArchiver zipArchiver; + @Component(role = RepositoryGenerator.class) + private Map generators; + + @Component + private MavenProjectHelper mavenProjectHelper; + @Override public void execute() throws MojoExecutionException, MojoFailureException { - XMLResourceGenerator resourceGenerator = new XMLResourceGenerator(); - resourceGenerator.name(repositoryName); - File folder; - if (repositoryLayout == RepositoryLayout.local) { - folder = new File(destination, FilenameUtils.getBaseName(repositoryFileName)); - folder.mkdirs(); - resourceGenerator.base(folder.toURI()); - } else { - folder = null; - } - for (MavenProject project : session.getProjects()) { - if (isInteresting(project)) { - ResourceBuilder rb = new ResourceBuilder(); - try { - URI uri; - File file = project.getArtifact().getFile(); - if (folder == null) { - uri = new URI("mvn:" + project.getGroupId() + ":" + project.getArtifactId() + ":" - + project.getVersion()); - } else { - uri = new File(folder, file.getName()).toURI(); - } - if (rb.addFile(project.getArtifact().getFile(), uri)) { - CapReqBuilder identity = new CapReqBuilder(MavenArtifactNamespace.MAVEN_ARTIFACT_NAMESPACE) - .addAttribute(MavenArtifactNamespace.CAPABILITY_GROUP_ATTRIBUTE, project.getGroupId()) - .addAttribute(MavenArtifactNamespace.MAVEN_ARTIFACT_NAMESPACE, project.getArtifactId()) - .addAttribute(MavenArtifactNamespace.CAPABILITY_VERSION_ATTRIBUTE, - project.getVersion()); - rb.addCapability(identity); - resourceGenerator.resource(rb.build()); - getLog().info("Adding " + project.getId()); - if (folder != null) { - FileUtils.copyFileToDirectory(file, folder); - } - } else { - getLog().info("Skip " + project.getId() + ": Not a bundle"); - } - } catch (Exception e) { - Log log = getLog(); - log.warn("Ignoring " + project.getId() + ": " + e, log.isDebugEnabled() ? e : null); - } - } + RepositoryGenerator generator = generators.get(repositoryType); + if (generator == null) { + throw new MojoFailureException( + "No repository implementation of type " + repositoryType + " found, available ones are " + + generators.keySet().stream().sorted().collect(Collectors.joining(", "))); } + List projects = session.getProjects().stream().filter(generator::isInteresting).toList(); try { - Artifact artifact = project.getArtifact(); - if (folder != null) { - File location = new File(folder, repositoryFileName); - resourceGenerator.save(location); - File destFile = new File(destination, project.getArtifactId() + "-" + folder.getName() + ".zip"); - zipArchiver.addDirectory(folder); + File repository = generator.createRepository(projects, this); + String executionId = execution.getExecutionId(); + if (repository.isDirectory()) { + File destFile = new File(destination, project.getArtifactId() + "-" + repository.getName() + ".zip"); + zipArchiver.addDirectory(repository); zipArchiver.setDestFile(destFile); zipArchiver.createArchive(); - artifact.setFile(destFile); - artifact.setArtifactHandler(new DefaultArtifactHandler("zip")); + if (executionId.startsWith("default-")) { + Artifact artifact = project.getArtifact(); + artifact.setFile(destFile); + artifact.setArtifactHandler(new DefaultArtifactHandler("zip")); + } else { + mavenProjectHelper.attachArtifact(project, "zip", executionId, destFile); + } } else { - File location = new File(destination, repositoryFileName); - resourceGenerator.save(location); - artifact.setArtifactHandler(new DefaultArtifactHandler("xml")); - artifact.setFile(location); + String extension = FilenameUtils.getExtension(repository.getName()); + if (executionId.startsWith("default-")) { + Artifact artifact = project.getArtifact(); + artifact.setArtifactHandler(new DefaultArtifactHandler(extension)); + artifact.setFile(repository); + } else { + mavenProjectHelper.attachArtifact(project, extension, executionId, repository); + } } } catch (IOException e) { - throw new MojoExecutionException("Could not write OSGi Repository!", e); + throw new MojoExecutionException("Could not write repository!", e); } } - public static boolean isInteresting(MavenProject other) { - String packaging = other.getPackaging(); - return "jar".equalsIgnoreCase(packaging) || "bundle".equalsIgnoreCase(packaging) - || ArtifactType.TYPE_ECLIPSE_PLUGIN.equals(packaging) - || ArtifactType.TYPE_BUNDLE_FRAGMENT.equals(packaging) - || ArtifactType.TYPE_ECLIPSE_TEST_PLUGIN.equals(packaging); + @Override + public File getDestination() { + return destination; + } + + @Override + public RepositoryLayout getLayout() { + return repositoryLayout; + } + + @Override + public String getRepositoryName() { + return repositoryName; + } + + @Override + public Log getLog() { + return super.getLog(); + } + + @Override + public PlexusConfiguration getConfiguration() { + if (settings == null) { + return NO_SETTINGS; + } + return settings; } } diff --git a/tycho-repository-plugin/src/main/java/org/eclipse/tycho/repository/plugin/RepositoryLayout.java b/tycho-repository-plugin/src/main/java/org/eclipse/tycho/repository/plugin/RepositoryLayout.java deleted file mode 100644 index fe678ce47b..0000000000 --- a/tycho-repository-plugin/src/main/java/org/eclipse/tycho/repository/plugin/RepositoryLayout.java +++ /dev/null @@ -1,24 +0,0 @@ -/******************************************************************************* - * Copyright (c) 2023 Christoph Läubrich 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: - * Christoph Läubrich - initial API and implementation - *******************************************************************************/ -package org.eclipse.tycho.repository.plugin; - -public enum RepositoryLayout { - /** - * reference artifacts using mvn: protocol - */ - maven, - /** - * references artifacts as local files and copy them into a folder - */ - local; -} \ No newline at end of file diff --git a/tycho-repository-plugin/src/main/java/org/eclipse/tycho/repository/plugin/TychoRepositoryPluginMavenLifecycleParticipant.java b/tycho-repository-plugin/src/main/java/org/eclipse/tycho/repository/plugin/TychoRepositoryPluginMavenLifecycleParticipant.java index b3088291be..82c275a639 100644 --- a/tycho-repository-plugin/src/main/java/org/eclipse/tycho/repository/plugin/TychoRepositoryPluginMavenLifecycleParticipant.java +++ b/tycho-repository-plugin/src/main/java/org/eclipse/tycho/repository/plugin/TychoRepositoryPluginMavenLifecycleParticipant.java @@ -12,42 +12,85 @@ *******************************************************************************/ package org.eclipse.tycho.repository.plugin; +import java.util.HashSet; import java.util.List; +import java.util.Map; +import java.util.Set; import org.apache.maven.AbstractMavenLifecycleParticipant; import org.apache.maven.MavenExecutionException; import org.apache.maven.execution.MavenSession; import org.apache.maven.model.Dependency; import org.apache.maven.model.Plugin; +import org.apache.maven.model.PluginExecution; import org.apache.maven.project.MavenProject; import org.codehaus.plexus.component.annotations.Component; +import org.codehaus.plexus.component.annotations.Requirement; +import org.codehaus.plexus.logging.Logger; +import org.codehaus.plexus.util.xml.Xpp3Dom; +import org.eclipse.tycho.helper.PluginConfigurationHelper; +import org.eclipse.tycho.helper.ProjectHelper; +import org.eclipse.tycho.packaging.RepositoryGenerator; @Component(role = AbstractMavenLifecycleParticipant.class) public class TychoRepositoryPluginMavenLifecycleParticipant extends AbstractMavenLifecycleParticipant { + @Requirement + Map generators; + + @Requirement + PluginConfigurationHelper configurationHelper; + + @Requirement + ProjectHelper projectHelper; + + @Requirement + Logger logger; + @Override public void afterProjectsRead(MavenSession session) throws MavenExecutionException { List projects = session.getProjects(); for (MavenProject project : projects) { - if ("repository".equals(project.getPackaging()) && project.getPlugin("org.eclipse.tycho:tycho-repository-plugin") != null) { - addInterestingProjects(project, projects); + Plugin plugin = project.getPlugin("org.eclipse.tycho:tycho-repository-plugin"); + if ("repository".equals(project.getPackaging()) && plugin != null) { + Set added = new HashSet(); + for (PluginExecution execution : plugin.getExecutions()) { + for (String goal : execution.getGoals()) { + addInterestingProjects(project, projects, session, goal, added); + } + } } } } - private void addInterestingProjects(MavenProject project, List projects) { + private void addInterestingProjects(MavenProject project, List projects, MavenSession session, + String goal, Set added) { + Xpp3Dom configuration = projectHelper.getPluginConfiguration("org.eclipse.tycho", "tycho-repository-plugin", + goal, project, session); + String repoType = configurationHelper.getConfiguration(configuration) + .getString(PackageRepositoryMojo.PARAMETER_REPOSITORY_TYPE) + .orElse(PackageRepositoryMojo.DEFAULT_REPOSITORY_TYPE); + RepositoryGenerator generator = generators.get(repoType); + if (generator == null) { + logger.warn( + "Can't determine projects that should be declared as automatic discovered dependencies because RepositoryGenerator of type '" + + repoType + "' was not found!"); + return; + } for (MavenProject other : projects) { - if (other == project) { + if (other == project || added.contains(other)) { continue; } - if (PackageRepositoryMojo.isInteresting(other)) { + if (generator.isInteresting(other)) { Dependency dependency = new Dependency(); dependency.setGroupId(other.getGroupId()); dependency.setArtifactId(other.getArtifactId()); dependency.setVersion(other.getVersion()); project.getModel().addDependency(dependency); + added.add(other); } } + } } diff --git a/tycho-spi/src/main/java/org/eclipse/tycho/packaging/RepositoryGenerator.java b/tycho-spi/src/main/java/org/eclipse/tycho/packaging/RepositoryGenerator.java new file mode 100644 index 0000000000..8f675f3fd3 --- /dev/null +++ b/tycho-spi/src/main/java/org/eclipse/tycho/packaging/RepositoryGenerator.java @@ -0,0 +1,107 @@ +/******************************************************************************* + * Copyright (c) 2023 Christoph Läubrich 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: + * Christoph Läubrich - initial API and implementation + *******************************************************************************/ +package org.eclipse.tycho.packaging; + +import java.io.File; +import java.io.IOException; +import java.util.List; + +import org.apache.maven.plugin.MojoExecutionException; +import org.apache.maven.plugin.MojoFailureException; +import org.apache.maven.plugin.logging.Log; +import org.apache.maven.project.MavenProject; +import org.codehaus.plexus.configuration.PlexusConfiguration; +import org.eclipse.tycho.ArtifactType; + +/** + * A {@link RepositoryGenerator} is responsible for generate a specific repository format from a set + * of items. + * + */ +public interface RepositoryGenerator { + + /** + * Creates a repository from the supplied list of maven projects + * + * @param projects + * the list of projects to use + * @param configuration + * the configuration for the resulting repository + * @return the generated repository + * @throws IOException + * @throws MojoExecutionException + * if an internal error occurs while generating the repository + * @throws MojoFailureException + * if a user configuration error occurs + */ + File createRepository(List projects, RepositoryConfiguration configuration) + throws IOException, MojoExecutionException, MojoFailureException; + + /** + * Determines if a given project is interesting for this generator, a generator might be capable + * of processing specific things and should probably be able to generate some content from such + * a project, the default implementation includes "jar", "bundle", + * {@value ArtifactType#TYPE_ECLIPSE_PLUGIN} and {@value ArtifactType#TYPE_ECLIPSE_TEST_PLUGIN} + * packaged projects as potentially interesting. + * + * @param mavenProject + * @return true if the project is interesting or false otherwise. + */ + default boolean isInteresting(MavenProject mavenProject) { + String packaging = mavenProject.getPackaging(); + return "jar".equalsIgnoreCase(packaging) || "bundle".equalsIgnoreCase(packaging) + || ArtifactType.TYPE_ECLIPSE_PLUGIN.equals(packaging) + || ArtifactType.TYPE_ECLIPSE_TEST_PLUGIN.equals(packaging); + } + + static enum RepositoryLayout { + /** + * reference artifacts using mvn: protocol + */ + maven, + /** + * references artifacts as local files and copy them into a folder + */ + local; + } + + static interface RepositoryConfiguration { + + /** + * @return he base folder where the repository should be generated + */ + File getDestination(); + + /** + * @return the desired repository layout, if the generator do not understand the layout it + * should throw a {@link MojoFailureException} + */ + RepositoryLayout getLayout(); + + /** + * @return the user visible name of the repository. This is a hint, if the provider does not + * support such thing it could ignore this + */ + String getRepositoryName(); + + /** + * @return the additional respository configuration. + */ + PlexusConfiguration getConfiguration(); + + /** + * @return the logger to issue logging messages + */ + Log getLog(); + } +}