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