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.tools.impl;
020
021 import java.io.File;
022 import java.net.URI;
023 import java.net.URL;
024 import java.util.Vector;
025
026 import net.dpml.library.Builder;
027 import net.dpml.library.Library;
028 import net.dpml.library.Resource;
029
030 import net.dpml.tools.Context;
031 import net.dpml.tools.BuildError;
032 import net.dpml.tools.info.BuilderDirective;
033 import net.dpml.tools.info.BuilderDirectiveHelper;
034
035 import net.dpml.transit.Artifact;
036 import net.dpml.transit.model.TransitModel;
037 import net.dpml.transit.tools.MainTask;
038
039 import net.dpml.util.Logger;
040
041 import org.apache.tools.ant.Project;
042 import org.apache.tools.ant.ProjectHelper;
043 import org.apache.tools.ant.BuildException;
044 import org.apache.tools.ant.BuildLogger;
045 import org.apache.tools.ant.DefaultLogger;
046 import org.apache.tools.ant.input.DefaultInputHandler;
047 import org.apache.tools.ant.DemuxInputStream;
048
049 /**
050 * The StandardBuilder is a plugin established by the Tools build controller
051 * used for the building of a project based on the Ant build system in conjunction
052 * with Transit plugin management services.
053 *
054 * @author <a href="http://www.dpml.net">Digital Product Meta Library</a>
055 * @version 1.1.3
056 */
057 public class StandardBuilder implements Builder
058 {
059 // ------------------------------------------------------------------------
060 // static
061 // ------------------------------------------------------------------------
062
063 /**
064 * The default template uri path.
065 */
066 public static final String DEFAULT_TEMPLATE_URN = "local:template:dpml/tools/standard";
067
068 /**
069 * The builder configuration.
070 */
071 public static final BuilderDirective CONFIGURATION = loadConfiguration();
072
073 private static BuilderDirective loadConfiguration()
074 {
075 try
076 {
077 return BuilderDirectiveHelper.build();
078 }
079 catch( Throwable e )
080 {
081 final String error =
082 "Internal error while attempting to establish the builder configuration.";
083 BuilderError be = new BuilderError( error, e );
084 be.printStackTrace();
085 return null;
086 }
087 }
088
089 // ------------------------------------------------------------------------
090 // state
091 // ------------------------------------------------------------------------
092
093 private Logger m_logger;
094 private TransitModel m_model;
095 private Library m_library;
096 private boolean m_verbose;
097 private Throwable m_result;
098
099 // ------------------------------------------------------------------------
100 // constructors
101 // ------------------------------------------------------------------------
102
103 /**
104 * Creation of a new standard builder.
105 *
106 * @param logger assigned logging channel
107 * @param library the library
108 */
109 public StandardBuilder( Logger logger, Library library )
110 {
111 this( logger, library, false );
112 }
113
114 /**
115 * Creation of a new standard builder.
116 *
117 * @param logger assigned logging channel
118 * @param library the library
119 * @param verbose verbose execution flag
120 */
121 public StandardBuilder( Logger logger, Library library, boolean verbose )
122 {
123 m_logger = logger;
124 m_verbose = verbose;
125 m_library = library;
126
127 Thread.currentThread().setContextClassLoader( Builder.class.getClassLoader() );
128 }
129
130 // ------------------------------------------------------------------------
131 // Builder
132 // ------------------------------------------------------------------------
133
134 /**
135 * Build the project defined by the supplied resource.
136 * @param resource the project definition
137 * @param targets an array of build target names
138 * @return the build success status
139 */
140 public boolean build( Resource resource, String[] targets )
141 {
142 Project project = null;
143 File template = null;
144 try
145 {
146 project = createProject( resource );
147 }
148 catch( Throwable e )
149 {
150 m_result = e;
151 if( m_logger.isErrorEnabled() )
152 {
153 final String error =
154 "Failed to construct embedded project."
155 + "\nResource: " + resource.getResourcePath()
156 + "\nBasedir: " + resource.getBaseDir();
157 m_logger.error( error, e );
158 }
159 return false;
160 }
161 try
162 {
163 template = getTemplateFile( resource );
164 }
165 catch( Throwable e )
166 {
167 m_result = e;
168 if( m_logger.isErrorEnabled() )
169 {
170 final String error =
171 "Failed to load template file."
172 + "\nResource: " + resource.getResourcePath()
173 + "\nBasedir: " + resource.getBaseDir();
174 m_logger.error( error, e );
175 }
176 return false;
177 }
178 return build( resource, project, template, targets );
179 }
180
181 /**
182 * Return the template for the resource.
183 * @param resource the project definition
184 * @return the template
185 */
186 public File getTemplateFile( Resource resource )
187 {
188 try
189 {
190 String systemOverride = System.getProperty( "project.template" );
191 String override = resource.getProperty( "project.template", systemOverride );
192 if( null != override )
193 {
194 File template = getTemplateFile( override );
195 return template;
196 }
197
198 File basedir = resource.getBaseDir();
199 String buildfile = resource.getProperty( "project.buildfile" );
200 String defaultBuildfile = resource.getProperty( "project.standard.buildfile", "build.xml" );
201 if( null != buildfile )
202 {
203 // there is an explicit 'project.buildfile' declaration in which case
204 // we check for existance and fail if it does not exist
205
206 File file = new File( basedir, buildfile );
207 if( file.exists() )
208 {
209 return file;
210 }
211 else
212 {
213 final String error =
214 "Resource buildfile ["
215 + file
216 + "] does not exist.";
217 throw new BuildException( error );
218 }
219 }
220 else if( null != defaultBuildfile )
221 {
222 // check if a buildfile of the default name exists in the project's
223 // basedir - and if so - use it to build the project
224
225 File file = new File( basedir, defaultBuildfile );
226 if( file.exists() )
227 {
228 return file;
229 }
230 }
231
232 // otherwise we build using either an explicit or default template
233 // resolved via a uri (typically a template stored in prefs)
234
235 String defaultTemplateSpec =
236 resource.getProperty( "project.standard.template", DEFAULT_TEMPLATE_URN );
237 String templateSpec = resource.getProperty( "project.template", defaultTemplateSpec );
238
239 if( null != templateSpec )
240 {
241 File template = getTemplateFile( templateSpec );
242 return template;
243 }
244 else
245 {
246 final String error =
247 "Resource template property 'project.template' is undefined.";
248 throw new BuildException( error );
249 }
250 }
251 catch( BuildException e )
252 {
253 throw e;
254 }
255 catch( Throwable e )
256 {
257 m_result = e;
258 final String error =
259 "Unexpected error while attempting to resolve project template."
260 + "\nResource path: "
261 + resource.getResourcePath();
262 throw new BuildError( error, e );
263 }
264 }
265
266 // ------------------------------------------------------------------------
267 // implementation
268 // ------------------------------------------------------------------------
269
270 boolean build( Resource resource, Project project, File template, String[] targets )
271 {
272 try
273 {
274 ProjectHelper helper = (ProjectHelper) project.getReference( "ant.projectHelper" );
275
276 if( null != template )
277 {
278 helper.parse( project, template );
279 }
280
281 Vector vector = new Vector();
282
283 if( targets.length == 0 )
284 {
285 if( null != project.getDefaultTarget() )
286 {
287 vector.addElement( project.getDefaultTarget() );
288 }
289 else
290 {
291 vector.addElement( CONFIGURATION.getDefaultPhase() );
292 }
293 }
294 else
295 {
296 for( int i=0; i<targets.length; i++ )
297 {
298 String target = targets[i];
299 vector.addElement( target );
300 }
301 }
302
303 if( vector.size() == 0 )
304 {
305 final String errorMessage =
306 "No targets requested and no default target declared.";
307 throw new BuildException( errorMessage );
308 }
309
310 project.executeTargets( vector );
311 return true;
312 }
313 catch( BuildException e )
314 {
315 m_result = e;
316 if( m_logger.isDebugEnabled() )
317 {
318 final String error =
319 "Build failure."
320 + "\nProject: " + resource.getResourcePath()
321 + "\nBasedir: " + resource.getBaseDir()
322 + "\nTemplate: " + template
323 + "\nLocation: " + e.getLocation();
324 Throwable cause = e.getCause();
325 m_logger.error( error, cause );
326 }
327 return false;
328 }
329 catch( Throwable e )
330 {
331 System.out.println(
332 "# UNEXPECTED: "
333 + e.toString()
334 + " ("
335 + m_logger.isErrorEnabled()
336 + ")" );
337
338 m_result = e;
339 if( m_logger.isErrorEnabled() )
340 {
341 final String error =
342 "Build error."
343 + "\nProject: " + resource.getResourcePath()
344 + "\nBasedir: " + resource.getBaseDir()
345 + "\nTemplate: " + template;
346 m_logger.error( error, e );
347 }
348 return false;
349 }
350 finally
351 {
352 project.fireBuildFinished( m_result );
353 }
354 }
355
356 private File getTemplateFile( String spec )
357 {
358 try
359 {
360 URI uri = new URI( spec );
361 if( Artifact.isRecognized( uri ) )
362 {
363 URL url = uri.toURL();
364 return (File) url.getContent( new Class[]{File.class} );
365 }
366 }
367 catch( Throwable e )
368 {
369 }
370 return new File( spec );
371 }
372
373 Project createProject( Resource resource )
374 {
375 try
376 {
377 Project project = newProject();
378 Context context = new DefaultContext( resource, project );
379 return project;
380 }
381 catch( Exception e )
382 {
383 final String error =
384 "Unable to establish build context."
385 + "\nProject: " + resource;
386 throw new BuildError( error, e );
387 }
388 }
389
390 private Project newProject()
391 {
392 Project project = new Project();
393 project.setSystemProperties();
394 project.setDefaultInputStream( System.in );
395 setupTransitComponentHelper( project );
396 project.setCoreLoader( getClass().getClassLoader() );
397 project.addBuildListener( createLogger() );
398 System.setIn( new DemuxInputStream( project ) );
399 project.setProjectReference( new DefaultInputHandler() );
400 ProjectHelper helper = ProjectHelper.getProjectHelper();
401 project.addReference( "ant.projectHelper", helper );
402 return project;
403 }
404
405 private void setupTransitComponentHelper( Project project )
406 {
407 try
408 {
409 MainTask task = new MainTask();
410 task.setProject( project );
411 task.init();
412 task.execute();
413 }
414 catch( BuildException e )
415 {
416 throw e;
417 }
418 catch( Exception e )
419 {
420 final String error =
421 "Setup failure.";
422 throw new BuildException( error, e );
423 }
424 }
425
426 private BuildLogger createLogger()
427 {
428 BuildLogger logger = new DefaultLogger();
429 if( m_verbose )
430 {
431 logger.setMessageOutputLevel( Project.MSG_VERBOSE );
432 }
433 else
434 {
435 logger.setMessageOutputLevel( Project.MSG_INFO );
436 }
437 logger.setOutputPrintStream( System.out );
438 logger.setErrorPrintStream( System.err );
439 return logger;
440 }
441
442 private Logger getLogger()
443 {
444 return m_logger;
445 }
446 }
447