1 package org.apache.maven.plugin.patch;
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
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.apache.maven.plugins.annotations.LifecyclePhase;
26 import org.apache.maven.plugins.annotations.Mojo;
27 import org.apache.maven.plugins.annotations.Parameter;
28 import org.codehaus.plexus.util.FileUtils;
29 import org.codehaus.plexus.util.IOUtil;
30 import org.codehaus.plexus.util.cli.CommandLineException;
31 import org.codehaus.plexus.util.cli.CommandLineUtils;
32 import org.codehaus.plexus.util.cli.Commandline;
33 import org.codehaus.plexus.util.cli.StreamConsumer;
34
35 import java.io.File;
36 import java.io.FileNotFoundException;
37 import java.io.FileWriter;
38 import java.io.IOException;
39 import java.io.StringWriter;
40 import java.util.ArrayList;
41 import java.util.Collections;
42 import java.util.Iterator;
43 import java.util.LinkedHashMap;
44 import java.util.List;
45 import java.util.Map;
46 import java.util.Map.Entry;
47
48
49
50
51 @Mojo( name = "apply", defaultPhase = LifecyclePhase.PROCESS_SOURCES )
52 public class ApplyMojo
53 extends AbstractMojo
54 {
55
56 public static final List PATCH_FAILURE_WATCH_PHRASES;
57
58 public static final List DEFAULT_IGNORED_PATCHES;
59
60 public static final List DEFAULT_IGNORED_PATCH_PATTERNS;
61
62 static
63 {
64 List watches = new ArrayList();
65
66 watches.add( "fail" );
67 watches.add( "skip" );
68 watches.add( "reject" );
69
70 PATCH_FAILURE_WATCH_PHRASES = watches;
71
72 List ignored = new ArrayList();
73
74 ignored.add( ".svn" );
75 ignored.add( "CVS" );
76
77 DEFAULT_IGNORED_PATCHES = ignored;
78
79 List ignoredPatterns = new ArrayList();
80
81 ignoredPatterns.add( ".svn/**" );
82 ignoredPatterns.add( "CVS/**" );
83
84 DEFAULT_IGNORED_PATCH_PATTERNS = ignoredPatterns;
85 }
86
87
88
89
90 @Parameter( defaultValue = "true" )
91 private boolean useDefaultIgnores;
92
93
94
95
96
97
98 @Parameter
99 protected List patches;
100
101
102
103
104 @Parameter( alias = "patch.apply.skip", defaultValue = "false" )
105 private boolean skipApplication;
106
107
108
109
110
111
112
113
114
115 @Parameter( defaultValue = "true" )
116 private boolean optimizations;
117
118
119
120
121
122
123
124 @Parameter( defaultValue = "${project.build.directory}/optimization-files/patches-applied.txt" )
125 private File patchTrackingFile;
126
127
128
129
130 @Parameter( alias = "patchTargetDir", defaultValue = "${project.build.sourceDirectory}" )
131 private File targetDirectory;
132
133
134
135
136 @Parameter( defaultValue = "true" )
137 private boolean failFast;
138
139
140
141
142
143 @Parameter( defaultValue = "false" )
144 private boolean naturalOrderProcessing;
145
146
147
148
149
150 @Parameter
151 private List ignoredPatches;
152
153
154
155
156
157
158
159
160
161
162 @Parameter( defaultValue = "false" )
163 private boolean strictPatching;
164
165
166
167
168
169 @Parameter( defaultValue = "0" )
170 private int strip;
171
172
173
174
175 @Parameter( defaultValue = "true" )
176 private boolean ignoreWhitespace;
177
178
179
180
181 @Parameter( defaultValue = "false" )
182 private boolean reverse;
183
184
185
186
187 @Parameter( defaultValue = "false" )
188 private boolean backups;
189
190
191
192
193
194
195 @Parameter
196 private List failurePhrases = PATCH_FAILURE_WATCH_PHRASES;
197
198
199
200
201
202 @Parameter
203 private File originalFile;
204
205
206
207
208
209 @Parameter
210 private File destFile;
211
212
213
214
215 @Parameter
216 private File patchFile;
217
218
219
220
221 @Parameter( defaultValue = "src/main/patches" )
222 private File patchDirectory;
223
224
225
226
227
228
229
230 @Parameter( defaultValue = "false" )
231 private boolean removeEmptyFiles;
232
233
234
235
236
237 @Parameter( defaultValue = "false" )
238 private boolean binary;
239
240
241
242
243
244 public void execute()
245 throws MojoExecutionException, MojoFailureException
246 {
247 boolean patchDirEnabled = ( ( patches != null ) && !patches.isEmpty() ) || naturalOrderProcessing;
248 boolean patchFileEnabled = patchFile != null;
249
250
251 if ( !patchFileEnabled && !patchDirEnabled )
252 {
253 getLog().info( "Patching is disabled for this project." );
254 return;
255 }
256
257 if ( skipApplication )
258 {
259 getLog().info( "Skipping patch file application (per configuration)." );
260 return;
261 }
262
263 patchTrackingFile.getParentFile().mkdirs();
264
265 Map patchesToApply;
266
267 try
268 {
269 if ( patchFileEnabled )
270 {
271 patchesToApply = Collections.singletonMap( patchFile.getName(), createPatchCommand( patchFile ) );
272 }
273 else
274 {
275 if ( !patchDirectory.isDirectory() )
276 {
277 throw new FileNotFoundException( "The base directory for patch files does not exist: "
278 + patchDirectory );
279 }
280
281 List foundPatchFiles = FileUtils.getFileNames( patchDirectory, "*", null, false );
282
283 patchesToApply = findPatchesToApply( foundPatchFiles, patchDirectory );
284
285 checkStrictPatchCompliance( foundPatchFiles );
286 }
287
288 String output = applyPatches( patchesToApply );
289
290 checkForWatchPhrases( output );
291
292 writeTrackingFile( patchesToApply );
293 }
294 catch ( IOException ioe )
295 {
296 throw new MojoExecutionException( "Unable to obtain list of patch files", ioe );
297 }
298 }
299
300 private Map findPatchesToApply( List foundPatchFiles, File patchSourceDir )
301 throws MojoFailureException
302 {
303 Map patchesApplied = new LinkedHashMap();
304
305 if ( naturalOrderProcessing )
306 {
307 patches = new ArrayList( foundPatchFiles );
308 Collections.sort( patches );
309 }
310
311 String alreadyAppliedPatches = "";
312
313 try
314 {
315 if ( optimizations && patchTrackingFile.exists() )
316 {
317 alreadyAppliedPatches = FileUtils.fileRead( patchTrackingFile );
318 }
319 }
320 catch ( IOException ioe )
321 {
322 throw new MojoFailureException( "unable to read patch tracking file: " + ioe.getMessage() );
323 }
324
325 for ( Object patche : patches )
326 {
327 String patch = (String) patche;
328
329 if ( !alreadyAppliedPatches.contains( patch ) )
330 {
331 File patchFile = new File( patchSourceDir, patch );
332
333 getLog().debug( "Looking for patch: " + patch + " in: " + patchFile );
334
335 if ( !patchFile.exists() )
336 {
337 if ( strictPatching )
338 {
339 throw new MojoFailureException( this, "Patch operation cannot proceed.",
340 "Cannot find specified patch: \'" + patch
341 + "\' in patch-source directory: \'" + patchSourceDir
342 + "\'.\n\nEither fix this error, "
343 + "or relax strictPatching." );
344 }
345 else
346 {
347 getLog().info( "Skipping patch: " + patch + " listed in the parameter \"patches\"; "
348 + "it is missing." );
349 }
350 }
351 else
352 {
353 foundPatchFiles.remove( patch );
354
355 patchesApplied.put( patch, createPatchCommand( patchFile ) );
356 }
357 }
358 }
359
360 return patchesApplied;
361 }
362
363 private void checkStrictPatchCompliance( List foundPatchFiles )
364 throws MojoExecutionException
365 {
366 if ( strictPatching )
367 {
368 List ignored = new ArrayList();
369
370 if ( ignoredPatches != null )
371 {
372 ignored.addAll( ignoredPatches );
373 }
374
375 if ( useDefaultIgnores )
376 {
377 ignored.addAll( DEFAULT_IGNORED_PATCHES );
378 }
379
380 List limbo = new ArrayList( foundPatchFiles );
381
382 for ( Object anIgnored : ignored )
383 {
384 String ignoredFile = (String) anIgnored;
385
386 limbo.remove( ignoredFile );
387 }
388
389 if ( !limbo.isEmpty() )
390 {
391 StringBuilder extraFileBuffer = new StringBuilder();
392
393 extraFileBuffer.append( "Found " ).append( limbo.size() ).append( " unlisted patch files:" );
394
395 for ( Object foundPatchFile : foundPatchFiles )
396 {
397 String patch = (String) foundPatchFile;
398
399 extraFileBuffer.append( "\n \'" ).append( patch ).append( '\'' );
400 }
401
402 extraFileBuffer.append( "\n\nEither remove these files, "
403 + "add them to the patches configuration list, " + "or relax strictPatching." );
404
405 throw new MojoExecutionException( extraFileBuffer.toString() );
406 }
407 }
408 }
409
410 private String applyPatches( Map patchesApplied )
411 throws MojoExecutionException
412 {
413 final StringWriter outputWriter = new StringWriter();
414
415 StreamConsumer consumer = new StreamConsumer()
416 {
417 public void consumeLine( String line )
418 {
419 if ( getLog().isDebugEnabled() )
420 {
421 getLog().debug( line );
422 }
423
424 outputWriter.write( line + "\n" );
425 }
426 };
427
428
429 List failedPatches = new ArrayList();
430
431 for ( Object o : patchesApplied.entrySet() )
432 {
433 Entry entry = (Entry) o;
434 String patchName = (String) entry.getKey();
435 Commandline cli = (Commandline) entry.getValue();
436
437 try
438 {
439 getLog().info( "Applying patch: " + patchName );
440
441 int result = executeCommandLine( cli, consumer, consumer );
442
443 if ( result != 0 )
444 {
445 if ( failFast )
446 {
447 throw new MojoExecutionException( "Patch command failed with exit code " + result + " for "
448 + patchName + ". Please see console and debug output for more information." );
449 }
450 else
451 {
452 failedPatches.add( patchName );
453 }
454 }
455 }
456 catch ( CommandLineException e )
457 {
458 throw new MojoExecutionException( "Failed to apply patch: " + patchName
459 + ". See debug output for more information.", e );
460 }
461 }
462
463 if ( !failedPatches.isEmpty() )
464 {
465 getLog().error( "Failed applying one or more patches:" );
466 for ( Object failedPatche : failedPatches )
467 {
468 getLog().error( "* " + failedPatche );
469 }
470 throw new MojoExecutionException( "Patch command failed for one or more patches."
471 + " Please see console and debug output for more information." );
472 }
473
474 return outputWriter.toString();
475 }
476
477 private int executeCommandLine( Commandline cli, StreamConsumer out, StreamConsumer err )
478 throws CommandLineException
479 {
480 if ( getLog().isDebugEnabled() )
481 {
482 getLog().debug( "Executing: " + cli );
483 }
484
485 int result = CommandLineUtils.executeCommandLine( cli, out, err );
486
487 if ( getLog().isDebugEnabled() )
488 {
489 getLog().debug( "Exit code: " + result );
490 }
491
492 return result;
493 }
494
495 private void writeTrackingFile( Map patchesApplied )
496 throws MojoExecutionException
497 {
498 FileWriter writer = null;
499 try
500 {
501 boolean appending = patchTrackingFile.exists();
502
503 writer = new FileWriter( patchTrackingFile, appending );
504
505 for ( Iterator it = patchesApplied.keySet().iterator(); it.hasNext(); )
506 {
507 if ( appending )
508 {
509 writer.write( System.getProperty( "line.separator" ) );
510 }
511
512 String patch = (String) it.next();
513 writer.write( patch );
514
515 if ( it.hasNext() )
516 {
517 writer.write( System.getProperty( "line.separator" ) );
518 }
519 }
520
521 writer.flush();
522 }
523 catch ( IOException e )
524 {
525 throw new MojoExecutionException( "Failed to write patch-tracking file: " + patchTrackingFile, e );
526 }
527 finally
528 {
529 IOUtil.close( writer );
530 }
531 }
532
533 private void checkForWatchPhrases( String output )
534 throws MojoExecutionException
535 {
536 for ( Object failurePhrase : failurePhrases )
537 {
538 String phrase = (String) failurePhrase;
539
540 if ( output.contains( phrase ) )
541 {
542 throw new MojoExecutionException( "Failed to apply patches (detected watch-phrase: \'" + phrase
543 + "\' in output). " + "If this is in error, configure the patchFailureWatchPhrases parameter." );
544 }
545 }
546 }
547
548
549
550
551
552 private Commandline createPatchCommand( File patchFile )
553 {
554 Commandline cli = new Commandline();
555
556 cli.setExecutable( "patch" );
557
558 cli.setWorkingDirectory( targetDirectory.getAbsolutePath() );
559
560 cli.createArg().setValue( "-p" + strip );
561
562 if ( binary )
563 {
564 cli.createArg().setValue( "--binary" );
565 }
566
567 if ( ignoreWhitespace )
568 {
569 cli.createArg().setValue( "-l" );
570 }
571
572 if ( reverse )
573 {
574 cli.createArg().setValue( "-R" );
575 }
576
577 if ( backups )
578 {
579 cli.createArg().setValue( "-b" );
580 }
581
582 if ( removeEmptyFiles )
583 {
584 cli.createArg().setValue( "-E" );
585 }
586
587 cli.createArg().setValue( "-i" );
588 cli.createArg().setFile( patchFile );
589
590 if ( destFile != null )
591 {
592 cli.createArg().setValue( "-o" );
593 cli.createArg().setFile( destFile );
594 }
595
596 if ( originalFile != null )
597 {
598 cli.createArg().setFile( originalFile );
599 }
600
601 return cli;
602 }
603
604 }