OSDN Git Service

1b140739b91213fde0ae343ffee73f7ba5fd193c
[handbrake-jp/handbrake-jp-git.git] / make / configure.py
1 ###############################################################################
2 ##
3 ## This script is coded for minimum version of Python 2.4 .
4 ## Pyhthon3 is incompatible.
5 ##
6 ## Authors: konablend
7 ##
8 ###############################################################################
9
10 import fnmatch
11 import optparse
12 import os
13 import platform
14 import re
15 import subprocess
16 import sys
17 import time
18
19 from optparse import OptionGroup
20 from optparse import OptionGroup
21 from optparse import OptionParser
22 from sys import stderr
23 from sys import stdout
24
25 class AbortError( Exception ):
26     def __init__( self, format, *args ):
27         self.value = format % args
28     def __str__( self ):
29         return self.value
30
31 ###############################################################################
32 ##
33 ## Main configure object.
34 ##
35 ## dir = containing this configure script
36 ## cwd = current working dir at time of script launch
37 ##
38 class Configure( object ):
39     OUT_QUIET   = 0
40     OUT_INFO    = 1
41     OUT_VERBOSE = 2
42
43     def __init__( self, verbose ):
44         self._log_info    = []
45         self._log_verbose = []
46         self._record      = False
47
48         self.verbose = verbose
49         self.dir = os.path.dirname( sys.argv[0] )
50         self.cwd = os.getcwd()
51
52         self.build_dir = '.'
53
54         ## compute src dir which is 2 dirs up from this script
55         self.src_dir = os.path.normpath( sys.argv[0] )
56         for i in range( 2 ):
57             self.src_dir = os.path.dirname( self.src_dir )
58         if len( self.src_dir ) == 0:
59             self.src_dir = os.curdir
60
61     def _final_dir( self, chdir, dir ):
62         dir = os.path.normpath( dir )
63         if not os.path.isabs( dir ):
64             if os.path.isabs( chdir ):
65                 dir = os.path.normpath( os.path.abspath(dir ))
66             else:
67                 dir = os.path.normpath( self.relpath( dir, chdir ))
68         return dir
69
70     ## output functions
71     def errln( self, format, *args ):
72         s = (format % args)
73         if re.match( '^.*[!?:;.]$', s ):
74             stderr.write( 'ERROR: %s configure stop.\n' % (s) )
75         else:
76             stderr.write( 'ERROR: %s; configure stop.\n' % (s) )
77         self.record_log()
78         sys.exit( 1 )
79     def infof( self, format, *args ):
80         line = format % args
81         self._log_verbose.append( line )
82         if cfg.verbose >= Configure.OUT_INFO:
83             self._log_info.append( line )
84             stdout.write( line )
85     def verbosef( self, format, *args ):
86         line = format % args
87         self._log_verbose.append( line )
88         if cfg.verbose >= Configure.OUT_VERBOSE:
89             stdout.write( line )
90
91     ## doc is ready to be populated
92     def doc_ready( self ):
93         ## compute final paths as they are after chdir into build
94         self.build_final  = os.curdir
95         self.src_final    = self._final_dir( self.build_dir, self.src_dir )
96         self.prefix_final = self._final_dir( self.build_dir, self.prefix_dir )
97
98         cfg.infof( 'compute: makevar SRC/    = %s\n', self.src_final )
99         cfg.infof( 'compute: makevar BUILD/  = %s\n', self.build_final )
100         cfg.infof( 'compute: makevar PREFIX/ = %s\n', self.prefix_final )
101
102         ## xcode does a chdir so we need appropriate values
103         macosx = os.path.join( self.src_dir, 'macosx' )
104         self.xcode_x_src    = self._final_dir( macosx, self.src_dir )
105         self.xcode_x_build  = self._final_dir( macosx, self.build_dir )
106         self.xcode_x_prefix = self._final_dir( macosx, self.prefix_dir )
107
108     ## perform chdir and enable log recording
109     def chdir( self ):
110         if os.path.abspath( self.build_dir ) == os.path.abspath( self.src_dir ):
111             cfg.errln( 'build (scratch) directory must not be the same as top-level source root!' )
112
113         if self.build_dir != os.curdir:
114             if os.path.exists( self.build_dir ):
115                 if not options.force:
116                     self.errln( 'build directory already exists: %s (use --force to overwrite)', self.build_dir )
117             else:
118                 self.mkdirs( self.build_dir )
119             self.infof( 'chdir: %s\n', self.build_dir )
120             os.chdir( self.build_dir )
121
122         ## enable logging
123         self._record = True
124
125     def mkdirs( self, dir ):
126         if len(dir) and not os.path.exists( dir ):
127             self.infof( 'mkdir: %s\n', dir )
128             os.makedirs( dir )
129
130     def open( self, *args ):
131         dir = os.path.dirname( args[0] )
132         if len(args) > 1 and args[1].find('w') != -1:
133             self.mkdirs( dir )
134         m = re.match( '^(.*)\.tmp$', args[0] )
135         if m:
136             self.infof( 'write: %s\n', m.group(1) )
137         else:
138             self.infof( 'write: %s\n', args[0] )
139
140         try:
141             return open( *args )
142         except Exception, x:
143             cfg.errln( 'open failure: %s', x )
144
145     def record_log( self ):
146         if not self._record:
147             return
148         self._record = False
149         self.verbose = Configure.OUT_QUIET
150         file = cfg.open( 'log/config.info.txt', 'w' )
151         for line in self._log_info:
152             file.write( line )
153         file.close()
154         file = cfg.open( 'log/config.verbose.txt', 'w' )
155         for line in self._log_verbose:
156             file.write( line )
157         file.close()
158
159     ## Find executable by searching path.
160     ## On success, returns full pathname of executable.
161     ## On fail, returns None.
162     def findExecutable( self, name ):
163         if len( os.path.split(name)[0] ):
164             if os.access( name, os.X_OK ):
165                 return name
166             return None
167         
168         if not os.environ.has_key( 'PATH' ) or os.environ[ 'PATH' ] == '':
169             path = os.defpath
170         else:
171             path = os.environ['PATH']
172         
173         for dir in path.split( os.pathsep ):
174             f = os.path.join( dir, name )
175             if os.access( f, os.X_OK ):
176                 return f
177         return None
178
179     ## taken from python2.6 -- we need it
180     def relpath( self, path, start=os.curdir ):
181         """Return a relative version of a path"""
182
183         if not path:
184             raise ValueError("no path specified")
185
186         start_list = os.path.abspath(start).split(os.sep)
187         path_list = os.path.abspath(path).split(os.sep)
188
189         # Work out how much of the filepath is shared by start and path.
190         i = len(os.path.commonprefix([start_list, path_list]))
191
192         rel_list = [os.pardir] * (len(start_list)-i) + path_list[i:]
193         if not rel_list:
194             return os.curdir
195         return os.path.join(*rel_list)
196
197     ## update with parsed cli options
198     def update_cli( self, options ):
199         self.src_dir    = os.path.normpath( options.src )
200         self.build_dir  = os.path.normpath( options.build )
201         self.prefix_dir = os.path.normpath( options.prefix )
202
203         ## special case if src == build: add build subdir
204         if os.path.abspath( self.src_dir ) == os.path.abspath( self.build_dir ):
205             self.build_dir = os.path.join( self.build_dir, 'build' )
206
207 ###############################################################################
208 ##
209 ## abstract action
210 ##
211 ## pretext = text which immediately follows 'probe:' output prefix
212 ## abort   = if true configure will exit on probe fail
213 ## head    = if true probe session is stripped of all but first line
214 ## session = output from command, including stderr
215 ## fail    = true if probe failed
216 ##
217 class Action( object ):
218     actions = []
219
220     def __init__( self, category, pretext='unknown', abort=False, head=False ):
221         if self not in Action.actions:
222             Action.actions.append( self )
223
224         self.category = category
225         self.pretext  = pretext
226         self.abort    = abort
227         self.head     = head
228         self.session  = None
229
230         self.run_done = False
231         self.fail     = True
232         self.msg_fail = 'fail'
233         self.msg_pass = 'pass'
234         self.msg_end  = 'end'
235
236     def _actionBegin( self ):
237         cfg.infof( '%s: %s...', self.category, self.pretext )
238
239     def _actionEnd( self ):
240         if self.fail:
241             cfg.infof( '(%s) %s\n', self.msg_fail, self.msg_end )
242             if self.abort:
243                 self._dumpSession( cfg.infof )
244                 cfg.errln( 'unable to continue' )
245             self._dumpSession( cfg.verbosef )
246         else:
247             cfg.infof( '(%s) %s\n', self.msg_pass, self.msg_end )
248             self._dumpSession( cfg.verbosef )
249
250     def _dumpSession( self, printf ):
251         if self.session and len(self.session):
252             for line in self.session:
253                 printf( '  : %s\n', line )
254         else:
255             printf( '  : <NO-OUTPUT>\n' )
256
257     def _parseSession( self ):
258         pass
259
260     def run( self ):
261         if self.run_done:
262             return
263         self.run_done = True
264         self._actionBegin()
265         self._action()
266         if not self.fail:
267             self._parseSession()
268         self._actionEnd()
269
270 ###############################################################################
271 ##
272 ## base probe: anything which runs in shell.
273 ##
274 ## pretext = text which immediately follows 'probe:' output prefix
275 ## command = full command and arguments to pipe
276 ## abort   = if true configure will exit on probe fail
277 ## head    = if true probe session is stripped of all but first line
278 ## session = output from command, including stderr
279 ## fail    = true if probe failed
280 ##
281 class ShellProbe( Action ):
282     def __init__( self, pretext, command, abort=False, head=False ):
283         super( ShellProbe, self ).__init__( 'probe', pretext, abort, head )
284         self.command = command
285
286     def _action( self ):
287         ## pipe and redirect stderr to stdout; effects communicate result
288         pipe = subprocess.Popen( self.command, shell=True, stdout=subprocess.PIPE, stderr=subprocess.STDOUT )
289
290         ## read data into memory buffers, only first element (stdout) data is used
291         data = pipe.communicate()
292         self.fail = pipe.returncode != 0
293
294         if data[0]:
295             self.session = data[0].splitlines()
296         else:
297             self.session = []
298
299         if pipe.returncode:
300             self.msg_end = 'code %d' % (pipe.returncode)
301
302     def _dumpSession( self, printf ):
303         printf( '  + %s\n', self.command )
304         super( ShellProbe, self )._dumpSession( printf )
305
306 ###############################################################################
307 ##
308 ## GNU host tuple probe: determine canonical platform type
309 ##
310 ## example results from various platforms:
311 ##
312 ##   i386-apple-darwin9.6.0     (Mac OS X 10.5.6 Intel)
313 ##   powerpc-apple-darwin9.6.0  (Mac OS X 10.5.6 PPC)
314 ##   i686-pc-cygwin             (Cygwin, Microsoft Vista)
315 ##   x86_64-unknown-linux-gnu   (Linux, Fedora 10 x86_64)
316 ##
317 class HostTupleProbe( ShellProbe, list ):
318     GNU_TUPLE_RE = '([^-]+)-?([^-]*)-([^0-9-]+)([^-]*)-?([^-]*)'
319
320     def __init__( self ):
321         super( HostTupleProbe, self ).__init__( 'host tuple', '%s/config.guess' % (cfg.dir), abort=True, head=True )
322
323     def _parseSession( self ):
324         if len(self.session):
325             self.spec = self.session[0]
326         else:
327             self.spec = ''
328
329         ## grok GNU host tuples
330         m = re.match( HostTupleProbe.GNU_TUPLE_RE, self.spec )
331         if not m:
332             self.fail = True
333             self.msg_end = 'invalid host tuple: %s' % (self.spec)
334             return
335
336         self.msg_end = self.spec
337
338         ## assign tuple from regex
339         self[:] = m.groups()
340
341         ## for clarity
342         self.machine = self[0]
343         self.vendor  = self[1]
344         self.system  = self[2]
345         self.release = self[3]
346         self.extra   = self[4]
347
348         ## nice formal name for 'system'
349         self.systemf = platform.system()
350
351         if self.match( '*-*-cygwin*' ):
352             self.systemf = self[2][0].upper() + self[2][1:]
353             
354     ## glob-match against spec
355     def match( self, *specs ):
356         for spec in specs:
357             if fnmatch.fnmatch( self.spec, spec ):
358                 return True
359         return False
360
361 ###############################################################################
362
363 class BuildAction( Action, list ):
364     def __init__( self ):
365         super( BuildAction, self ).__init__( 'compute', 'build tuple', abort=True )
366
367     def _action( self ):
368         ## check if --cross spec was used; must maintain 5-tuple compatibility with regex
369         if options.cross:
370             self.spec = os.path.basename( options.cross ).rstrip( '-' )
371         else:
372             self.spec = arch.mode[arch.mode.mode]
373
374         ## grok GNU host tuples
375         m = re.match( HostTupleProbe.GNU_TUPLE_RE, self.spec )
376         if not m:
377             self.msg_end = 'invalid host tuple: %s' % (self.spec)
378             return
379
380         self.msg_end = self.spec
381
382         ## assign tuple from regex
383         self[:] = m.groups()
384
385         ## for clarity
386         self.machine = self[0]
387         self.vendor  = self[1]
388         self.system  = self[2]
389         self.release = self[3]
390         self.extra   = self[4]
391         self.systemf = host.systemf
392
393         ## when cross we need switch for platforms
394         if options.cross:
395             if self.match( '*mingw*' ):
396                 self.systemf = 'MinGW'
397             elif self.systemf:
398                 self.systemf[0] = self.systemf[0].upper()
399             self.title = '%s %s' % (build.systemf,self.machine)
400         else:
401             self.title = '%s %s' % (build.systemf,arch.mode.mode)
402         self.fail = False
403
404     ## glob-match against spec
405     def match( self, *specs ):
406         for spec in specs:
407             if fnmatch.fnmatch( self.spec, spec ):
408                 return True
409         return False
410
411 ###############################################################################
412 ##
413 ## value wrapper; value is accepted only if one of host specs matcheds
414 ## otherwise it is None (or a keyword-supplied val)
415 ##
416 ## result is attribute 'value'
417 ##
418 class IfHost( object ):
419     def __init__( self, value, *specs, **kwargs ):
420         self.value = kwargs.get('none',None)
421         for spec in specs:
422             if host.match( spec ):
423                 self.value = value
424                 break
425
426     def __nonzero__( self ):
427         return self.value != None
428         
429     def __str__( self ):
430         return self.value
431
432
433 ###############################################################################
434 ##
435 ## platform conditional value; loops through list of tuples comparing
436 ## to first host match and sets value accordingly; the first value is
437 ## always default.
438 ##
439 class ForHost( object ):
440     def __init__( self, default, *tuples ):
441         self.value = default
442         for tuple in tuples:
443             if host.match( tuple[1] ):
444                 self.value = tuple[0]
445                 break
446
447     def __str__( self ):
448         return self.value
449
450 ###############################################################################
451
452 class ArchAction( Action ):
453     def __init__( self ):
454         super( ArchAction, self ).__init__( 'compute', 'available architectures', abort=True )
455         self.mode = SelectMode( 'architecture', (host.machine,host.spec) )
456
457     def _action( self ):
458         self.fail = False
459
460         ## some match on system should be made here; otherwise we signal a warning. 
461         if host.match( '*-*-cygwin*' ):
462             pass
463         elif host.match( '*-*-darwin*' ):
464             self.mode['i386']   = 'i386-apple-darwin%s'      % (host.release)
465             self.mode['x86_64'] = 'x86_64-apple-darwin%s'    % (host.release)
466             self.mode['ppc']    = 'powerpc-apple-darwin%s'   % (host.release)
467             self.mode['ppc64']  = 'powerpc64-apple-darwin%s' % (host.release)
468
469             ## special cases in that powerpc does not match gcc -arch value
470             ## which we like to use; so it has to be removed.
471             ## note: we don't know if apple will release Ssnow Leopad/ppc64 yet; just a guess.
472             if 'powerpc' in self.mode:
473                 del self.mode['powerpc']
474                 self.mode.mode = 'ppc'
475             elif 'powerpc64' in self.mode:
476                 del self.mode['powerpc64']
477                 self.mode.mode = 'ppc64'
478         elif host.match( '*-*-linux*' ):
479             pass
480         else:
481             self.msg_pass = 'WARNING'
482
483         self.msg_end = self.mode.toString()
484
485     ## glob-match against spec
486     def match( self, spec ):
487         return fnmatch.fnmatch( self.spec, spec )
488
489 ###############################################################################
490
491 class CoreProbe( Action ):
492     def __init__( self ):
493         super( CoreProbe, self ).__init__( 'probe', 'number of CPU cores' )
494         self.count = 1
495
496     def _action( self ):
497         if self.fail:
498             ## good for darwin9.6.0 and linux
499             try:
500                 self.count = os.sysconf( 'SC_NPROCESSORS_ONLN' )
501                 if self.count < 1:
502                     self.count = 1
503                 self.fail = False
504             except:
505                 pass
506
507         if self.fail:
508             ## windows
509             try:
510                 self.count = int( os.environ['NUMBER_OF_PROCESSORS'] )
511                 if self.count < 1:
512                     self.count = 1
513                 self.fail = False
514             except:
515                 pass
516
517         ## clamp
518         if self.count < 1:
519             self.count = 1
520         elif self.count > 32:
521             self.count = 32
522
523         if options.launch:
524             if options.launch_jobs == 0:
525                 self.jobs = core.count
526             else:
527                 self.jobs = options.launch_jobs
528         else:
529             self.jobs = core.count
530
531         self.msg_end = str(self.count)
532
533 ###############################################################################
534
535 class SelectMode( dict ):
536     def __init__( self, descr, *modes, **kwargs ):
537         super( SelectMode, self ).__init__( modes )
538         self.descr    = descr
539         self.modes    = modes
540         self.default  = kwargs.get('default',modes[0][0])
541         self.mode     = self.default
542
543     def cli_add_option( self, parser, option ):
544         parser.add_option( option, default=self.mode, metavar='MODE',
545             help='select %s mode: %s' % (self.descr,self.toString()),
546             action='callback', callback=self.cli_callback, type='str' )
547
548     def cli_callback( self, option, opt_str, value, parser, *args, **kwargs ):
549         if value not in self:
550             raise optparse.OptionValueError( 'invalid %s mode: %s (choose from %s)'
551                 % (self.descr,value,self.toString( True )) )
552         self.mode = value
553
554     def toString( self, nodefault=False ):
555         keys = self.keys()
556         keys.sort()
557         if len(self) == 1:
558             value = self.mode
559         elif nodefault:
560             value = ' '.join( keys )
561         else:
562             value = '%s [%s]' % (' '.join( keys ), self.mode )
563         return value
564
565 ###############################################################################
566 ##
567 ## Repository object.
568 ## Holds information gleaned from subversion working dir.
569 ##
570 ## Builds are classed into one of the following types:
571 ##
572 ##  release
573 ##      must be built from official svn with '/tags/' in the url
574 ##  developer
575 ##      must be built from official svn but is not a release
576 ##  unofficial
577 ##      all other builds
578 ##
579 class RepoProbe( ShellProbe ):
580     def __init__( self ):
581         super( RepoProbe, self ).__init__( 'svn info', 'svn info %s' % (cfg.src_dir) )
582
583         self.url       = 'svn://nowhere.com/project/unknown'
584         self.root      = 'svn://nowhere.com/project'
585         self.branch    = 'unknown'
586         self.uuid      = '00000000-0000-0000-0000-000000000000';
587         self.rev       = 0
588         self.date      = '0000-00-00 00:00:00 -0000'
589         self.official  = 0
590         self.type      = 'unofficial'
591
592     def _parseSession( self ):
593         for line in self.session:
594             ## grok fields
595             m = re.match( '([^:]+):\\s+(.+)', line )
596             if not m:
597                 continue
598
599             (name,value) = m.groups()
600             if name == 'URL':
601                 self.url = value
602             elif name == 'Repository Root':
603                 self.root = value
604             elif name == 'Repository UUID':
605                 self.uuid = value
606             elif name == 'Revision':
607                 self.rev = int( value )
608             elif name == 'Last Changed Date':
609                 # strip chars in parens
610                 if value.find( ' (' ):
611                     self.date = value[0:value.find(' (')]
612                 else:
613                     self.date = value
614
615         ## grok branch
616         i = self.url.rfind( '/' )
617         if i != -1 and i < len(self.url)-1:
618             self.branch = self.url[i+1:]
619
620         # type-classification via repository UUID
621         if self.uuid == 'b64f7644-9d1e-0410-96f1-a4d463321fa5':
622             self.official = 1
623             m = re.match( '([^:]+)://([^/]+)/(.+)', self.url )
624             if m and re.match( 'tags/', m.group( 3 )):
625                 self.type = 'release'
626             else:
627                 self.type = 'developer'
628
629         self.msg_end = self.url
630
631 ###############################################################################
632 ##
633 ## project object.
634 ##
635 ## Contains manually updated version numbers consistent with HB releases
636 ## and other project metadata.
637 ##
638 class Project( Action ):
639     def __init__( self ):
640         super( Project, self ).__init__( 'compute', 'project data' )
641
642         self.name          = 'HandBrake'
643         self.acro_lower    = 'hb'
644         self.acro_upper    = 'HB'
645         self.url_website   = 'http://handbrake.fr'
646         self.url_community = 'http://forum.handbrake.fr'
647         self.url_irc       = 'irc://irc.freenode.net/handbrake'
648
649         self.name_lower = self.name.lower()
650         self.name_upper = self.name.upper()
651
652         self.vmajor = 0
653         self.vminor = 9
654         self.vpoint = 4
655
656     def _action( self ):
657         ## add architecture to URL only for Mac
658         if fnmatch.fnmatch( build.spec, '*-*-darwin*' ):
659             url_arch = '.%s' % (arch.mode.mode)
660         else:
661             url_arch = ''
662
663         if repo.type == 'release':
664             self.version = '%d.%d.%d' % (self.vmajor,self.vminor,self.vpoint)
665             url_ctype = ''
666             url_ntype = 'stable'
667             self.build = time.strftime('%Y%m%d') + '00'
668             self.title = '%s %s (%s)' % (self.name,self.version,self.build)
669         elif repo.type == 'developer':
670             self.version = 'svn%d' % (repo.rev)
671             url_ctype = '_unstable'
672             url_ntype = 'unstable'
673             self.build = time.strftime('%Y%m%d') + '01'
674             self.title = '%s svn%d (%s)' % (self.name,repo.rev,self.build)
675         else:
676             self.version = 'rev%d' % (repo.rev)
677             url_ctype = '_unofficial'
678             url_ntype = 'unofficial'
679             self.build = time.strftime('%Y%m%d') + '99'
680             self.title = '%s rev%d (%s)' % (self.name,repo.rev,self.build)
681
682         self.url_appcast = 'http://handbrake.fr/appcast%s%s.xml' % (url_ctype,url_arch)
683         self.url_appnote = 'http://handbrake.fr/appcast/%s.html' % (url_ntype)
684
685         self.msg_end = '%s (%s)' % (self.name,repo.type)
686         self.fail = False
687
688 ###############################################################################
689
690 class ToolProbe( Action ):
691     tools = []
692
693     def __init__( self, var, *names, **kwargs ):
694         super( ToolProbe, self ).__init__( 'find', abort=kwargs.get('abort',True) )
695         if not self in ToolProbe.tools:
696             ToolProbe.tools.append( self )
697         self.var    = var
698         self.names  = []
699         self.kwargs = kwargs
700         for name in names:
701             if name:
702                 self.names.append( str(name) )
703         self.name = self.names[0]
704         self.pretext = self.name
705         self.pathname = self.names[0]
706
707     def _action( self ):
708         self.session = []
709         for i,name in enumerate(self.names):
710             self.session.append( 'name[%d] = %s' % (i,name) )
711         for name in self.names:
712             f = cfg.findExecutable( name )
713             if f:
714                 self.pathname = f
715                 self.fail = False
716                 self.msg_end = f
717                 break
718         if self.fail:
719             self.msg_end = 'not found'
720
721     def cli_add_option( self, parser ):
722         parser.add_option( '--'+self.name, metavar='PROG',
723             help='[%s]' % (self.pathname),
724             action='callback', callback=self.cli_callback, type='str' )
725
726     def cli_callback( self, option, opt_str, value, parser, *args, **kwargs ):
727         self.__init__( self.var, value, **self.kwargs )
728         self.run()
729
730     def doc_add( self, doc ):
731         doc.add( self.var, self.pathname )
732
733 ###############################################################################
734
735 class SelectTool( Action ):
736     selects = []
737
738     def __init__( self, var, name, *pool, **kwargs ):
739         super( SelectTool, self ).__init__( 'select', abort=kwargs.get('abort',True) )
740         self.pretext = name
741         if not self in SelectTool.selects:
742             SelectTool.selects.append( self )
743         self.var      = var
744         self.name     = name
745         self.pool     = pool
746         self.kwargs   = kwargs
747
748     def _action( self ):
749         self.session = []
750         for i,(name,tool) in enumerate(self.pool):
751             self.session.append( 'tool[%d] = %s (%s)' % (i,name,tool.pathname) )
752         for (name,tool) in self.pool:
753             if not tool.fail:
754                 self.selected = name
755                 self.fail = False
756                 self.msg_end = '%s (%s)' % (name,tool.pathname)
757                 break
758         if self.fail:
759             self.msg_end = 'not found'
760
761     def cli_add_option( self, parser ):
762         parser.add_option( '--'+self.name, metavar='MODE',
763             help='select %s mode: %s' % (self.name,self.toString()),
764             action='callback', callback=self.cli_callback, type='str' )
765
766     def cli_callback( self, option, opt_str, value, parser, *args, **kwargs ):
767         found = False
768         for (name,tool) in self.pool:
769             if name == value:
770                 found = True
771                 self.__init__( self.var, self.name, [name,tool], **kwargs )
772                 self.run()
773                 break
774         if not found:
775             raise optparse.OptionValueError( 'invalid %s mode: %s (choose from %s)'
776                 % (self.name,value,self.toString( True )) )
777
778     def doc_add( self, doc ):
779         doc.add( self.var, self.selected )
780
781     def toString( self, nodefault=False ):
782         if len(self.pool) == 1:
783             value = self.pool[0][0]
784         else:
785             s = ''
786             for key,value in self.pool:
787                 s += ' ' + key
788             if nodefault:
789                 value = s[1:]
790             else:
791                 value = '%s [%s]' % (s[1:], self.selected )
792         return value
793
794 ###############################################################################
795 ##
796 ## config object used to output gnu-make or gnu-m4 output.
797 ##
798 ## - add() to add NAME/VALUE pairs suitable for both make/m4.
799 ## - addBlank() to add a linefeed for both make/m4.
800 ## - addMake() to add a make-specific line.
801 ## - addM4() to add a m4-specific line.
802 ##
803 class ConfigDocument:
804     def __init__( self ):
805         self._elements = []
806
807     def _outputMake( self, file, namelen, name, value, append ):
808         if append:
809             if value == None or len(str(value)) == 0:
810                 file.write( '%-*s +=\n' % (namelen, name) )
811             else:
812                 file.write( '%-*s += %s\n' % (namelen, name, value) )
813         else:
814             if value == None or len(str(value)) == 0:
815                 file.write( '%-*s  =\n' % (namelen, name) )
816             else:
817                 file.write( '%-*s  = %s\n' % (namelen, name, value) )
818
819     def _outputM4( self, file, namelen, name, value ):
820         namelen += 7
821         name = '<<__%s>>,' % name.replace( '.', '_' )
822         file.write( 'define(%-*s  <<%s>>)dnl\n' % (namelen, name, value ))
823
824     def add( self, name, value, append=False ):
825         self._elements.append( [name,value,append] )
826
827     def addBlank( self ):
828         self._elements.append( None )
829
830     def addComment( self, format, *args ):
831         self.addMake( '## ' + format % args )
832         self.addM4( 'dnl ' + format % args )
833
834     def addMake( self, line ):
835         self._elements.append( ('?make',line) )
836
837     def addM4( self, line ):
838         self._elements.append( ('?m4',line) )
839
840     def output( self, file, type ):
841         namelen = 0
842         for item in self._elements:
843             if item == None or item[0].find( '?' ) == 0:
844                 continue
845             if len(item[0]) > namelen:
846                 namelen = len(item[0])
847         for item in self._elements:
848             if item == None:
849                 if type == 'm4':
850                     file.write( 'dnl\n' )
851                 else:
852                     file.write( '\n' )
853                 continue
854             if item[0].find( '?' ) == 0:
855                 if item[0].find( type, 1 ) == 1:
856                     file.write( '%s\n' % (item[1]) )
857                 continue
858
859             if type == 'm4':
860                 self._outputM4( file, namelen, item[0], item[1] )
861             else:
862                 self._outputMake( file, namelen, item[0], item[1], item[2] )
863
864     def update( self, name, value ):
865         for item in self._elements:
866             if item == None:
867                 continue
868             if item[0] == name:
869                 item[1] = value
870                 return
871         raise ValueError( 'element not found: %s' % (name) )
872
873     def write( self, type ):
874         if type == 'make':
875             fname = 'GNUmakefile'
876         elif type == 'm4':
877             fname = os.path.join( 'project', project.name_lower + '.m4' )
878         else:
879             raise ValueError, 'unknown file type: ' + type
880
881         ftmp  = fname + '.tmp'
882         try:
883             try:
884                 file = cfg.open( ftmp, 'w' )
885                 self.output( file, type )
886             finally:
887                 try:
888                     file.close()
889                 except:
890                     pass
891         except Exception, x:
892             try:
893                 os.remove( ftmp )
894             except Exception, x:
895                 pass
896             cfg.errln( 'failed writing to %s\n%s', ftmp, x )
897
898         try:
899             os.rename( ftmp, fname )
900         except Exception, x:
901             cfg.errln( 'failed writing to %s\n%s', fname, x )
902
903 ###############################################################################
904 ##
905 ## create cli parser
906 ##
907
908 ## class to hook options and create CONF.args list
909 class Option( optparse.Option ):
910     conf_args = []
911
912     def _conf_record( self, opt, value ):
913         ## skip conf,force,launch
914         if re.match( '^--(conf|force|launch).*$', opt ):
915             return
916
917         ## remove duplicates (last duplicate wins)
918         for i,arg in enumerate( Option.conf_args ):
919             if opt == arg[0]:
920                 del Option.conf_args[i]
921                 break
922
923         if value:
924             Option.conf_args.append( [opt,'%s=%s' % (opt,value)] )
925         else:
926             Option.conf_args.append( [opt,'%s' % (opt)] )
927
928     def take_action( self, action, dest, opt, value, values, parser ):
929         self._conf_record( opt, value )
930         return optparse.Option.take_action( self, action, dest, opt, value, values, parser )
931
932 def createCLI():
933     cli = OptionParser( 'usage: %prog [OPTIONS...] [TARGETS...]' )
934     cli.option_class = Option
935
936     cli.description = ''
937     cli.description += 'Configure %s build system.' % (project.name)
938
939     ## add hidden options
940     cli.add_option( '--conf-method', default='terminal', action='store', help=optparse.SUPPRESS_HELP )
941     cli.add_option( '--force', default=False, action='store_true', help='overwrite existing build config' )
942     cli.add_option( '--verbose', default=False, action='store_true', help='increase verbosity' )
943
944     ## add install options
945     grp = OptionGroup( cli, 'Directory Locations' )
946     grp.add_option( '--src', default=cfg.src_dir, action='store', metavar='DIR',
947         help='specify top-level source dir [%s]' % (cfg.src_dir) )
948     grp.add_option( '--build', default=cfg.build_dir, action='store', metavar='DIR',
949         help='specify build scratch/output dir [%s]' % (cfg.build_dir) )
950     grp.add_option( '--prefix', default=cfg.prefix_dir, action='store', metavar='DIR',
951         help='specify install dir for products [%s]' % (cfg.prefix_dir) )
952     cli.add_option_group( grp )
953
954     ## add feature options
955     grp = OptionGroup( cli, 'Feature Options' )
956
957     h = IfHost( 'enable assembly code in non-contrib modules', 'NOMATCH*-*-darwin*', 'NOMATCH*-*-linux*', none=optparse.SUPPRESS_HELP ).value
958     grp.add_option( '--enable-asm', default=False, action='store_true', help=h )
959
960     h = IfHost( 'disable GTK GUI', '*-*-linux*', none=optparse.SUPPRESS_HELP ).value
961     grp.add_option( '--disable-gtk', default=False, action='store_true', help=h )
962     h = IfHost( 'enable GTK GUI (mingw)', '*-*-mingw*', none=optparse.SUPPRESS_HELP ).value
963     grp.add_option( '--enable-gtk-mingw', default=False, action='store_true', help=h )
964
965     h = IfHost( 'disable Xcode', '*-*-darwin*', none=optparse.SUPPRESS_HELP ).value
966     grp.add_option( '--disable-xcode', default=False, action='store_true', help=h )
967
968     cli.add_option_group( grp )
969
970     ## add launch options
971     grp = OptionGroup( cli, 'Launch Options' )
972     grp.add_option( '--launch', default=False, action='store_true',
973         help='launch build, capture log and wait for completion' )
974     grp.add_option( '--launch-jobs', default=1, action='store', metavar='N', type='int',
975         help='allow N jobs at once; 0 to match CPU count [1]' )
976     grp.add_option( '--launch-args', default=None, action='store', metavar='ARGS',
977         help='specify additional ARGS for launch command' )
978     grp.add_option( '--launch-quiet', default=False, action='store_true',
979         help='do not echo build output while waiting' )
980     cli.add_option_group( grp )
981
982     ## add compile options
983     grp = OptionGroup( cli, 'Compiler Options' )
984     debugMode.cli_add_option( grp, '--debug' )
985     optimizeMode.cli_add_option( grp, '--optimize' )
986     arch.mode.cli_add_option( grp, '--arch' )
987     grp.add_option( '--cross', default=None, action='store', metavar='SPEC',
988         help='specify GCC cross-compilation spec' )
989     cli.add_option_group( grp )
990
991     ## add tool locations
992     grp = OptionGroup( cli, 'Tool Basenames and Locations' )
993     for tool in ToolProbe.tools:
994         tool.cli_add_option( grp )
995     cli.add_option_group( grp )
996
997     ## add tool modes
998     grp = OptionGroup( cli, 'Tool Options' )
999     for select in SelectTool.selects:
1000         select.cli_add_option( grp )
1001     cli.add_option_group( grp )
1002     return cli
1003
1004 ###############################################################################
1005 ##
1006 ## launcher - used for QuickStart method; launch; build and capture log.
1007 ##
1008 class Launcher:
1009     def __init__( self, targets ):
1010         # open build logfile
1011         self._file = cfg.open( 'log/build.txt', 'w' )
1012
1013         cmd = '%s -j%d' % (Tools.gmake.pathname,core.jobs)
1014         if options.launch_args:
1015             cmd += ' ' + options.launch_args
1016         if len(targets):
1017             cmd += ' ' + ' '.join(targets)
1018
1019         ## record begin
1020         timeBegin = time.time()
1021         self.infof( 'time begin: %s\n', time.asctime() )
1022         self.infof( 'launch: %s\n', cmd )
1023         if options.launch_quiet:
1024             stdout.write( 'building to %s ...\n' % (os.path.abspath( cfg.build_final )))
1025         else:
1026             stdout.write( '%s\n' % ('-' * 79) )
1027
1028         ## launch/pipe
1029         try:
1030             pipe = subprocess.Popen( cmd, shell=True, bufsize=1, stdout=subprocess.PIPE, stderr=subprocess.STDOUT )
1031         except Exception, x:
1032             cfg.errln( 'launch failure: %s', x )
1033         for line in pipe.stdout:
1034             self.echof( '%s', line )
1035         pipe.wait()
1036
1037         ## record end
1038         timeEnd = time.time()
1039         elapsed = timeEnd - timeBegin
1040
1041         if pipe.returncode:
1042             result = 'FAILURE (code %d)' % pipe.returncode
1043         else:
1044             result = 'SUCCESS'
1045
1046         ## present duration in decent format
1047         seconds = elapsed
1048         hours = int(seconds / 3600)
1049         seconds -= hours * 3600
1050         minutes = int(seconds / 60)
1051         seconds -= minutes * 60
1052
1053         segs = []
1054         duration = ''
1055
1056         if hours == 1:
1057             segs.append( '%d hour' % hours )
1058         elif hours > 1:
1059             segs.append( '%d hours' % hours )
1060
1061         if len(segs) or minutes == 1:
1062             segs.append( '%d minute' % minutes )
1063         elif len(segs) or  minutes > 1:
1064             segs.append( '%d minutes' % minutes )
1065
1066         if seconds == 1:
1067             segs.append( '%d second' % seconds )
1068         else:
1069             segs.append( '%d seconds' % seconds )
1070
1071         if not options.launch_quiet:
1072             stdout.write( '%s\n' % ('-' * 79) )
1073         self.infof( 'time end: %s\n', time.asctime() )
1074         self.infof( 'duration: %s (%.2fs)\n', ', '.join(segs), elapsed )
1075         self.infof( 'result: %s\n', result )
1076
1077         ## cleanup
1078         self._file.close()
1079
1080     def echof( self, format, *args ):
1081         line = format % args
1082         self._file.write( line )
1083         if not options.launch_quiet:
1084             stdout.write( '  : %s' % line )
1085             stdout.flush()
1086
1087     def infof( self, format, *args ):
1088         line = format % args
1089         self._file.write( line )
1090         cfg.infof( '%s', line )
1091
1092 ###############################################################################
1093 ##
1094 ## main program
1095 ##
1096 try:
1097     ## we need to pre-check argv for -h or --help or --verbose to deal with
1098     ## initializing Configure correctly.
1099     verbose = Configure.OUT_INFO
1100     for arg in sys.argv:
1101         if arg == '-h' or arg == '--help':
1102             verbose = Configure.OUT_QUIET
1103             break
1104         if arg == '--verbose':
1105             verbose = Configure.OUT_VERBOSE
1106
1107     ## create main objects; actions/probes run() is delayed.
1108     ## if any actions must be run earlier (eg: for configure --help purposes)
1109     ## then run() must be invoked earlier. subequent run() invocations
1110     ## are ignored.
1111     cfg   = Configure( verbose )
1112     host  = HostTupleProbe(); host.run()
1113
1114     cfg.prefix_dir = ForHost( '/usr/local', ['/Applications','*-*-darwin*'] ).value
1115
1116     build = BuildAction()
1117     arch  = ArchAction(); arch.run()
1118
1119     ## create remaining main objects
1120     core    = CoreProbe()
1121     repo    = RepoProbe()
1122     project = Project()
1123
1124     ## create tools in a scope
1125     class Tools:
1126         ar    = ToolProbe( 'AR.exe',    'ar' )
1127         cp    = ToolProbe( 'CP.exe',    'cp' )
1128         curl  = ToolProbe( 'CURL.exe',  'curl', abort=False )
1129         gcc   = ToolProbe( 'GCC.gcc',   'gcc', IfHost( 'gcc-4', '*-*-cygwin*' ))
1130
1131         if host.match( '*-*-darwin*' ):
1132             gmake = ToolProbe( 'GMAKE.exe', 'make', 'gmake' )
1133         else:
1134             gmake = ToolProbe( 'GMAKE.exe', 'gmake', 'make' )
1135
1136         m4     = ToolProbe( 'M4.exe',     'm4' )
1137         mkdir  = ToolProbe( 'MKDIR.exe',  'mkdir' )
1138         patch  = ToolProbe( 'PATCH.exe',  'gpatch', 'patch' )
1139         rm     = ToolProbe( 'RM.exe',     'rm' )
1140         ranlib = ToolProbe( 'RANLIB.exe', 'ranlib' )
1141         strip  = ToolProbe( 'STRIP.exe',  'strip' )
1142         tar    = ToolProbe( 'TAR.exe',    'gtar', 'tar' )
1143         wget   = ToolProbe( 'WGET.exe',   'wget', abort=False )
1144         yasm   = ToolProbe( 'YASM.exe',   'yasm', abort=False )
1145
1146         xcodebuild = ToolProbe( 'XCODEBUILD.exe', 'xcodebuild', abort=False )
1147         lipo       = ToolProbe( 'LIPO.exe',       'lipo', abort=False )
1148
1149         fetch = SelectTool( 'FETCH.select', 'fetch', ['wget',wget], ['curl',curl] )
1150
1151     ## run tool probes
1152     for tool in ToolProbe.tools:
1153         tool.run()
1154     for select in SelectTool.selects:
1155         select.run()
1156
1157     debugMode = SelectMode( 'debug', ('none','none'), ('min','min'), ('std','std'), ('max','max') )
1158     optimizeMode = SelectMode( 'optimize', ('none','none'), ('speed','speed'), ('size','size'), default='speed' )
1159
1160     ## create CLI and parse
1161     cli = createCLI()
1162     (options,args) = cli.parse_args()
1163
1164     ## update cfg with cli directory locations
1165     cfg.update_cli( options )
1166
1167     ## prepare list of targets and NAME=VALUE args to pass to make
1168     targets = []
1169     exports = []
1170     rx_exports = re.compile( '([^=]+)=(.*)' )
1171     for arg in args:
1172         m = rx_exports.match( arg )
1173         if m:
1174             exports.append( m.groups() )
1175         else:
1176             targets.append( arg )
1177
1178     ## re-run tools with cross-compilation needs
1179     if options.cross:
1180         for tool in ( Tools.ar, Tools.gcc, Tools.ranlib, Tools.strip ):
1181             tool.__init__( tool.var, '%s-%s' % (options.cross,tool.name), **tool.kwargs )
1182             tool.run()
1183
1184     ## run delayed actions
1185     for action in Action.actions:
1186         action.run()
1187
1188     ## cfg hook before doc prep
1189     cfg.doc_ready()
1190
1191     ## create document object
1192     doc = ConfigDocument()
1193     doc.addComment( 'generated by configure on %s', time.strftime( '%c' ))
1194
1195     ## add configure line for reconfigure purposes
1196     doc.addBlank()
1197     args = []
1198     for arg in Option.conf_args:
1199         args.append( arg[1] )
1200     doc.add( 'CONF.args', ' '.join( args ))
1201
1202     doc.addBlank()
1203     doc.add( 'HB.title',       project.title )
1204     doc.add( 'HB.name',        project.name )
1205     doc.add( 'HB.name.lower',  project.name_lower )
1206     doc.add( 'HB.name.upper',  project.name_upper )
1207     doc.add( 'HB.acro.lower',  project.acro_lower )
1208     doc.add( 'HB.acro.upper',  project.acro_upper )
1209
1210     doc.add( 'HB.url.website',    project.url_website )
1211     doc.add( 'HB.url.community',  project.url_community )
1212     doc.add( 'HB.url.irc',        project.url_irc )
1213     doc.add( 'HB.url.appcast',    project.url_appcast )
1214     doc.add( 'HB.url.appnote',    project.url_appnote )
1215
1216     doc.add( 'HB.version.major',  project.vmajor )
1217     doc.add( 'HB.version.minor',  project.vminor )
1218     doc.add( 'HB.version.point',  project.vpoint )
1219     doc.add( 'HB.version',        project.version )
1220     doc.add( 'HB.version.hex',    '%04x%02x%02x%08x' % (project.vmajor,project.vminor,project.vpoint,repo.rev) )
1221
1222     doc.add( 'HB.build', project.build )
1223
1224     doc.add( 'HB.repo.url',       repo.url )
1225     doc.add( 'HB.repo.root',      repo.root )
1226     doc.add( 'HB.repo.branch',    repo.branch )
1227     doc.add( 'HB.repo.uuid',      repo.uuid )
1228     doc.add( 'HB.repo.rev',       repo.rev )
1229     doc.add( 'HB.repo.date',      repo.date )
1230     doc.add( 'HB.repo.official',  repo.official )
1231     doc.add( 'HB.repo.type',      repo.type )
1232
1233     doc.addBlank()
1234     doc.add( 'HOST.spec',    host.spec )
1235     doc.add( 'HOST.machine', host.machine )
1236     doc.add( 'HOST.vendor',  host.vendor )
1237     doc.add( 'HOST.system',  host.system )
1238     doc.add( 'HOST.systemf', host.systemf )
1239     doc.add( 'HOST.release', host.release )
1240     doc.add( 'HOST.extra',   host.extra )
1241     doc.add( 'HOST.title',   '%s %s' % (host.systemf,arch.mode.default) )
1242     doc.add( 'HOST.ncpu',    core.count )
1243
1244     doc.addBlank()
1245     doc.add( 'BUILD.spec',    build.spec )
1246     doc.add( 'BUILD.machine', build.machine )
1247     doc.add( 'BUILD.vendor',  build.vendor )
1248     doc.add( 'BUILD.system',  build.system )
1249     doc.add( 'BUILD.systemf', build.systemf )
1250     doc.add( 'BUILD.release', build.release )
1251     doc.add( 'BUILD.extra',   build.extra )
1252     doc.add( 'BUILD.title',   build.title )
1253     doc.add( 'BUILD.ncpu',    core.count )
1254     doc.add( 'BUILD.jobs',    core.jobs )
1255
1256     doc.add( 'BUILD.cross',        int(options.cross != None or arch.mode.mode != arch.mode.default) )
1257     if options.cross:
1258         doc.add( 'BUILD.cross.prefix', '%s-' % (options.cross) )
1259     else:
1260         doc.add( 'BUILD.cross.prefix', '' )
1261
1262     doc.add( 'BUILD.method', 'terminal' )
1263     doc.add( 'BUILD.date',   time.strftime('%c') )
1264     doc.add( 'BUILD.arch',   arch.mode.mode )
1265
1266     doc.addBlank()
1267     doc.add( 'CONF.method', options.conf_method )
1268
1269     doc.addBlank()
1270     doc.add( 'SRC',     cfg.src_final )
1271     doc.add( 'SRC/',    cfg.src_final + os.sep )
1272     doc.add( 'BUILD',   cfg.build_final )
1273     doc.add( 'BUILD/',  cfg.build_final + os.sep )
1274     doc.add( 'PREFIX',  cfg.prefix_final )
1275     doc.add( 'PREFIX/', cfg.prefix_final + os.sep )
1276     
1277     doc.addBlank()
1278     doc.add( 'FEATURE.asm',   'disabled' )
1279     doc.add( 'FEATURE.gtk',   int( not options.disable_gtk ))
1280     doc.add( 'FEATURE.gtk.mingw',   int( options.enable_gtk_mingw ))
1281     doc.add( 'FEATURE.xcode', int( not (Tools.xcodebuild.fail or options.disable_xcode or options.cross) ))
1282
1283     if not Tools.xcodebuild.fail and not options.disable_xcode:
1284         doc.addBlank()
1285         doc.add( 'XCODE.external.src',    cfg.xcode_x_src )
1286         doc.add( 'XCODE.external.build',  cfg.xcode_x_build )
1287         doc.add( 'XCODE.external.prefix', cfg.xcode_x_prefix )
1288
1289     doc.addMake( '' )
1290     doc.addMake( '## include definitions' )
1291     doc.addMake( 'include $(SRC/)make/include/main.defs' )
1292
1293     doc.addBlank()
1294     for tool in ToolProbe.tools:
1295         tool.doc_add( doc )
1296
1297     doc.addBlank()
1298     for select in SelectTool.selects:
1299         select.doc_add( doc )
1300
1301     doc.addBlank()
1302     if arch.mode.mode != arch.mode.default:
1303         doc.add( 'GCC.archs', arch.mode.mode )
1304     else:
1305         doc.add( 'GCC.archs', '' )
1306     doc.add( 'GCC.g', debugMode.mode )
1307     doc.add( 'GCC.O', optimizeMode.mode )
1308
1309     if options.enable_asm and not Tools.yasm.fail:
1310         asm = ''
1311         if build.match( 'i?86-*' ):
1312             asm = 'x86'
1313             doc.add( 'LIBHB.GCC.D', 'HAVE_MMX', append=True )
1314             doc.add( 'LIBHB.YASM.D', 'ARCH_X86', append=True )
1315             if build.match( '*-*-darwin*' ):
1316                 doc.add( 'LIBHB.YASM.f', 'macho32' )
1317             else:
1318                 doc.add( 'LIBHB.YASM.f', 'elf32' )
1319             doc.add( 'LIBHB.YASM.m', 'x86' )
1320         elif build.match( 'x86_64-*' ):
1321             asm = 'x86'
1322             doc.add( 'LIBHB.GCC.D', 'HAVE_MMX ARCH_X86_64', append=True )
1323             if build.match( '*-*-darwin*' ):
1324                 doc.add( 'LIBHB.YASM.D', 'ARCH_X86_64 PIC', append=True )
1325                 doc.add( 'LIBHB.YASM.f', 'macho64' )
1326             else:
1327                 doc.add( 'LIBHB.YASM.D', 'ARCH_X86_64', append=True )
1328                 doc.add( 'LIBHB.YASM.f', 'elf64' )
1329             doc.add( 'LIBHB.YASM.m', 'amd64' )
1330         doc.update( 'FEATURE.asm', asm )
1331
1332     ## add exports to make
1333     if len(exports):
1334         doc.addBlank()
1335         doc.addComment( 'overrides via VARIABLE=VALUE on command-line' )
1336         for nv in exports:
1337             doc.add( nv[0], nv[1] )
1338
1339     doc.addMake( '' )
1340     doc.addMake( '## include custom definitions' )
1341     doc.addMake( '-include $(SRC/)custom.defs' )
1342     doc.addMake( '-include $(BUILD/)GNUmakefile.custom.defs' )
1343
1344     doc.addMake( '' )
1345     doc.addMake( '## include rules' )
1346     doc.addMake( 'include $(SRC/)make/include/main.rules' )
1347     doc.addMake( '-include $(SRC/)custom.rules' )
1348     doc.addMake( '-include $(BUILD/)GNUmakefile.custom.rules' )
1349
1350     ## chdir
1351     cfg.chdir()
1352
1353     ## perform
1354     doc.write( 'make' )
1355     doc.write( 'm4' )
1356     if options.launch:
1357         Launcher( targets )
1358
1359     cfg.record_log()
1360
1361     if os.path.normpath( cfg.build_dir ) == os.curdir:
1362         nocd = True
1363     else:
1364         nocd = False
1365
1366     stdout.write( '%s\n' % ('-' * 79) )
1367     if options.launch:
1368         stdout.write( 'Build is finished!\n' )
1369         if nocd:
1370             stdout.write( 'You may now examine the output.\n' )
1371         else:
1372             stdout.write( 'You may now cd into %s and examine the output.\n' % (cfg.build_dir) )
1373     else:
1374         stdout.write( 'Build is configured!\n' )
1375         if nocd:
1376             stdout.write( 'You may now run make (%s).\n' % (Tools.gmake.pathname) )
1377         else:
1378             stdout.write( 'You may now cd into %s and run make (%s).\n' % (cfg.build_dir,Tools.gmake.pathname) )
1379
1380 except AbortError, x:
1381     stderr.write( 'ERROR: %s\n' % (x) )
1382     try:
1383         cfg.record_log()
1384     except:
1385         pass        
1386     sys.exit( 1 )    
1387
1388 sys.exit( 0 )