1 package org.codehaus.mojo.build;
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24 import org.apache.maven.execution.MavenSession;
25 import org.apache.maven.plugin.AbstractMojo;
26 import org.apache.maven.plugin.MojoExecutionException;
27 import org.apache.maven.plugin.MojoFailureException;
28 import org.apache.maven.project.MavenProject;
29 import org.apache.maven.scm.CommandParameter;
30 import org.apache.maven.scm.CommandParameters;
31 import org.apache.maven.scm.ScmException;
32 import org.apache.maven.scm.ScmFile;
33 import org.apache.maven.scm.ScmFileSet;
34 import org.apache.maven.scm.ScmResult;
35 import org.apache.maven.scm.command.info.InfoItem;
36 import org.apache.maven.scm.command.info.InfoScmResult;
37 import org.apache.maven.scm.command.status.StatusScmResult;
38 import org.apache.maven.scm.command.update.UpdateScmResult;
39 import org.apache.maven.scm.command.update.UpdateScmResultWithRevision;
40 import org.apache.maven.scm.log.ScmLogDispatcher;
41 import org.apache.maven.scm.log.ScmLogger;
42 import org.apache.maven.scm.manager.ScmManager;
43 import org.apache.maven.scm.provider.ScmProvider;
44 import org.apache.maven.scm.provider.ScmProviderRepository;
45 import org.apache.maven.scm.provider.git.repository.GitScmProviderRepository;
46 import org.apache.maven.scm.repository.ScmRepository;
47 import org.codehaus.plexus.util.StringUtils;
48
49 import java.io.File;
50 import java.io.FileInputStream;
51 import java.io.FileOutputStream;
52 import java.io.IOException;
53 import java.text.MessageFormat;
54 import java.util.Calendar;
55 import java.util.Collections;
56 import java.util.Date;
57 import java.util.Iterator;
58 import java.util.List;
59 import java.util.Locale;
60 import java.util.Map;
61 import java.util.Map.Entry;
62 import java.util.Properties;
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81 public class CreateMojo
82 extends AbstractMojo
83 {
84
85 public final String DEFAULT_BRANCH_NAME = "UNKNOWN_BRANCH";
86
87
88
89
90
91 private String urlScm;
92
93
94
95
96
97
98 private String readUrlScm;
99
100
101
102
103
104
105
106 private String username;
107
108
109
110
111
112
113
114 private String password;
115
116
117
118
119
120
121
122 private File scmDirectory;
123
124
125
126
127
128
129
130
131
132 private String buildNumberPropertyName;
133
134
135
136
137
138
139
140 private String timestampPropertyName;
141
142
143
144
145
146
147
148
149
150 private boolean doCheck;
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167 private boolean doUpdate;
168
169
170
171
172
173
174
175
176 private String format;
177
178
179
180
181
182
183
184
185 private File buildNumberPropertiesFileLocation;
186
187
188
189
190
191
192
193
194 private List items;
195
196
197
198
199
200
201
202
203
204 private String locale;
205
206
207
208
209
210
211
212 private boolean useLastCommittedRevision;
213
214
215
216
217
218
219
220
221 private String timestampFormat;
222
223
224
225
226
227
228
229
230 private String revisionOnScmFailure;
231
232
233
234
235
236
237
238
239
240 private Map<String, String> providerImplementations;
241
242
243
244
245 private ScmManager scmManager;
246
247
248
249
250
251
252
253 private MavenProject project;
254
255
256
257
258
259
260
261
262 private List reactorProjects;
263
264
265
266
267
268
269
270
271 private boolean getRevisionOnlyOnce;
272
273
274
275
276
277
278
279
280 private String scmBranchPropertyName;
281
282
283
284
285
286
287
288 protected MavenSession session;
289
290 private ScmLogDispatcher logger;
291
292 private String revision;
293
294
295
296
297
298
299
300 private int shortRevisionLength = DEFAULT_SHORT_REVISION_DISABLED;
301
302
303
304
305 private static final int DEFAULT_SHORT_REVISION_DISABLED = -1;
306
307 public void execute()
308 throws MojoExecutionException, MojoFailureException
309 {
310 if ( providerImplementations != null )
311 {
312 for ( Entry<String, String> entry : providerImplementations.entrySet() )
313 {
314 String providerType = entry.getKey();
315 String providerImplementation = entry.getValue();
316 getLog().info(
317 "Change the default '" + providerType + "' provider implementation to '" + providerImplementation
318 + "'." );
319 scmManager.setScmProviderImplementation( providerType, providerImplementation );
320 }
321 }
322 Date now = Calendar.getInstance().getTime();
323 if ( format != null )
324 {
325 if ( items == null )
326 {
327 throw new MojoExecutionException(
328 " if you set a format, you must provide at least one item, please check documentation " );
329 }
330
331
332 Object[] itemAry = new Object[items.size()];
333 for ( int i = 0; i < items.size(); i++ )
334 {
335 Object item = items.get( i );
336 if ( item instanceof String )
337 {
338 String s = (String) item;
339 if ( s.equals( "timestamp" ) )
340 {
341 itemAry[i] = now;
342 }
343 else if ( s.startsWith( "buildNumber" ) )
344 {
345
346 File propertiesFile = this.buildNumberPropertiesFileLocation;
347
348
349 if ( !propertiesFile.exists() )
350 {
351 try
352 {
353 if ( !propertiesFile.getParentFile().exists() )
354 {
355 propertiesFile.getParentFile().mkdirs();
356 }
357 propertiesFile.createNewFile();
358 }
359 catch ( IOException e )
360 {
361 throw new MojoExecutionException( "Couldn't create properties file: " + propertiesFile,
362 e );
363 }
364 }
365
366 Properties properties = new Properties();
367 String buildNumberString = null;
368 try
369 {
370
371 properties.load( new FileInputStream( propertiesFile ) );
372 buildNumberString = properties.getProperty( s );
373 if ( buildNumberString == null )
374 {
375 buildNumberString = "0";
376 }
377 int buildNumber = Integer.valueOf( buildNumberString ).intValue();
378
379
380 properties.setProperty( s, String.valueOf( ++buildNumber ) );
381 properties.store( new FileOutputStream( propertiesFile ),
382 "maven.buildNumber.plugin properties file" );
383
384
385 itemAry[i] = new Integer( buildNumber );
386 }
387 catch ( NumberFormatException e )
388 {
389 throw new MojoExecutionException(
390 "Couldn't parse buildNumber in properties file to an Integer: " + buildNumberString );
391 }
392 catch ( IOException e )
393 {
394 throw new MojoExecutionException( "Couldn't load properties file: " + propertiesFile, e );
395 }
396 }
397 else
398 {
399 itemAry[i] = item;
400 }
401 }
402 else
403 {
404 itemAry[i] = item;
405 }
406 }
407
408 revision = format( itemAry );
409 }
410 else
411 {
412
413 revision = project.getProperties().getProperty( this.buildNumberPropertyName );
414 if ( this.getRevisionOnlyOnce && revision != null )
415 {
416 getLog().debug( "Revision available from previous execution" );
417 return;
418 }
419
420 if ( doCheck )
421 {
422
423 checkForLocalModifications();
424 }
425 else
426 {
427 getLog().info( "Checking for local modifications: skipped." );
428 }
429 if ( session.getSettings().isOffline() )
430 {
431 getLog().info( "maven is executed in offline mode, Updating project files from SCM: skipped." );
432 }
433 else
434 {
435 if ( doUpdate )
436 {
437
438
439
440 List<ScmFile> changedFiles = update();
441 for ( ScmFile file : changedFiles )
442 {
443 getLog().info( "Updated: " + file );
444 }
445 if ( changedFiles.size() == 0 )
446 {
447 getLog().info( "No files needed updating." );
448 }
449 }
450 else
451 {
452 getLog().info( "Updating project files from SCM: skipped." );
453 }
454 }
455 revision = getRevision();
456 }
457
458 if ( project != null )
459 {
460 String timestamp = String.valueOf( now.getTime() );
461 if ( timestampFormat != null )
462 {
463 timestamp = MessageFormat.format( timestampFormat, new Object[]{ now } );
464 }
465
466 getLog().info( MessageFormat.format( "Storing buildNumber: {0} at timestamp: {1}",
467 new Object[]{ revision, timestamp } ) );
468 if ( revision != null )
469 {
470 project.getProperties().put( buildNumberPropertyName, revision );
471 }
472 project.getProperties().put( timestampPropertyName, timestamp );
473
474 String scmBranch = getScmBranch();
475 getLog().info( "Storing buildScmBranch: " + scmBranch );
476 project.getProperties().put( scmBranchPropertyName, scmBranch );
477
478
479 if ( getRevisionOnlyOnce && reactorProjects != null )
480 {
481 Iterator projIter = reactorProjects.iterator();
482 while ( projIter.hasNext() )
483 {
484 MavenProject nextProj = (MavenProject) projIter.next();
485 if ( revision != null )
486 {
487 nextProj.getProperties().put( this.buildNumberPropertyName, revision );
488 }
489 nextProj.getProperties().put( this.timestampPropertyName, timestamp );
490 nextProj.getProperties().put( this.scmBranchPropertyName, scmBranch );
491 }
492 }
493 }
494 }
495
496
497
498
499
500
501 private String format( Object[] arguments )
502 {
503 Locale l = Locale.getDefault();
504 if ( locale != null )
505 {
506 String[] parts = locale.split( "_", 3 );
507 if ( parts.length <= 1 )
508 {
509 l = new Locale( locale );
510 }
511 else if ( parts.length == 2 )
512 {
513 l = new Locale( parts[0], parts[1] );
514 }
515 else
516 {
517 l = new Locale( parts[0], parts[1], parts[2] );
518 }
519 }
520
521 return new MessageFormat( format, l ).format( arguments );
522 }
523
524 private void checkForLocalModifications()
525 throws MojoExecutionException
526 {
527 getLog().info( "Verifying there are no local modifications ..." );
528
529 List<ScmFile> changedFiles;
530
531 try
532 {
533 changedFiles = getStatus();
534 }
535 catch ( ScmException e )
536 {
537 throw new MojoExecutionException( "An error has occurred while checking scm status.", e );
538 }
539
540 if ( !changedFiles.isEmpty() )
541 {
542 StringBuilder message = new StringBuilder();
543
544 String ls = System.getProperty( "line.separator" );
545
546 for ( ScmFile file : changedFiles )
547 {
548 message.append( file.toString() );
549
550 message.append( ls );
551 }
552
553 throw new MojoExecutionException(
554 "Cannot create the build number because you have local modifications : \n" + message );
555 }
556
557 }
558
559 public List<ScmFile> update()
560 throws MojoExecutionException
561 {
562 try
563 {
564 ScmRepository repository = getScmRepository();
565
566 ScmProvider scmProvider = scmManager.getProviderByRepository( repository );
567
568 UpdateScmResult result = scmProvider.update( repository, new ScmFileSet( scmDirectory ) );
569
570 if ( result == null )
571 {
572 return Collections.emptyList();
573 }
574
575 checkResult( result );
576
577 if ( result instanceof UpdateScmResultWithRevision )
578 {
579 String revision = ( (UpdateScmResultWithRevision) result ).getRevision();
580 getLog().info( "Got a revision during update: " + revision );
581 this.revision = revision;
582 }
583
584 return result.getUpdatedFiles();
585 }
586 catch ( ScmException e )
587 {
588 throw new MojoExecutionException( "Couldn't update project. " + e.getMessage(), e );
589 }
590
591 }
592
593 public List<ScmFile> getStatus()
594 throws ScmException
595 {
596
597 ScmRepository repository = getScmRepository();
598
599 ScmProvider scmProvider = scmManager.getProviderByRepository( repository );
600
601 StatusScmResult result = scmProvider.status( repository, new ScmFileSet( scmDirectory ) );
602
603 if ( result == null )
604 {
605 return Collections.emptyList();
606 }
607
608 checkResult( result );
609
610 return result.getChangedFiles();
611
612 }
613
614
615
616
617
618
619
620
621 public String getScmBranch()
622 throws MojoExecutionException
623 {
624 String scmUrl = null;
625 try
626 {
627 ScmRepository repository = getScmRepository();
628 InfoScmResult scmResult = info( repository, new ScmFileSet( scmDirectory ) );
629 if ( scmResult == null || !scmResult.isSuccess() )
630 {
631 getLog().debug( "Cannot get the branch information from the scm repository : " + ( scmResult == null
632 ? ""
633 : scmResult.getCommandOutput() ) );
634 return DEFAULT_BRANCH_NAME;
635 }
636 if ( scmResult.getInfoItems().isEmpty() )
637 {
638 if ( !StringUtils.isEmpty( revisionOnScmFailure ) )
639 {
640 setDoCheck( false );
641 setDoUpdate( false );
642
643 return DEFAULT_BRANCH_NAME;
644 }
645 }
646 if ( !scmResult.getInfoItems().isEmpty() )
647 {
648 InfoItem info = scmResult.getInfoItems().get( 0 );
649 scmUrl = info.getURL();
650 }
651 }
652 catch ( ScmException e )
653 {
654 if ( !StringUtils.isEmpty( revisionOnScmFailure ) )
655 {
656 getLog().warn(
657 "Cannot get the branch information from the scm repository, proceeding with " + DEFAULT_BRANCH_NAME
658 + " : \n" + e.getLocalizedMessage() );
659
660 setDoCheck( false );
661 setDoUpdate( false );
662
663 return DEFAULT_BRANCH_NAME;
664 }
665 throw new MojoExecutionException(
666 "Cannot get the branch information from the scm repository : \n" + e.getLocalizedMessage(), e );
667 }
668
669 return filterBranchFromScmUrl( scmUrl );
670 }
671
672 protected String filterBranchFromScmUrl( String scmUrl )
673 {
674 String scmBranch = "UNKNOWN";
675
676 if ( StringUtils.contains( scmUrl, "/trunk" ) )
677 {
678 scmBranch = "trunk";
679 }
680 else if ( StringUtils.contains( scmUrl, "/branches" ) || StringUtils.contains( scmUrl, "/tags" ) )
681 {
682 scmBranch = scmUrl.replaceFirst( ".*((branches|tags)[^/]*).*?", "$1" );
683 }
684 return scmBranch;
685 }
686
687
688
689
690
691
692
693 public String getRevision()
694 throws MojoExecutionException
695 {
696
697 if ( format != null )
698 {
699 return revision;
700 }
701
702 try
703 {
704 ScmRepository repository = getScmRepository();
705
706 InfoScmResult scmResult = info( repository, new ScmFileSet( scmDirectory ) );
707
708 if ( scmResult == null || scmResult.getInfoItems().isEmpty() )
709 {
710 return ( !StringUtils.isEmpty( revisionOnScmFailure ) ) ? revisionOnScmFailure : null;
711 }
712
713 checkResult( scmResult );
714
715 InfoItem info = scmResult.getInfoItems().get( 0 );
716
717 if ( useLastCommittedRevision )
718 {
719 return info.getLastChangedRevision();
720 }
721
722 return info.getRevision();
723 }
724 catch ( ScmException e )
725 {
726 if ( !StringUtils.isEmpty( revisionOnScmFailure ) )
727 {
728 getLog().warn(
729 "Cannot get the revision information from the scm repository, proceeding with " + "revision of "
730 + revisionOnScmFailure + " : \n" + e.getLocalizedMessage() );
731
732 setDoCheck( false );
733 setDoUpdate( false );
734
735 return revisionOnScmFailure;
736 }
737
738 throw new MojoExecutionException(
739 "Cannot get the revision information from the scm repository : \n" + e.getLocalizedMessage(), e );
740
741 }
742
743 }
744
745
746
747
748
749
750
751
752
753
754
755 public InfoScmResult info( ScmRepository repository, ScmFileSet fileSet )
756 throws ScmException
757 {
758 CommandParameters commandParameters = new CommandParameters();
759
760 if ( GitScmProviderRepository.PROTOCOL_GIT.equals(
761 scmManager.getProviderByRepository( repository ).getScmType() )
762 && this.shortRevisionLength != DEFAULT_SHORT_REVISION_DISABLED )
763 {
764 getLog().info( "ShortRevision tag detected. The value is '" + this.shortRevisionLength + "'." );
765 if ( shortRevisionLength >= 0 && shortRevisionLength < 4 )
766 {
767 getLog().warn(
768 "shortRevision parameter less then 4. ShortRevisionLength is relaying on 'git rev-parese --short=LENGTH' command, accordingly to Git rev-parse specification the LENGTH value is miminum 4. " );
769 }
770 commandParameters.setInt( CommandParameter.SCM_SHORT_REVISION_LENGTH, this.shortRevisionLength );
771 }
772
773 return scmManager.getProviderByRepository( repository ).info( repository.getProviderRepository(), fileSet,
774 commandParameters );
775 }
776
777
778
779
780
781
782 private ScmLogger getLogger()
783 {
784 if ( logger == null )
785 {
786 logger = new ScmLogDispatcher();
787 }
788 return logger;
789 }
790
791 private ScmRepository getScmRepository()
792 throws ScmException
793 {
794 ScmRepository repository = scmManager.makeScmRepository( StringUtils.isBlank( urlScm ) ? readUrlScm : urlScm );
795
796 ScmProviderRepository scmRepo = repository.getProviderRepository();
797
798 if ( !StringUtils.isEmpty( username ) )
799 {
800 scmRepo.setUser( username );
801 }
802
803 if ( !StringUtils.isEmpty( password ) )
804 {
805 scmRepo.setPassword( password );
806 }
807
808 return repository;
809 }
810
811 private void checkResult( ScmResult result )
812 throws ScmException
813 {
814 if ( !result.isSuccess() )
815 {
816
817 getLog().error( "Provider message:" );
818
819 getLog().error( result.getProviderMessage() );
820
821 getLog().error( "Command output:" );
822
823 getLog().error( result.getCommandOutput() );
824
825 throw new ScmException( "Error!" );
826 }
827 }
828
829
830
831 public void setScmManager( ScmManager scmManager )
832 {
833 this.scmManager = scmManager;
834 }
835
836 public void setUrlScm( String urlScm )
837 {
838 this.urlScm = urlScm;
839 }
840
841 public void setUsername( String username )
842 {
843 this.username = username;
844 }
845
846 public void setPassword( String password )
847 {
848 this.password = password;
849 }
850
851 public void setDoCheck( boolean doCheck )
852 {
853 String doCheckSystemProperty = System.getProperty( "maven.buildNumber.doCheck" );
854 if ( doCheckSystemProperty != null )
855 {
856
857 this.doCheck = Boolean.valueOf( doCheckSystemProperty ).booleanValue();
858 }
859 else
860 {
861 this.doCheck = doCheck;
862 }
863 }
864
865 public void setDoUpdate( boolean doUpdate )
866 {
867 String doUpdateSystemProperty = System.getProperty( "maven.buildNumber.doUpdate" );
868 if ( doUpdateSystemProperty != null )
869 {
870
871 this.doUpdate = Boolean.valueOf( doUpdateSystemProperty ).booleanValue();
872 }
873 else
874 {
875 this.doUpdate = doUpdate;
876 }
877 }
878
879 void setFormat( String format )
880 {
881 this.format = format;
882 }
883
884 void setLocale( String locale )
885 {
886 this.locale = locale;
887 }
888
889 void setItems( List items )
890 {
891 this.items = items;
892 }
893
894 public void setBuildNumberPropertiesFileLocation( File buildNumberPropertiesFileLocation )
895 {
896 this.buildNumberPropertiesFileLocation = buildNumberPropertiesFileLocation;
897 }
898
899 public void setScmDirectory( File scmDirectory )
900 {
901 this.scmDirectory = scmDirectory;
902 }
903
904 public void setRevisionOnScmFailure( String revisionOnScmFailure )
905 {
906 this.revisionOnScmFailure = revisionOnScmFailure;
907 }
908
909 public void setShortRevisionLength( int shortRevision )
910 {
911 this.shortRevisionLength = shortRevision;
912 }
913 }