OSDN Git Service

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