All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.codehaus.mojo.versions.UseDepVersionMojo Maven / Gradle / Ivy

Go to download

Versions Plugin for Maven. The Versions Plugin updates the versions of components in the POM.

The newest version!
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 javax.xml.stream.XMLStreamException;
import javax.xml.transform.TransformerException;

import java.io.IOException;
import java.io.Writer;
import java.nio.charset.Charset;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Optional;
import java.util.Set;
import java.util.TreeSet;
import java.util.stream.Collectors;
import java.util.stream.Stream;

import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.tuple.ImmutablePair;
import org.apache.maven.artifact.Artifact;
import org.apache.maven.artifact.handler.manager.ArtifactHandlerManager;
import org.apache.maven.model.Dependency;
import org.apache.maven.model.Model;
import org.apache.maven.model.ModelBase;
import org.apache.maven.model.Profile;
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.wagon.Wagon;
import org.codehaus.mojo.versions.api.PomHelper;
import org.codehaus.mojo.versions.api.VersionRetrievalException;
import org.codehaus.mojo.versions.api.recording.ChangeRecorder;
import org.codehaus.mojo.versions.api.recording.DependencyChangeRecord.ChangeKind;
import org.codehaus.mojo.versions.recording.DefaultPropertyChangeRecord;
import org.codehaus.mojo.versions.rewriting.MutableXMLStreamReader;
import org.codehaus.mojo.versions.utils.DependencyComparator;
import org.codehaus.mojo.versions.utils.ModelNode;
import org.eclipse.aether.RepositorySystem;

import static java.nio.file.StandardCopyOption.REPLACE_EXISTING;
import static java.util.Collections.emptyList;
import static java.util.Collections.singletonList;
import static java.util.Optional.ofNullable;

/**
 * Updates a dependency to a specific version.
 * This can be useful if you have to manage versions for a very large (100+ module) projects where you can’t always use
 * the most up-to-date version of a particular third party component.
 *
 * @author Dan Arcari
 * @since 2.3
 */
@Mojo(name = "use-dep-version", aggregator = true, threadSafe = true)
public class UseDepVersionMojo extends AbstractVersionsDependencyUpdaterMojo {

    /**
     * The exact version to be applied for the included dependencies
     */
    @Parameter(property = "depVersion", required = true)
    protected String depVersion;

    /**
     * If set to {@code true}, will use whatever version is supplied without attempting to validate that such
     * a version is obtainable from the repository chain.
     */
    @Parameter(property = "forceVersion", defaultValue = "false")
    protected boolean forceVersion;

    /**
     * 

Will augment normal processing by, if a dependency value is set using a property, trying to update * the value of the property.

*

If the property value is specified directly, will process it normally (as with {@code processProperties} being * {@code false}. If the property being updated is redefined in the reactor tree, will only change the property * value which lies closest to the dependency being updated. If the same property is also used to set * the value of another dependency, will not update that property value, and log a warning instead. * Finally, if the property value is specified in a parent file which is outside of the project, will log * a message.

*

Default is {@code false}.

* * @since 2.15.0 */ @Parameter(property = "processProperties", defaultValue = "false") protected boolean processProperties; @Inject public UseDepVersionMojo( ArtifactHandlerManager artifactHandlerManager, RepositorySystem repositorySystem, Map wagonMap, Map changeRecorders) { super(artifactHandlerManager, repositorySystem, wagonMap, changeRecorders); } @Override protected void validateInput() throws MojoExecutionException { super.validateInput(); if (StringUtils.isBlank(depVersion)) { throw new IllegalArgumentException( "depVersion must be supplied with use-specific-version, and cannot be blank."); } if (!forceVersion && !hasIncludes()) { throw new IllegalArgumentException( "The use-specific-version goal is intended to be used with a single artifact. " + "Please specify a value for the 'includes' parameter, " + "or use -DforceVersion=true to override this check."); } } @Override protected void update(MutableXMLStreamReader pom) throws MojoExecutionException, MojoFailureException, XMLStreamException, VersionRetrievalException { // not used } @Override public void execute() throws MojoExecutionException, MojoFailureException { validateInput(); List rawModels; try { MutableXMLStreamReader pomReader = new MutableXMLStreamReader(getProject().getFile().toPath()); ModelNode rootNode = new ModelNode( PomHelper.getRawModel(pomReader.getSource(), getProject().getFile()), pomReader); rawModels = PomHelper.getRawModelTree(rootNode, getLog()); // reversing to process depth-first Collections.reverse(rawModels); Set propertyBacklog = new HashSet<>(); Map> propertyConflicts = new HashMap<>(); for (ModelNode node : rawModels) { processModel( node, propertyBacklog, propertyConflicts, ofNullable(pomReader.getEncoding()) .map(Charset::forName) .orElse(Charset.defaultCharset())); } propertyBacklog.forEach(p -> getLog().warn("Not updating property ${" + p + "}: defined in parent")); } catch (IOException | XMLStreamException | TransformerException e) { throw new MojoFailureException(e.getMessage(), e); } catch (RuntimeException e) { if (e.getCause() instanceof MojoFailureException) { throw (MojoFailureException) e.getCause(); } else if (e.getCause() instanceof MojoExecutionException) { throw (MojoExecutionException) e.getCause(); } throw e; } } /** * Processes a single model and POM file associated with it * @param node tree node to process * @param propertyBacklog a {@link Set} instance used to store dependencies to be updated, but which were not found * in the current subtree. These properties need to be carried over to the parent node for * processing. * @param propertyConflicts an {@link Map} instance to store properties * which are associated with dependencies which do not fit the filter and thus may not * be changed. This is then used for conflict detection if a dependency to be changed * used one of these properties. Such a change is not allowed and must be reported instead. * @param charset charset for file writing * @return {@code true} if the file has been changed */ protected boolean processModel( ModelNode node, Set propertyBacklog, Map> propertyConflicts, Charset charset) throws MojoFailureException, MojoExecutionException { // 1) process the properties carried over from children propertyBacklog.removeIf(p -> updatePropertyValue(node, p)); // 2) process dependencies and properties from this node try { if (isProcessingDependencyManagement() && node.getModel().getDependencyManagement() != null) { useDepVersion( node, node.getModel().getDependencyManagement().getDependencies(), ChangeKind.DEPENDENCY_MANAGEMENT, propertyBacklog, propertyConflicts); } if (isProcessingDependencies()) { useDepVersion( node, getDependencies(node.getModel()), ChangeKind.DEPENDENCY, propertyBacklog, propertyConflicts); } if (getProject().getParent() != null && isProcessingParent()) { useDepVersion( node, singletonList(getParentDependency()), ChangeKind.PARENT, propertyBacklog, propertyConflicts); } } catch (XMLStreamException e) { throw new MojoFailureException( "Unable to parse the pom " + node.getModel().getPomFile(), e); } catch (VersionRetrievalException e) { throw new MojoFailureException( "Unable to retrieve a dependency version while processing " + node.getModel().getPomFile(), e); } if (node.getMutableXMLStreamReader().isModified()) { if (generateBackupPoms) { Objects.requireNonNull(node.getModel().getPomFile()); Objects.requireNonNull(node.getModel().getPomFile().toPath().getParent()); Path backupFile = node.getModel() .getPomFile() .toPath() .getParent() .resolve(node.getModel().getPomFile().toPath().getFileName() + ".versionsBackup"); if (!Files.exists(backupFile)) { if (getLog().isDebugEnabled()) { getLog().debug("Backing up " + node.getModel().getPomFile() + " to " + backupFile); } try { Files.copy(node.getModel().getPomFile().toPath(), backupFile, REPLACE_EXISTING); } catch (IOException e) { throw new MojoFailureException( "Error backing up the " + node.getModel().getPomFile(), e); } } else { if (getLog().isDebugEnabled()) { getLog().debug("Leaving existing backup " + backupFile + " unmodified"); } } } else { getLog().debug("Skipping the generation of a backup file"); } try (Writer writer = Files.newBufferedWriter(node.getModel().getPomFile().toPath(), charset)) { writer.write(node.getMutableXMLStreamReader().getSource()); } catch (IOException e) { throw new MojoFailureException( "Unable to write the changed file " + node.getModel().getPomFile(), e); } } try { saveChangeRecorderResults(); } catch (IOException e) { getLog().warn( "Cannot save the change recorder result for file " + node.getModel().getPomFile(), e); } return node.getMutableXMLStreamReader().isModified(); } private static List getDependencies(Model model) { List dependencies = ofNullable(model.getDependencies()).orElse(new ArrayList<>()); dependencies.addAll(ofNullable(model.getProfiles()) .flatMap(profiles -> profiles.stream() .map(ModelBase::getDependencies) .reduce((l1, l2) -> { l1.addAll(l2); return l1; })) .orElse(emptyList())); return dependencies; } /** *

Will process the given module tree node, updating the {@link MutableXMLStreamReader} associated with the * node if it finds a dependency matching the filter that needs to be changed or, if {@link #processProperties} * is {@code true}, a property value that can be updated.

*

The method will use the set passed as the {@code backlog} argument to store the properties which it needs * to update, but which were not found in the current tree. These properties need to be carried over to the parent * node for processing.

*

Similarly, method will use the map passed as the {@code propertyConflicts} argument to store properties * which are associated with dependencies which do not fit the filter and thus may not be changed. This is then * used for conflict detection if a dependency to be changed used one of these properties. Such a change * is not allowed and must be reported instead.

* * @param node model tree node to process * @param dependencies collection of dependencies to process (can be taken from dependency management, * parent, or dependencies) * @param changeKind {@link ChangeKind} instance for the change recorder * @param propertyBacklog a {@link Set} instance used to store dependencies to be updated, but which were not found * in the current subtree. These properties need to be carried over to the parent node for * processing. * @param propertyConflicts an {@link Map} instance to store properties * which are associated with dependencies which do not fit the filter and thus may not * be changed. This is then used for conflict detection if a dependency to be changed * used one of these properties. Such a change is not allowed and must be reported instead. * @throws MojoExecutionException thrown if a version may not be changed * @throws XMLStreamException thrown if a {@link MutableXMLStreamReader} can't be updated * @throws VersionRetrievalException thrown if dependency versions cannot be retrieved */ private void useDepVersion( ModelNode node, Collection dependencies, ChangeKind changeKind, Set propertyBacklog, Map> propertyConflicts) throws MojoExecutionException, XMLStreamException, VersionRetrievalException { // an additional pass is necessary to collect conflicts if processProperties is enabled if (processProperties) { dependencies.stream() .filter(dep -> { try { return !isIncluded(toArtifact(dep)); } catch (MojoExecutionException e) { throw new RuntimeException(e); } }) .forEach(dep -> // if a dependency that is _not_ to be changed is set using a property, register that // property // in propertyConflicts; these are the properties that must not be changed // the list in the value is the list of dependencies that use the said property PomHelper.extractExpression(dep.getVersion()) .ifPresent(p -> propertyConflicts.compute(p, (k, v) -> ofNullable(v) .map(set -> { set.add(dep); return set; }) .orElseGet(() -> { Set set = new TreeSet<>(DependencyComparator.INSTANCE); set.add(dep); return set; })))); } // 2nd pass: check dependencies for (Dependency dep : dependencies) { if (isExcludeReactor() && isProducedByReactor(dep)) { getLog().info("Ignoring a reactor dependency: " + toString(dep)); continue; } Optional propertyName = PomHelper.extractExpression(dep.getVersion()); if (propertyName.isPresent() && !processProperties) { getLog().info("Ignoring a dependency with the version set using a property: " + toString(dep)); continue; } Artifact artifact = toArtifact(dep); if (isIncluded(artifact)) { if (dep.getVersion() == null) { getLog().warn(String.format( "Not updating %s:%s in dependencies: version defined " + "in dependencyManagement", dep.getGroupId(), dep.getArtifactId())); } else { if (!forceVersion) { if (!getHelper().lookupArtifactVersions(artifact, false).containsVersion(depVersion)) { throw new MojoExecutionException(String.format( "Version %s is not available for artifact %s:%s", depVersion, artifact.getGroupId(), artifact.getArtifactId())); } } if (!propertyName.isPresent()) { updateDependencyVersion(node.getMutableXMLStreamReader(), dep, depVersion, changeKind); } else { // propertyName is present ofNullable(propertyConflicts.get(propertyName.get())) .map(conflict -> { getLog().warn("Cannot update property ${" + propertyName.get() + "}: " + "controls more than one dependency: " + conflict.stream() .map(Dependency::getArtifactId) .collect(Collectors.joining(", "))); return false; }) .orElseGet(() -> { if (!updatePropertyValue(node, propertyName.get())) { propertyBacklog.add(propertyName.get()); } else { if (getLog().isDebugEnabled()) { getLog().debug(String.format( "Updated the %s property value to %s.", propertyName.get(), depVersion)); } } return true; }); } } } } // third pass: if a property is defined at this node, it is not going to conflict with anything from parent propertyConflicts.keySet().removeIf(key -> ofNullable(node.getModel().getProperties()) .filter(p -> p.containsKey(key)) .isPresent()); propertyConflicts.keySet().removeIf(key -> ofNullable(node.getModel().getProfiles()) .map(list -> list.stream().anyMatch(p -> ofNullable(p.getProperties()) .filter(prop -> prop.containsKey(key)) .isPresent())) .orElse(false)); } private boolean updatePropertyValue(ModelNode node, String property) { // concatenating properties from the main build section // with properties from profiles return Stream.concat( Stream.of(node.getModel().getProperties().getProperty(property)) .filter(Objects::nonNull) .map(value -> new ImmutablePair(null, value)), node.getModel().getProfiles().stream() .map(profile -> new ImmutablePair<>(profile, profile.getProperties())) .map(pair -> ofNullable(pair.getRight().getProperty(property)) .map(value -> new ImmutablePair<>(pair.getLeft(), value)) .orElse(null))) // and processing them .filter(Objects::nonNull) .map(pair -> { try { boolean result = PomHelper.setPropertyVersion( node.getMutableXMLStreamReader(), ofNullable(pair.getLeft()).map(Profile::getId).orElse(null), property, depVersion); if (result) { try { getChangeRecorder() .recordChange(DefaultPropertyChangeRecord.builder() .withProperty(property) .withOldValue(pair.getRight()) .withNewValue(depVersion) .build()); } catch (MojoExecutionException e) { throw new RuntimeException(e); } } return result; } catch (XMLStreamException e) { throw new RuntimeException(e); } }) .reduce(Boolean::logicalOr) .orElse(false); } }




© 2015 - 2024 Weber Informatics LLC | Privacy Policy