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

org.codehaus.mojo.patch.ApplyPatchDirectoryMojo Maven / Gradle / Ivy

The newest version!
package org.codehaus.mojo.patch;

/*
 * 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.
 */

import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
import java.io.StringWriter;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;

import org.apache.maven.plugin.MojoExecutionException;
import org.apache.maven.plugin.MojoFailureException;
import org.codehaus.mojo.tools.cli.CommandLineManager;
import org.codehaus.plexus.util.IOUtil;
import org.codehaus.plexus.util.cli.CommandLineException;
import org.codehaus.plexus.util.cli.Commandline;
import org.codehaus.plexus.util.cli.StreamConsumer;
import org.codehaus.plexus.util.cli.shell.BourneShell;

/**
 * Apply a set of patches to project sources.
 * 
 * @goal apply-directory
 * @phase generate-sources
 */
public class ApplyPatchDirectoryMojo
    extends AbstractPatchMojo
{

    /**
     * phrases which indicate a failure returned by the patch utility
     */
    public static final List < String > PATCH_FAILURE_WATCH_PHRASES;

    static
    {
        List < String > watches = new ArrayList < String > ();

        watches.add( "fail" );
        watches.add( "skip" );
        watches.add( "reject" );

        PATCH_FAILURE_WATCH_PHRASES = watches;
    }

    /**
     * Whether to skip this mojo's execution.
     * 
     * @parameter default-value="false" alias="patch.apply.skip"
     * @since 1.0-alpha-2
     */
    private boolean skipApplication;

    /**
     * Flag to enable/disable optimization file from being written. This file tracks the patches that were applied the
     * last time this mojo actually executed. It is required for cases where project-sources optimizations are enabled,
     * since project-sources will not be re-unpacked if they are at least as fresh as the source archive. If we avoid
     * re-unpacking project sources, we need to make sure we don't reapply patches. This flag is true by default. 
* NOTE: If the list of patches changes and this flag is enabled, a `mvn clean` must be executed before the * next build, to remove the tracking file. * * Functionality deprecated. CBUILDS is favoring standard artifact handlers like wagon-maven-plugin and * maven-dependency-plugin and deleted project-sources-maven-plugin. This feature was error prone anyway. * If you are working with remote tarballs, run mvn clean install and redo your build. If you * are running from a source code system, its best not to use patches, just edit code in place. * * @parameter default-value="false" * @since 1.0-alpha-2 * @deprecated */ private boolean optimizations; /** * This is the tracking file used to maintain a list of the patches applied to the unpacked project sources which * are currently in the target directory. If this file is present, and project-source unpacking is optimized * (meaning it won't re-unpack unless the project-sources archive is newer), this mojo will not execute and no * patches will be applied in the current build. * * @parameter default-value="${project.build.directory}/optimization-files/patches-applied.txt" * @since 1.0-alpha-2 */ private File patchTrackingFile; /** * The target directory for applying patches. Files in this directory will be modified. * * @parameter alias="patchTargetDir" default-value="${project.build.sourceDirectory}" * @since 1.0-alpha-2 * @required */ private File targetDirectory; /** * When the strictPatching flag is set, this parameter is useful to mark certain contents of the patch-source * directory that should be ignored without causing the build to fail. * * @parameter * @since 1.0-alpha-2 */ private List < String > ignoredPatches; /** * Flag that, when set to true, will make sure that all patches included in the 'patches' list must be present and * describe the full contents of the patch directory. If strictPatching is set to true, and the patches list has a * value that does not correspond to a file in the patch directory, the build will fail. If strictPatching is set to * true, and the patch directory contains files not listed in the patches parameter, the build will fail. If set to * false, only the patches listed in the patches parameter that have corresponding files will be applied; the rest * will be ignored. Default value for this parameter is false. * * @parameter default-value="false" * @since 1.0-alpha-2 * @required */ private boolean strictPatching; /** * The number of directories to be stripped from patch file paths, before applying, starting from the leftmost, or * root-est. * * @parameter * @since 1.0-alpha-5 */ private int strip = 0; /** * Whether to ignore whitespaces when applying the patches. * * @parameter * @since 1.0-alpha-2 */ private boolean ignoreWhitespace = true; /** * Whether to treat these patches as having reversed source and dest in the patch syntax. * * @parameter * @since 1.0-alpha-2 */ private boolean reverse = false; /** * Whether to make backups of the original files before modding them. * * @parameter * @since 1.0-alpha-2 */ private boolean backups = false; /** * List of phrases to watch for in patch-command output. If one is found, it will cause * the build to fail. All phrases should be lower-case ONLY. * * @parameter * @since 1.0-alpha-2 */ private List < String > patchFailureWatchPhrases = PATCH_FAILURE_WATCH_PHRASES; /** * If your project has never been deployed to a remote repo, your patches will need to be found * locally, and patchDirectory will be the location where patch-maven-plugin will * find any patches you want to apply. When downloading patches from a remote repo, this * parameter and local patches are ignored. When patch-maven-plugin uses local patches, it * will also generate a zip or tarball bundle of those patches and attach the artifact to the * project for later release and deployment to the maven repos. * * @parameter expression="${patchDirectory}" default-value="src/patches" * @since 1.0-beta-1 * @required */ private File patchDirectory; /** * If your project's patches are retrieved from a remote repository (as an attached project * artifact), the patches will be downloaded and installed into the directory specified by * patchArtifactUnpackDirectory. * * @parameter default-value="${project.build.directory}/unpacked-patches" * @since 1.0-beta-1 * @required */ private File patchArtifactUnpackDirectory; /** * @component role-hint="default" */ private CommandLineManager cliManager; /** * Apply the patches. Give preference to patchFile over patchSourceDir/patches, and preference to * originalFile over workDir. * * @throws MojoFailureException thrown if strictPatching set and no patches to apply * @throws MojoExecutionException thown if error encountered during patch process */ public void doExecute() throws MojoExecutionException, MojoFailureException { if ( skipApplication ) { getLog().info( "Skipping patchfile application (per configuration)." ); return; } patchTrackingFile.getParentFile().mkdirs(); PatchContext ctx = new PatchContext( ); File patchSourceDir = ctx.getPatchDirectory( patchArtifactUnpackDirectory, patchDirectory ); if ( patchSourceDir == null ) { throw new MojoExecutionException( "Patch directory was not set. Please ensure that you " + "have the resolve-patches mojo bound earlier in the lifecycle.\n\n" + "(Tip: use 'mvn help:effective-pom' for more information.)" ); } List < String > foundPatchFiles = new ArrayList < String > ( Arrays.asList( patchSourceDir.list() ) ); Map < String, Commandline > patchesApplied = findPatchesToApply( foundPatchFiles, patchSourceDir ); checkStrictPatchCompliance( foundPatchFiles ); String output = applyPatches( patchesApplied ); checkForWatchPhrases( output ); writeTrackingFile( patchesApplied ); } /** * Find and return a list of patches to apply * * @param foundPatchFiles list of String objects of patch files found to apply to the project * @param patchSourceDir String object of the directory of patches * @return Returns a map of patches to apply * @throws MojoFailureException thrown if strictPatching set and no patches to be applied */ private Map < String, Commandline > findPatchesToApply( List < String > foundPatchFiles, File patchSourceDir ) throws MojoFailureException { List < String > patches = getPatches(); Map < String, Commandline > patchesApplied = new LinkedHashMap < String, Commandline > ( patches.size() ); for ( Iterator < String > it = patches.iterator(); it.hasNext(); ) { String patch = it.next(); File patchFile = new File( patchSourceDir, patch ); getLog().debug( "Looking for patch: " + patch + " in: " + patchFile ); if ( !patchFile.exists() ) { if ( strictPatching ) { throw new MojoFailureException( this, "Patch operation cannot proceed.", "Cannot find specified patch: \'" + patch + "\' in patch-source directory: \'" + patchSourceDir + "\'.\n\nEither fix this error, or relax strictPatching." ); } else { getLog().info( "Skipping patch: " + patch + " listed in the patches parameter; it is missing." ); } } else { foundPatchFiles.remove( patch ); patchesApplied.put( patch, createPatchCommand( patchFile ) ); } } return patchesApplied; } /** * Extra checks done if using strictPatching, will filter patch files from an ignored list * depending on settings of ignoredPatches and useDefaultIgnores * * @param foundPatchFiles List of patches to apply * @throws MojoExecutionException thrown if all the patch files passed are to be ignored */ private void checkStrictPatchCompliance( List < String > foundPatchFiles ) throws MojoExecutionException { if ( strictPatching ) { List < String > ignored = new ArrayList < String > (); if ( ignoredPatches != null ) { ignored.addAll( ignoredPatches ); } if ( useDefaultIgnores() ) { ignored.addAll( DEFAULT_IGNORED_PATCHES ); } List < String > limbo = new ArrayList < String > ( foundPatchFiles ); for ( Iterator < String > it = ignored.iterator(); it.hasNext(); ) { String ignoredFile = it.next(); limbo.remove( ignoredFile ); } if ( !limbo.isEmpty() ) { StringBuffer extraFileBuffer = new StringBuffer(); extraFileBuffer.append( "Found " + limbo.size() + " unlisted patch files:" ); for ( Iterator < String > it = foundPatchFiles.iterator(); it.hasNext(); ) { String patch = it.next(); extraFileBuffer.append( "\n \'" ).append( patch ).append( '\'' ); } extraFileBuffer.append( "\n\nEither remove these files, add them to the " + "patches configuration list, or relax strictPatching." ); throw new MojoExecutionException( extraFileBuffer.toString() ); } } } /** * * @param patchesApplied Map of String which is the patch and Commandline which is the shell command for the patch * @return the output of the patch command is consumed and returned * @throws MojoExecutionException thrown when a CommadLineException is caught running the patch command */ private String applyPatches( Map < String, Commandline > patchesApplied ) throws MojoExecutionException { final StringWriter outputWriter = new StringWriter(); StreamConsumer consumer = new StreamConsumer() { public void consumeLine( String line ) { if ( getLog().isDebugEnabled() ) { getLog().debug( line ); } outputWriter.write( line + "\n" ); } }; for ( Iterator < Entry < String, Commandline > > it = patchesApplied.entrySet().iterator(); it.hasNext(); ) { Map.Entry < String, Commandline > entry = it.next(); String patchName = ( String ) entry.getKey(); Commandline cli = ( Commandline ) entry.getValue(); try { int result = cliManager.execute( cli, consumer, consumer ); if ( result != 0 ) { throw new MojoExecutionException( "Patch command failed (exit value != 0). " + "Please see debug output for more information." ); } } catch ( CommandLineException e ) { throw new MojoExecutionException( "Failed to apply patch: " + patchName + ". See debug output for more information.", e ); } } return outputWriter.toString(); } /** * Method writes a tracking file which lists the patches applied for the project * * @param patchesApplied Map of String which is the patch and Commandline which is the shell command for the patch * @throws MojoExecutionException thrown when an IOException is caught */ private void writeTrackingFile( Map < String, Commandline > patchesApplied ) throws MojoExecutionException { FileWriter writer = null; try { writer = new FileWriter( patchTrackingFile ); for ( Iterator < String > it = patchesApplied.keySet().iterator(); it.hasNext(); ) { String patch = it.next(); writer.write( patch ); if ( it.hasNext() ) { writer.write( System.getProperty( "line.separator" ) ); } } writer.flush(); } catch ( IOException e ) { throw new MojoExecutionException( "Failed to write patch-tracking file: " + patchTrackingFile, e ); } finally { IOUtil.close( writer ); } } /** * Method scans for error text sent by the patch command * * @param output Standard output collected from running a patch command * @throws MojoExecutionException throws this exception when a patch error is detected */ private void checkForWatchPhrases( String output ) throws MojoExecutionException { for ( Iterator < String > it = patchFailureWatchPhrases.iterator(); it.hasNext(); ) { String phrase = it.next(); if ( output.indexOf( phrase ) > -1 ) { throw new MojoExecutionException( "Failed to apply patches (detected watch-phrase: \'" + phrase + "\' in output). " + "If this is in error, configure the patchFailureWatchPhrases parameter." ); } } } /** * Add a new Patch task to the Ant calling mechanism. Give preference to originalFile/destFile, * then workDir, and finally ${basedir}. * * @param patchFile Java File object of patch file to create * @return a constructed command line which can be executed to create a patch file */ private Commandline createPatchCommand( File patchFile ) { Commandline cli = new Commandline( new BourneShell() ); cli.setExecutable( "patch" ); cli.setWorkingDirectory( targetDirectory.getAbsolutePath() ); cli.createArg().setLine( "-p" + strip ); if ( ignoreWhitespace ) { cli.createArg().setValue( "-l" ); } if ( reverse ) { cli.createArg().setValue( "-R" ); } if ( backups ) { cli.createArg().setValue( "-b" ); } cli.createArg().setLine( "--input=" + patchFile.getAbsolutePath() ); return cli; } }




© 2015 - 2024 Weber Informatics LLC | Privacy Policy