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