OSDN Git Service

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