001 /* 002 * Copyright 2005 Stephen J. McConnell. 003 * 004 * Licensed under the Apache License, Version 2.0 (the "License"); 005 * you may not use this file except in compliance with the License. 006 * You may obtain a copy of the License at 007 * 008 * http://www.apache.org/licenses/LICENSE-2.0 009 * 010 * Unless required by applicable law or agreed to in writing, software 011 * distributed under the License is distributed on an "AS IS" BASIS, 012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 013 * implied. 014 * 015 * See the License for the specific language governing permissions and 016 * limitations under the License. 017 */ 018 019 package net.dpml.library.console; 020 021 import java.io.File; 022 import java.io.IOException; 023 import java.net.URI; 024 import java.util.ArrayList; 025 import java.util.List; 026 import java.util.Map; 027 import java.util.Hashtable; 028 029 import net.dpml.util.Logger; 030 031 import net.dpml.lang.Part; 032 033 import net.dpml.library.Module; 034 import net.dpml.library.Resource; 035 import net.dpml.library.Builder; 036 import net.dpml.library.Type; 037 import net.dpml.library.info.ResourceDirective.Classifier; 038 import net.dpml.library.info.Scope; 039 import net.dpml.library.impl.DefaultLibrary; 040 041 import net.dpml.cli.Option; 042 import net.dpml.cli.Group; 043 import net.dpml.cli.CommandLine; 044 import net.dpml.cli.commandline.Parser; 045 import net.dpml.cli.util.HelpFormatter; 046 import net.dpml.cli.OptionException; 047 import net.dpml.cli.DisplaySetting; 048 import net.dpml.cli.builder.ArgumentBuilder; 049 import net.dpml.cli.builder.GroupBuilder; 050 import net.dpml.cli.builder.DefaultOptionBuilder; 051 import net.dpml.cli.builder.CommandBuilder; 052 import net.dpml.cli.option.PropertyOption; 053 import net.dpml.cli.validation.URIValidator; 054 055 /** 056 * Plugin that handles multi-project builds based on supplied commandline arguments. 057 * 058 * @author <a href="http://www.dpml.net">Digital Product Meta Library</a> 059 * @version 1.0.0 060 */ 061 public class BuilderPlugin 062 { 063 // ------------------------------------------------------------------------ 064 // state 065 // ------------------------------------------------------------------------ 066 067 private final Logger m_logger; 068 private final DefaultLibrary m_library; 069 private final Map m_map = new Hashtable(); 070 071 private boolean m_verbose; 072 private boolean m_expand = false; 073 074 // ------------------------------------------------------------------------ 075 // constructors 076 // ------------------------------------------------------------------------ 077 078 /** 079 * Builder establishment. System property have already been assigned 080 * to the current jvm by depot. 081 * 082 * @param logger the assigned logging channel 083 * @param args supplimentary command line arguments 084 * @exception Exception if the build fails 085 */ 086 public BuilderPlugin( Logger logger, String[] args ) 087 throws Exception 088 { 089 m_logger = logger; 090 m_library = new DefaultLibrary( logger ); 091 092 Thread.currentThread().setContextClassLoader( Builder.class.getClassLoader() ); 093 094 Parser parser = new Parser(); 095 parser.setGroup( COMMAND_GROUP ); 096 097 try 098 { 099 CommandLine line = parser.parse( args ); 100 m_verbose = line.hasOption( VERBOSE_OPTION ); 101 if( line.hasOption( HELP_OPTION ) ) 102 { 103 processHelp(); 104 System.exit( 0 ); 105 } 106 else 107 { 108 // setup the build version 109 110 if( line.hasOption( VERSION_OPTION ) ) 111 { 112 String version = (String) line.getValue( VERSION_OPTION, "SNAPSHOT" ); 113 System.setProperty( "build.signature", version ); 114 if( m_verbose ) 115 { 116 getLogger().info( "Setting version to: " + version ); 117 } 118 } 119 120 if( line.hasOption( LIST_OPTION ) ) 121 { 122 m_expand = line.hasOption( EXPAND_OPTION ); 123 Resource[] resources = getTargetSelection( line ); 124 if( resources.length == 0 ) 125 { 126 getLogger().info( "Empty selection." ); 127 System.exit( 0 ); 128 } 129 else 130 { 131 list( resources ); 132 } 133 } 134 else 135 { 136 Resource[] resources = getSelection( line ); 137 if( resources.length == 0 ) 138 { 139 getLogger().info( "Empty selection." ); 140 System.exit( 0 ); 141 } 142 boolean result = process( line, resources ); 143 if( !result ) 144 { 145 System.exit( 1 ); 146 } 147 else 148 { 149 System.exit( 0 ); 150 } 151 } 152 } 153 } 154 catch( OptionException e ) 155 { 156 m_logger.error( e.getMessage() ); 157 } 158 } 159 160 private Part createPart( URI uri ) throws Exception 161 { 162 try 163 { 164 Thread.currentThread().setContextClassLoader( Builder.class.getClassLoader() ); 165 return Part.load( uri ); 166 } 167 catch( Exception e ) 168 { 169 final String error = 170 "Unexpected error occured while attempting to load builder.\nURI: " 171 + uri; 172 throw new BuilderError( error, e ); 173 } 174 } 175 176 private Builder createBuilder( Part part ) throws Exception 177 { 178 Object[] params = new Object[]{m_logger, m_library, new Boolean( m_verbose )}; 179 return (Builder) part.instantiate( params ); 180 } 181 182 /** 183 * Resolve the project selection taking into account any overriding -s 184 * selection option, the -c switch, or in the absence of a selction, the 185 * implicit slection relative to the current working directory. 186 * 187 * @param line the commandline 188 * @return the resolved array of resources sorted relative to build sequence 189 */ 190 private Resource[] getSelection( CommandLine line ) throws Exception 191 { 192 ArrayList list = new ArrayList(); 193 Resource[] targets = getTargetSelection( line ); 194 for( int i=0; i<targets.length; i++ ) 195 { 196 Resource project = targets[i]; 197 if( Classifier.LOCAL.equals( project.getClassifier() ) ) 198 { 199 list.add( project ); 200 } 201 } 202 return (Resource[]) list.toArray( new Resource[ list.size() ] ); 203 } 204 205 /** 206 * Get the base selection and check if the consumer switch is present and 207 * if so build the consumers list from the selection list. 208 * 209 * @param line the commandline 210 * @return the array of projects in build order 211 */ 212 private Resource[] getTargetSelection( CommandLine line ) throws Exception 213 { 214 Resource[] resources = getBaseSelection( line ); 215 if( resources.length == 0 ) 216 { 217 return resources; 218 } 219 boolean flag = line.hasOption( CONSUMERS_OPTION ); 220 if( flag ) 221 { 222 if( resources.length != 1 ) 223 { 224 final String error = 225 "Consumer resolution against a multi-element selection is not supported."; 226 getLogger().error( error ); 227 return new Resource[0]; 228 } 229 else 230 { 231 Resource resource = resources[0]; 232 return resource.getConsumers( true, true ); 233 } 234 } 235 else 236 { 237 return resources; 238 } 239 } 240 241 /** 242 * Get the set of projects taking into consideration either the 243 * overriding selection option or the base directory if no selection specificed. 244 * 245 * @param line the commandline 246 * @return the array of projects in build order 247 */ 248 private Resource[] getBaseSelection( CommandLine line ) throws Exception 249 { 250 String selection = (String) line.getValue( SELECT_OPTION, null ); 251 if( null != selection ) 252 { 253 getLogger().debug( "parsing selection: " + selection ); 254 Resource[] resources = m_library.select( selection, false, true ); 255 getLogger().debug( "selection: " + resources.length ); 256 return resources; 257 } 258 else 259 { 260 String work = System.getProperty( "user.dir" ); 261 getLogger().debug( "resolving selection in: " + work ); 262 File file = new File( work ).getCanonicalFile(); 263 Resource[] resources = m_library.select( file ); 264 return resources; 265 } 266 } 267 268 /** 269 * Build the supplied set of projects. If a build filure occurs then 270 * abort the build sequence and exit. 271 * 272 * @param line the commandline 273 * @param resources the sorted sequence of projects to build 274 */ 275 private boolean process( CommandLine line, Resource[] resources ) throws Exception 276 { 277 URI uri = (URI) line.getValue( BUILDER_URI_OPTION, ANT_BUILDER_URI ); 278 279 Part part = createPart( uri ); 280 Builder builder = createBuilder( part ); 281 282 if( resources.length > 1 ) 283 { 284 StringBuffer buffer = 285 new StringBuffer( "Initiating build sequence: (" + resources.length + ")\n" ); 286 for( int i=0; i<resources.length; i++ ) 287 { 288 Resource resource = resources[i]; 289 buffer.append( "\n (" + ( i+1 ) + ")\t" + resource.getResourcePath() ); 290 } 291 buffer.append( "\n" ); 292 getLogger().info( buffer.toString() ); 293 } 294 295 List list = line.getValues( TARGETS ); 296 String[] targets = (String[]) list.toArray( new String[ list.size() ] ); 297 for( int i=0; i<resources.length; i++ ) 298 { 299 Resource resource = resources[i]; 300 boolean status = builder.build( resource, targets ); 301 if( !status ) 302 { 303 return status; 304 } 305 System.gc(); 306 } 307 return true; 308 } 309 310 private static final URI ANT_BUILDER_URI; 311 312 static 313 { 314 try 315 { 316 ANT_BUILDER_URI = new URI( "artifact:part:dpml/depot/dpml-tools-builder#1.0.0" ); 317 } 318 catch( Exception e ) 319 { 320 throw new RuntimeException( "will not happen", e ); 321 } 322 } 323 324 /** 325 * Build the supplied set of projects. If a build filure occurs then 326 * abort the build sequence and exit. 327 * 328 * @param resources the sorted sequence of prouject to build 329 * @exception Exception if an error occurs 330 */ 331 private void list( Resource[] resources ) throws Exception 332 { 333 if( resources.length == 1 ) 334 { 335 Resource resource = resources[0]; 336 if( resource instanceof Module ) 337 { 338 listModule( (Module) resource ); 339 } 340 else 341 { 342 listResource( resource ); 343 } 344 } 345 else 346 { 347 listResources( resources ); 348 } 349 } 350 351 private void listModule( Module module ) throws Exception 352 { 353 print( "Listing module [" + module.getResourcePath() + "]\n" ); 354 listResource( " ", module, 0 ); 355 print( "" ); 356 } 357 358 private void listResource( Resource project ) throws Exception 359 { 360 print( "Listing project: " + project.getResourcePath() + "\n" ); 361 listResource( " ", project, 0 ); 362 print( "" ); 363 } 364 365 private void listResources( Resource[] resources ) throws Exception 366 { 367 print( "Selection: [" + resources.length + "]\n" ); 368 for( int i=0; i<resources.length; i++ ) 369 { 370 Resource resource = resources[i]; 371 String label = getLabel( i+1 ); 372 print( label + resource ); 373 } 374 print( "" ); 375 } 376 377 378 private void listResource( String pad, Resource resource, int n ) throws Exception 379 { 380 if( n > 0 ) 381 { 382 print( "\n[" + n + "] " + resource ); 383 } 384 else 385 { 386 print( "\n" + resource ); 387 } 388 print( "" ); 389 print( pad + "version: " + resource.getVersion() ); 390 print( pad + "basedir: " + resource.getBaseDir() ); 391 String p = pad + " "; 392 Type[] types = resource.getTypes(); 393 if( types.length > 0 ) 394 { 395 print( pad + "types: (" + types.length + ")" ); 396 for( int i=0; i<types.length; i++ ) 397 { 398 print( p + types[i].getID() ); 399 } 400 } 401 402 Resource[] resources = resource.getProviders( Scope.BUILD, m_expand, true ); 403 if( resources.length > 0 ) 404 { 405 print( pad + "build phase providers: (" + resources.length + ")" ); 406 for( int i=0; i<resources.length; i++ ) 407 { 408 Resource res = resources[i]; 409 print( p + res ); 410 } 411 } 412 resources = resource.getProviders( Scope.RUNTIME, m_expand, true ); 413 if( resources.length > 0 ) 414 { 415 print( pad + "runtime providers: (" + resources.length + ")" ); 416 for( int i=0; i<resources.length; i++ ) 417 { 418 Resource res = resources[i]; 419 print( p + res ); 420 } 421 } 422 resources = resource.getProviders( Scope.TEST, m_expand, true ); 423 if( resources.length > 0 ) 424 { 425 print( pad + "test providers: (" + resources.length + ")" ); 426 for( int i=0; i<resources.length; i++ ) 427 { 428 Resource res = resources[i]; 429 print( p + res ); 430 } 431 } 432 } 433 434 static void print( String message ) 435 { 436 System.out.println( message ); 437 } 438 439 private static String getLabel( int n ) 440 { 441 StringBuffer buffer = new StringBuffer(); 442 buffer.append( " [" + n ); 443 buffer.append( "]" ); 444 buffer.append( " " ); 445 String tag = buffer.toString(); 446 return tag.substring( 0, 7 ) + " "; 447 } 448 449 /** 450 * List general command help to the console. 451 * @exception IOException if an I/O error occurs 452 */ 453 private void processHelp() throws IOException 454 { 455 HelpFormatter formatter = new HelpFormatter( 456 HelpFormatter.DEFAULT_GUTTER_LEFT, 457 HelpFormatter.DEFAULT_GUTTER_CENTER, 458 HelpFormatter.DEFAULT_GUTTER_RIGHT, 459 100 ); 460 461 formatter.getDisplaySettings().add( DisplaySetting.DISPLAY_GROUP_OUTER ); 462 formatter.getDisplaySettings().add( DisplaySetting.DISPLAY_PROPERTY_OPTION ); 463 formatter.getDisplaySettings().add( DisplaySetting.DISPLAY_ARGUMENT_BRACKETED ); 464 465 formatter.getFullUsageSettings().add( DisplaySetting.DISPLAY_OPTIONAL ); 466 formatter.getFullUsageSettings().add( DisplaySetting.DISPLAY_GROUP_OUTER ); 467 formatter.getFullUsageSettings().add( DisplaySetting.DISPLAY_PROPERTY_OPTION ); 468 formatter.getFullUsageSettings().add( DisplaySetting.DISPLAY_OPTIONAL ); 469 formatter.getFullUsageSettings().add( DisplaySetting.DISPLAY_ARGUMENT_BRACKETED ); 470 formatter.getFullUsageSettings().remove( DisplaySetting.DISPLAY_PARENT_CHILDREN ); 471 472 formatter.getLineUsageSettings().add( DisplaySetting.DISPLAY_PROPERTY_OPTION ); 473 formatter.getLineUsageSettings().add( DisplaySetting.DISPLAY_ARGUMENT_BRACKETED ); 474 formatter.getLineUsageSettings().remove( DisplaySetting.DISPLAY_PARENT_CHILDREN ); 475 formatter.getLineUsageSettings().remove( DisplaySetting.DISPLAY_GROUP_EXPANDED ); 476 477 formatter.setGroup( COMMAND_GROUP ); 478 formatter.setShellCommand( "build" ); 479 formatter.print(); 480 } 481 482 private Logger getLogger() 483 { 484 return m_logger; 485 } 486 487 private static final DefaultOptionBuilder OPTION_BUILDER = new DefaultOptionBuilder(); 488 private static final ArgumentBuilder ARGUMENT_BUILDER = new ArgumentBuilder(); 489 private static final GroupBuilder GROUP_BUILDER = new GroupBuilder(); 490 private static final CommandBuilder COMMAND_BUILDER = new CommandBuilder(); 491 private static final PropertyOption PROPERTY_OPTION = new PropertyOption(); 492 493 private static final Option HELP_OPTION = 494 OPTION_BUILDER 495 .withShortName( "help" ) 496 .withShortName( "h" ) 497 .withDescription( "List command help." ) 498 .withRequired( false ) 499 .create(); 500 501 private static final Option SELECT_OPTION = 502 OPTION_BUILDER 503 .withShortName( "select" ) 504 .withShortName( "s" ) 505 .withDescription( "Build selected project(s)." ) 506 .withRequired( false ) 507 .withArgument( 508 ARGUMENT_BUILDER 509 .withDescription( "Project." ) 510 .withName( "pattern" ) 511 .withMinimum( 1 ) 512 .withMaximum( 1 ) 513 .create() ) 514 .create(); 515 516 private static final Option VERBOSE_OPTION = 517 OPTION_BUILDER 518 .withShortName( "verbose" ) 519 .withShortName( "v" ) 520 .withDescription( "Enable verbose mode." ) 521 .withRequired( false ) 522 .create(); 523 524 private static final Option LIST_OPTION = 525 OPTION_BUILDER 526 .withShortName( "list" ) 527 .withShortName( "l" ) 528 .withDescription( "List selected project(s)." ) 529 .withRequired( false ) 530 .withArgument( 531 ARGUMENT_BUILDER 532 .withDescription( "Project." ) 533 .withName( "pattern" ) 534 .withMinimum( 0 ) 535 .withMaximum( 1 ) 536 .create() ) 537 .create(); 538 539 private static final Option EXPAND_OPTION = 540 OPTION_BUILDER 541 .withShortName( "expand" ) 542 .withShortName( "e" ) 543 .withDescription( "Expand dependencies." ) 544 .withRequired( false ) 545 .create(); 546 547 private static final Option CONSUMERS_OPTION = 548 OPTION_BUILDER 549 .withShortName( "consumers" ) 550 .withShortName( "c" ) 551 .withDescription( "Consumer switch." ) 552 .withRequired( false ) 553 .create(); 554 555 private static final Option VERSION_OPTION = 556 OPTION_BUILDER 557 .withShortName( "version" ) 558 .withDescription( "Build output artifact version." ) 559 .withRequired( false ) 560 .withArgument( 561 ARGUMENT_BUILDER 562 .withDescription( "Created artifact version." ) 563 .withName( "version" ) 564 .withMinimum( 1 ) 565 .withMaximum( 1 ) 566 .create() ) 567 .create(); 568 569 private static final Option BUILDER_URI_OPTION = 570 OPTION_BUILDER 571 .withShortName( "plugin" ) 572 .withDescription( "Default builder plugin uri." ) 573 .withRequired( false ) 574 .withArgument( 575 ARGUMENT_BUILDER 576 .withDescription( "Artifact reference." ) 577 .withName( "artifact" ) 578 .withMinimum( 1 ) 579 .withMaximum( 1 ) 580 .withValidator( new URIValidator() ) 581 .create() ) 582 .create(); 583 584 private static final Option TARGETS = 585 ARGUMENT_BUILDER 586 .withName( "target" ) 587 .create(); 588 589 private static final Group LIST_GROUP = 590 GROUP_BUILDER 591 .withMinimum( 0 ) 592 .withOption( LIST_OPTION ) 593 .withOption( EXPAND_OPTION ) 594 .withOption( CONSUMERS_OPTION ) 595 .create(); 596 597 private static final Group BUILD_GROUP = 598 GROUP_BUILDER 599 .withMinimum( 0 ) 600 .withOption( SELECT_OPTION ) 601 .withOption( CONSUMERS_OPTION ) 602 .withOption( VERBOSE_OPTION ) 603 .withOption( BUILDER_URI_OPTION ) 604 .withOption( VERSION_OPTION ) 605 .withOption( PROPERTY_OPTION ) 606 .create(); 607 608 private static final Group COMMAND_GROUP = 609 GROUP_BUILDER 610 .withName( "options" ) 611 .withOption( HELP_OPTION ) 612 .withOption( LIST_GROUP ) 613 .withOption( BUILD_GROUP ) 614 .withOption( TARGETS ) 615 .create(); 616 } 617