OSDN Git Service

Cygwin: removed faad2 patch no longer required as per faad2 bump.
[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
879 ## class to hook options and create CONF.args list
880 class Option( optparse.Option ):
881     conf_args = []
882
883     def _conf_record( self, opt, value ):
884         ## skip conf,force,launch
885         if re.match( '^--(conf|force|launch).*$', opt ):
886             return
887
888         ## remove duplicates (last duplicate wins)
889         for i,arg in enumerate( Option.conf_args ):
890             if opt == arg[0]:
891                 del Option.conf_args[i]
892                 break
893
894         if value:
895             Option.conf_args.append( [opt,'%s=%s' % (opt,value)] )
896         else:
897             Option.conf_args.append( [opt,'%s' % (opt)] )
898
899     def take_action( self, action, dest, opt, value, values, parser ):
900         self._conf_record( opt, value )
901         return optparse.Option.take_action( self, action, dest, opt, value, values, parser )
902
903 def createCLI():
904     cli = OptionParser( 'usage: %prog [OPTIONS...] [TARGETS...]' )
905     cli.option_class = Option
906
907     cli.description = ''
908     cli.description += 'Configure %s build system.' % (project.name)
909
910     ## add hidden options
911     cli.add_option( '--conf-method', default='terminal', action='store', help=optparse.SUPPRESS_HELP )
912     cli.add_option( '--force', default=False, action='store_true', help='overwrite existing build config' )
913     cli.add_option( '--verbose', default=False, action='store_true', help='increase verbosity' )
914
915     ## add install options
916     grp = OptionGroup( cli, 'Directory Locations' )
917     grp.add_option( '--src', default=cfg.src_dir, action='store', metavar='DIR',
918         help='specify top-level source dir [%s]' % (cfg.src_dir) )
919     grp.add_option( '--build', default=cfg.build_dir, action='store', metavar='DIR',
920         help='specify build scratch/output dir [%s]' % (cfg.build_dir) )
921     grp.add_option( '--prefix', default=cfg.prefix_dir, action='store', metavar='DIR',
922         help='specify install dir for products [%s]' % (cfg.prefix_dir) )
923     cli.add_option_group( grp )
924
925     ## add feature options
926     grp = OptionGroup( cli, 'Feature Options' )
927
928     h = IfHost( 'enable assembly code in non-contrib modules', 'NOMATCH*-*-darwin*', 'NOMATCH*-*-linux*', none=optparse.SUPPRESS_HELP ).value
929     grp.add_option( '--enable-asm', default=False, action='store_true', help=h )
930
931     h = IfHost( 'disable GTK GUI', '*-*-linux*', none=optparse.SUPPRESS_HELP ).value
932     grp.add_option( '--disable-gtk', default=False, action='store_true', help=h )
933
934     h = IfHost( 'disable Xcode', '*-*-darwin*', none=optparse.SUPPRESS_HELP ).value
935     grp.add_option( '--disable-xcode', default=False, action='store_true', help=h )
936
937     cli.add_option_group( grp )
938
939     ## add launch options
940     grp = OptionGroup( cli, 'Launch Options' )
941     grp.add_option( '--launch', default=False, action='store_true',
942         help='launch build, capture log and wait for completion' )
943     grp.add_option( '--launch-jobs', default=1, action='store', metavar='N', type='int',
944         help='allow N jobs at once; 0 to match CPU count [1]' )
945     grp.add_option( '--launch-args', default=None, action='store', metavar='ARGS',
946         help='specify additional ARGS for launch command' )
947     grp.add_option( '--launch-quiet', default=False, action='store_true',
948         help='do not echo build output while waiting' )
949     cli.add_option_group( grp )
950
951     ## add compile options
952     grp = OptionGroup( cli, 'Compiler Options' )
953     debugMode.cli_add_option( grp, '--debug' )
954     optimizeMode.cli_add_option( grp, '--optimize' )
955     arch.mode.cli_add_option( grp, '--arch' )
956     cli.add_option_group( grp )
957
958     ## add tool locations
959     grp = OptionGroup( cli, 'Tool Basenames and Locations' )
960     for tool in ToolProbe.tools:
961         tool.cli_add_option( grp )
962     cli.add_option_group( grp )
963
964     ## add tool modes
965     grp = OptionGroup( cli, 'Tool Options' )
966     for select in SelectTool.selects:
967         select.cli_add_option( grp )
968     cli.add_option_group( grp )
969     return cli
970
971 ###############################################################################
972 ##
973 ## launcher - used for QuickStart method; launch; build and capture log.
974 ##
975 class Launcher:
976     def __init__( self, targets ):
977         # open build logfile
978         self._file = cfg.open( 'log/build.txt', 'w' )
979
980         cmd = '%s -j%d' % (Tools.gmake.pathname,core.jobs)
981         if options.launch_args:
982             cmd += ' ' + options.launch_args
983         if len(targets):
984             cmd += ' ' + ' '.join(targets)
985
986         ## record begin
987         timeBegin = time.time()
988         self.infof( 'time begin: %s\n', time.asctime() )
989         self.infof( 'launch: %s\n', cmd )
990         if options.launch_quiet:
991             stdout.write( 'building to %s ...\n' % (os.path.abspath( cfg.build_final )))
992         else:
993             stdout.write( '%s\n' % ('-' * 79) )
994
995         ## launch/pipe
996         try:
997             pipe = subprocess.Popen( cmd, shell=True, bufsize=1, stdout=subprocess.PIPE, stderr=subprocess.STDOUT )
998         except Exception, x:
999             cfg.errln( 'launch failure: %s', x )
1000         for line in pipe.stdout:
1001             self.echof( '%s', line )
1002         pipe.wait()
1003
1004         ## record end
1005         timeEnd = time.time()
1006         elapsed = timeEnd - timeBegin
1007
1008         if pipe.returncode:
1009             result = 'FAILURE (code %d)' % pipe.returncode
1010         else:
1011             result = 'SUCCESS'
1012
1013         ## present duration in decent format
1014         seconds = elapsed
1015         hours = int(seconds / 3600)
1016         seconds -= hours * 3600
1017         minutes = int(seconds / 60)
1018         seconds -= minutes * 60
1019
1020         segs = []
1021         duration = ''
1022
1023         if hours == 1:
1024             segs.append( '%d hour' % hours )
1025         elif hours > 1:
1026             segs.append( '%d hours' % hours )
1027
1028         if len(segs) or minutes == 1:
1029             segs.append( '%d minute' % minutes )
1030         elif len(segs) or  minutes > 1:
1031             segs.append( '%d minutes' % minutes )
1032
1033         if seconds == 1:
1034             segs.append( '%d second' % seconds )
1035         else:
1036             segs.append( '%d seconds' % seconds )
1037
1038         if not options.launch_quiet:
1039             stdout.write( '%s\n' % ('-' * 79) )
1040         self.infof( 'time end: %s\n', time.asctime() )
1041         self.infof( 'duration: %s (%.2fs)\n', ', '.join(segs), elapsed )
1042         self.infof( 'result: %s\n', result )
1043
1044         ## cleanup
1045         self._file.close()
1046
1047     def echof( self, format, *args ):
1048         line = format % args
1049         self._file.write( line )
1050         if not options.launch_quiet:
1051             stdout.write( '  : %s' % line )
1052             stdout.flush()
1053
1054     def infof( self, format, *args ):
1055         line = format % args
1056         self._file.write( line )
1057         cfg.infof( '%s', line )
1058
1059 ###############################################################################
1060 ##
1061 ## main program
1062 ##
1063 try:
1064     ## we need to pre-check argv for -h or --help or --verbose to deal with
1065     ## initializing Configure correctly.
1066     verbose = Configure.OUT_INFO
1067     for arg in sys.argv:
1068         if arg == '-h' or arg == '--help':
1069             verbose = Configure.OUT_QUIET
1070             break
1071         if arg == '--verbose':
1072             verbose = Configure.OUT_VERBOSE
1073
1074     ## create main objects; actions/probes run() is delayed.
1075     ## if any actions must be run earlier (eg: for configure --help purposes)
1076     ## then run() must be invoked earlier. subequent run() invocations
1077     ## are ignored.
1078     cfg   = Configure( verbose )
1079     host  = HostTupleProbe(); host.run()
1080
1081     cfg.prefix_dir = ForHost( '/usr/local', ['/Applications','*-*-darwin*'] ).value
1082
1083     build = BuildAction()
1084     arch  = ArchAction(); arch.run()
1085
1086     ## create remaining main objects
1087     core    = CoreProbe()
1088     repo    = RepoProbe()
1089     project = Project()
1090
1091     ## create tools in a scope
1092     class Tools:
1093         ar    = ToolProbe( 'AR.exe',    'ar' )
1094         cp    = ToolProbe( 'CP.exe',    'cp' )
1095         curl  = ToolProbe( 'CURL.exe',  'curl', abort=False )
1096         gcc   = ToolProbe( 'GCC.gcc',   'gcc', IfHost( 'gcc-4', '*-*-cygwin*' ))
1097
1098         if host.match( '*-*-darwin*' ):
1099             gmake = ToolProbe( 'GMAKE.exe', 'make', 'gmake' )
1100         else:
1101             gmake = ToolProbe( 'GMAKE.exe', 'gmake', 'make' )
1102
1103         m4    = ToolProbe( 'M4.exe',    'm4' )
1104         mkdir = ToolProbe( 'MKDIR.exe', 'mkdir' )
1105         patch = ToolProbe( 'PATCH.exe', 'gpatch', 'patch' )
1106         rm    = ToolProbe( 'RM.exe',    'rm' )
1107         tar   = ToolProbe( 'TAR.exe',   'gtar', 'tar' )
1108         wget  = ToolProbe( 'WGET.exe',  'wget', abort=False )
1109         yasm  = ToolProbe( 'YASM.exe',  'yasm', abort=False )
1110
1111         xcodebuild = ToolProbe( 'XCODEBUILD.exe', 'xcodebuild', abort=False )
1112         lipo       = ToolProbe( 'LIPO.exe',       'lipo', abort=False )
1113
1114         fetch = SelectTool( 'FETCH.select', 'fetch', ['wget',wget], ['curl',curl] )
1115
1116     ## run tool probes
1117     for tool in ToolProbe.tools:
1118         tool.run()
1119     for select in SelectTool.selects:
1120         select.run()
1121
1122     debugMode = SelectMode( 'debug', ('none','none'), ('min','min'), ('std','std'), ('max','max') )
1123     optimizeMode = SelectMode( 'optimize', ('none','none'), ('speed','speed'), ('size','size'), default='speed' )
1124
1125     ## create CLI and parse
1126     cli = createCLI()
1127     (options,args) = cli.parse_args()
1128
1129     ## update cfg with cli directory locations
1130     cfg.update_cli( options )
1131
1132     ## prepare list of targets and NAME=VALUE args to pass to make
1133     targets = []
1134     exports = []
1135     rx_exports = re.compile( '([^=]+)=(.*)' )
1136     for arg in args:
1137         m = rx_exports.match( arg )
1138         if m:
1139             exports.append( m.groups() )
1140         else:
1141             targets.append( arg )
1142
1143     ## run delayed actions
1144     for action in Action.actions:
1145         action.run()
1146
1147     ## cfg hook before doc prep
1148     cfg.doc_ready()
1149
1150     ## create document object
1151     doc = ConfigDocument()
1152     doc.addComment( 'generated by configure on %s', time.strftime( '%c' ))
1153
1154     ## add configure line for reconfigure purposes
1155     doc.addBlank()
1156     args = []
1157     for arg in Option.conf_args:
1158         args.append( arg[1] )
1159     doc.add( 'CONF.args', ' '.join( args ))
1160
1161     doc.addBlank()
1162     doc.add( 'HB.title',         project.title )
1163     doc.add( 'HB.name',          project.name )
1164     doc.add( 'HB.name.lower',    project.name_lower )
1165     doc.add( 'HB.name.upper',    project.name_upper )
1166     doc.add( 'HB.acro.lower',    project.acro_lower )
1167     doc.add( 'HB.acro.upper',    project.acro_upper )
1168
1169     doc.add( 'HB.url.website',   project.url_website )
1170     doc.add( 'HB.url.community', project.url_community )
1171     doc.add( 'HB.url.irc',       project.url_irc )
1172     doc.add( 'HB.url.appcast',   project.url_appcast )
1173
1174     doc.add( 'HB.version.major',  project.vmajor )
1175     doc.add( 'HB.version.minor',  project.vminor )
1176     doc.add( 'HB.version.point',  project.vpoint )
1177     doc.add( 'HB.version',        project.version )
1178     doc.add( 'HB.version.hex',    '%04x%02x%02x%08x' % (project.vmajor,project.vminor,project.vpoint,repo.rev) )
1179
1180     doc.add( 'HB.build', project.build )
1181
1182     doc.add( 'HB.repo.url',       repo.url )
1183     doc.add( 'HB.repo.root',      repo.root )
1184     doc.add( 'HB.repo.branch',    repo.branch )
1185     doc.add( 'HB.repo.uuid',      repo.uuid )
1186     doc.add( 'HB.repo.rev',       repo.rev )
1187     doc.add( 'HB.repo.date',      repo.date )
1188     doc.add( 'HB.repo.official',  repo.official )
1189     doc.add( 'HB.repo.type',      repo.type )
1190
1191     doc.addBlank()
1192     doc.add( 'HOST.spec',    host.spec )
1193     doc.add( 'HOST.machine', host.machine )
1194     doc.add( 'HOST.vendor',  host.vendor )
1195     doc.add( 'HOST.system',  host.system )
1196     doc.add( 'HOST.systemf', host.systemf )
1197     doc.add( 'HOST.release', host.release )
1198     doc.add( 'HOST.extra',   host.extra )
1199     doc.add( 'HOST.title',   '%s %s' % (host.systemf,arch.mode.default) )
1200     doc.add( 'HOST.ncpu',    core.count )
1201
1202     doc.addBlank()
1203     doc.add( 'BUILD.spec',    build.spec )
1204     doc.add( 'BUILD.machine', build.machine )
1205     doc.add( 'BUILD.vendor',  build.vendor )
1206     doc.add( 'BUILD.system',  build.system )
1207     doc.add( 'BUILD.systemf', build.systemf )
1208     doc.add( 'BUILD.release', build.release )
1209     doc.add( 'BUILD.extra',   build.extra )
1210     doc.add( 'BUILD.title',   '%s %s' % (build.systemf,arch.mode.mode) )
1211     doc.add( 'BUILD.ncpu',    core.count )
1212     doc.add( 'BUILD.jobs',    core.jobs )
1213
1214     doc.add( 'BUILD.cross',   int(arch.mode.mode != arch.mode.default) )
1215     doc.add( 'BUILD.method',  'terminal' )
1216     doc.add( 'BUILD.date',    time.strftime('%c') )
1217     doc.add( 'BUILD.arch',    arch.mode.mode )
1218
1219     doc.addBlank()
1220     doc.add( 'CONF.method', options.conf_method )
1221
1222     doc.addBlank()
1223     doc.add( 'SRC',     cfg.src_final )
1224     doc.add( 'SRC/',    cfg.src_final + os.sep )
1225     doc.add( 'BUILD',   cfg.build_final )
1226     doc.add( 'BUILD/',  cfg.build_final + os.sep )
1227     doc.add( 'PREFIX',  cfg.prefix_final )
1228     doc.add( 'PREFIX/', cfg.prefix_final + os.sep )
1229     
1230     doc.addBlank()
1231     doc.add( 'FEATURE.asm',   'disabled' )
1232     doc.add( 'FEATURE.gtk',   int( not options.disable_gtk ))
1233     doc.add( 'FEATURE.xcode', int( not (Tools.xcodebuild.fail or options.disable_xcode) ))
1234
1235     if not Tools.xcodebuild.fail and not options.disable_xcode:
1236         doc.addBlank()
1237         doc.add( 'XCODE.external.src',    cfg.xcode_x_src )
1238         doc.add( 'XCODE.external.build',  cfg.xcode_x_build )
1239         doc.add( 'XCODE.external.prefix', cfg.xcode_x_prefix )
1240
1241     doc.addMake( '' )
1242     doc.addMake( '## include definitions' )
1243     doc.addMake( 'include $(SRC/)make/include/main.defs' )
1244
1245     doc.addBlank()
1246     for tool in ToolProbe.tools:
1247         tool.doc_add( doc )
1248
1249     doc.addBlank()
1250     for select in SelectTool.selects:
1251         select.doc_add( doc )
1252
1253     doc.addBlank()
1254     if arch.mode.mode != arch.mode.default:
1255         doc.add( 'GCC.archs', arch.mode.mode )
1256     else:
1257         doc.add( 'GCC.archs', '' )
1258     doc.add( 'GCC.g', debugMode.mode )
1259     doc.add( 'GCC.O', optimizeMode.mode )
1260
1261     if options.enable_asm and not Tools.yasm.fail:
1262         asm = ''
1263         if build.match( 'i?86-*' ):
1264             asm = 'x86'
1265             doc.add( 'LIBHB.GCC.D', 'HAVE_MMX', append=True )
1266             doc.add( 'LIBHB.YASM.D', 'ARCH_X86', append=True )
1267             if build.match( '*-*-darwin*' ):
1268                 doc.add( 'LIBHB.YASM.f', 'macho32' )
1269             else:
1270                 doc.add( 'LIBHB.YASM.f', 'elf32' )
1271             doc.add( 'LIBHB.YASM.m', 'x86' )
1272         elif build.match( 'x86_64-*' ):
1273             asm = 'x86'
1274             doc.add( 'LIBHB.GCC.D', 'HAVE_MMX ARCH_X86_64', append=True )
1275             if build.match( '*-*-darwin*' ):
1276                 doc.add( 'LIBHB.YASM.D', 'ARCH_X86_64 PIC', append=True )
1277                 doc.add( 'LIBHB.YASM.f', 'macho64' )
1278             else:
1279                 doc.add( 'LIBHB.YASM.D', 'ARCH_X86_64', append=True )
1280                 doc.add( 'LIBHB.YASM.f', 'elf64' )
1281             doc.add( 'LIBHB.YASM.m', 'amd64' )
1282         doc.update( 'FEATURE.asm', asm )
1283
1284     ## add exports to make
1285     if len(exports):
1286         doc.addBlank()
1287         doc.addComment( 'overrides via VARIABLE=VALUE on command-line' )
1288         for nv in exports:
1289             doc.add( nv[0], nv[1] )
1290
1291     doc.addMake( '' )
1292     doc.addMake( '## include custom definitions' )
1293     doc.addMake( '-include $(SRC/)custom.defs' )
1294     doc.addMake( '-include $(BUILD/)GNUmakefile.custom.defs' )
1295
1296     doc.addMake( '' )
1297     doc.addMake( '## include rules' )
1298     doc.addMake( 'include $(SRC/)make/include/main.rules' )
1299     doc.addMake( '-include $(SRC/)custom.rules' )
1300     doc.addMake( '-include $(BUILD/)GNUmakefile.custom.rules' )
1301
1302     ## chdir
1303     cfg.chdir()
1304
1305     ## perform
1306     doc.write( 'make' )
1307     doc.write( 'm4' )
1308     if options.launch:
1309         Launcher( targets )
1310
1311     cfg.record_log()
1312
1313     if os.path.normpath( cfg.build_dir ) == os.curdir:
1314         nocd = True
1315     else:
1316         nocd = False
1317
1318     stdout.write( '%s\n' % ('-' * 79) )
1319     if options.launch:
1320         stdout.write( 'Build is finished!\n' )
1321         if nocd:
1322             stdout.write( 'You may now examine the output.\n' )
1323         else:
1324             stdout.write( 'You may now cd into %s and examine the output.\n' % (cfg.build_dir) )
1325     else:
1326         stdout.write( 'Build is configured!\n' )
1327         if nocd:
1328             stdout.write( 'You may now run make (%s).\n' % (Tools.gmake.pathname) )
1329         else:
1330             stdout.write( 'You may now cd into %s and run make (%s).\n' % (cfg.build_dir,Tools.gmake.pathname) )
1331
1332 except AbortError, x:
1333     stderr.write( 'ERROR: %s\n' % (x) )
1334     try:
1335         cfg.record_log()
1336     except:
1337         pass        
1338     sys.exit( 1 )    
1339
1340 sys.exit( 0 )