From 7a828143393c136e83c5d7e60e5c503e190721c3 Mon Sep 17 00:00:00 2001 From: Christopher Ng Date: Mon, 11 Nov 2024 16:22:42 +0000 Subject: [PATCH 1/2] [MARTIFACT-80] Apply `ignore` configuration to main project artifact as well Previously the `ignore` configuration did not apply to the main project artifact, only to secondary attached artifacts --- .../maven/plugins/artifact/buildinfo/BuildInfoWriter.java | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/src/main/java/org/apache/maven/plugins/artifact/buildinfo/BuildInfoWriter.java b/src/main/java/org/apache/maven/plugins/artifact/buildinfo/BuildInfoWriter.java index bcb3c52..1f10ac5 100644 --- a/src/main/java/org/apache/maven/plugins/artifact/buildinfo/BuildInfoWriter.java +++ b/src/main/java/org/apache/maven/plugins/artifact/buildinfo/BuildInfoWriter.java @@ -210,7 +210,13 @@ void printArtifacts(MavenProject project) throws MojoExecutionException { } if (project.getArtifact().getFile() != null) { - printArtifact(prefix, n++, RepositoryUtils.toArtifact(project.getArtifact())); + Artifact artifact = RepositoryUtils.toArtifact(project.getArtifact()); + if (isIgnore(artifact)) { + p.println("# ignored " + getArtifactFilename(artifact)); + artifacts.put(artifact, null); + } else { + printArtifact(prefix, n++, RepositoryUtils.toArtifact(project.getArtifact())); + } } for (Artifact attached : RepositoryUtils.toArtifacts(project.getAttachedArtifacts())) { From 2b71741c835ae3ede5d4f5e49ffb784cc345dd4a Mon Sep 17 00:00:00 2001 From: Christopher Ng Date: Tue, 12 Nov 2024 12:07:24 +0000 Subject: [PATCH 2/2] [MARTIFACT-80] Use per-module ignore configuration when generating aggregated buildinfo in `compare` Use the per-module ignore configuration when generating buildinfo for comparison in the aggregator (usually the 'last' module). It's a bit of a bodge because it relies on the ignore config being stored when the `compare` goal runs in each module. If for some reason a module doesn't run the `compare` goal then it falls back to whatever config exists in the aggregator. There's probably a much more complex solution that involves parsing the config out of the project model that would be more consistent. --- src/it/compare-ignore/invoker.properties | 21 +++ src/it/compare-ignore/modA/pom.xml | 61 +++++++++ .../src/main/resources/filtered.properties | 18 +++ src/it/compare-ignore/modB/pom.xml | 42 ++++++ src/it/compare-ignore/pom.xml | 70 ++++++++++ src/it/settings.xml | 3 + .../buildinfo/AbstractBuildinfoMojo.java | 22 +-- .../artifact/buildinfo/BuildInfoWriter.java | 129 +++++++++++++++--- .../artifact/buildinfo/CompareMojo.java | 2 +- .../buildinfo/DescribeBuildOutputMojo.java | 8 +- 10 files changed, 347 insertions(+), 29 deletions(-) create mode 100644 src/it/compare-ignore/invoker.properties create mode 100644 src/it/compare-ignore/modA/pom.xml create mode 100644 src/it/compare-ignore/modA/src/main/resources/filtered.properties create mode 100644 src/it/compare-ignore/modB/pom.xml create mode 100644 src/it/compare-ignore/pom.xml diff --git a/src/it/compare-ignore/invoker.properties b/src/it/compare-ignore/invoker.properties new file mode 100644 index 0000000..6350e3b --- /dev/null +++ b/src/it/compare-ignore/invoker.properties @@ -0,0 +1,21 @@ +# 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. + +# initial reference build: install +invoker.goals.1=clean install +# second build: verify (could be package, but not install to avoid overriding reference) +invoker.goals.2=clean verify artifact:compare diff --git a/src/it/compare-ignore/modA/pom.xml b/src/it/compare-ignore/modA/pom.xml new file mode 100644 index 0000000..5a65571 --- /dev/null +++ b/src/it/compare-ignore/modA/pom.xml @@ -0,0 +1,61 @@ + + + + + + 4.0.0 + + + org.apache.maven.plugins.it + ignore + 1.0-SNAPSHOT + + ignore-modA + jar + ignore module A + + + + + src/main/resources + true + + + + + + @project.groupId@ + @project.artifactId@ + @project.version@ + + */ignore-modA-*.jar + + + + + compare + + + + + + + diff --git a/src/it/compare-ignore/modA/src/main/resources/filtered.properties b/src/it/compare-ignore/modA/src/main/resources/filtered.properties new file mode 100644 index 0000000..3fa6b09 --- /dev/null +++ b/src/it/compare-ignore/modA/src/main/resources/filtered.properties @@ -0,0 +1,18 @@ +# 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.build.outputTimestamp=${project.build.outputTimestamp} diff --git a/src/it/compare-ignore/modB/pom.xml b/src/it/compare-ignore/modB/pom.xml new file mode 100644 index 0000000..18e0e2f --- /dev/null +++ b/src/it/compare-ignore/modB/pom.xml @@ -0,0 +1,42 @@ + + + + + + 4.0.0 + + + org.apache.maven.plugins.it + ignore + 1.0-SNAPSHOT + + ignore-modB + pom + ignore module B + + + + org.apache.maven.plugins.it + ignore-modA + 1.0-SNAPSHOT + + + diff --git a/src/it/compare-ignore/pom.xml b/src/it/compare-ignore/pom.xml new file mode 100644 index 0000000..d580e61 --- /dev/null +++ b/src/it/compare-ignore/pom.xml @@ -0,0 +1,70 @@ + + + + + + 4.0.0 + + org.apache.maven.plugins.it + ignore + 1.0-SNAPSHOT + pom + + An IT verifying compare works when resuming a multi-module build. + + + UTF-8 + + + + 3.0.5 + + + + + local-snapshots + file://${basedir}/target/remote-repo + false + + + + + modB + modA + + + + + + @project.groupId@ + @project.artifactId@ + @project.version@ + + + + compare + + + + + + + diff --git a/src/it/settings.xml b/src/it/settings.xml index 43a94d4..2592384 100644 --- a/src/it/settings.xml +++ b/src/it/settings.xml @@ -26,6 +26,9 @@ true + + nexus + local.central diff --git a/src/main/java/org/apache/maven/plugins/artifact/buildinfo/AbstractBuildinfoMojo.java b/src/main/java/org/apache/maven/plugins/artifact/buildinfo/AbstractBuildinfoMojo.java index 76f8678..f647548 100644 --- a/src/main/java/org/apache/maven/plugins/artifact/buildinfo/AbstractBuildinfoMojo.java +++ b/src/main/java/org/apache/maven/plugins/artifact/buildinfo/AbstractBuildinfoMojo.java @@ -76,14 +76,14 @@ public abstract class AbstractBuildinfoMojo extends AbstractMojo { * Ignore javadoc attached artifacts from buildinfo generation. */ @Parameter(property = "buildinfo.ignoreJavadoc", defaultValue = "true") - private boolean ignoreJavadoc; + boolean ignoreJavadoc; /** * Artifacts to ignore, specified as a glob matching against ${groupId}/${filename}, for example * */*.xml. */ @Parameter(property = "buildinfo.ignore", defaultValue = "") - private List ignore; + List ignore; /** * Detect projects/modules with install or deploy skipped: avoid taking fingerprints. @@ -148,6 +148,8 @@ public void execute() throws MojoExecutionException { hasBadOutputTimestamp(outputTimestamp, getLog(), project, session.getProjects(), diagnose); + initModuleBuildInfo(); + if (!mono) { // if module skips install and/or deploy if (isSkip(project)) { @@ -171,6 +173,10 @@ public void execute() throws MojoExecutionException { execute(artifacts); } + protected void initModuleBuildInfo() { + BuildInfoWriter.addModuleBuildInfo(getPluginContext(), ignore, ignoreJavadoc); + } + static boolean hasBadOutputTimestamp( String outputTimestamp, Log log, @@ -303,9 +309,9 @@ protected void copyAggregateToRoot(File aggregate) throws MojoExecutionException protected BuildInfoWriter newBuildInfoWriter(PrintWriter p, boolean mono) { BuildInfoWriter bi = new BuildInfoWriter(getLog(), p, mono, rtInformation); - bi.setIgnoreJavadoc(ignoreJavadoc); - bi.setIgnore(ignore); bi.setToolchain(getToolchain()); + bi.setSession(session); + bi.setPluginContext(getPluginContext()); return bi; } @@ -329,11 +335,11 @@ protected Map generateBuildinfo(boolean mono) throws MojoExecu // artifact(s) fingerprints if (mono) { - bi.printArtifacts(project); + bi.printArtifacts(project, project); } else { - for (MavenProject project : session.getProjects()) { - if (!isSkip(project)) { - bi.printArtifacts(project); + for (MavenProject moduleProject : session.getProjects()) { + if (!isSkip(moduleProject)) { + bi.printArtifacts(moduleProject, project); } } } diff --git a/src/main/java/org/apache/maven/plugins/artifact/buildinfo/BuildInfoWriter.java b/src/main/java/org/apache/maven/plugins/artifact/buildinfo/BuildInfoWriter.java index 1f10ac5..9ede247 100644 --- a/src/main/java/org/apache/maven/plugins/artifact/buildinfo/BuildInfoWriter.java +++ b/src/main/java/org/apache/maven/plugins/artifact/buildinfo/BuildInfoWriter.java @@ -37,7 +37,9 @@ import org.apache.commons.codec.digest.DigestUtils; import org.apache.maven.RepositoryUtils; +import org.apache.maven.execution.MavenSession; import org.apache.maven.plugin.MojoExecutionException; +import org.apache.maven.plugin.descriptor.PluginDescriptor; import org.apache.maven.plugin.logging.Log; import org.apache.maven.project.MavenProject; import org.apache.maven.rtinfo.RuntimeInformation; @@ -46,19 +48,26 @@ import org.eclipse.aether.artifact.DefaultArtifact; import org.eclipse.aether.util.artifact.ArtifactIdUtils; +import static java.util.Objects.requireNonNull; + /** * Buildinfo content writer. */ class BuildInfoWriter { + + private static final String MODULE_BUILD_INFO_KEY = "moduleBuildInfo"; + private final Log log; private final PrintWriter p; private final boolean mono; private final RuntimeInformation rtInformation; private final Map artifacts = new LinkedHashMap<>(); private int projectCount = -1; - private boolean ignoreJavadoc = true; - private List ignore; private Toolchain toolchain; + private MavenSession session; + + @SuppressWarnings("rawtypes") + private Map pluginContext; BuildInfoWriter(Log log, PrintWriter p, boolean mono, RuntimeInformation rtInformation) { this.log = log; @@ -151,7 +160,7 @@ private void printSourceInformation(MavenProject project) { } } - void printArtifacts(MavenProject project) throws MojoExecutionException { + void printArtifacts(MavenProject project, MavenProject aggregator) throws MojoExecutionException { String prefix = "outputs."; if (!mono) { // aggregated buildinfo output @@ -209,9 +218,10 @@ void printArtifacts(MavenProject project) throws MojoExecutionException { return; } + final ModuleBuildInfo moduleBuildInfo = getModuleBuildInfo(project, aggregator); if (project.getArtifact().getFile() != null) { Artifact artifact = RepositoryUtils.toArtifact(project.getArtifact()); - if (isIgnore(artifact)) { + if (moduleBuildInfo.isIgnore(artifact)) { p.println("# ignored " + getArtifactFilename(artifact)); artifacts.put(artifact, null); } else { @@ -228,11 +238,11 @@ void printArtifacts(MavenProject project) throws MojoExecutionException { // ignore pgp signatures continue; } - if (ignoreJavadoc && "javadoc".equals(attached.getClassifier())) { + if (moduleBuildInfo.ignoreJavadoc && "javadoc".equals(attached.getClassifier())) { // TEMPORARY ignore javadoc, waiting for MJAVADOC-627 in m-javadoc-p 3.2.0 continue; } - if (isIgnore(attached)) { + if (moduleBuildInfo.isIgnore(attached)) { p.println("# ignored " + getArtifactFilename(attached)); artifacts.put(attached, null); continue; @@ -326,25 +336,108 @@ static Properties loadOutputProperties(File buildinfo) throws MojoExecutionExcep return prop; } - boolean getIgnoreJavadoc() { - return ignoreJavadoc; + boolean getIgnoreJavadoc(MavenProject project) { + // atm this is only called by DescribeBuildOutputMojo, where aggregator is always the same as project + return getModuleBuildInfo(project, project).ignoreJavadoc; } - void setIgnoreJavadoc(boolean ignoreJavadoc) { - this.ignoreJavadoc = ignoreJavadoc; + boolean isIgnore(MavenProject project, Artifact attached) { + // atm this is only called by DescribeBuildOutputMojo, where aggregator is always the same as project + return getModuleBuildInfo(project, project).isIgnore(attached); } - void setIgnore(List ignore) { - FileSystem fs = FileSystems.getDefault(); - this.ignore = ignore.stream().map(i -> fs.getPathMatcher("glob:" + i)).collect(Collectors.toList()); + public void setToolchain(Toolchain toolchain) { + this.toolchain = toolchain; } - boolean isIgnore(Artifact attached) { - Path path = Paths.get(attached.getGroupId() + '/' + getArtifactFilename(attached)); - return ignore.stream().anyMatch(m -> m.matches(path)); + public void setSession(MavenSession session) { + this.session = session; } - public void setToolchain(Toolchain toolchain) { - this.toolchain = toolchain; + public void setPluginContext(Map pluginContext) { + this.pluginContext = pluginContext; + } + + private ModuleBuildInfo getModuleBuildInfo(MavenProject project, MavenProject aggregator) { + // pluginContext is project-specific + PluginDescriptor pluginDescriptor = requireNonNull( + (PluginDescriptor) pluginContext.get("pluginDescriptor"), + "pluginDescriptor not found in pluginContext"); + ModuleBuildInfo moduleBuildInfo = tryGetModuleBuildInfo(project, pluginDescriptor); + if (moduleBuildInfo == null) { + // fallback to aggregator if not found for module - this can happen e.g. if the plugin + // did not run for the module (e.g. we're resuming the reactor build). the aggregator + // should be the project we're actually running in, it should always be available). + moduleBuildInfo = requireNonNull( + tryGetModuleBuildInfo(aggregator, pluginDescriptor), + "moduleBuildInfo not found for project: " + aggregator); + } + return moduleBuildInfo; + } + + @SuppressWarnings("rawtypes") + private ModuleBuildInfo tryGetModuleBuildInfo(MavenProject project, PluginDescriptor pluginDescriptor) { + Map projectPluginContext = session.getPluginContext(pluginDescriptor, project); + return ModuleBuildInfo.tryLoad(projectPluginContext); + } + + /** + * {@code static} because we invoke it at times when no {@code BuildInfoWriter} is available. + */ + @SuppressWarnings("rawtypes") + static void addModuleBuildInfo(Map pluginContext, List ignore, boolean ignoreJavadoc) { + new ModuleBuildInfo(ignore, ignoreJavadoc).store(pluginContext); + } + + /** + * Stores module-specific buildInfo configuration - this is used in the aggregator to ensure that + * the aggregator respects each module's configuration. For clarify the modules of a + * multi-module are exposed in the code as {@link MavenProject MavenProjects}. + */ + static class ModuleBuildInfo { + + @SuppressWarnings({"rawtypes", "unchecked"}) + public static ModuleBuildInfo tryLoad(Map pluginContext) { + Object[] params; + synchronized (pluginContext) { + params = (Object[]) pluginContext.get(MODULE_BUILD_INFO_KEY); + } + if (params == null) { + return null; + } + return new ModuleBuildInfo((List) params[0], (Boolean) params[1]); + } + + private final List ignore; + final boolean ignoreJavadoc; + + ModuleBuildInfo(List ignore, boolean ignoreJavadoc) { + FileSystem fs = FileSystems.getDefault(); + this.ignore = + ignore.stream().map(i -> fs.getPathMatcher("glob:" + i)).collect(Collectors.toList()); + this.ignoreJavadoc = ignoreJavadoc; + } + + private ModuleBuildInfo(List ignore, Boolean ignoreJavadoc) { + this.ignore = ignore; + this.ignoreJavadoc = ignoreJavadoc; + } + + public boolean isIgnore(Artifact attached) { + Path path = Paths.get(attached.getGroupId() + '/' + getArtifactFilename(attached)); + return ignore.stream().anyMatch(m -> m.matches(path)); + } + + /** + * It's possible for the plugin to be executed in different classloaders for different modules, so because + * we're storing data in the session for use by different modules (i.e. the individual modules, and at the end + * the aggregator module), we can't store a class specific to this plugin. Use primitives/jdk classes. + */ + @SuppressWarnings("rawtypes") + public void store(Map pluginContext) { + synchronized (pluginContext) { + pluginContext.put(MODULE_BUILD_INFO_KEY, new Object[] {ignore, ignoreJavadoc}); + } + } } } diff --git a/src/main/java/org/apache/maven/plugins/artifact/buildinfo/CompareMojo.java b/src/main/java/org/apache/maven/plugins/artifact/buildinfo/CompareMojo.java index 9cfd262..5da2ef7 100644 --- a/src/main/java/org/apache/maven/plugins/artifact/buildinfo/CompareMojo.java +++ b/src/main/java/org/apache/maven/plugins/artifact/buildinfo/CompareMojo.java @@ -100,7 +100,7 @@ public class CompareMojo extends AbstractBuildinfoMojo { private boolean fail; @Override - public void execute(Map artifacts) throws MojoExecutionException { + void execute(Map artifacts) throws MojoExecutionException { getLog().info("Checking against reference build from " + referenceRepo + "..."); checkAgainstReference(artifacts, session.getProjects().size() == 1); } diff --git a/src/main/java/org/apache/maven/plugins/artifact/buildinfo/DescribeBuildOutputMojo.java b/src/main/java/org/apache/maven/plugins/artifact/buildinfo/DescribeBuildOutputMojo.java index 88f6681..8284ad1 100644 --- a/src/main/java/org/apache/maven/plugins/artifact/buildinfo/DescribeBuildOutputMojo.java +++ b/src/main/java/org/apache/maven/plugins/artifact/buildinfo/DescribeBuildOutputMojo.java @@ -60,6 +60,7 @@ public void execute() throws MojoExecutionException { diagnose(outputTimestamp, getLog(), project, session.getProjects(), effective); getLog().info(""); + initModuleBuildInfo(); describeBuildOutput(); } @@ -142,13 +143,16 @@ private void describeBuildOutput() throws MojoExecutionException { } private boolean isIgnore(Artifact a) { + // because this is an aggregator mojo it only runs in the aggregator project so it's the only + // one that will have `ModuleBuildInfo` available (i.e. none of the other session.projects + // will have it). if (a.getExtension().endsWith(".asc")) { return true; } - if (bi.getIgnoreJavadoc() && "javadoc".equals(a.getClassifier())) { + if (bi.getIgnoreJavadoc(project) && "javadoc".equals(a.getClassifier())) { return true; } - return bi.isIgnore(a); + return bi.isIgnore(project, a); } private String describeArtifact(Artifact a) throws MojoExecutionException {