Coverage Report - org.apache.maven.plugin.patch.ApplyMojo
 
Classes in this File Line Coverage Branch Coverage Complexity
ApplyMojo
0%
0/151
0%
0/78
6,556
ApplyMojo$1
0%
0/5
0%
0/2
6,556
 
 1  
 package org.apache.maven.plugin.patch;
 2  
 
 3  
 /*
 4  
  * Licensed to the Apache Software Foundation (ASF) under one
 5  
  * or more contributor license agreements.  See the NOTICE file
 6  
  * distributed with this work for additional information
 7  
  * regarding copyright ownership.  The ASF licenses this file
 8  
  * to you under the Apache License, Version 2.0 (the
 9  
  * "License"); you may not use this file except in compliance
 10  
  * with the License.  You may obtain a copy of the License at
 11  
  *
 12  
  *   http://www.apache.org/licenses/LICENSE-2.0
 13  
  *
 14  
  * Unless required by applicable law or agreed to in writing,
 15  
  * software distributed under the License is distributed on an
 16  
  * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
 17  
  * KIND, either express or implied.  See the License for the
 18  
  * specific language governing permissions and limitations
 19  
  * under the License.
 20  
  */
 21  
 
 22  
 import org.apache.maven.plugin.AbstractMojo;
 23  
 import org.apache.maven.plugin.MojoExecutionException;
 24  
 import org.apache.maven.plugin.MojoFailureException;
 25  
 import org.codehaus.plexus.util.FileUtils;
 26  
 import org.codehaus.plexus.util.IOUtil;
 27  
 import org.codehaus.plexus.util.cli.CommandLineException;
 28  
 import org.codehaus.plexus.util.cli.CommandLineUtils;
 29  
 import org.codehaus.plexus.util.cli.Commandline;
 30  
 import org.codehaus.plexus.util.cli.StreamConsumer;
 31  
 
 32  
 import java.io.File;
 33  
 import java.io.FileNotFoundException;
 34  
 import java.io.FileWriter;
 35  
 import java.io.IOException;
 36  
 import java.io.StringWriter;
 37  
 import java.util.ArrayList;
 38  
 import java.util.Collections;
 39  
 import java.util.Iterator;
 40  
 import java.util.LinkedHashMap;
 41  
 import java.util.List;
 42  
 import java.util.Map;
 43  
 import java.util.Map.Entry;
 44  
 
 45  
 /**
 46  
  * Apply one or more patches to project sources.
 47  
  * 
 48  
  * @goal apply
 49  
  * @phase process-sources
 50  
  */
 51  0
 public class ApplyMojo
 52  
     extends AbstractMojo
 53  
 {
 54  
 
 55  
     public static final List PATCH_FAILURE_WATCH_PHRASES;
 56  
 
 57  
     public static final List DEFAULT_IGNORED_PATCHES;
 58  
 
 59  
     public static final List DEFAULT_IGNORED_PATCH_PATTERNS;
 60  
 
 61  
     static
 62  
     {
 63  0
         List watches = new ArrayList();
 64  
 
 65  0
         watches.add( "fail" );
 66  0
         watches.add( "skip" );
 67  0
         watches.add( "reject" );
 68  
 
 69  0
         PATCH_FAILURE_WATCH_PHRASES = watches;
 70  
 
 71  0
         List ignored = new ArrayList();
 72  
 
 73  0
         ignored.add( ".svn" );
 74  0
         ignored.add( "CVS" );
 75  
 
 76  0
         DEFAULT_IGNORED_PATCHES = ignored;
 77  
 
 78  0
         List ignoredPatterns = new ArrayList();
 79  
 
 80  0
         ignoredPatterns.add( ".svn/**" );
 81  0
         ignoredPatterns.add( "CVS/**" );
 82  
 
 83  0
         DEFAULT_IGNORED_PATCH_PATTERNS = ignoredPatterns;
 84  0
     }
 85  
 
 86  
     /**
 87  
      * Whether to exclude default ignored patch items, such as <code>.svn</code> or <code>CVS</code> directories.
 88  
      * 
 89  
      * @parameter default-value="true"
 90  
      */
 91  
     private boolean useDefaultIgnores;
 92  
 
 93  
     /**
 94  
      * The list of patch file names, supplying the order in which patches should be applied. The path names in this list
 95  
      * must be relative to the base directory specified by the parameter <code>patchDirectory</code>. This parameter
 96  
      * is mutually exclusive with the <code>patchfile</code> parameter.
 97  
      * 
 98  
      * @parameter
 99  
      */
 100  
     protected List patches;
 101  
 
 102  
     /**
 103  
      * Whether to skip this goal's execution.
 104  
      * 
 105  
      * @parameter default-value="false" alias="patch.apply.skip"
 106  
      */
 107  
     private boolean skipApplication;
 108  
 
 109  
     /**
 110  
      * Flag to enable/disable optimization file from being written. This file tracks the patches that were applied the
 111  
      * last time this goal actually executed. It is required for cases where project-sources optimizations are enabled,
 112  
      * since project-sources will not be re-unpacked if they are at least as fresh as the source archive. If we avoid
 113  
      * re-unpacking project sources, we need to make sure we don't reapply patches.<br/> <strong>Note:</strong> If the
 114  
      * list of patches changes and this flag is enabled, a "<code>mvn clean</code>" must be executed before the next
 115  
      * build, to remove the tracking file.
 116  
      * 
 117  
      * @parameter default-value="true"
 118  
      */
 119  
     private boolean optimizations;
 120  
 
 121  
     /**
 122  
      * This is the tracking file used to maintain a list of the patches applied to the unpacked project sources which
 123  
      * are currently in the target directory. If this file is present, and project-source unpacking is optimized
 124  
      * (meaning it won't re-unpack unless the project-sources archive is newer), this goal will not execute and no
 125  
      * patches will be applied in the current build.
 126  
      * 
 127  
      * @parameter default-value="${project.build.directory}/optimization-files/patches-applied.txt"
 128  
      */
 129  
     private File patchTrackingFile;
 130  
 
 131  
     /**
 132  
      * The target directory for applying patches. Files in this directory will be modified.
 133  
      * 
 134  
      * @parameter alias="patchTargetDir" default-value="${project.build.sourceDirectory}"
 135  
      */
 136  
     private File targetDirectory;
 137  
 
 138  
     /**
 139  
      * Flag being <code>true</code> if the desired behavior is to fail the build on the first failed patch detected.
 140  
      * 
 141  
      * @parameter default-value="true"
 142  
      */
 143  
     private boolean failFast;
 144  
 
 145  
     /**
 146  
      * Setting natural order processing to <code>true</code> will cause all patches in a directory to be processed in
 147  
      * a natural order alleviating the need to declare patches directly in the project file.
 148  
      * 
 149  
      * @parameter default-value="false"
 150  
      */
 151  
     private boolean naturalOrderProcessing;
 152  
 
 153  
     /**
 154  
      * When the <code>strictPatching</code> flag is set, this parameter is useful to mark certain contents of the
 155  
      * patch-source directory that should be ignored without causing the build to fail.
 156  
      * 
 157  
      * @parameter
 158  
      */
 159  
     private List ignoredPatches;
 160  
 
 161  
     /**
 162  
      * Flag that, when set to <code>true</code>, will make sure that all patches included in the <code>patches</code>
 163  
      * list must be present and describe the full contents of the patch directory. If <code>strictPatching</code> is
 164  
      * set to <code>true</code>, and the <code>patches</code> list has a value that does not correspond to a file
 165  
      * in the patch directory, the build will fail. If <code>strictPatching</code> is set to <code>true</code>, and
 166  
      * the patch directory contains files not listed in the <code>patches</code> parameter, the build will fail. If
 167  
      * set to <code>false</code>, only the patches listed in the <code>patches</code> parameter that have
 168  
      * corresponding files will be applied; the rest will be ignored.
 169  
      * 
 170  
      * @parameter default-value="false"
 171  
      */
 172  
     private boolean strictPatching;
 173  
 
 174  
     /**
 175  
      * The number of directories to be stripped from patch file paths, before applying, starting from the leftmost, or
 176  
      * root-est.
 177  
      * 
 178  
      * @parameter default-value="0"
 179  
      */
 180  
     private int strip;
 181  
 
 182  
     /**
 183  
      * Whether to ignore whitespaces when applying the patches.
 184  
      * 
 185  
      * @parameter default-value="true"
 186  
      */
 187  
     private boolean ignoreWhitespace;
 188  
 
 189  
     /**
 190  
      * Whether to treat these patches as having reversed source and dest in the patch syntax.
 191  
      * 
 192  
      * @parameter default-value="false"
 193  
      */
 194  
     private boolean reverse;
 195  
 
 196  
     /**
 197  
      * Whether to make backups of the original files before modding them.
 198  
      * 
 199  
      * @parameter default-value="false"
 200  
      */
 201  
     private boolean backups;
 202  
 
 203  
     /**
 204  
      * List of phrases to watch for in the command output from the patch tool. If one is found, it will cause the build
 205  
      * to fail. All phrases should be lower-case <em>only</em>. By default, the phrases <code>fail</code>,
 206  
      * <code>skip</code> and <code>reject</code> are used.
 207  
      * 
 208  
      * @parameter
 209  
      */
 210  0
     private List failurePhrases = PATCH_FAILURE_WATCH_PHRASES;
 211  
 
 212  
     /**
 213  
      * The original file which will be modified by the patch. By default, the patch tool will automatically derive the
 214  
      * original file from the header of the patch file.
 215  
      * 
 216  
      * @parameter
 217  
      */
 218  
     private File originalFile;
 219  
 
 220  
     /**
 221  
      * The output file which is the original file, plus modifications from the patch.
 222  
      * 
 223  
      * @parameter
 224  
      */
 225  
     private File destFile;
 226  
 
 227  
     /**
 228  
      * The single patch file to apply. This parameter is mutually exclusive with the <code>patches</code> parameter.
 229  
      * 
 230  
      * @parameter
 231  
      */
 232  
     private File patchFile;
 233  
 
 234  
     /**
 235  
      * The base directory for the file names specified by the parameter <code>patches</code>.
 236  
      * 
 237  
      * @parameter default-value="src/main/patches"
 238  
      */
 239  
     private File patchDirectory;
 240  
 
 241  
     /**
 242  
      * When set to <code>true</code>, the empty files resulting from the patching process are removed. Empty ancestor
 243  
      * directories are removed as well.
 244  
      * 
 245  
      * @parameter default-value="false"
 246  
      * @since 1.1
 247  
      */
 248  
     private boolean removeEmptyFiles;
 249  
 
 250  
     /**
 251  
      * Apply the patches. Give preference to patchFile over patchSourceDir/patches, and preference to originalFile over
 252  
      * workDir.
 253  
      */
 254  
     public void execute()
 255  
         throws MojoExecutionException, MojoFailureException
 256  
     {
 257  0
         boolean patchDirEnabled = ( ( patches != null ) && !patches.isEmpty() ) || naturalOrderProcessing;
 258  0
         boolean patchFileEnabled = patchFile != null;
 259  
 
 260  
         // if patches is null or empty, and naturalOrderProcessing is not true then disable patching
 261  0
         if ( !patchFileEnabled && !patchDirEnabled )
 262  
         {
 263  0
             getLog().info( "Patching is disabled for this project." );
 264  0
             return;
 265  
         }
 266  
 
 267  0
         if ( skipApplication )
 268  
         {
 269  0
             getLog().info( "Skipping patch file application (per configuration)." );
 270  0
             return;
 271  
         }
 272  
 
 273  0
         patchTrackingFile.getParentFile().mkdirs();
 274  
 
 275  0
         Map patchesToApply = null;
 276  
 
 277  
         try
 278  
         {
 279  0
             if ( patchFileEnabled )
 280  
             {
 281  0
                 patchesToApply = Collections.singletonMap( patchFile.getName(), createPatchCommand( patchFile ) );
 282  
             }
 283  
             else
 284  
             {
 285  0
                 if ( !patchDirectory.isDirectory() )
 286  
                 {
 287  0
                     throw new FileNotFoundException( "The base directory for patch files does not exist: "
 288  
                         + patchDirectory );
 289  
                 }
 290  
 
 291  0
                 List foundPatchFiles = FileUtils.getFileNames( patchDirectory, "*", null, false );
 292  
 
 293  0
                 patchesToApply = findPatchesToApply( foundPatchFiles, patchDirectory );
 294  
 
 295  0
                 checkStrictPatchCompliance( foundPatchFiles );
 296  
             }
 297  
 
 298  0
             String output = applyPatches( patchesToApply );
 299  
 
 300  0
             checkForWatchPhrases( output );
 301  
 
 302  0
             writeTrackingFile( patchesToApply );
 303  
         }
 304  0
         catch ( IOException ioe )
 305  
         {
 306  0
             throw new MojoExecutionException( "Unable to obtain list of patch files", ioe );
 307  0
         }
 308  0
     }
 309  
 
 310  
     private Map findPatchesToApply( List foundPatchFiles, File patchSourceDir )
 311  
         throws MojoFailureException
 312  
     {
 313  0
         Map patchesApplied = new LinkedHashMap();
 314  
 
 315  0
         if ( naturalOrderProcessing )
 316  
         {
 317  0
             patches = new ArrayList( foundPatchFiles );
 318  0
             Collections.sort( patches );
 319  
         }
 320  
 
 321  0
         String alreadyAppliedPatches = "";
 322  
 
 323  
         try
 324  
         {
 325  0
             if ( optimizations && patchTrackingFile.exists() )
 326  
             {
 327  0
                 alreadyAppliedPatches = FileUtils.fileRead( patchTrackingFile );
 328  
             }
 329  
         }
 330  0
         catch ( IOException ioe )
 331  
         {
 332  0
             throw new MojoFailureException( "unable to read patch tracking file: " + ioe.getMessage() );
 333  0
         }
 334  
 
 335  0
         for ( Iterator it = patches.iterator(); it.hasNext(); )
 336  
         {
 337  0
             String patch = (String) it.next();
 338  
 
 339  0
             if ( alreadyAppliedPatches.indexOf( patch ) == -1 )
 340  
             {
 341  0
                 File patchFile = new File( patchSourceDir, patch );
 342  
 
 343  0
                 getLog().debug( "Looking for patch: " + patch + " in: " + patchFile );
 344  
 
 345  0
                 if ( !patchFile.exists() )
 346  
                 {
 347  0
                     if ( strictPatching )
 348  
                     {
 349  0
                         throw new MojoFailureException( this, "Patch operation cannot proceed.",
 350  
                                                         "Cannot find specified patch: \'" + patch
 351  
                                                             + "\' in patch-source directory: \'" + patchSourceDir
 352  
                                                             + "\'.\n\nEither fix this error, "
 353  
                                                             + "or relax strictPatching." );
 354  
                     }
 355  
                     else
 356  
                     {
 357  0
                         getLog().info(
 358  
                                        "Skipping patch: " + patch + " listed in the parameter \"patches\"; "
 359  
                                            + "it is missing." );
 360  
                     }
 361  
                 }
 362  
                 else
 363  
                 {
 364  0
                     foundPatchFiles.remove( patch );
 365  
 
 366  0
                     patchesApplied.put( patch, createPatchCommand( patchFile ) );
 367  
                 }
 368  
             }
 369  
         }
 370  
 
 371  0
         return patchesApplied;
 372  
     }
 373  
 
 374  
     private void checkStrictPatchCompliance( List foundPatchFiles )
 375  
         throws MojoExecutionException
 376  
     {
 377  0
         if ( strictPatching )
 378  
         {
 379  0
             List ignored = new ArrayList();
 380  
 
 381  0
             if ( ignoredPatches != null )
 382  
             {
 383  0
                 ignored.addAll( ignoredPatches );
 384  
             }
 385  
 
 386  0
             if ( useDefaultIgnores )
 387  
             {
 388  0
                 ignored.addAll( DEFAULT_IGNORED_PATCHES );
 389  
             }
 390  
 
 391  0
             List limbo = new ArrayList( foundPatchFiles );
 392  
 
 393  0
             for ( Iterator it = ignored.iterator(); it.hasNext(); )
 394  
             {
 395  0
                 String ignoredFile = (String) it.next();
 396  
 
 397  0
                 limbo.remove( ignoredFile );
 398  
             }
 399  
 
 400  0
             if ( !limbo.isEmpty() )
 401  
             {
 402  0
                 StringBuffer extraFileBuffer = new StringBuffer();
 403  
 
 404  0
                 extraFileBuffer.append( "Found " + limbo.size() + " unlisted patch files:" );
 405  
 
 406  0
                 for ( Iterator it = foundPatchFiles.iterator(); it.hasNext(); )
 407  
                 {
 408  0
                     String patch = (String) it.next();
 409  
 
 410  0
                     extraFileBuffer.append( "\n  \'" ).append( patch ).append( '\'' );
 411  
                 }
 412  
 
 413  0
                 extraFileBuffer.append( "\n\nEither remove these files, "
 414  
                     + "add them to the patches configuration list, " + "or relax strictPatching." );
 415  
 
 416  0
                 throw new MojoExecutionException( extraFileBuffer.toString() );
 417  
             }
 418  
         }
 419  0
     }
 420  
 
 421  
     private String applyPatches( Map patchesApplied )
 422  
         throws MojoExecutionException
 423  
     {
 424  0
         final StringWriter outputWriter = new StringWriter();
 425  
 
 426  0
         StreamConsumer consumer = new StreamConsumer()
 427  
         {
 428  0
             public void consumeLine( String line )
 429  
             {
 430  0
                 if ( getLog().isDebugEnabled() )
 431  
                 {
 432  0
                     getLog().debug( line );
 433  
                 }
 434  
 
 435  0
                 outputWriter.write( line + "\n" );
 436  0
             }
 437  
         };
 438  
 
 439  
         // used if failFast is false
 440  0
         String failedPatches = null;
 441  
 
 442  0
         for ( Iterator it = patchesApplied.entrySet().iterator(); it.hasNext(); )
 443  
         {
 444  0
             Map.Entry entry = (Entry) it.next();
 445  0
             String patchName = (String) entry.getKey();
 446  0
             Commandline cli = (Commandline) entry.getValue();
 447  
 
 448  
             try
 449  
             {
 450  0
                 getLog().info( "Applying patch: " + patchName );
 451  0
                 int result = executeCommandLine( cli, consumer, consumer );
 452  
 
 453  0
                 getLog().info( "patch command returned: " + result );
 454  
 
 455  0
                 if ( result != 0 )
 456  
                 {
 457  0
                     if ( failFast )
 458  
                     {
 459  0
                         throw new MojoExecutionException( "Patch command failed (exit value != 0) for " + patchName
 460  
                             + ". Please see debug output for more information." );
 461  
                     }
 462  
                     else
 463  
                     {
 464  0
                         if ( failedPatches == null )
 465  
                         {
 466  0
                             failedPatches = new String();
 467  
                         }
 468  0
                         failedPatches = failedPatches + patchName + "\n";
 469  
                     }
 470  
                 }
 471  
             }
 472  0
             catch ( CommandLineException e )
 473  
             {
 474  0
                 throw new MojoExecutionException( "Failed to apply patch: " + patchName
 475  
                     + ". See debug output for more information.", e );
 476  0
             }
 477  
         }
 478  
 
 479  0
         if ( failedPatches != null )
 480  
         {
 481  0
             getLog().info( "Failed applying one or more patches:" );
 482  0
             getLog().info( failedPatches );
 483  0
             throw new MojoExecutionException( "Patch command failed for one or more patches."
 484  
                 + " Please see console and debug output for more information." );
 485  
         }
 486  
 
 487  0
         return outputWriter.toString();
 488  
     }
 489  
 
 490  
     private int executeCommandLine( Commandline cli, StreamConsumer out, StreamConsumer err )
 491  
         throws CommandLineException
 492  
     {
 493  0
         if ( getLog().isDebugEnabled() )
 494  
         {
 495  0
             getLog().debug( "Executing:\n" + cli + "\n" );
 496  
         }
 497  
 
 498  0
         getLog().info( Commandline.toString( cli.getShellCommandline() ) );
 499  
 
 500  0
         return CommandLineUtils.executeCommandLine( cli, out, err );
 501  
     }
 502  
 
 503  
     private void writeTrackingFile( Map patchesApplied )
 504  
         throws MojoExecutionException
 505  
     {
 506  0
         FileWriter writer = null;
 507  
         try
 508  
         {
 509  0
             boolean appending = patchTrackingFile.exists();
 510  
 
 511  0
             writer = new FileWriter( patchTrackingFile, appending );
 512  
 
 513  0
             for ( Iterator it = patchesApplied.keySet().iterator(); it.hasNext(); )
 514  
             {
 515  0
                 if ( appending )
 516  
                 {
 517  0
                     writer.write( System.getProperty( "line.separator" ) );
 518  
                 }
 519  
 
 520  0
                 String patch = (String) it.next();
 521  0
                 writer.write( patch );
 522  
 
 523  0
                 if ( it.hasNext() )
 524  
                 {
 525  0
                     writer.write( System.getProperty( "line.separator" ) );
 526  
                 }
 527  
             }
 528  
 
 529  0
             writer.flush();
 530  
         }
 531  0
         catch ( IOException e )
 532  
         {
 533  0
             throw new MojoExecutionException( "Failed to write patch-tracking file: " + patchTrackingFile, e );
 534  
         }
 535  
         finally
 536  
         {
 537  0
             IOUtil.close( writer );
 538  0
         }
 539  0
     }
 540  
 
 541  
     private void checkForWatchPhrases( String output )
 542  
         throws MojoExecutionException
 543  
     {
 544  0
         for ( Iterator it = failurePhrases.iterator(); it.hasNext(); )
 545  
         {
 546  0
             String phrase = (String) it.next();
 547  
 
 548  0
             if ( output.indexOf( phrase ) > -1 )
 549  
             {
 550  0
                 throw new MojoExecutionException( "Failed to apply patches (detected watch-phrase: \'" + phrase
 551  
                     + "\' in output). " + "If this is in error, configure the patchFailureWatchPhrases parameter." );
 552  
             }
 553  
         }
 554  0
     }
 555  
 
 556  
     /**
 557  
      * Add a new Patch task to the Ant calling mechanism. Give preference to originalFile/destFile, then workDir, and
 558  
      * finally ${basedir}.
 559  
      */
 560  
     private Commandline createPatchCommand( File patchFile )
 561  
     {
 562  0
         Commandline cli = new Commandline();
 563  
 
 564  0
         cli.setExecutable( "patch" );
 565  
 
 566  0
         cli.setWorkingDirectory( targetDirectory.getAbsolutePath() );
 567  
 
 568  0
         if ( originalFile != null )
 569  
         {
 570  0
             cli.createArg().setFile( originalFile );
 571  
 
 572  0
             if ( destFile != null )
 573  
             {
 574  0
                 cli.createArg().setValue( "-o" );
 575  0
                 cli.createArg().setFile( destFile );
 576  
             }
 577  
 
 578  0
             cli.createArg().setFile( patchFile );
 579  
         }
 580  
 
 581  0
         cli.createArg().setValue( "-p" + strip );
 582  
 
 583  0
         if ( ignoreWhitespace )
 584  
         {
 585  0
             cli.createArg().setValue( "-l" );
 586  
         }
 587  
 
 588  0
         if ( reverse )
 589  
         {
 590  0
             cli.createArg().setValue( "-R" );
 591  
         }
 592  
 
 593  0
         if ( backups )
 594  
         {
 595  0
             cli.createArg().setValue( "-b" );
 596  
         }
 597  
 
 598  0
         if ( removeEmptyFiles )
 599  
         {
 600  0
             cli.createArg().setValue( "-E" );
 601  
         }
 602  
 
 603  0
         cli.createArg().setValue( "-i" );
 604  0
         cli.createArg().setFile( patchFile );
 605  
 
 606  0
         return cli;
 607  
     }
 608  
 
 609  
 }