| /* |
| * 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.shared.release.phase; |
| |
| import javax.inject.Inject; |
| import javax.inject.Named; |
| import javax.inject.Singleton; |
| |
| import java.util.ArrayList; |
| import java.util.Arrays; |
| import java.util.Collections; |
| import java.util.HashSet; |
| import java.util.Iterator; |
| import java.util.List; |
| import java.util.Locale; |
| import java.util.Map; |
| import java.util.Set; |
| import java.util.concurrent.atomic.AtomicReference; |
| |
| import org.apache.maven.artifact.Artifact; |
| import org.apache.maven.artifact.ArtifactUtils; |
| import org.apache.maven.project.MavenProject; |
| import org.apache.maven.shared.release.ReleaseExecutionException; |
| import org.apache.maven.shared.release.ReleaseFailureException; |
| import org.apache.maven.shared.release.ReleaseResult; |
| import org.apache.maven.shared.release.config.ReleaseDescriptor; |
| import org.apache.maven.shared.release.env.ReleaseEnvironment; |
| import org.apache.maven.shared.release.versions.DefaultVersionInfo; |
| import org.apache.maven.shared.release.versions.VersionInfo; |
| import org.apache.maven.shared.release.versions.VersionParseException; |
| import org.codehaus.plexus.components.interactivity.Prompter; |
| import org.codehaus.plexus.components.interactivity.PrompterException; |
| |
| import static java.util.Objects.requireNonNull; |
| |
| /** |
| * Check the dependencies of all projects being released to see if there are any unreleased snapshots. |
| * |
| * @author <a href="mailto:brett@apache.org">Brett Porter</a> |
| */ |
| // TODO plugins with no version will be resolved to RELEASE which is not a snapshot, but remains unresolved to this |
| // point. This is a potential hole in the check, and should be revisited after the release pom writing is done and |
| // resolving versions to verify whether it is. |
| // TODO plugins injected by the lifecycle are not tested here. They will be injected with a RELEASE version so are |
| // covered under the above point. |
| @Singleton |
| @Named("check-dependency-snapshots") |
| public class CheckDependencySnapshotsPhase extends AbstractReleasePhase { |
| public static final String RESOLVE_SNAPSHOT_MESSAGE = "There are still some remaining snapshot dependencies.\n"; |
| |
| public static final String RESOLVE_SNAPSHOT_PROMPT = "Do you want to resolve them now?"; |
| |
| public static final String RESOLVE_SNAPSHOT_TYPE_MESSAGE = "Dependency type to resolve,"; |
| |
| public static final String RESOLVE_SNAPSHOT_TYPE_PROMPT = |
| "specify the selection number ( 0:All 1:Project Dependencies 2:Plugins 3:Reports 4:Extensions ):"; |
| |
| /** |
| * Component used to prompt for input. |
| */ |
| private final AtomicReference<Prompter> prompter; |
| |
| // Be aware of the difference between usedSnapshots and specifiedSnapshots: |
| // UsedSnapshots end up on the classpath. |
| // SpecifiedSnapshots are defined anywhere in the pom. |
| // We'll probably need to introduce specifiedSnapshots as well. |
| // @TODO MRELEASE-378: verify custom dependencies in plugins. Be aware of deprecated/removed Components in M3, such |
| // as PluginCollector |
| // @TODO MRELEASE-763: verify all dependencies in inactive profiles |
| |
| // Don't prompt for every project in reactor, remember state of questions |
| private String resolveSnapshot; |
| |
| private String resolveSnapshotType; |
| |
| @Inject |
| public CheckDependencySnapshotsPhase(Prompter prompter) { |
| this.prompter = new AtomicReference<>(requireNonNull(prompter)); |
| } |
| |
| /** |
| * For easier testing only! |
| */ |
| public void setPrompter(Prompter prompter) { |
| this.prompter.set(prompter); |
| } |
| |
| @Override |
| public ReleaseResult execute( |
| ReleaseDescriptor releaseDescriptor, |
| ReleaseEnvironment releaseEnvironment, |
| List<MavenProject> reactorProjects) |
| throws ReleaseExecutionException, ReleaseFailureException { |
| ReleaseResult result = new ReleaseResult(); |
| |
| if (!releaseDescriptor.isAllowTimestampedSnapshots()) { |
| logInfo(result, "Checking dependencies and plugins for snapshots ..."); |
| |
| for (MavenProject project : reactorProjects) { |
| checkProject(project, releaseDescriptor); |
| } |
| } else { |
| logInfo(result, "Ignoring SNAPSHOT dependencies and plugins ..."); |
| } |
| result.setResultCode(ReleaseResult.SUCCESS); |
| |
| return result; |
| } |
| |
| private void checkProject(MavenProject project, ReleaseDescriptor releaseDescriptor) |
| throws ReleaseFailureException, ReleaseExecutionException { |
| Map<String, Artifact> artifactMap = ArtifactUtils.artifactMapByVersionlessId(project.getArtifacts()); |
| |
| Set<Artifact> usedSnapshotDependencies = new HashSet<>(); |
| |
| if (project.getParentArtifact() != null) { |
| if (checkArtifact(project.getParentArtifact(), artifactMap, releaseDescriptor)) { |
| usedSnapshotDependencies.add(project.getParentArtifact()); |
| } |
| } |
| |
| Set<Artifact> dependencyArtifacts = project.getDependencyArtifacts(); |
| usedSnapshotDependencies.addAll(checkDependencies(releaseDescriptor, artifactMap, dependencyArtifacts)); |
| |
| // @todo check dependencyManagement |
| |
| Set<Artifact> pluginArtifacts = project.getPluginArtifacts(); |
| Set<Artifact> usedSnapshotPlugins = checkPlugins(releaseDescriptor, artifactMap, pluginArtifacts); |
| |
| // @todo check pluginManagement |
| |
| Set<Artifact> reportArtifacts = project.getReportArtifacts(); |
| Set<Artifact> usedSnapshotReports = checkReports(releaseDescriptor, artifactMap, reportArtifacts); |
| |
| Set<Artifact> extensionArtifacts = project.getExtensionArtifacts(); |
| Set<Artifact> usedSnapshotExtensions = checkExtensions(releaseDescriptor, artifactMap, extensionArtifacts); |
| |
| // @todo check profiles |
| |
| if (!usedSnapshotDependencies.isEmpty() |
| || !usedSnapshotReports.isEmpty() |
| || !usedSnapshotExtensions.isEmpty() |
| || !usedSnapshotPlugins.isEmpty()) { |
| if (releaseDescriptor.isInteractive() || null != releaseDescriptor.getAutoResolveSnapshots()) { |
| resolveSnapshots( |
| usedSnapshotDependencies, |
| usedSnapshotReports, |
| usedSnapshotExtensions, |
| usedSnapshotPlugins, |
| releaseDescriptor); |
| } |
| |
| if (!usedSnapshotDependencies.isEmpty() |
| || !usedSnapshotReports.isEmpty() |
| || !usedSnapshotExtensions.isEmpty() |
| || !usedSnapshotPlugins.isEmpty()) { |
| StringBuilder message = new StringBuilder(); |
| |
| printSnapshotDependencies(usedSnapshotDependencies, message); |
| printSnapshotDependencies(usedSnapshotReports, message); |
| printSnapshotDependencies(usedSnapshotExtensions, message); |
| printSnapshotDependencies(usedSnapshotPlugins, message); |
| message.append("in project '" + project.getName() + "' (" + project.getId() + ")"); |
| |
| throw new ReleaseFailureException( |
| "Can't release project due to non released dependencies :\n" + message); |
| } |
| } |
| } |
| |
| private Set<Artifact> checkPlugins( |
| ReleaseDescriptor releaseDescriptor, Map<String, Artifact> artifactMap, Set<Artifact> pluginArtifacts) |
| throws ReleaseExecutionException { |
| Set<Artifact> usedSnapshotPlugins = new HashSet<>(); |
| for (Artifact artifact : pluginArtifacts) { |
| if (checkArtifact(artifact, artifactMap, releaseDescriptor)) { |
| boolean addToFailures; |
| |
| if ("org.apache.maven.plugins".equals(artifact.getGroupId()) |
| && "maven-release-plugin".equals(artifact.getArtifactId())) { |
| // It's a snapshot of the release plugin. Maybe just testing - ask |
| // By default, we fail as for any other plugin |
| if (releaseDescriptor.isSnapshotReleasePluginAllowed()) { |
| addToFailures = false; |
| } else if (releaseDescriptor.isInteractive()) { |
| try { |
| String result; |
| if (!releaseDescriptor.isSnapshotReleasePluginAllowed()) { |
| prompter.get() |
| .showMessage("This project relies on a SNAPSHOT of the release plugin. " |
| + "This may be necessary during testing.\n"); |
| result = prompter.get() |
| .prompt( |
| "Do you want to continue with the release?", |
| Arrays.asList("yes", "no"), |
| "no"); |
| } else { |
| result = "yes"; |
| } |
| |
| addToFailures = !result.toLowerCase(Locale.ENGLISH).startsWith("y"); |
| } catch (PrompterException e) { |
| throw new ReleaseExecutionException(e.getMessage(), e); |
| } |
| } else { |
| addToFailures = true; |
| } |
| } else { |
| addToFailures = true; |
| } |
| |
| if (addToFailures) { |
| usedSnapshotPlugins.add(artifact); |
| } |
| } |
| } |
| return usedSnapshotPlugins; |
| } |
| |
| private Set<Artifact> checkDependencies( |
| ReleaseDescriptor releaseDescriptor, Map<String, Artifact> artifactMap, Set<Artifact> dependencyArtifacts) { |
| Set<Artifact> usedSnapshotDependencies = new HashSet<>(); |
| for (Artifact artifact : dependencyArtifacts) { |
| if (checkArtifact(artifact, artifactMap, releaseDescriptor)) { |
| usedSnapshotDependencies.add(getArtifactFromMap(artifact, artifactMap)); |
| } |
| } |
| return usedSnapshotDependencies; |
| } |
| |
| private Set<Artifact> checkReports( |
| ReleaseDescriptor releaseDescriptor, Map<String, Artifact> artifactMap, Set<Artifact> reportArtifacts) { |
| Set<Artifact> usedSnapshotReports = new HashSet<>(); |
| for (Artifact artifact : reportArtifacts) { |
| if (checkArtifact(artifact, artifactMap, releaseDescriptor)) { |
| // snapshotDependencies.add( artifact ); |
| usedSnapshotReports.add(artifact); |
| } |
| } |
| return usedSnapshotReports; |
| } |
| |
| private Set<Artifact> checkExtensions( |
| ReleaseDescriptor releaseDescriptor, Map<String, Artifact> artifactMap, Set<Artifact> extensionArtifacts) { |
| Set<Artifact> usedSnapshotExtensions = new HashSet<>(); |
| for (Artifact artifact : extensionArtifacts) { |
| if (checkArtifact(artifact, artifactMap, releaseDescriptor)) { |
| usedSnapshotExtensions.add(artifact); |
| } |
| } |
| return usedSnapshotExtensions; |
| } |
| |
| private static boolean checkArtifact( |
| Artifact artifact, Map<String, Artifact> artifactMapByVersionlessId, ReleaseDescriptor releaseDescriptor) { |
| Artifact checkArtifact = getArtifactFromMap(artifact, artifactMapByVersionlessId); |
| |
| return checkArtifact(checkArtifact, releaseDescriptor); |
| } |
| |
| private static Artifact getArtifactFromMap(Artifact artifact, Map<String, Artifact> artifactMapByVersionlessId) { |
| String versionlessId = ArtifactUtils.versionlessKey(artifact); |
| Artifact checkArtifact = artifactMapByVersionlessId.get(versionlessId); |
| |
| if (checkArtifact == null) { |
| checkArtifact = artifact; |
| } |
| return checkArtifact; |
| } |
| |
| private static boolean checkArtifact(Artifact artifact, ReleaseDescriptor releaseDescriptor) { |
| String versionlessKey = ArtifactUtils.versionlessKey(artifact.getGroupId(), artifact.getArtifactId()); |
| String releaseDescriptorResolvedVersion = releaseDescriptor.getDependencyReleaseVersion(versionlessKey); |
| |
| boolean releaseDescriptorResolvedVersionIsSnapshot = releaseDescriptorResolvedVersion == null |
| || releaseDescriptorResolvedVersion.contains(Artifact.SNAPSHOT_VERSION); |
| |
| // We are only looking at dependencies external to the project - ignore anything found in the reactor as |
| // it's version will be updated |
| boolean bannedVersion = artifact.isSnapshot() |
| && !artifact.getBaseVersion().equals(releaseDescriptor.getProjectOriginalVersion(versionlessKey)) |
| && releaseDescriptorResolvedVersionIsSnapshot; |
| |
| // If we have a snapshot but allowTimestampedSnapshots is true, accept the artifact if the version |
| // indicates that it is a timestamped snapshot. |
| if (bannedVersion && releaseDescriptor.isAllowTimestampedSnapshots()) { |
| bannedVersion = artifact.getVersion().contains(Artifact.SNAPSHOT_VERSION); |
| } |
| |
| return bannedVersion; |
| } |
| |
| @Override |
| public ReleaseResult simulate( |
| ReleaseDescriptor releaseDescriptor, |
| ReleaseEnvironment releaseEnvironment, |
| List<MavenProject> reactorProjects) |
| throws ReleaseExecutionException, ReleaseFailureException { |
| // It makes no modifications, so simulate is the same as execute |
| return execute(releaseDescriptor, releaseEnvironment, reactorProjects); |
| } |
| |
| private void printSnapshotDependencies(Set<Artifact> snapshotsSet, StringBuilder message) { |
| List<Artifact> snapshotsList = new ArrayList<>(snapshotsSet); |
| |
| Collections.sort(snapshotsList); |
| |
| for (Artifact artifact : snapshotsList) { |
| message.append(" "); |
| |
| message.append(artifact); |
| |
| message.append("\n"); |
| } |
| } |
| |
| private void resolveSnapshots( |
| Set<Artifact> projectDependencies, |
| Set<Artifact> reportDependencies, |
| Set<Artifact> extensionDependencies, |
| Set<Artifact> pluginDependencies, |
| ReleaseDescriptor releaseDescriptor) |
| throws ReleaseExecutionException { |
| try { |
| String autoResolveSnapshots = releaseDescriptor.getAutoResolveSnapshots(); |
| if (resolveSnapshot == null) { |
| prompter.get().showMessage(RESOLVE_SNAPSHOT_MESSAGE); |
| if (autoResolveSnapshots != null) { |
| resolveSnapshot = "yes"; |
| prompter.get().showMessage(RESOLVE_SNAPSHOT_PROMPT + " " + resolveSnapshot); |
| } else { |
| resolveSnapshot = prompter.get().prompt(RESOLVE_SNAPSHOT_PROMPT, Arrays.asList("yes", "no"), "no"); |
| } |
| } |
| |
| if (resolveSnapshot.toLowerCase(Locale.ENGLISH).startsWith("y")) { |
| if (resolveSnapshotType == null) { |
| prompter.get().showMessage(RESOLVE_SNAPSHOT_TYPE_MESSAGE); |
| int defaultAnswer = -1; |
| if (autoResolveSnapshots != null) { |
| if ("all".equalsIgnoreCase(autoResolveSnapshots)) { |
| defaultAnswer = 0; |
| } else if ("dependencies".equalsIgnoreCase(autoResolveSnapshots)) { |
| defaultAnswer = 1; |
| } else if ("plugins".equalsIgnoreCase(autoResolveSnapshots)) { |
| defaultAnswer = 2; |
| } else if ("reports".equalsIgnoreCase(autoResolveSnapshots)) { |
| defaultAnswer = 3; |
| } else if ("extensions".equalsIgnoreCase(autoResolveSnapshots)) { |
| defaultAnswer = 4; |
| } else { |
| try { |
| defaultAnswer = Integer.parseInt(autoResolveSnapshots); |
| } catch (NumberFormatException e) { |
| throw new ReleaseExecutionException(e.getMessage(), e); |
| } |
| } |
| } |
| if (defaultAnswer >= 0 && defaultAnswer <= 4) { |
| prompter.get().showMessage(RESOLVE_SNAPSHOT_TYPE_PROMPT + " " + autoResolveSnapshots); |
| resolveSnapshotType = Integer.toString(defaultAnswer); |
| } else { |
| resolveSnapshotType = prompter.get() |
| .prompt(RESOLVE_SNAPSHOT_TYPE_PROMPT, Arrays.asList("0", "1", "2", "3"), "1"); |
| } |
| } |
| |
| switch (Integer.parseInt(resolveSnapshotType.toLowerCase(Locale.ENGLISH))) { |
| // all |
| case 0: |
| processSnapshot(projectDependencies, releaseDescriptor, autoResolveSnapshots); |
| processSnapshot(pluginDependencies, releaseDescriptor, autoResolveSnapshots); |
| processSnapshot(reportDependencies, releaseDescriptor, autoResolveSnapshots); |
| processSnapshot(extensionDependencies, releaseDescriptor, autoResolveSnapshots); |
| break; |
| |
| // project dependencies |
| case 1: |
| processSnapshot(projectDependencies, releaseDescriptor, autoResolveSnapshots); |
| break; |
| |
| // plugins |
| case 2: |
| processSnapshot(pluginDependencies, releaseDescriptor, autoResolveSnapshots); |
| break; |
| |
| // reports |
| case 3: |
| processSnapshot(reportDependencies, releaseDescriptor, autoResolveSnapshots); |
| break; |
| |
| // extensions |
| case 4: |
| processSnapshot(extensionDependencies, releaseDescriptor, autoResolveSnapshots); |
| break; |
| |
| default: |
| } |
| } |
| } catch (PrompterException | VersionParseException e) { |
| throw new ReleaseExecutionException(e.getMessage(), e); |
| } |
| } |
| |
| private void processSnapshot( |
| Set<Artifact> snapshotSet, ReleaseDescriptor releaseDescriptor, String autoResolveSnapshots) |
| throws PrompterException, VersionParseException { |
| Iterator<Artifact> iterator = snapshotSet.iterator(); |
| |
| while (iterator.hasNext()) { |
| Artifact currentArtifact = iterator.next(); |
| String versionlessKey = ArtifactUtils.versionlessKey(currentArtifact); |
| |
| VersionInfo versionInfo = new DefaultVersionInfo(currentArtifact.getBaseVersion()); |
| releaseDescriptor.addDependencyOriginalVersion(versionlessKey, versionInfo.toString()); |
| |
| prompter.get() |
| .showMessage("Dependency '" + versionlessKey + "' is a snapshot (" + currentArtifact.getVersion() |
| + ")\n"); |
| String message = "Which release version should it be set to?"; |
| String result; |
| if (null != autoResolveSnapshots) { |
| result = versionInfo.getReleaseVersionString(); |
| prompter.get().showMessage(message + " " + result); |
| } else { |
| result = prompter.get().prompt(message, versionInfo.getReleaseVersionString()); |
| } |
| |
| releaseDescriptor.addDependencyReleaseVersion(versionlessKey, result); |
| |
| iterator.remove(); |
| |
| // by default, keep the same version for the dependency after release, unless it was previously newer |
| // the user may opt to type in something different |
| VersionInfo nextVersionInfo = new DefaultVersionInfo(result); |
| |
| String nextVersion; |
| if (nextVersionInfo.compareTo(versionInfo) > 0) { |
| nextVersion = nextVersionInfo.toString(); |
| } else { |
| nextVersion = versionInfo.toString(); |
| } |
| |
| message = "What version should the dependency be reset to for development?"; |
| if (null != autoResolveSnapshots) { |
| result = nextVersion; |
| prompter.get().showMessage(message + " " + result); |
| } else { |
| result = prompter.get().prompt(message, nextVersion); |
| } |
| |
| releaseDescriptor.addDependencyDevelopmentVersion(versionlessKey, result); |
| } |
| } |
| } |