From d75f0370224ed81038388a937e2f120e7bd30ddf Mon Sep 17 00:00:00 2001 From: Andrzej Jarmoniuk <gh@jarmoniuk.nl> Date: Tue, 24 Jan 2023 21:16:44 +0100 Subject: [PATCH] Issue #74: Add display-extension-updates --- pom.xml | 7 + versions-common/pom.xml | 4 + .../versions/api/DefaultVersionsHelper.java | 83 +++-- .../mojo/versions/api/VersionsHelper.java | 68 +++- .../versions/filtering/DependencyFilter.java | 30 +- .../versions/utils/CoreExtensionUtils.java | 64 ++++ .../versions/utils/DependencyBuilder.java | 32 +- .../mojo/versions/utils/ExtensionBuilder.java | 103 ++++++ .../utils/CoreExtensionUtilsTest.java | 77 +++++ .../utils/core-extensions/.mvn/extensions.xml | 13 + .../invoker.properties | 8 + .../it-display-extension-updates-001/pom.xml | 25 ++ .../verify.groovy | 10 + .../.mvn/extensions.xml | 13 + .../invoker.properties | 8 + .../it-display-extension-updates-002/pom.xml | 11 + .../verify.groovy | 10 + .../.mvn/extensions.xml | 8 + .../invoker.properties | 5 + .../it-display-extension-updates-003/pom.xml | 21 ++ .../verify.groovy | 7 + .../versions/DisplayExtensionUpdatesMojo.java | 293 ++++++++++++++++++ .../DisplayExtensionUpdatesMojoTest.java | 157 ++++++++++ .../mojo/versions/utils/MockUtils.java | 45 +-- 24 files changed, 1008 insertions(+), 94 deletions(-) create mode 100644 versions-common/src/main/java/org/codehaus/mojo/versions/utils/CoreExtensionUtils.java create mode 100644 versions-common/src/main/java/org/codehaus/mojo/versions/utils/ExtensionBuilder.java create mode 100644 versions-common/src/test/java/org/codehaus/mojo/versions/utils/CoreExtensionUtilsTest.java create mode 100644 versions-common/src/test/resources/org/codehaus/mojo/versions/utils/core-extensions/.mvn/extensions.xml create mode 100644 versions-maven-plugin/src/it/it-display-extension-updates-001/invoker.properties create mode 100644 versions-maven-plugin/src/it/it-display-extension-updates-001/pom.xml create mode 100644 versions-maven-plugin/src/it/it-display-extension-updates-001/verify.groovy create mode 100644 versions-maven-plugin/src/it/it-display-extension-updates-002/.mvn/extensions.xml create mode 100644 versions-maven-plugin/src/it/it-display-extension-updates-002/invoker.properties create mode 100644 versions-maven-plugin/src/it/it-display-extension-updates-002/pom.xml create mode 100644 versions-maven-plugin/src/it/it-display-extension-updates-002/verify.groovy create mode 100644 versions-maven-plugin/src/it/it-display-extension-updates-003/.mvn/extensions.xml create mode 100644 versions-maven-plugin/src/it/it-display-extension-updates-003/invoker.properties create mode 100644 versions-maven-plugin/src/it/it-display-extension-updates-003/pom.xml create mode 100644 versions-maven-plugin/src/it/it-display-extension-updates-003/verify.groovy create mode 100644 versions-maven-plugin/src/main/java/org/codehaus/mojo/versions/DisplayExtensionUpdatesMojo.java create mode 100644 versions-maven-plugin/src/test/java/org/codehaus/mojo/versions/DisplayExtensionUpdatesMojoTest.java diff --git a/pom.xml b/pom.xml index ea9d1135e8..d9a2750a02 100644 --- a/pom.xml +++ b/pom.xml @@ -126,6 +126,7 @@ <maven.compiler.source>${mojo.java.target}</maven.compiler.source> <junitBomVersion>5.9.1</junitBomVersion> <mavenVersion>3.2.5</mavenVersion> + <mavenEmbedderVersion>3.3.9</mavenEmbedderVersion> <doxiaVersion>1.12.0</doxiaVersion> <doxia-sitetoolsVersion>1.11.1</doxia-sitetoolsVersion> <pluginVersion>${project.version}</pluginVersion> @@ -177,6 +178,12 @@ <artifactId>maven-compat</artifactId> <version>${mavenVersion}</version> </dependency> + <dependency> + <groupId>org.apache.maven</groupId> + <artifactId>maven-embedder</artifactId> + <version>${mavenEmbedderVersion}</version> + <scope>provided</scope> + </dependency> <dependency> <groupId>org.apache.maven.enforcer</groupId> diff --git a/versions-common/pom.xml b/versions-common/pom.xml index 6276a72d71..a65e215b0e 100644 --- a/versions-common/pom.xml +++ b/versions-common/pom.xml @@ -50,6 +50,10 @@ <groupId>org.apache.maven</groupId> <artifactId>maven-settings</artifactId> </dependency> + <dependency> + <groupId>org.apache.maven</groupId> + <artifactId>maven-embedder</artifactId> + </dependency> <dependency> <groupId>com.fasterxml.woodstox</groupId> <artifactId>woodstox-core</artifactId> diff --git a/versions-common/src/main/java/org/codehaus/mojo/versions/api/DefaultVersionsHelper.java b/versions-common/src/main/java/org/codehaus/mojo/versions/api/DefaultVersionsHelper.java index d3844285c1..9a75757bf4 100644 --- a/versions-common/src/main/java/org/codehaus/mojo/versions/api/DefaultVersionsHelper.java +++ b/versions-common/src/main/java/org/codehaus/mojo/versions/api/DefaultVersionsHelper.java @@ -1,22 +1,18 @@ package org.codehaus.mojo.versions.api; /* - * 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 + * Copyright MojoHaus and Contributors + * Licensed 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 + * 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. + * 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. */ import java.io.BufferedInputStream; @@ -45,6 +41,7 @@ import java.util.concurrent.Future; import java.util.regex.Pattern; import java.util.stream.Collectors; +import java.util.stream.Stream; import org.apache.commons.lang3.tuple.ImmutablePair; import org.apache.commons.lang3.tuple.Pair; @@ -89,6 +86,7 @@ import org.eclipse.aether.resolution.VersionRangeRequest; import org.eclipse.aether.resolution.VersionRangeResolutionException; +import static java.util.Collections.emptyList; import static java.util.Collections.singletonList; import static java.util.Optional.empty; import static java.util.Optional.of; @@ -172,12 +170,35 @@ public Log getLog() { public ArtifactVersions lookupArtifactVersions( Artifact artifact, VersionRange versionRange, boolean usePluginRepositories) throws VersionRetrievalException { + return lookupArtifactVersions(artifact, versionRange, usePluginRepositories, !usePluginRepositories); + } + + @Override + public ArtifactVersions lookupArtifactVersions( + Artifact artifact, VersionRange versionRange, boolean usePluginRepositories, boolean useProjectRepositories) + throws VersionRetrievalException { try { Collection<IgnoreVersion> ignoredVersions = getIgnoredVersions(artifact); if (!ignoredVersions.isEmpty() && getLog().isDebugEnabled()) { getLog().debug("Found ignored versions: " + ignoredVersions.stream().map(IgnoreVersion::toString).collect(Collectors.joining(", "))); } + + final List<RemoteRepository> repositories; + if (usePluginRepositories && !useProjectRepositories) { + repositories = mavenSession.getCurrentProject().getRemotePluginRepositories(); + } else if (!usePluginRepositories && useProjectRepositories) { + repositories = mavenSession.getCurrentProject().getRemoteProjectRepositories(); + } else if (usePluginRepositories) { + repositories = Stream.concat( + mavenSession.getCurrentProject().getRemoteProjectRepositories().stream(), + mavenSession.getCurrentProject().getRemotePluginRepositories().stream()) + .distinct() + .collect(Collectors.toList()); + } else { + // testing? + repositories = emptyList(); + } return new ArtifactVersions( artifact, aetherRepositorySystem @@ -191,13 +212,7 @@ public ArtifactVersions lookupArtifactVersions( .findFirst() .map(Restriction::toString)) .orElse("(,)")), - usePluginRepositories - ? mavenSession - .getCurrentProject() - .getRemotePluginRepositories() - : mavenSession - .getCurrentProject() - .getRemoteProjectRepositories(), + repositories, "lookupArtifactVersions")) .getVersions() .stream() @@ -428,16 +443,20 @@ public ArtifactVersion createArtifactVersion(String version) { return DefaultArtifactVersionCache.of(version); } - @Override public Map<Dependency, ArtifactVersions> lookupDependenciesUpdates( - Set<Dependency> dependencies, boolean usePluginRepositories, boolean allowSnapshots) + Set<Dependency> dependencies, + boolean usePluginRepositories, + boolean useProjectRepositories, + boolean allowSnapshots) throws VersionRetrievalException { ExecutorService executor = Executors.newFixedThreadPool(LOOKUP_PARALLEL_THREADS); try { Map<Dependency, ArtifactVersions> dependencyUpdates = new TreeMap<>(DependencyComparator.INSTANCE); List<Future<? extends Pair<Dependency, ArtifactVersions>>> futures = dependencies.stream() .map(dependency -> executor.submit(() -> new ImmutablePair<>( - dependency, lookupDependencyUpdates(dependency, usePluginRepositories, allowSnapshots)))) + dependency, + lookupDependencyUpdates( + dependency, usePluginRepositories, useProjectRepositories, allowSnapshots)))) .collect(Collectors.toList()); for (Future<? extends Pair<Dependency, ArtifactVersions>> details : futures) { Pair<Dependency, ArtifactVersions> pair = details.get(); @@ -453,12 +472,22 @@ dependency, lookupDependencyUpdates(dependency, usePluginRepositories, allowSnap } } + @Override + public Map<Dependency, ArtifactVersions> lookupDependenciesUpdates( + Set<Dependency> dependencies, boolean usePluginRepositories, boolean allowSnapshots) + throws VersionRetrievalException { + return lookupDependenciesUpdates(dependencies, usePluginRepositories, !usePluginRepositories, allowSnapshots); + } + @Override public ArtifactVersions lookupDependencyUpdates( - Dependency dependency, boolean usePluginRepositories, boolean allowSnapshots) + Dependency dependency, + boolean usePluginRepositories, + boolean useProjectRepositories, + boolean allowSnapshots) throws VersionRetrievalException { - ArtifactVersions allVersions = - lookupArtifactVersions(createDependencyArtifact(dependency), usePluginRepositories); + ArtifactVersions allVersions = lookupArtifactVersions( + createDependencyArtifact(dependency), null, usePluginRepositories, useProjectRepositories); return new ArtifactVersions( allVersions.getArtifact(), Arrays.stream(allVersions.getAllUpdates(allowSnapshots)).collect(Collectors.toList()), diff --git a/versions-common/src/main/java/org/codehaus/mojo/versions/api/VersionsHelper.java b/versions-common/src/main/java/org/codehaus/mojo/versions/api/VersionsHelper.java index d10b4e0d92..0c3e2347e0 100644 --- a/versions-common/src/main/java/org/codehaus/mojo/versions/api/VersionsHelper.java +++ b/versions-common/src/main/java/org/codehaus/mojo/versions/api/VersionsHelper.java @@ -1,22 +1,18 @@ package org.codehaus.mojo.versions.api; /* - * 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 + * Copyright MojoHaus and Contributors + * Licensed 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 + * 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. + * 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. */ import java.util.Collection; @@ -152,7 +148,7 @@ Artifact createDependencyArtifact( * <b>The resulting {@link ArtifactVersions} instance will contain all versions, including snapshots.</b> * * @param artifact The artifact to look for versions of. - * @param usePluginRepositories <code>true</code> will consult the pluginRepositories, while <code>false</code> will + * @param usePluginRepositories {@code true} will consult the pluginRepositories, while {@code false} will * consult the repositories for normal dependencies. * @return The details of the available artifact versions. * @throws VersionRetrievalException thrown if version resolution fails @@ -167,7 +163,24 @@ ArtifactVersions lookupArtifactVersions(Artifact artifact, boolean usePluginRepo * <b>The resulting {@link ArtifactVersions} instance will contain all versions, including snapshots.</b> * * @param artifact The artifact to look for versions of. - * @param versionRange versionRange to restrict the search + * @param versionRange versionRange to restrict the search, may be {@code null} + * @param usePluginRepositories {@code true} will consult the pluginRepositories + * @param useProjectRepositories {@code true} will consult regular project repositories + * @return The details of the available artifact versions. + * @throws VersionRetrievalException thrown if version resolution fails + * @since 2.15.0 + */ + ArtifactVersions lookupArtifactVersions( + Artifact artifact, VersionRange versionRange, boolean usePluginRepositories, boolean useProjectRepositories) + throws VersionRetrievalException; + + /** + * Looks up the versions of the specified artifact that are available in either the local repository, or the + * appropriate remote repositories. + * <b>The resulting {@link ArtifactVersions} instance will contain all versions, including snapshots.</b> + * + * @param artifact The artifact to look for versions of. + * @param versionRange versionRange to restrict the search, may be {@code null} * @param usePluginRepositories <code>true</code> will consult the pluginRepositories, while <code>false</code> will * consult the repositories for normal dependencies. * @return The details of the available artifact versions. @@ -190,18 +203,39 @@ Map<Dependency, ArtifactVersions> lookupDependenciesUpdates( Set<Dependency> dependencies, boolean usePluginRepositories, boolean allowSnapshots) throws VersionRetrievalException; + /** + * Returns a map of all possible updates per dependency. The lookup is done in parallel using + * {@code LOOKUP_PARALLEL_THREADS} threads. + * + * @param dependencies The set of {@link Dependency} instances to look up. + * @param usePluginRepositories Search the plugin repositories. + * @param useProjectRepositories whether to use regular project repositories + * @param allowSnapshots whether snapshots should be included + * @return map containing the ArtifactVersions object per dependency + */ + Map<Dependency, ArtifactVersions> lookupDependenciesUpdates( + Set<Dependency> dependencies, + boolean usePluginRepositories, + boolean useProjectRepositories, + boolean allowSnapshots) + throws VersionRetrievalException; + /** * Creates an {@link org.codehaus.mojo.versions.api.ArtifactVersions} instance from a dependency. * * @param dependency The dependency. * @param usePluginRepositories Search the plugin repositories. + * @param useProjectRepositories whether to use regular project repositories * @param allowSnapshots whether snapshots should be included * @return The details of updates to the dependency. * @throws VersionRetrievalException thrown if version resolution fails * @since 1.0-beta-1 */ ArtifactVersions lookupDependencyUpdates( - Dependency dependency, boolean usePluginRepositories, boolean allowSnapshots) + Dependency dependency, + boolean usePluginRepositories, + boolean useProjectRepositories, + boolean allowSnapshots) throws VersionRetrievalException; /** diff --git a/versions-common/src/main/java/org/codehaus/mojo/versions/filtering/DependencyFilter.java b/versions-common/src/main/java/org/codehaus/mojo/versions/filtering/DependencyFilter.java index 4af2a4fbfa..d2808a0a76 100644 --- a/versions-common/src/main/java/org/codehaus/mojo/versions/filtering/DependencyFilter.java +++ b/versions-common/src/main/java/org/codehaus/mojo/versions/filtering/DependencyFilter.java @@ -1,22 +1,18 @@ package org.codehaus.mojo.versions.filtering; /* - * 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 + * Copyright MojoHaus and Contributors + * Licensed 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 + * 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. + * 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. */ import java.util.Collection; @@ -66,10 +62,14 @@ public Set<Dependency> removingFrom(Collection<Dependency> dependencies) { return filterBy(dependencies, not(this::matchersMatch)); } - private boolean matchersMatch(Dependency dependency) { + public boolean matchersMatch(Dependency dependency) { return matchers.stream().anyMatch(m -> m.test(dependency)); } + public boolean matchersDontMatch(Dependency dependency) { + return !matchersMatch(dependency); + } + private TreeSet<Dependency> filterBy(Collection<Dependency> dependencies, Predicate<Dependency> predicate) { return dependencies.stream() .filter(predicate) diff --git a/versions-common/src/main/java/org/codehaus/mojo/versions/utils/CoreExtensionUtils.java b/versions-common/src/main/java/org/codehaus/mojo/versions/utils/CoreExtensionUtils.java new file mode 100644 index 0000000000..62638fac85 --- /dev/null +++ b/versions-common/src/main/java/org/codehaus/mojo/versions/utils/CoreExtensionUtils.java @@ -0,0 +1,64 @@ +package org.codehaus.mojo.versions.utils; + +/* + * Copyright MojoHaus and Contributors + * + * Licensed 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. + */ + +import java.io.BufferedReader; +import java.io.IOException; +import java.io.InputStreamReader; +import java.io.Reader; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.stream.Stream; + +import org.apache.maven.cli.internal.extension.model.io.xpp3.CoreExtensionsXpp3Reader; +import org.apache.maven.execution.MavenSession; +import org.apache.maven.model.Extension; +import org.codehaus.plexus.util.xml.pull.XmlPullParserException; + +/** + * Utilities for reading and handling core extensions. + * + * @author Andrzej Jarmoniuk + * @since 2.15.0 + */ +public final class CoreExtensionUtils { + /** + * Reads the core extensions (not build extensions) configured for the given project + * from the {@code ${project}/.mvn/extensions.xml} file. + * + * @param session {@link MavenSession} instance + * @return stream of core extensions defined in the {@code ${project}/.mvn/extensions.xml} file + * @throws IOException thrown if a file I/O operation fails + * @throws XmlPullParserException thrown if the file cannot be parsed + * @since 2.15.0 + */ + public static Stream<Extension> getCoreExtensions(MavenSession session) throws IOException, XmlPullParserException { + Path extensionsFile = session.getCurrentProject().getBasedir().toPath().resolve(".mvn/extensions.xml"); + if (!Files.isRegularFile(extensionsFile)) { + return Stream.empty(); + } + + try (Reader reader = new BufferedReader(new InputStreamReader(Files.newInputStream(extensionsFile)))) { + return new CoreExtensionsXpp3Reader() + .read(reader).getExtensions().stream().map(ex -> ExtensionBuilder.newBuilder() + .withGroupId(ex.getGroupId()) + .withArtifactId(ex.getArtifactId()) + .withVersion(ex.getVersion()) + .build()); + } + } +} diff --git a/versions-common/src/main/java/org/codehaus/mojo/versions/utils/DependencyBuilder.java b/versions-common/src/main/java/org/codehaus/mojo/versions/utils/DependencyBuilder.java index 58ca87c2ff..6574f4b557 100644 --- a/versions-common/src/main/java/org/codehaus/mojo/versions/utils/DependencyBuilder.java +++ b/versions-common/src/main/java/org/codehaus/mojo/versions/utils/DependencyBuilder.java @@ -1,24 +1,20 @@ +package org.codehaus.mojo.versions.utils; + /* - * 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 + * Copyright MojoHaus and Contributors + * Licensed 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 + * 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. + * 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.codehaus.mojo.versions.utils; - import java.util.Optional; import org.apache.maven.model.Dependency; @@ -136,7 +132,9 @@ public static DependencyBuilder newBuilder() { * @param artifactId artifactId of the dependency * @param version version of the dependency * @return new instance of {@linkplain Dependency} + * @deprecated please use the {@link #newBuilder()} method instead */ + @Deprecated public static Dependency dependencyWith(String groupId, String artifactId, String version) { return newBuilder() .withGroupId(groupId) @@ -154,7 +152,9 @@ public static Dependency dependencyWith(String groupId, String artifactId, Strin * @param classifier classifier of the dependency * @param scope scope of the dependency * @return new instance of {@linkplain Dependency} + * @deprecated please use the {@link #newBuilder()} method instead */ + @Deprecated public static Dependency dependencyWith( String groupId, String artifactId, String version, String type, String classifier, String scope) { return newBuilder() diff --git a/versions-common/src/main/java/org/codehaus/mojo/versions/utils/ExtensionBuilder.java b/versions-common/src/main/java/org/codehaus/mojo/versions/utils/ExtensionBuilder.java new file mode 100644 index 0000000000..7d763606d2 --- /dev/null +++ b/versions-common/src/main/java/org/codehaus/mojo/versions/utils/ExtensionBuilder.java @@ -0,0 +1,103 @@ +package org.codehaus.mojo.versions.utils; + +/* + * Copyright MojoHaus and Contributors + * + * Licensed 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. + */ + +import java.util.HashMap; +import java.util.Map; +import java.util.Optional; + +import org.apache.maven.model.Dependency; +import org.apache.maven.model.Extension; +import org.apache.maven.model.InputLocation; + +import static java.util.Optional.empty; +import static java.util.Optional.ofNullable; + +/** + * Builder class for {@linkplain Extension} + */ +@SuppressWarnings("OptionalUsedAsFieldOrParameterType") +public class ExtensionBuilder { + private Optional<String> groupId = empty(); + private Optional<String> artifactId = empty(); + private Optional<String> version = empty(); + private Map<Object, InputLocation> locations = new HashMap<>(); + + private ExtensionBuilder() {} + + /** + * Passes groupId to the builder + * @param groupId given groupId + * @return builder instance + */ + public ExtensionBuilder withGroupId(String groupId) { + this.groupId = ofNullable(groupId); + return this; + } + + /** + * Passes artifactId to the builder + * @param artifactId given artifactId + * @return builder instance + */ + public ExtensionBuilder withArtifactId(String artifactId) { + this.artifactId = ofNullable(artifactId); + return this; + } + + /** + * Passes version to the builder + * @param version given version + * @return builder instance + */ + public ExtensionBuilder withVersion(String version) { + this.version = ofNullable(version); + return this; + } + + /** + * Passes type to the builder + * @param key location key + * @param location input location + * @return builder instance + */ + public ExtensionBuilder withLocation(Object key, InputLocation location) { + this.locations.put(key, location); + return this; + } + + /** + * Creates a new instance of the builder + * @return new instance of the builder + */ + public static ExtensionBuilder newBuilder() { + return new ExtensionBuilder(); + } + + /** + * Builds the {@linkplain Dependency} instance + * @return {@linkplain Dependency} instance + */ + public Extension build() { + Extension inst = new Extension(); + groupId.ifPresent(inst::setGroupId); + artifactId.ifPresent(inst::setArtifactId); + version.ifPresent(inst::setVersion); + locations.forEach(inst::setLocation); + return inst; + } +} diff --git a/versions-common/src/test/java/org/codehaus/mojo/versions/utils/CoreExtensionUtilsTest.java b/versions-common/src/test/java/org/codehaus/mojo/versions/utils/CoreExtensionUtilsTest.java new file mode 100644 index 0000000000..f0500f15dc --- /dev/null +++ b/versions-common/src/test/java/org/codehaus/mojo/versions/utils/CoreExtensionUtilsTest.java @@ -0,0 +1,77 @@ +package org.codehaus.mojo.versions.utils; +/* + * Copyright MojoHaus and Contributors + * + * Licensed 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. + */ + +import java.io.File; +import java.io.IOException; +import java.util.Optional; +import java.util.Set; +import java.util.stream.Collectors; + +import org.apache.maven.execution.MavenSession; +import org.apache.maven.model.Extension; +import org.apache.maven.project.MavenProject; +import org.codehaus.plexus.util.xml.pull.XmlPullParserException; +import org.junit.Test; + +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.hasItems; +import static org.hamcrest.Matchers.is; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +/** + * Unit tests for {@link CoreExtensionUtils} + * + * @author Andrzej Jarmoniuk + */ +public class CoreExtensionUtilsTest { + + @Test + public void testNoExtensions() throws XmlPullParserException, IOException { + MavenProject project = mock(MavenProject.class); + when(project.getBasedir()) + .thenReturn( + new File("src/test/resources/org/codehaus/mojo/versions/utils/core-extensions/no-extensions")); + MavenSession session = mock(MavenSession.class); + when(session.getCurrentProject()).thenReturn(project); + assertThat(CoreExtensionUtils.getCoreExtensions(session).findAny(), is(Optional.empty())); + } + + @Test + public void testExtensionsFound() throws XmlPullParserException, IOException { + MavenProject project = mock(MavenProject.class); + when(project.getBasedir()) + .thenReturn(new File("src/test/resources/org/codehaus/mojo/versions/utils/core-extensions")); + MavenSession session = mock(MavenSession.class); + when(session.getCurrentProject()).thenReturn(project); + Set<Extension> extensions = + CoreExtensionUtils.getCoreExtensions(session).collect(Collectors.toSet()); + assertThat( + extensions, + hasItems( + ExtensionBuilder.newBuilder() + .withGroupId("default-group") + .withArtifactId("artifactA") + .withVersion("1.0.0") + .build(), + ExtensionBuilder.newBuilder() + .withGroupId("default-group") + .withArtifactId("artifactB") + .withVersion("2.0.0") + .build())); + } +} diff --git a/versions-common/src/test/resources/org/codehaus/mojo/versions/utils/core-extensions/.mvn/extensions.xml b/versions-common/src/test/resources/org/codehaus/mojo/versions/utils/core-extensions/.mvn/extensions.xml new file mode 100644 index 0000000000..172cdbc1f9 --- /dev/null +++ b/versions-common/src/test/resources/org/codehaus/mojo/versions/utils/core-extensions/.mvn/extensions.xml @@ -0,0 +1,13 @@ +<extensions xmlns="http://maven.apache.org/EXTENSIONS/1.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:schemaLocation="http://maven.apache.org/EXTENSIONS/1.0.0 http://maven.apache.org/xsd/core-extensions-1.0.0.xsd"> + <extension> + <groupId>default-group</groupId> + <artifactId>artifactA</artifactId> + <version>1.0.0</version> + </extension> + <extension> + <groupId>default-group</groupId> + <artifactId>artifactB</artifactId> + <version>2.0.0</version> + </extension> +</extensions> \ No newline at end of file diff --git a/versions-maven-plugin/src/it/it-display-extension-updates-001/invoker.properties b/versions-maven-plugin/src/it/it-display-extension-updates-001/invoker.properties new file mode 100644 index 0000000000..5da846218c --- /dev/null +++ b/versions-maven-plugin/src/it/it-display-extension-updates-001/invoker.properties @@ -0,0 +1,8 @@ +invoker.goals.1 = ${project.groupId}:${project.artifactId}:${project.version}:display-extension-updates +invoker.mavenOpts.1 = -Dversions.outputFile=./output1.txt -DoutputEncoding=UTF-8 + +invoker.goals.2 = ${project.groupId}:${project.artifactId}:${project.version}:display-extension-updates +invoker.mavenOpts.2 = -Dversions.outputFile=./output2.txt -DoutputEncoding=UTF-8 -DextensionExcludes=localhost + +invoker.goals.3 = ${project.groupId}:${project.artifactId}:${project.version}:display-extension-updates +invoker.mavenOpts.3 = -Dversions.outputFile=./output3.txt -DoutputEncoding=UTF-8 -DextensionIncludes=localhost -DextensionExcludes=localhost:dummy-api diff --git a/versions-maven-plugin/src/it/it-display-extension-updates-001/pom.xml b/versions-maven-plugin/src/it/it-display-extension-updates-001/pom.xml new file mode 100644 index 0000000000..fe8f43302d --- /dev/null +++ b/versions-maven-plugin/src/it/it-display-extension-updates-001/pom.xml @@ -0,0 +1,25 @@ +<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/xsd/maven-4.0.0.xsd"> + <modelVersion>4.0.0</modelVersion> + <groupId>localhost</groupId> + <artifactId>it-display-extension-updates-001</artifactId> + + <version>1.0</version> + <packaging>pom</packaging> + + <build> + <extensions> + <extension> + <groupId>localhost</groupId> + <artifactId>dummy-maven-plugin</artifactId> + <version>1.0</version> + </extension> + <extension> + <groupId>localhost</groupId> + <artifactId>dummy-api</artifactId> + <version>1.0</version> + </extension> + </extensions> + </build> +</project> diff --git a/versions-maven-plugin/src/it/it-display-extension-updates-001/verify.groovy b/versions-maven-plugin/src/it/it-display-extension-updates-001/verify.groovy new file mode 100644 index 0000000000..4f5fe3ab69 --- /dev/null +++ b/versions-maven-plugin/src/it/it-display-extension-updates-001/verify.groovy @@ -0,0 +1,10 @@ +def output1 = new File( basedir, "output1.txt").text +assert output1 =~ /\Qlocalhost:dummy-maven-plugin\E\s*\.*\s*\Q1.0\E\s+->\s+\Q3.1\E/ +assert output1 =~ /\Qlocalhost:dummy-api\E\s*\.*\s*\Q1.0\E\s+->\s+\Q3.0\E/ + +def output2 = new File( basedir, "output2.txt") +assert !output2.exists() + +def output3 = new File( basedir, "output3.txt").text +assert output3 =~ /\Qlocalhost:dummy-maven-plugin\E\s*\.*\s*\Q1.0\E\s+->\s+\Q3.1\E/ +assert !( output3 =~ /\Qlocalhost:dummy-api\E\s*\.*\s*\Q1.0\E\s+->\s+\Q3.0\E/ ) diff --git a/versions-maven-plugin/src/it/it-display-extension-updates-002/.mvn/extensions.xml b/versions-maven-plugin/src/it/it-display-extension-updates-002/.mvn/extensions.xml new file mode 100644 index 0000000000..1f4f4fbac7 --- /dev/null +++ b/versions-maven-plugin/src/it/it-display-extension-updates-002/.mvn/extensions.xml @@ -0,0 +1,13 @@ +<extensions xmlns="http://maven.apache.org/EXTENSIONS/1.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:schemaLocation="http://maven.apache.org/EXTENSIONS/1.0.0 http://maven.apache.org/xsd/core-extensions-1.0.0.xsd"> + <extension> + <groupId>localhost</groupId> + <artifactId>dummy-maven-plugin</artifactId> + <version>1.0</version> + </extension> + <extension> + <groupId>localhost</groupId> + <artifactId>dummy-api</artifactId> + <version>1.0</version> + </extension> +</extensions> \ No newline at end of file diff --git a/versions-maven-plugin/src/it/it-display-extension-updates-002/invoker.properties b/versions-maven-plugin/src/it/it-display-extension-updates-002/invoker.properties new file mode 100644 index 0000000000..5da846218c --- /dev/null +++ b/versions-maven-plugin/src/it/it-display-extension-updates-002/invoker.properties @@ -0,0 +1,8 @@ +invoker.goals.1 = ${project.groupId}:${project.artifactId}:${project.version}:display-extension-updates +invoker.mavenOpts.1 = -Dversions.outputFile=./output1.txt -DoutputEncoding=UTF-8 + +invoker.goals.2 = ${project.groupId}:${project.artifactId}:${project.version}:display-extension-updates +invoker.mavenOpts.2 = -Dversions.outputFile=./output2.txt -DoutputEncoding=UTF-8 -DextensionExcludes=localhost + +invoker.goals.3 = ${project.groupId}:${project.artifactId}:${project.version}:display-extension-updates +invoker.mavenOpts.3 = -Dversions.outputFile=./output3.txt -DoutputEncoding=UTF-8 -DextensionIncludes=localhost -DextensionExcludes=localhost:dummy-api diff --git a/versions-maven-plugin/src/it/it-display-extension-updates-002/pom.xml b/versions-maven-plugin/src/it/it-display-extension-updates-002/pom.xml new file mode 100644 index 0000000000..28820da695 --- /dev/null +++ b/versions-maven-plugin/src/it/it-display-extension-updates-002/pom.xml @@ -0,0 +1,11 @@ +<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/xsd/maven-4.0.0.xsd"> + <modelVersion>4.0.0</modelVersion> + <groupId>localhost</groupId> + <artifactId>it-display-extension-updates-002</artifactId> + + <version>1.0</version> + <packaging>pom</packaging> + +</project> diff --git a/versions-maven-plugin/src/it/it-display-extension-updates-002/verify.groovy b/versions-maven-plugin/src/it/it-display-extension-updates-002/verify.groovy new file mode 100644 index 0000000000..4f5fe3ab69 --- /dev/null +++ b/versions-maven-plugin/src/it/it-display-extension-updates-002/verify.groovy @@ -0,0 +1,10 @@ +def output1 = new File( basedir, "output1.txt").text +assert output1 =~ /\Qlocalhost:dummy-maven-plugin\E\s*\.*\s*\Q1.0\E\s+->\s+\Q3.1\E/ +assert output1 =~ /\Qlocalhost:dummy-api\E\s*\.*\s*\Q1.0\E\s+->\s+\Q3.0\E/ + +def output2 = new File( basedir, "output2.txt") +assert !output2.exists() + +def output3 = new File( basedir, "output3.txt").text +assert output3 =~ /\Qlocalhost:dummy-maven-plugin\E\s*\.*\s*\Q1.0\E\s+->\s+\Q3.1\E/ +assert !( output3 =~ /\Qlocalhost:dummy-api\E\s*\.*\s*\Q1.0\E\s+->\s+\Q3.0\E/ ) diff --git a/versions-maven-plugin/src/it/it-display-extension-updates-003/.mvn/extensions.xml b/versions-maven-plugin/src/it/it-display-extension-updates-003/.mvn/extensions.xml new file mode 100644 index 0000000000..18a2a6a1d2 --- /dev/null +++ b/versions-maven-plugin/src/it/it-display-extension-updates-003/.mvn/extensions.xml @@ -0,0 +1,8 @@ +<extensions xmlns="http://maven.apache.org/EXTENSIONS/1.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:schemaLocation="http://maven.apache.org/EXTENSIONS/1.0.0 http://maven.apache.org/xsd/core-extensions-1.0.0.xsd"> + <extension> + <groupId>localhost</groupId> + <artifactId>dummy-api</artifactId> + <version>1.0</version> + </extension> +</extensions> \ No newline at end of file diff --git a/versions-maven-plugin/src/it/it-display-extension-updates-003/invoker.properties b/versions-maven-plugin/src/it/it-display-extension-updates-003/invoker.properties new file mode 100644 index 0000000000..0bd6e32e03 --- /dev/null +++ b/versions-maven-plugin/src/it/it-display-extension-updates-003/invoker.properties @@ -0,0 +1,5 @@ +invoker.goals.1 = ${project.groupId}:${project.artifactId}:${project.version}:display-extension-updates +invoker.mavenOpts.1 = -DprocessBuildExtensions=false -Dversions.outputFile=./output1.txt -DoutputEncoding=UTF-8 + +invoker.goals.2 = ${project.groupId}:${project.artifactId}:${project.version}:display-extension-updates +invoker.mavenOpts.2 = -DprocessCoreExtensions=false -Dversions.outputFile=./output2.txt -DoutputEncoding=UTF-8 diff --git a/versions-maven-plugin/src/it/it-display-extension-updates-003/pom.xml b/versions-maven-plugin/src/it/it-display-extension-updates-003/pom.xml new file mode 100644 index 0000000000..655a5ea9e1 --- /dev/null +++ b/versions-maven-plugin/src/it/it-display-extension-updates-003/pom.xml @@ -0,0 +1,21 @@ +<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/xsd/maven-4.0.0.xsd"> + <modelVersion>4.0.0</modelVersion> + <groupId>localhost</groupId> + <artifactId>it-display-extension-updates-003</artifactId> + + <version>1.0</version> + <packaging>pom</packaging> + + <build> + <extensions> + <extension> + <groupId>localhost</groupId> + <artifactId>dummy-maven-plugin</artifactId> + <version>1.0</version> + </extension> + </extensions> + </build> + +</project> diff --git a/versions-maven-plugin/src/it/it-display-extension-updates-003/verify.groovy b/versions-maven-plugin/src/it/it-display-extension-updates-003/verify.groovy new file mode 100644 index 0000000000..f2e88457db --- /dev/null +++ b/versions-maven-plugin/src/it/it-display-extension-updates-003/verify.groovy @@ -0,0 +1,7 @@ +def output1 = new File( basedir, "output1.txt").text +assert !( output1 =~ /\Qlocalhost:dummy-maven-plugin\E\s*\.*\s*\Q1.0\E\s+->\s+\Q3.1\E/ ) +assert output1 =~ /\Qlocalhost:dummy-api\E\s*\.*\s*\Q1.0\E\s+->\s+\Q3.0\E/ + +def output2 = new File( basedir, "output2.txt").text +assert output2 =~ /\Qlocalhost:dummy-maven-plugin\E\s*\.*\s*\Q1.0\E\s+->\s+\Q3.1\E/ +assert !( output2 =~ /\Qlocalhost:dummy-api\E\s*\.*\s*\Q1.0\E\s+->\s+\Q3.0\E/ ) diff --git a/versions-maven-plugin/src/main/java/org/codehaus/mojo/versions/DisplayExtensionUpdatesMojo.java b/versions-maven-plugin/src/main/java/org/codehaus/mojo/versions/DisplayExtensionUpdatesMojo.java new file mode 100644 index 0000000000..d24c98b5fa --- /dev/null +++ b/versions-maven-plugin/src/main/java/org/codehaus/mojo/versions/DisplayExtensionUpdatesMojo.java @@ -0,0 +1,293 @@ +package org.codehaus.mojo.versions; + +/* + * Copyright MojoHaus and Contributors + * Licensed 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. + */ + +import javax.inject.Inject; + +import java.io.IOException; +import java.util.ArrayList; +import java.util.List; +import java.util.Map; +import java.util.Optional; +import java.util.Set; +import java.util.stream.Collectors; +import java.util.stream.Stream; + +import org.apache.maven.artifact.ArtifactUtils; +import org.apache.maven.artifact.versioning.ArtifactVersion; +import org.apache.maven.model.Dependency; +import org.apache.maven.model.Extension; +import org.apache.maven.plugin.MojoExecutionException; +import org.apache.maven.plugin.MojoFailureException; +import org.apache.maven.plugins.annotations.Mojo; +import org.apache.maven.plugins.annotations.Parameter; +import org.apache.maven.repository.RepositorySystem; +import org.apache.maven.wagon.Wagon; +import org.codehaus.mojo.versions.api.ArtifactVersions; +import org.codehaus.mojo.versions.api.Segment; +import org.codehaus.mojo.versions.api.VersionRetrievalException; +import org.codehaus.mojo.versions.api.recording.ChangeRecorder; +import org.codehaus.mojo.versions.filtering.DependencyFilter; +import org.codehaus.mojo.versions.filtering.WildcardMatcher; +import org.codehaus.mojo.versions.rewriting.ModifiedPomXMLEventReader; +import org.codehaus.mojo.versions.utils.CoreExtensionUtils; +import org.codehaus.mojo.versions.utils.DependencyBuilder; +import org.codehaus.mojo.versions.utils.SegmentUtils; +import org.codehaus.plexus.util.StringUtils; +import org.codehaus.plexus.util.xml.pull.XmlPullParserException; + +import static java.util.Optional.of; +import static org.codehaus.mojo.versions.api.Segment.MAJOR; + +/** + * Displays all build and core extensions that have newer versions available. + * + * @author Andrzej Jarmoniuk + * @since 2.15.0 + */ +@Mojo(name = "display-extension-updates", threadSafe = true) +public class DisplayExtensionUpdatesMojo extends AbstractVersionsDisplayMojo { + + // ------------------------------ FIELDS ------------------------------ + + /** + * The width to pad info messages. + * + * @since 1.0-alpha-1 + */ + private static final int INFO_PAD_SIZE = 72; + + /** + * <p>Specifies a comma-separated list of GAV patterns to consider + * when looking for updates. If the trailing parts of the GAV are omitted, then can assume any value.</p> + * + * <p>The wildcard "*" can be used as the only, first, last or both characters in each token. + * The version token does support version ranges.</p> + * + * Examples: {@code "mygroup:artifact:*"}, {@code "mygroup:artifact"}, {@code "mygroup"} + * + * @since 2.15.0 + */ + @Parameter(property = "extensionIncludes", defaultValue = WildcardMatcher.WILDCARD) + private List<String> extensionIncludes; + + /** + * <p>Specifies a comma-separated list of GAV patterns to <b>NOT</b> consider + * when looking for updates. If the trailing parts of the GAV are omitted, then can assume any value.</p> + * + * <p>This list is taken into account <u>after</u> {@link #extensionIncludes}</p>. + * + * <p>The wildcard "*" can be used as the only, first, last or both characters in each token. + * The version token does support version ranges.</p> + * + * Examples: {@code "mygroup:artifact:*"}, {@code "mygroup:artifact"}, {@code "mygroup"} + * + * @since 2.15.0 + */ + @Parameter(property = "extensionExcludes") + private List<String> extensionExcludes; + + /** + * Whether to allow the major version number to be changed. + * + * @since 2.15.0 + */ + @Parameter(property = "allowMajorUpdates", defaultValue = "true") + private boolean allowMajorUpdates = true; + + /** + * <p>Whether to allow the minor version number to be changed.</p> + * + * <p><b>Note: {@code false} also implies {@linkplain #allowMajorUpdates} + * to be {@code false}</b></p> + * + * @since 2.15.0 + */ + @Parameter(property = "allowMinorUpdates", defaultValue = "true") + private boolean allowMinorUpdates = true; + + /** + * <p>Whether to allow the incremental version number to be changed.</p> + * + * <p><b>Note: {@code false} also implies {@linkplain #allowMajorUpdates} + * and {@linkplain #allowMinorUpdates} to be {@code false}</b></p> + * + * @since 2.15.0 + */ + @Parameter(property = "allowIncrementalUpdates", defaultValue = "true") + private boolean allowIncrementalUpdates = true; + + /** + * <p>Whether to process core extensions. Default is {@code true}.</p> + * @since 2.15.0 + */ + @Parameter(property = "processCoreExtensions", defaultValue = "true") + private boolean processCoreExtensions = true; + + /** + * <p>Whether to process build extensions. Default is {@code true}.</p> + * @since 2.15.0 + */ + @Parameter(property = "processBuildExtensions", defaultValue = "true") + private boolean processBuildExtensions = true; + + /** + * Whether to show additional information such as extensions that do not need updating. Defaults to false. + * + * @since 2.15.0 + */ + @Parameter(property = "verbose", defaultValue = "false") + private boolean verbose; + + @Inject + public DisplayExtensionUpdatesMojo( + RepositorySystem repositorySystem, + org.eclipse.aether.RepositorySystem aetherRepositorySystem, + Map<String, Wagon> wagonMap, + Map<String, ChangeRecorder> changeRecorders) { + super(repositorySystem, aetherRepositorySystem, wagonMap, changeRecorders); + } + + @Override + public void execute() throws MojoExecutionException, MojoFailureException { + logInit(); + validateInput(); + + if (!processCoreExtensions && !processBuildExtensions) { + getLog().info("Neither core nor build extensions are to be processed. Nothing to do."); + return; + } + + DependencyFilter includeFilter = DependencyFilter.parseFrom(extensionIncludes); + DependencyFilter excludeFilter = DependencyFilter.parseFrom(extensionExcludes); + + Set<Dependency> dependencies; + try { + Stream<Extension> extensions; + if (processCoreExtensions) { + extensions = CoreExtensionUtils.getCoreExtensions(session); + } else { + extensions = Stream.empty(); + } + if (processBuildExtensions) { + extensions = Stream.concat(extensions, session.getCurrentProject().getBuildExtensions().stream()); + } + + dependencies = extensions + .map(e -> DependencyBuilder.newBuilder() + .withGroupId(e.getGroupId()) + .withArtifactId(e.getArtifactId()) + .withVersion(e.getVersion()) + .build()) + .filter(includeFilter::matchersMatch) + .filter(excludeFilter::matchersDontMatch) + .collect(Collectors.toSet()); + } catch (IOException | XmlPullParserException e) { + throw new MojoExecutionException(e.getMessage()); + } + if (dependencies.isEmpty()) { + getLog().info("Extensions set filtered by include- and extensions-filters is empty. Nothing to do."); + return; + } + + try { + logUpdates(getHelper().lookupDependenciesUpdates(dependencies, true, true, allowSnapshots)); + } catch (VersionRetrievalException e) { + throw new MojoExecutionException(e.getMessage(), e); + } + } + + private Optional<Segment> calculateUpdateScope() { + return of(SegmentUtils.determineUnchangedSegment( + allowMajorUpdates, allowMinorUpdates, allowIncrementalUpdates, getLog()) + .map(s -> Segment.of(s.value() + 1)) + .orElse(MAJOR)); + } + + private void logUpdates(Map<Dependency, ArtifactVersions> updates) { + List<String> withUpdates = new ArrayList<>(); + List<String> usingCurrent = new ArrayList<>(); + for (ArtifactVersions versions : updates.values()) { + String left = " " + ArtifactUtils.versionlessKey(versions.getArtifact()) + " "; + final String current; + ArtifactVersion latest; + if (versions.isCurrentVersionDefined()) { + current = versions.getCurrentVersion().toString(); + latest = versions.getNewestUpdate(calculateUpdateScope(), allowSnapshots); + } else { + ArtifactVersion newestVersion = + versions.getNewestVersion(versions.getArtifact().getVersionRange(), allowSnapshots); + current = versions.getArtifact().getVersionRange().toString(); + latest = newestVersion == null + ? null + : versions.getNewestUpdate(newestVersion, calculateUpdateScope(), allowSnapshots); + if (latest != null + && ArtifactVersions.isVersionInRange( + latest, versions.getArtifact().getVersionRange())) { + latest = null; + } + } + String right = " " + (latest == null ? current : current + " -> " + latest); + List<String> t = latest == null ? usingCurrent : withUpdates; + if (right.length() + left.length() + 3 > INFO_PAD_SIZE + getOutputLineWidthOffset()) { + t.add(left + "..."); + t.add(StringUtils.leftPad(right, INFO_PAD_SIZE + getOutputLineWidthOffset())); + + } else { + t.add(StringUtils.rightPad(left, INFO_PAD_SIZE + getOutputLineWidthOffset() - right.length(), ".") + + right); + } + } + + if (verbose) { + if (usingCurrent.isEmpty()) { + if (!withUpdates.isEmpty()) { + logLine(false, "No extensions are using the newest version."); + logLine(false, ""); + } + } else { + logLine(false, "The following extensions are using the newest version:"); + for (String s : usingCurrent) { + logLine(false, s); + } + logLine(false, ""); + } + } + + if (withUpdates.isEmpty()) { + if (!usingCurrent.isEmpty()) { + logLine(false, "No extensions have newer versions."); + logLine(false, ""); + } + } else { + logLine(false, "The following extensions have newer versions:"); + for (String withUpdate : withUpdates) { + logLine(false, withUpdate); + } + logLine(false, ""); + } + } + + /** + * @param pom the pom to update. + * @see AbstractVersionsUpdaterMojo#update(ModifiedPomXMLEventReader) + * @since 1.0-alpha-1 + */ + @Override + protected void update(ModifiedPomXMLEventReader pom) { + // do nothing + } +} diff --git a/versions-maven-plugin/src/test/java/org/codehaus/mojo/versions/DisplayExtensionUpdatesMojoTest.java b/versions-maven-plugin/src/test/java/org/codehaus/mojo/versions/DisplayExtensionUpdatesMojoTest.java new file mode 100644 index 0000000000..6e6b6ab7c1 --- /dev/null +++ b/versions-maven-plugin/src/test/java/org/codehaus/mojo/versions/DisplayExtensionUpdatesMojoTest.java @@ -0,0 +1,157 @@ +package org.codehaus.mojo.versions; + +/* + * Copyright MojoHaus and Contributors + * + * Licensed 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. + */ + +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.Collections; +import java.util.HashMap; + +import org.apache.maven.model.Build; +import org.apache.maven.model.Model; +import org.apache.maven.plugin.MojoExecutionException; +import org.apache.maven.plugin.MojoFailureException; +import org.apache.maven.project.MavenProject; +import org.codehaus.mojo.versions.utils.ExtensionBuilder; +import org.junit.After; +import org.junit.Before; +import org.junit.Test; + +import static java.util.Collections.emptyList; +import static java.util.Collections.singleton; +import static java.util.Collections.singletonList; +import static org.apache.maven.plugin.testing.ArtifactStubFactory.setVariableValueToObject; +import static org.codehaus.mojo.versions.utils.MockUtils.mockAetherRepositorySystem; +import static org.codehaus.mojo.versions.utils.MockUtils.mockMavenSession; +import static org.codehaus.mojo.versions.utils.MockUtils.mockRepositorySystem; +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.containsString; +import static org.hamcrest.Matchers.empty; + +/** + * Basic tests for {@linkplain DisplayExtensionUpdatesMojo}. + * + * @author Andrzej Jarmoniuk + */ +public class DisplayExtensionUpdatesMojoTest { + private DisplayExtensionUpdatesMojo mojo; + private Path tempPath; + + @Before + public void setUp() throws IllegalAccessException, IOException { + mojo = new DisplayExtensionUpdatesMojo(mockRepositorySystem(), mockAetherRepositorySystem(), null, null); + mojo.project = new MavenProject() { + { + setModel(new Model() { + { + setGroupId("default-group"); + setArtifactId("default-artifact"); + setVersion("1.0.0"); + } + }); + } + }; + mojo.project.setRemoteArtifactRepositories(emptyList()); + mojo.project.setPluginArtifactRepositories(emptyList()); + mojo.session = mockMavenSession(mojo.project); + tempPath = Files.createTempFile("display-extension-updates-", ".log"); + mojo.outputFile = tempPath.toFile(); + mojo.outputEncoding = "UTF-8"; + setVariableValueToObject(mojo, "processCoreExtensions", false); + + mojo.setPluginContext(new HashMap<String, Object>() { + { + put( + "org.codehaus.mojo.versions.AbstractVersionsDisplayMojo.outputFile", + singleton(tempPath.toFile().getCanonicalPath())); + } + }); + } + + @After + public void tearDown() throws IOException { + Files.deleteIfExists(tempPath); + } + + @Test + public void testNoBuildExists() + throws MojoExecutionException, MojoFailureException, IllegalAccessException, IOException { + setVariableValueToObject(mojo, "extensionIncludes", singletonList("*")); + setVariableValueToObject(mojo, "extensionExcludes", emptyList()); + mojo.execute(); + } + + @Test + public void testIncludesMakesSetEmpty() + throws MojoExecutionException, MojoFailureException, IllegalAccessException, IOException { + setVariableValueToObject(mojo, "extensionIncludes", singletonList("other-group")); + setVariableValueToObject(mojo, "extensionExcludes", emptyList()); + + mojo.getProject().setBuild(new Build()); + mojo.getProject() + .getBuild() + .setExtensions(Collections.singletonList(ExtensionBuilder.newBuilder() + .withGroupId("default-group") + .withArtifactId("artifactA") + .withVersion("1.0.0") + .build())); + mojo.execute(); + + assertThat(Files.readAllLines(tempPath), empty()); + } + + @Test + public void testIncludesMakesSetNonEmpty() + throws MojoExecutionException, MojoFailureException, IllegalAccessException, IOException { + setVariableValueToObject(mojo, "extensionIncludes", singletonList("default-group")); + setVariableValueToObject(mojo, "extensionExcludes", emptyList()); + + mojo.getProject().setBuild(new Build()); + mojo.getProject() + .getBuild() + .setExtensions(Collections.singletonList(ExtensionBuilder.newBuilder() + .withGroupId("default-group") + .withArtifactId("artifactA") + .withVersion("1.0.0") + .build())); + mojo.execute(); + + assertThat( + String.join("", Files.readAllLines(tempPath)), + containsString("default-group:artifactA ... 1.0.0 -> 2.0.0")); + } + + @Test + public void testIncludesExcludesMakesSetEmpty() + throws MojoExecutionException, MojoFailureException, IllegalAccessException, IOException { + setVariableValueToObject(mojo, "extensionIncludes", singletonList("default-group")); + setVariableValueToObject(mojo, "extensionExcludes", singletonList("default-group:artifactA")); + + mojo.getProject().setBuild(new Build()); + mojo.getProject() + .getBuild() + .setExtensions(Collections.singletonList(ExtensionBuilder.newBuilder() + .withGroupId("default-group") + .withArtifactId("artifactA") + .withVersion("1.0.0") + .build())); + mojo.execute(); + + assertThat(Files.readAllLines(tempPath), empty()); + } +} diff --git a/versions-test/src/main/java/org/codehaus/mojo/versions/utils/MockUtils.java b/versions-test/src/main/java/org/codehaus/mojo/versions/utils/MockUtils.java index 92a1d0cab7..e08b9322ab 100644 --- a/versions-test/src/main/java/org/codehaus/mojo/versions/utils/MockUtils.java +++ b/versions-test/src/main/java/org/codehaus/mojo/versions/utils/MockUtils.java @@ -1,24 +1,20 @@ +package org.codehaus.mojo.versions.utils; + /* - * 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 + * Copyright MojoHaus and Contributors + * Licensed 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 + * 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. + * 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.codehaus.mojo.versions.utils; - import java.io.File; import java.util.ArrayList; import java.util.Arrays; @@ -152,11 +148,22 @@ public static RepositorySystem mockRepositorySystem() { * @return mocked {@link MavenSession} */ public static MavenSession mockMavenSession() { + MavenProject project = mock(MavenProject.class); + when(project.getRemotePluginRepositories()).thenReturn(emptyList()); + when(project.getRemoteProjectRepositories()).thenReturn(emptyList()); + return mockMavenSession(project); + } + + /** + * Creates a very simple mock of {@link MavenSession} + * by providing only a non-{@code null} implementation of its {@link MavenSession#getRepositorySession()} method. + * @param project {@link MavenProject} to link to + * @return mocked {@link MavenSession} + */ + public static MavenSession mockMavenSession(MavenProject project) { MavenSession session = mock(MavenSession.class); when(session.getRepositorySession()).thenReturn(mock(RepositorySystemSession.class)); - when(session.getCurrentProject()).thenReturn(mock(MavenProject.class)); - when(session.getCurrentProject().getRemotePluginRepositories()).thenReturn(emptyList()); - when(session.getCurrentProject().getRemoteProjectRepositories()).thenReturn(emptyList()); + when(session.getCurrentProject()).thenReturn(project); return session; } }