From d219c5fcec9877fee2b6acfbd7370dde2f17ce3c Mon Sep 17 00:00:00 2001 From: Konrad Windszus Date: Mon, 9 Dec 2024 11:52:20 +0100 Subject: [PATCH] [MSITE-911] Add goal to archive and install/deploy site resources along with the project Consider those site-resource archives when setting up the site context --- pom.xml | 6 + .../plugins/site/SiteResourcesAttachMojo.java | 154 +++++++++++++ .../render/AbstractSiteRenderingMojo.java | 8 + .../site/render/SiteResourcesResolver.java | 218 ++++++++++++++++++ 4 files changed, 386 insertions(+) create mode 100644 src/main/java/org/apache/maven/plugins/site/SiteResourcesAttachMojo.java create mode 100644 src/main/java/org/apache/maven/plugins/site/render/SiteResourcesResolver.java diff --git a/pom.xml b/pom.xml index 505bd596..5055a809 100644 --- a/pom.xml +++ b/pom.xml @@ -547,6 +547,12 @@ under the License. + + + org.eclipse.sisu + sisu-maven-plugin + + diff --git a/src/main/java/org/apache/maven/plugins/site/SiteResourcesAttachMojo.java b/src/main/java/org/apache/maven/plugins/site/SiteResourcesAttachMojo.java new file mode 100644 index 00000000..cfbe4069 --- /dev/null +++ b/src/main/java/org/apache/maven/plugins/site/SiteResourcesAttachMojo.java @@ -0,0 +1,154 @@ +/* + * 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.plugins.site; + +import java.io.File; +import java.io.IOException; +import java.util.Set; + +import org.apache.maven.plugin.MojoExecutionException; +import org.apache.maven.plugins.annotations.Component; +import org.apache.maven.plugins.annotations.LifecyclePhase; +import org.apache.maven.plugins.annotations.Mojo; +import org.apache.maven.plugins.annotations.Parameter; +import org.apache.maven.project.MavenProjectHelper; +import org.codehaus.plexus.archiver.Archiver; +import org.codehaus.plexus.archiver.ArchiverException; +import org.codehaus.plexus.archiver.FileSet; +import org.codehaus.plexus.archiver.util.DefaultFileSet; + +/** + * Adds the site resources (compressed in a ZIP archive) as dedicated artifact with classifier {@value #CLASSIFIER} to be installed/deployed. + * Usually used in combination with {@link org.apache.maven.plugins.site.descriptor.SiteDescriptorAttachMojo} to also deploy the actual site descriptor. + * This is used for sites inheriting from this project + * + * @since next + */ +@Mojo(name = SiteResourcesAttachMojo.GOAL_NAME, defaultPhase = LifecyclePhase.PACKAGE, threadSafe = true) +public class SiteResourcesAttachMojo extends AbstractSiteMojo { + + public static final String GOAL_NAME = "attach-site-resources"; + + /** + * Directory containing the site.xml file and the source for hand written docs (one directory + * per Doxia-source-supported markup types) + * @see Doxia Markup Languages References. + */ + @Parameter(defaultValue = "${basedir}/src/site") + protected File siteDirectory; + + /** + * Maven ProjectHelper. + */ + @Component + private MavenProjectHelper projectHelper; + + @Component(hint = ARCHIVE_EXTENSION) + private Archiver zipArchiver; + + /** + * The file name patterns to exclude (potentially in addition to the default ones mentioned at {@link #addDefaultExcludes}). + * The format of each pattern is described in {@link org.codehaus.plexus.util.DirectoryScanner}. + * The comparison is performed against the file path relative to the {@link #siteDirectory}. + *

+ * Each value is either a regex pattern if enclosed within {@code %regex[} and {@code ]}, otherwise an + * Ant pattern. + * Exclusions take precedence over inclusions via {@link #includes}. + */ + @Parameter(defaultValue = "**/.gitignore,**/.gitattributes", required = true) + protected Set excludes; + + /** + * The file name patterns to include. The format of each pattern is described in {@link org.codehaus.plexus.util.DirectoryScanner}. + * The comparison is performed against the file path relative to the {@link #siteDirectory}. + * Since this is hardly predictable it is recommended to use only filename/directory name patterns here + * but not take into account file system hierarchies! + *

+ * Each value is either a regex pattern if enclosed within {@code %regex[} and {@code ]}, otherwise an + * Ant pattern. + * If this is not set, everything is included. + */ + @Parameter(required = false) + protected Set includes; + + /** + * By default certain metadata files are excluded which means they will not be copied into the package. + * If you need them for a particular reason you can do that by setting this parameter to {@code false}. + * + * @see org.codehaus.plexus.util.AbstractScanner#DEFAULTEXCLUDES + */ + @Parameter(defaultValue = "true") + protected boolean addDefaultExcludes; + + /** + * Attach site resources in archive only if packaging is pom. + */ + @Parameter(defaultValue = "true") + private boolean pomPackagingOnly; + + public static final String CLASSIFIER = "site-resources"; + public static final String ARCHIVE_EXTENSION = "zip"; + + public void execute() throws MojoExecutionException { + if (pomPackagingOnly && !"pom".equals(project.getPackaging())) { + // https://issues.apache.org/jira/browse/MSITE-597 + getLog().info("Skipping because packaging '" + project.getPackaging() + "' is not pom."); + return; + } + + if (siteDirectory.exists()) { + try { + File destFile = new File( + project.getBuild().getDirectory(), + project.getBuild().getFinalName() + CLASSIFIER + "." + ARCHIVE_EXTENSION); + + File siteResourcesArchiveFile = createArchive(zipArchiver, destFile); + // Attach the site resources archive + getLog().info("Attaching site resources archive with classifier '" + CLASSIFIER + "'."); + projectHelper.attachArtifact(project, ARCHIVE_EXTENSION, CLASSIFIER, siteResourcesArchiveFile); + } catch (IOException e) { + throw new MojoExecutionException("Unable to archive site resources", e); + } + } else { + getLog().warn("No site resources found: nothing to attach."); + } + } + + private FileSet createFileSet() { + DefaultFileSet fileSet = new DefaultFileSet(); + fileSet.setDirectory(siteDirectory); + fileSet.setExcludes(excludes.toArray(new String[0])); + if (includes != null && !includes.isEmpty()) { + fileSet.setIncludes(includes.toArray(new String[0])); + } + fileSet.setUsingDefaultExcludes(addDefaultExcludes); + return fileSet; + } + + public File createArchive(Archiver archiver, File destFile) throws ArchiverException, IOException { + archiver.setDestFile(destFile); + archiver.addFileSet(createFileSet()); + archiver.createArchive(); + return archiver.getDestFile(); + } + + public File getSiteDirectory() { + return siteDirectory; + } +} diff --git a/src/main/java/org/apache/maven/plugins/site/render/AbstractSiteRenderingMojo.java b/src/main/java/org/apache/maven/plugins/site/render/AbstractSiteRenderingMojo.java index 1030d63c..82202d4e 100644 --- a/src/main/java/org/apache/maven/plugins/site/render/AbstractSiteRenderingMojo.java +++ b/src/main/java/org/apache/maven/plugins/site/render/AbstractSiteRenderingMojo.java @@ -179,6 +179,12 @@ public abstract class AbstractSiteRenderingMojo extends AbstractSiteDescriptorMo @Component protected MavenReportExecutor mavenReportExecutor; + @Component + private MavenSession session; + + @Component + protected SiteResourcesResolver siteResourcesResolver; + /** * Gets the input files encoding. * @@ -319,6 +325,8 @@ protected SiteRenderingContext createSiteRenderingContext(Locale locale) context.addSiteDirectory(new SiteDirectory(generatedSiteDirectory, false)); } + // potentially add inherited site resources + siteResourcesResolver.resolveParentSiteResources(session, project, generatedSiteDirectory, getLog()); if (moduleExcludes != null) { context.setModuleExcludes(moduleExcludes); } diff --git a/src/main/java/org/apache/maven/plugins/site/render/SiteResourcesResolver.java b/src/main/java/org/apache/maven/plugins/site/render/SiteResourcesResolver.java new file mode 100644 index 00000000..f0f8ffaf --- /dev/null +++ b/src/main/java/org/apache/maven/plugins/site/render/SiteResourcesResolver.java @@ -0,0 +1,218 @@ +/* + * 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.plugins.site.render; + +import javax.inject.Inject; +import javax.inject.Named; +import javax.inject.Singleton; + +import java.io.File; +import java.io.IOException; +import java.util.List; +import java.util.Optional; + +import org.apache.maven.RepositoryUtils; +import org.apache.maven.execution.MavenSession; +import org.apache.maven.model.Plugin; +import org.apache.maven.plugin.InvalidPluginDescriptorException; +import org.apache.maven.plugin.MavenPluginManager; +import org.apache.maven.plugin.MojoExecution; +import org.apache.maven.plugin.MojoNotFoundException; +import org.apache.maven.plugin.PluginConfigurationException; +import org.apache.maven.plugin.PluginContainerException; +import org.apache.maven.plugin.PluginDescriptorParsingException; +import org.apache.maven.plugin.PluginResolutionException; +import org.apache.maven.plugin.descriptor.MojoDescriptor; +import org.apache.maven.plugin.logging.Log; +import org.apache.maven.plugins.site.SiteResourcesAttachMojo; +import org.apache.maven.project.MavenProject; +import org.codehaus.plexus.archiver.ArchiverException; +import org.codehaus.plexus.archiver.UnArchiver; +import org.codehaus.plexus.archiver.dir.DirectoryArchiver; +import org.codehaus.plexus.util.xml.Xpp3Dom; +import org.eclipse.aether.RepositorySystem; +import org.eclipse.aether.RepositorySystemSession; +import org.eclipse.aether.artifact.Artifact; +import org.eclipse.aether.artifact.DefaultArtifact; +import org.eclipse.aether.repository.RemoteRepository; +import org.eclipse.aether.resolution.ArtifactRequest; +import org.eclipse.aether.resolution.ArtifactResolutionException; +import org.eclipse.aether.resolution.ArtifactResult; +import org.eclipse.aether.transfer.ArtifactNotFoundException; + +/** + * Resolves and extracts/copies inherited site resources from parent projects. + * @see SiteResourcesAttachMojo + */ +@Named +@Singleton +public class SiteResourcesResolver { + + private final UnArchiver unArchiver; + private final RepositorySystem repoSystem; + private final MavenPluginManager pluginManager; + + @Inject + public SiteResourcesResolver( + @Named(SiteResourcesAttachMojo.ARCHIVE_EXTENSION) UnArchiver unArchiver, + RepositorySystem repoSystem, + MavenPluginManager pluginManager) { + super(); + this.unArchiver = unArchiver; + this.repoSystem = repoSystem; + this.pluginManager = pluginManager; + } + + public void resolveParentSiteResources( + MavenSession mavenSession, MavenProject project, File generatedSiteDirectory, Log log) throws IOException { + MavenProject parent = project.getParent(); + if (parent != null) { + // start with the topmost one to allow child projects to override parent resources + resolveParentSiteResources(mavenSession, parent, generatedSiteDirectory, log); + // is it part of a multi-module project? + if (parent.getBasedir() != null) { + resolveLocalSiteResources(mavenSession, parent, generatedSiteDirectory, log); + } else { + resolveRepositorySiteResources( + RepositoryUtils.toArtifact(parent.getArtifact()), + mavenSession.getRepositorySession(), + parent.getRemoteProjectRepositories(), + generatedSiteDirectory, + log); + } + } + } + + protected void resolveRepositorySiteResources( + Artifact artifact, + RepositorySystemSession repoSession, + List remoteRepositories, + File generatedSiteDirectory, + Log log) + throws IOException { + final Artifact siteResourcesArtifact; + try { + siteResourcesArtifact = resolveSiteResources(artifact, repoSession, remoteRepositories); + } catch (ArtifactResolutionException e) { + throw new IOException("Error resolving site resources artifact for " + artifact, e); + } + if (siteResourcesArtifact != null) { + try { + extractArchive(siteResourcesArtifact.getFile(), generatedSiteDirectory); + } catch (ArchiverException e) { + throw new IOException("Error extracting archive " + siteResourcesArtifact.getFile(), e); + } + log.info("Copied inherited site resources from " + artifact + " to " + generatedSiteDirectory); + } else { + log.debug("No site resources found for " + artifact); + } + } + + protected Artifact resolveSiteResources( + Artifact parentProjectArtifact, + RepositorySystemSession repoSession, + List remoteRepositories) + throws ArtifactResolutionException { + try { + Artifact siteResourcesArtifact = new DefaultArtifact( + parentProjectArtifact.getGroupId(), + parentProjectArtifact.getArtifactId(), + SiteResourcesAttachMojo.CLASSIFIER, + SiteResourcesAttachMojo.ARCHIVE_EXTENSION, + parentProjectArtifact.getVersion()); + ArtifactRequest request = + new ArtifactRequest(siteResourcesArtifact, remoteRepositories, "remote-site-resources"); + ArtifactResult result = repoSystem.resolveArtifact(repoSession, request); + return result.getArtifact(); + } catch (ArtifactResolutionException e) { + if (e.getCause() instanceof ArtifactNotFoundException) { + // this is no error + return null; + } + throw e; + } + } + + protected void extractArchive(File archiveFile, File destDirectory) throws ArchiverException { + unArchiver.setSourceFile(archiveFile); + unArchiver.setDestDirectory(destDirectory); + unArchiver.setOverwrite(true); + unArchiver.extract(); + } + + private void resolveLocalSiteResources( + MavenSession mavenSession, MavenProject currentProject, File generatedSiteDirectory, Log log) + throws IOException { + // evaluate the configuration of the according mojo + Plugin sitePlugin = currentProject.getPlugin("org.apache.maven.plugins:maven-site-plugin"); + if (sitePlugin != null) { + // just use the first potential execution's configuration + Optional configuration = sitePlugin.getExecutions().stream() + .filter(e -> e.getGoals().contains("attach-site-resources")) + .filter(e -> e.getPhase() != null && !e.getPhase().isEmpty()) + .map(e -> (Xpp3Dom) e.getConfiguration()) + .findFirst(); + if (configuration.isPresent()) { + try { + copySiteResourcesToDirectory( + mavenSession, currentProject, sitePlugin, configuration.get(), generatedSiteDirectory, log); + } catch (ArchiverException e1) { + throw new IOException("Error copying site resources to " + generatedSiteDirectory, e1); + } catch (MojoNotFoundException + | PluginResolutionException + | PluginDescriptorParsingException + | InvalidPluginDescriptorException + | PluginContainerException + | PluginConfigurationException e1) { + throw new IOException( + "Error extracting mojo configuration from " + currentProject.getArtifact(), e1); + } + } + } + } + + void copySiteResourcesToDirectory( + MavenSession mavenSession, + MavenProject project, + Plugin plugin, + Xpp3Dom configuration, + File destDirectory, + Log log) + throws ArchiverException, IOException, MojoNotFoundException, PluginResolutionException, + PluginDescriptorParsingException, InvalidPluginDescriptorException, PluginContainerException, + PluginConfigurationException { + // modify MavenSession to use the basedir of another project as context + MavenSession mavenSessionForOtherProject = mavenSession.clone(); + mavenSessionForOtherProject.setCurrentProject(project); + + MojoDescriptor mojoDescriptor = pluginManager.getMojoDescriptor( + plugin, + SiteResourcesAttachMojo.GOAL_NAME, + project.getRemotePluginRepositories(), + mavenSession.getRepositorySession()); + MojoExecution mojoExecution = new MojoExecution(mojoDescriptor, configuration); + // TODO: use different baseDir for resolving default configuration + SiteResourcesAttachMojo mojo = pluginManager.getConfiguredMojo( + SiteResourcesAttachMojo.class, mavenSessionForOtherProject, mojoExecution); + // this is a pseudo archiver which just copies from source to destination considering the fileSet configuration + DirectoryArchiver archiver = new DirectoryArchiver(); + mojo.createArchive(archiver, destDirectory); + log.info("Copied inherited site resources from " + mojo.getSiteDirectory() + " to " + destDirectory); + } +}