OSDN Git Service

BuildSystem: effects Darwin platforms only
[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_RX = '([^-]+)-([^-]+)-([^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_RX, 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, spec ):
356         return fnmatch.fnmatch( self.spec, spec )
357
358 ###############################################################################
359
360 class BuildAction( Action, list ):
361     def __init__( self ):
362         super( BuildAction, self ).__init__( 'compute', 'build tuple', abort=True )
363
364     def _action( self ):
365         self.spec = arch.mode[arch.mode.mode]
366
367         ## grok GNU host tuples
368         m = re.match( HostTupleProbe.GNU_TUPLE_RX, self.spec )
369         if not m:
370             self.msg_end = 'invalid host tuple: %s' % (self.spec)
371             return
372
373         self.msg_end = self.spec
374
375         ## assign tuple from regex
376         self[:] = m.groups()
377
378         ## for clarity
379         self.machine = self[0]
380         self.vendor  = self[1]
381         self.system  = self[2]
382         self.release = self[3]
383         self.extra   = self[4]
384         self.systemf = host.systemf
385
386         self.fail = False
387
388 ###############################################################################
389 ##
390 ## platform conditional string; if specs do not match host:
391 ##
392 ## - str value is blank
393 ## - evaluates to False
394 ##
395 class IfHost( object ):
396     def __init__( self, value, *specs ):
397         self.value = ''
398         for spec in specs:
399             if host.match( spec ):
400                 self.value = value
401                 break
402
403     def __nonzero__( self ):
404         return len(self.value) != 0
405         
406     def __str__( self ):
407         return self.value
408
409 ###############################################################################
410 ##
411 ## platform conditional value; loops through list of tuples comparing
412 ## to first host match and sets value accordingly; the first value is
413 ## always default.
414 ##
415 class ForHost( object ):
416     def __init__( self, default, *tuples ):
417         self.value = default
418         for tuple in tuples:
419             if host.match( tuple[1] ):
420                 self.value = tuple[0]
421                 break
422
423     def __str__( self ):
424         return self.value
425
426 ###############################################################################
427
428 class ArchAction( Action ):
429     def __init__( self ):
430         super( ArchAction, self ).__init__( 'compute', 'available architectures', abort=True )
431         self.mode = SelectMode( 'architecture', (host.machine,host.spec) )
432
433     def _action( self ):
434         self.fail = False
435
436         ## some match on system should be made here; otherwise we signal a warning. 
437         if host.match( '*-*-cygwin*' ):
438             pass
439         elif host.match( '*-*-darwin*' ):
440             self.mode['i386']   = 'i386-apple-darwin%s'      % (host.release)
441             self.mode['x86_64'] = 'x86_64-apple-darwin%s'    % (host.release)
442             self.mode['ppc']    = 'powerpc-apple-darwin%s'   % (host.release)
443             self.mode['ppc64']  = 'powerpc64-apple-darwin%s' % (host.release)
444
445             ## special cases in that powerpc does not match gcc -arch value
446             ## which we like to use; so it has to be removed.
447             ## note: we don't know if apple will release Ssnow Leopad/ppc64 yet; just a guess.
448             if 'powerpc' in self.mode:
449                 del self.mode['powerpc']
450                 self.mode.mode = 'ppc'
451             elif 'powerpc64' in self.mode:
452                 del self.mode['powerpc64']
453                 self.mode.mode = 'ppc64'
454         elif host.match( '*-*-linux*' ):
455             pass
456         else:
457             self.msg_pass = 'WARNING'
458
459         self.msg_end = self.mode.toString()
460
461     ## glob-match against spec
462     def match( self, spec ):
463         return fnmatch.fnmatch( self.spec, spec )
464
465 ###############################################################################
466
467 class CoreProbe( Action ):
468     def __init__( self ):
469         super( CoreProbe, self ).__init__( 'probe', 'number of CPU cores' )
470         self.count = 1
471
472     def _action( self ):
473         if self.fail:
474             ## good for darwin9.6.0 and linux
475             try:
476                 self.count = os.sysconf( 'SC_NPROCESSORS_ONLN' )
477                 if self.count < 1:
478                     self.count = 1
479                 self.fail = False
480             except:
481                 pass
482
483         if self.fail:
484             ## windows
485             try:
486                 self.count = int( os.environ['NUMBER_OF_PROCESSORS'] )
487                 if self.count < 1:
488                     self.count = 1
489                 self.fail = False
490             except:
491                 pass
492
493         ## clamp
494         if self.count < 1:
495             self.count = 1
496         elif self.count > 32:
497             self.count = 32
498
499         if options.launch:
500             if options.launch_jobs == 0:
501                 self.jobs = core.count
502             else:
503                 self.jobs = options.launch_jobs
504         else:
505             self.jobs = core.count
506
507         self.msg_end = str(self.count)
508
509 ###############################################################################
510
511 class SelectMode( dict ):
512     def __init__( self, descr, *modes, **kwargs ):
513         super( SelectMode, self ).__init__( modes )
514         self.descr    = descr
515         self.modes    = modes
516         self.default  = kwargs.get('default',modes[0][0])
517         self.mode     = self.default
518
519     def cli_add_option( self, parser, option ):
520         parser.add_option( '', option, default=self.mode, metavar='MODE',
521             help='select %s mode: %s' % (self.descr,self.toString()),
522             action='callback', callback=self.cli_callback, type='str' )
523
524     def cli_callback( self, option, opt_str, value, parser, *args, **kwargs ):
525         if value not in self:
526             raise optparse.OptionValueError( 'invalid %s mode: %s (choose from %s)'
527                 % (self.descr,value,self.toString( True )) )
528         self.mode = value
529
530     def toString( self, nodefault=False ):
531         keys = self.keys()
532         keys.sort()
533         if len(self) == 1:
534             value = self.mode
535         elif nodefault:
536             value = ' '.join( keys )
537         else:
538             value = '%s [%s]' % (' '.join( keys ), self.mode )
539         return value
540
541 ###############################################################################
542 ##
543 ## Repository object.
544 ## Holds information gleaned from subversion working dir.
545 ##
546 ## Builds are classed into one of the following types:
547 ##
548 ##  release
549 ##      must be built from official svn with '/tags/' in the url
550 ##  developer
551 ##      must be built from official svn but is not a release
552 ##  unofficial
553 ##      all other builds
554 ##
555 class RepoProbe( ShellProbe ):
556     def __init__( self ):
557         super( RepoProbe, self ).__init__( 'svn info', 'svn info %s' % (cfg.src_dir) )
558
559         self.url       = 'svn://nowhere.com/project/unknown'
560         self.root      = 'svn://nowhere.com/project'
561         self.branch    = 'unknown'
562         self.uuid      = '00000000-0000-0000-0000-000000000000';
563         self.rev       = 0
564         self.date      = '0000-00-00 00:00:00 -0000'
565         self.official  = 0
566         self.type      = 'unofficial'
567
568     def _parseSession( self ):
569         for line in self.session:
570             ## grok fields
571             m = re.match( '([^:]+):\\s+(.+)', line )
572             if not m:
573                 continue
574
575             (name,value) = m.groups()
576             if name == 'URL':
577                 self.url = value
578             elif name == 'Repository Root':
579                 self.root = value
580             elif name == 'Repository UUID':
581                 self.uuid = value
582             elif name == 'Revision':
583                 self.rev = int( value )
584             elif name == 'Last Changed Date':
585                 # strip chars in parens
586                 if value.find( ' (' ):
587                     self.date = value[0:value.find(' (')]
588                 else:
589                     self.date = value
590
591         ## grok branch
592         i = self.url.rfind( '/' )
593         if i != -1 and i < len(self.url)-1:
594             self.branch = self.url[i+1:]
595
596         # type-classification via repository UUID
597         if self.uuid == 'b64f7644-9d1e-0410-96f1-a4d463321fa5':
598             self.official = 1
599             m = re.match( '([^:]+)://([^/]+)/(.+)', self.url )
600             if m and re.match( 'tags/', m.group( 3 )):
601                 self.type = 'release'
602             else:
603                 self.type = 'developer'
604
605         self.msg_end = self.url
606
607 ###############################################################################
608 ##
609 ## project object.
610 ##
611 ## Contains manually updated version numbers consistent with HB releases
612 ## and other project metadata.
613 ##
614 class Project( Action ):
615     def __init__( self ):
616         super( Project, self ).__init__( 'compute', 'project data' )
617
618         self.name          = 'HandBrake'
619         self.acro_lower    = 'hb'
620         self.acro_upper    = 'HB'
621         self.url_website   = 'http://handbrake.fr'
622         self.url_community = 'http://forum.handbrake.fr'
623         self.url_irc       = 'irc://irc.freenode.net/handbrake'
624
625         self.name_lower = self.name.lower()
626         self.name_upper = self.name.upper()
627
628         self.vmajor = 0
629         self.vminor = 9
630         self.vpoint = 4
631
632     def _action( self ):
633         appcastfmt = 'http://handbrake.fr/appcast%s.xml'
634
635         if repo.type == 'release':
636             self.version = '%d.%d.%d' % (self.vmajor,self.vminor,self.vpoint)
637             self.url_appcast = appcastfmt % ('')
638             self.build = time.strftime('%Y%m%d') + '00'
639             self.title = '%s %s (%s)' % (self.name,self.version,self.build)
640         elif repo.type == 'developer':
641             self.version = 'svn%d' % (repo.rev)
642             self.url_appcast = appcastfmt % ('_unstable')
643             self.build = time.strftime('%Y%m%d') + '01'
644             self.title = '%s svn%d (%s)' % (self.name,repo.rev,self.build)
645         else:
646             self.version = 'svn%d' % (repo.rev)
647             self.url_appcast = appcastfmt % ('_unofficial')
648             self.build = time.strftime('%Y%m%d') + '99'
649             self.title = 'Unofficial svn%d (%s)' % (repo.rev,self.build)
650
651         self.msg_end = '%s (%s)' % (self.name,repo.type)
652         self.fail = False
653
654 ###############################################################################
655
656 class ToolProbe( Action ):
657     tools = []
658
659     def __init__( self, var, *names, **kwargs ):
660         super( ToolProbe, self ).__init__( 'find', abort=kwargs.get('abort',True) )
661         if not self in ToolProbe.tools:
662             ToolProbe.tools.append( self )
663         self.var    = var
664         self.names  = []
665         self.kwargs = kwargs
666         for name in names:
667             if name:
668                 self.names.append( str(name) )
669         self.name = self.names[0]
670         self.pretext = self.name
671         self.pathname = self.names[0]
672
673     def _action( self ):
674         self.session = []
675         for i,name in enumerate(self.names):
676             self.session.append( 'name[%d] = %s' % (i,name) )
677         for name in self.names:
678             f = cfg.findExecutable( name )
679             if f:
680                 self.pathname = f
681                 self.fail = False
682                 self.msg_end = f
683                 break
684         if self.fail:
685             self.msg_end = 'not found'
686
687     def cli_add_option( self, parser ):
688         parser.add_option( '', '--'+self.name, metavar='PROG',
689             help='[%s]' % (self.pathname),
690             action='callback', callback=self.cli_callback, type='str' )
691
692     def cli_callback( self, option, opt_str, value, parser, *args, **kwargs ):
693         self.__init__( self.var, value, **self.kwargs )
694         self.run()
695
696     def doc_add( self, doc ):
697         doc.add( self.var, self.pathname )
698
699 ###############################################################################
700
701 class SelectTool( Action ):
702     selects = []
703
704     def __init__( self, var, name, *pool, **kwargs ):
705         super( SelectTool, self ).__init__( 'select', abort=kwargs.get('abort',True) )
706         self.pretext = name
707         if not self in SelectTool.selects:
708             SelectTool.selects.append( self )
709         self.var      = var
710         self.name     = name
711         self.pool     = pool
712         self.kwargs   = kwargs
713
714     def _action( self ):
715         self.session = []
716         for i,(name,tool) in enumerate(self.pool):
717             self.session.append( 'tool[%d] = %s (%s)' % (i,name,tool.pathname) )
718         for (name,tool) in self.pool:
719             if not tool.fail:
720                 self.selected = name
721                 self.fail = False
722                 self.msg_end = '%s (%s)' % (name,tool.pathname)
723                 break
724         if self.fail:
725             self.msg_end = 'not found'
726
727     def cli_add_option( self, parser ):
728         parser.add_option( '', '--'+self.name, metavar='MODE',
729             help='select %s mode: %s' % (self.name,self.toString()),
730             action='callback', callback=self.cli_callback, type='str' )
731
732     def cli_callback( self, option, opt_str, value, parser, *args, **kwargs ):
733         found = False
734         for (name,tool) in self.pool:
735             if name == value:
736                 found = True
737                 self.__init__( self.var, self.name, [name,tool], **kwargs )
738                 self.run()
739                 break
740         if not found:
741             raise optparse.OptionValueError( 'invalid %s mode: %s (choose from %s)'
742                 % (self.name,value,self.toString( True )) )
743
744     def doc_add( self, doc ):
745         doc.add( self.var, self.selected )
746
747     def toString( self, nodefault=False ):
748         if len(self.pool) == 1:
749             value = self.pool[0][0]
750         else:
751             s = ''
752             for key,value in self.pool:
753                 s += ' ' + key
754             if nodefault:
755                 value = s[1:]
756             else:
757                 value = '%s [%s]' % (s[1:], self.selected )
758         return value
759
760 ###############################################################################
761 ##
762 ## config object used to output gnu-make or gnu-m4 output.
763 ##
764 ## - add() to add NAME/VALUE pairs suitable for both make/m4.
765 ## - addBlank() to add a linefeed for both make/m4.
766 ## - addMake() to add a make-specific line.
767 ## - addM4() to add a m4-specific line.
768 ##
769 class ConfigDocument:
770     def __init__( self ):
771         self._elements = []
772
773     def _outputMake( self, file, namelen, name, value ):
774         file.write( '%-*s = %s\n' % (namelen, name, value ))
775
776     def _outputM4( self, file, namelen, name, value ):
777         namelen += 7
778         name = '<<__%s>>,' % name.replace( '.', '_' )
779         file.write( 'define(%-*s  <<%s>>)dnl\n' % (namelen, name, value ))
780
781     def add( self, name, value ):
782         self._elements.append( (name,value) )
783
784     def addBlank( self ):
785         self._elements.append( None )
786
787     def addComment( self, format, *args ):
788         self.addMake( '## ' + format % args )
789         self.addM4( 'dnl ' + format % args )
790
791     def addMake( self, line ):
792         self._elements.append( ('?make',line) )
793
794     def addM4( self, line ):
795         self._elements.append( ('?m4',line) )
796
797     def output( self, file, type ):
798         namelen = 0
799         for item in self._elements:
800             if item == None or item[0].find( '?' ) == 0:
801                 continue
802             if len(item[0]) > namelen:
803                 namelen = len(item[0])
804         for item in self._elements:
805             if item == None:
806                 if type == 'm4':
807                     file.write( 'dnl\n' )
808                 else:
809                     file.write( '\n' )
810                 continue
811             if item[0].find( '?' ) == 0:
812                 if item[0].find( type, 1 ) == 1:
813                     file.write( '%s\n' % (item[1]) )
814                 continue
815
816             if type == 'm4':
817                 self._outputM4( file, namelen, item[0], item[1] )
818             else:
819                 self._outputMake( file, namelen, item[0], item[1] )
820
821     def write( self, type ):
822         if type == 'make':
823             fname = 'GNUmakefile'
824         elif type == 'm4':
825             fname = os.path.join( 'project', project.name_lower + '.m4' )
826         else:
827             raise ValueError, 'unknown file type: ' + type
828
829         ftmp  = fname + '.tmp'
830         try:
831             try:
832                 file = cfg.open( ftmp, 'w' )
833                 self.output( file, type )
834             finally:
835                 try:
836                     file.close()
837                 except:
838                     pass
839         except Exception, x:
840             try:
841                 os.remove( ftmp )
842             except Exception, x:
843                 pass
844             cfg.errln( 'failed writing to %s\n%s', ftmp, x )
845
846         try:
847             os.rename( ftmp, fname )
848         except Exception, x:
849             cfg.errln( 'failed writing to %s\n%s', fname, x )
850
851 ###############################################################################
852 ##
853 ## create cli parser
854 ##
855 def createCLI():
856     cli = OptionParser( 'usage: %prog [OPTIONS...] [TARGETS...]' )
857
858     cli.description = ''
859     cli.description += 'Configure %s build system.' % (project.name)
860
861     ## add hidden options
862     cli.add_option( '', '--conf-method', default='terminal', action='store', help=optparse.SUPPRESS_HELP )
863     cli.add_option( '', '--force', default=False, action='store_true', help='overwrite existing build config' )
864     cli.add_option( '', '--verbose', default=False, action='store_true', help='increase verbosity' )
865
866     ## add install options
867     grp = OptionGroup( cli, 'Directory Locations' )
868     grp.add_option( '', '--src', default=cfg.src_dir, action='store', metavar='DIR',
869         help='specify top-level source dir [%s]' % (cfg.src_dir) )
870     grp.add_option( '', '--build', default=cfg.build_dir, action='store', metavar='DIR',
871         help='specify build scratch/output dir [%s]' % (cfg.build_dir) )
872     grp.add_option( '', '--prefix', default=cfg.prefix_dir, action='store', metavar='DIR',
873         help='specify install dir for products [%s]' % (cfg.prefix_dir) )
874     cli.add_option_group( grp )
875
876     ## add feature options
877     grp = OptionGroup( cli, 'Feature Options' )
878     h = ForHost( optparse.SUPPRESS_HELP, ['disable Xcode (Darwin only)','*-*-darwin*'] ).value
879     grp.add_option( '', '--disable-xcode', default=False, action='store_true', help=h )
880     h = ForHost( optparse.SUPPRESS_HELP, ['disable GTK GUI (Linux only)','*-*-linux*'] ).value
881     grp.add_option( '', '--disable-gtk', default=False, action='store_true', help=h )
882     cli.add_option_group( grp )
883
884     ## add launch options
885     grp = OptionGroup( cli, 'Launch Options' )
886     grp.add_option( '', '--launch', default=False, action='store_true',
887         help='launch build, capture log and wait for completion' )
888     grp.add_option( '', '--launch-jobs', default=1, action='store', metavar='N', type='int',
889         help='allow N jobs at once; 0 to match CPU count [1]' )
890     grp.add_option( '', '--launch-args', default=None, action='store', metavar='ARGS',
891         help='specify additional ARGS for launch command' )
892     grp.add_option( '', '--launch-quiet', default=False, action='store_true',
893         help='do not echo build output' )
894     cli.add_option_group( grp )
895
896     ## add compile options
897     grp = OptionGroup( cli, 'Compiler Options' )
898     debugMode.cli_add_option( grp, '--debug' )
899     optimizeMode.cli_add_option( grp, '--optimize' )
900     arch.mode.cli_add_option( grp, '--arch' )
901     cli.add_option_group( grp )
902
903     ## add tool locations
904     grp = OptionGroup( cli, 'Tool Basenames and Locations' )
905     for tool in ToolProbe.tools:
906         tool.cli_add_option( grp )
907     cli.add_option_group( grp )
908
909     ## add tool modes
910     grp = OptionGroup( cli, 'Tool Options' )
911     for select in SelectTool.selects:
912         select.cli_add_option( grp )
913     cli.add_option_group( grp )
914     return cli
915
916 ###############################################################################
917 ##
918 ## launcher - used for QuickStart method; launch; build and capture log.
919 ##
920 class Launcher:
921     def __init__( self, targets ):
922         # open build logfile
923         self._file = cfg.open( 'log/build.txt', 'w' )
924
925         cmd = '%s -j%d' % (Tools.gmake.pathname,core.jobs)
926         if options.launch_args:
927             cmd += ' ' + options.launch_args
928         if len(targets):
929             cmd += ' ' + ' '.join(targets)
930
931         ## record begin
932         timeBegin = time.time()
933         self.infof( 'time begin: %s\n', time.asctime() )
934         self.infof( 'launch: %s\n', cmd )
935         if options.launch_quiet:
936             stdout.write( 'building to %s ...\n' % (os.path.abspath( cfg.build_final )))
937         else:
938             stdout.write( '%s\n' % ('-' * 79) )
939
940         ## launch/pipe
941         try:
942             pipe = subprocess.Popen( cmd, shell=True, bufsize=1, stdout=subprocess.PIPE, stderr=subprocess.STDOUT )
943         except Exception, x:
944             cfg.errln( 'launch failure: %s', x )
945         for line in pipe.stdout:
946             self.echof( '%s', line )
947         pipe.wait()
948
949         ## record end
950         timeEnd = time.time()
951         elapsed = timeEnd - timeBegin
952
953         if pipe.returncode:
954             result = 'FAILURE (code %d)' % pipe.returncode
955         else:
956             result = 'SUCCESS'
957
958         ## present duration in decent format
959         seconds = elapsed
960         hours = int(seconds / 3600)
961         seconds -= hours * 3600
962         minutes = int(seconds / 60)
963         seconds -= minutes * 60
964
965         segs = []
966         duration = ''
967
968         if hours == 1:
969             segs.append( '%d hour' % hours )
970         elif hours > 1:
971             segs.append( '%d hours' % hours )
972
973         if len(segs) or minutes == 1:
974             segs.append( '%d minute' % minutes )
975         elif len(segs) or  minutes > 1:
976             segs.append( '%d minutes' % minutes )
977
978         if seconds == 1:
979             segs.append( '%d second' % seconds )
980         else:
981             segs.append( '%d seconds' % seconds )
982
983         if not options.launch_quiet:
984             stdout.write( '%s\n' % ('-' * 79) )
985         self.infof( 'time end: %s\n', time.asctime() )
986         self.infof( 'duration: %s (%.2fs)\n', ', '.join(segs), elapsed )
987         self.infof( 'result: %s\n', result )
988
989         ## cleanup
990         self._file.close()
991
992     def echof( self, format, *args ):
993         line = format % args
994         self._file.write( line )
995         if not options.launch_quiet:
996             stdout.write( '  : %s' % line )
997             stdout.flush()
998
999     def infof( self, format, *args ):
1000         line = format % args
1001         self._file.write( line )
1002         cfg.infof( '%s', line )
1003
1004 ###############################################################################
1005 ##
1006 ## main program
1007 ##
1008 try:
1009     ## we need to pre-check argv for -h or --help or --verbose to deal with
1010     ## initializing Configure correctly.
1011     verbose = Configure.OUT_INFO
1012     for arg in sys.argv:
1013         if arg == '-h' or arg == '--help':
1014             verbose = Configure.OUT_QUIET
1015             break
1016         if arg == '--verbose':
1017             verbose = Configure.OUT_VERBOSE
1018
1019     ## create main objects; actions/probes run() is delayed.
1020     ## if any actions must be run earlier (eg: for configure --help purposes)
1021     ## then run() must be invoked earlier. subequent run() invocations
1022     ## are ignored.
1023     cfg   = Configure( verbose )
1024     host  = HostTupleProbe(); host.run()
1025
1026     cfg.prefix_dir = ForHost( '/usr/local', ['/Applications','*-*-darwin*'] ).value
1027
1028     build = BuildAction()
1029     arch  = ArchAction(); arch.run()
1030
1031     ## create remaining main objects
1032     core    = CoreProbe()
1033     repo    = RepoProbe()
1034     project = Project()
1035
1036     ## create tools in a scope
1037     class Tools:
1038         ar    = ToolProbe( 'AR.exe',    'ar' )
1039         cp    = ToolProbe( 'CP.exe',    'cp' )
1040         curl  = ToolProbe( 'CURL.exe',  'curl', abort=False )
1041         gcc   = ToolProbe( 'GCC.gcc',   'gcc', IfHost('gcc-4','*-*-cygwin*') )
1042
1043         if host.match( '*-*-darwin*' ):
1044             gmake = ToolProbe( 'GMAKE.exe', 'make', 'gmake' )
1045         else:
1046             gmake = ToolProbe( 'GMAKE.exe', 'gmake', 'make' )
1047
1048         m4    = ToolProbe( 'M4.exe',    'm4' )
1049         mkdir = ToolProbe( 'MKDIR.exe', 'mkdir' )
1050         patch = ToolProbe( 'PATCH.exe', 'gpatch', 'patch' )
1051         rm    = ToolProbe( 'RM.exe',    'rm' )
1052         tar   = ToolProbe( 'TAR.exe',   'gtar', 'tar' )
1053         wget  = ToolProbe( 'WGET.exe',  'wget', abort=False )
1054
1055         xcodebuild = ToolProbe( 'XCODEBUILD.exe', 'xcodebuild', abort=False )
1056         lipo       = ToolProbe( 'LIPO.exe',       'lipo', abort=False )
1057
1058         fetch = SelectTool( 'FETCH.select', 'fetch', ['wget',wget], ['curl',curl] )
1059
1060     ## run tool probes
1061     for tool in ToolProbe.tools:
1062         tool.run()
1063     for select in SelectTool.selects:
1064         select.run()
1065
1066     debugMode = SelectMode( 'debug', ('none','none'), ('min','min'), ('std','std'), ('max','max') )
1067     optimizeMode = SelectMode( 'optimize', ('none','none'), ('speed','speed'), ('size','size'), default='speed' )
1068
1069     ## create CLI and parse
1070     cli = createCLI()
1071     (options,args) = cli.parse_args()
1072
1073     ## update cfg with cli directory locations
1074     cfg.update_cli( options )
1075
1076     ## prepare list of targets and NAME=VALUE args to pass to make
1077     targets = []
1078     exports = []
1079     rx_exports = re.compile( '([^=]+)=(.*)' )
1080     for arg in args:
1081         m = rx_exports.match( arg )
1082         if m:
1083             exports.append( m.groups() )
1084         else:
1085             targets.append( arg )
1086
1087     ## run delayed actions
1088     for action in Action.actions:
1089         action.run()
1090
1091     ## cfg hook before doc prep
1092     cfg.doc_ready()
1093
1094     ## create document object
1095     doc = ConfigDocument()
1096     doc.addComment( 'generated by configure on %s', time.strftime( '%c' ))
1097
1098     ## add configure line for reconfigure purposes
1099     doc.addBlank()
1100     args = []
1101     for arg in sys.argv[1:]:
1102         if arg == '--launch':
1103             continue
1104         args.append( arg )
1105     doc.add( 'CONF.args', ' '.join( args ))
1106
1107     doc.addBlank()
1108     doc.add( 'HB.title',         project.title )
1109     doc.add( 'HB.name',          project.name )
1110     doc.add( 'HB.name.lower',    project.name_lower )
1111     doc.add( 'HB.name.upper',    project.name_upper )
1112     doc.add( 'HB.acro.lower',    project.acro_lower )
1113     doc.add( 'HB.acro.upper',    project.acro_upper )
1114
1115     doc.add( 'HB.url.website',   project.url_website )
1116     doc.add( 'HB.url.community', project.url_community )
1117     doc.add( 'HB.url.irc',       project.url_irc )
1118     doc.add( 'HB.url.appcast',   project.url_appcast )
1119
1120     doc.add( 'HB.version.major',  project.vmajor )
1121     doc.add( 'HB.version.minor',  project.vminor )
1122     doc.add( 'HB.version.point',  project.vpoint )
1123     doc.add( 'HB.version',        project.version )
1124     doc.add( 'HB.version.hex',    '%04x%02x%02x%08x' % (project.vmajor,project.vminor,project.vpoint,repo.rev) )
1125
1126     doc.add( 'HB.build', project.build )
1127
1128     doc.add( 'HB.repo.url',       repo.url )
1129     doc.add( 'HB.repo.root',      repo.root )
1130     doc.add( 'HB.repo.branch',    repo.branch )
1131     doc.add( 'HB.repo.uuid',      repo.uuid )
1132     doc.add( 'HB.repo.rev',       repo.rev )
1133     doc.add( 'HB.repo.date',      repo.date )
1134     doc.add( 'HB.repo.official',  repo.official )
1135     doc.add( 'HB.repo.type',      repo.type )
1136
1137     doc.addBlank()
1138     doc.add( 'HOST.spec',    host.spec )
1139     doc.add( 'HOST.machine', host.machine )
1140     doc.add( 'HOST.vendor',  host.vendor )
1141     doc.add( 'HOST.system',  host.system )
1142     doc.add( 'HOST.systemf', host.systemf )
1143     doc.add( 'HOST.release', host.release )
1144     doc.add( 'HOST.extra',   host.extra )
1145     doc.add( 'HOST.title',   '%s %s' % (host.systemf,arch.mode.default) )
1146     doc.add( 'HOST.ncpu',    core.count )
1147
1148     doc.addBlank()
1149     doc.add( 'BUILD.spec',    build.spec )
1150     doc.add( 'BUILD.machine', build.machine )
1151     doc.add( 'BUILD.vendor',  build.vendor )
1152     doc.add( 'BUILD.system',  build.system )
1153     doc.add( 'BUILD.systemf', build.systemf )
1154     doc.add( 'BUILD.release', build.release )
1155     doc.add( 'BUILD.extra',   build.extra )
1156     doc.add( 'BUILD.title',   '%s %s' % (build.systemf,arch.mode.mode) )
1157     doc.add( 'BUILD.ncpu',    core.count )
1158     doc.add( 'BUILD.jobs',    core.jobs )
1159
1160     doc.add( 'BUILD.cross',   int(arch.mode.mode != arch.mode.default) )
1161     doc.add( 'BUILD.method',  'terminal' )
1162     doc.add( 'BUILD.date',    time.strftime('%c') )
1163     doc.add( 'BUILD.arch',    arch.mode.mode )
1164
1165     doc.addBlank()
1166     doc.add( 'CONF.method', options.conf_method )
1167
1168     doc.addBlank()
1169     doc.add( 'SRC',     cfg.src_final )
1170     doc.add( 'SRC/',    cfg.src_final + os.sep )
1171     doc.add( 'BUILD',   cfg.build_final )
1172     doc.add( 'BUILD/',  cfg.build_final + os.sep )
1173     doc.add( 'PREFIX',  cfg.prefix_final )
1174     doc.add( 'PREFIX/', cfg.prefix_final + os.sep )
1175     
1176     doc.addBlank()
1177     doc.add( 'FEATURE.xcode', int( not (Tools.xcodebuild.fail or options.disable_xcode) ))
1178     doc.add( 'FEATURE.gtk',   int( not options.disable_gtk ))
1179
1180     if not Tools.xcodebuild.fail and not options.disable_xcode:
1181         doc.addBlank()
1182         doc.add( 'XCODE.external.src',    cfg.xcode_x_src )
1183         doc.add( 'XCODE.external.build',  cfg.xcode_x_build )
1184         doc.add( 'XCODE.external.prefix', cfg.xcode_x_prefix )
1185
1186     doc.addMake( '' )
1187     doc.addMake( '## include definitions' )
1188     doc.addMake( 'include $(SRC/)make/include/main.defs' )
1189
1190     doc.addBlank()
1191     for tool in ToolProbe.tools:
1192         tool.doc_add( doc )
1193
1194     doc.addBlank()
1195     for select in SelectTool.selects:
1196         select.doc_add( doc )
1197
1198     doc.addBlank()
1199     if arch.mode.mode != arch.mode.default:
1200         doc.add( 'GCC.archs', arch.mode.mode )
1201     else:
1202         doc.add( 'GCC.archs', '' )
1203     doc.add( 'GCC.g', debugMode.mode )
1204     doc.add( 'GCC.O', optimizeMode.mode )
1205
1206     ## add exports to make
1207     if len(exports):
1208         doc.addBlank()
1209         doc.addComment( 'overrides via VARIABLE=VALUE on command-line' )
1210         for nv in exports:
1211             doc.add( nv[0], nv[1] )
1212
1213     doc.addMake( '' )
1214     doc.addMake( '## include custom definitions' )
1215     doc.addMake( '-include $(SRC/)custom.defs' )
1216     doc.addMake( '-include $(BUILD/)GNUmakefile.custom.defs' )
1217
1218     doc.addMake( '' )
1219     doc.addMake( '## include rules' )
1220     doc.addMake( 'include $(SRC/)make/include/main.rules' )
1221     doc.addMake( '-include $(SRC/)custom.rules' )
1222     doc.addMake( '-include $(BUILD/)GNUmakefile.custom.rules' )
1223
1224     ## chdir
1225     cfg.chdir()
1226
1227     ## perform
1228     doc.write( 'make' )
1229     doc.write( 'm4' )
1230     if options.launch:
1231         Launcher( targets )
1232
1233     cfg.record_log()
1234
1235     if os.path.normpath( cfg.build_dir ) == os.curdir:
1236         nocd = True
1237     else:
1238         nocd = False
1239
1240     stdout.write( '%s\n' % ('-' * 79) )
1241     if options.launch:
1242         stdout.write( 'Build is finished!\n' )
1243         if nocd:
1244             stdout.write( 'You may now examine the output.\n' )
1245         else:
1246             stdout.write( 'You may now cd into %s and examine the output.\n' % (cfg.build_dir) )
1247     else:
1248         stdout.write( 'Build is configured!\n' )
1249         if nocd:
1250             stdout.write( 'You may now run make (%s).\n' % (Tools.gmake.pathname) )
1251         else:
1252             stdout.write( 'You may now cd into %s and run make (%s).\n' % (cfg.build_dir,Tools.gmake.pathname) )
1253
1254 except AbortError, x:
1255     stderr.write( 'ERROR: %s\n' % (x) )
1256     try:
1257         cfg.record_log()
1258     except:
1259         pass        
1260     sys.exit( 1 )    
1261
1262 sys.exit( 0 )