''' Yabs3 is a particular high-level interface onto Yabs. I use this for building projects consisting of C, C++ and Cmm files (Cmm is an extended C++ that supports multimethods, using a source processor called cmm). It could easily be extended to support other types of source files. Supported build flags include: debug, release, threads, boost (support use of the Boost library), gtk (support GTK-2 compile/link). Supported compilers: gcc/g++ (compiling, linking, preprocessing) vc++ (compiling/linking only) ''' import yabs, yabs2, re, string, os, yabserrors def _gcc_extradeps( filename, state, simple_prerequisites=False): ''' Returns list of filenames in a makefile-style dependency file. We return [None] if we can't open the dependency file, or if it doesn't contain any items. This ensures that we default to a rebuild if the dependency file is faulty. If is true, we look at the modification times of the prerequisites, and discard any prerequisites that are older than the output file. This can give a significant speed increase in the common case where source files are not targets of Yabs rules - removing them at this stage avoids the need for Yabs to attempt to look for a matching rule for each prerequisite. ''' assert isinstance( state, yabs.State) if state.debug>2: print 'yabs3 _gcc_extradeps(): getting items from dep file ' + filename if yabs.mtime( filename, state.mtimecache) == 0: return [ None] deps = [] o_mtime = None dep_file_ok = False for line in file( filename): for item in line.split(): dep_file_ok = True if item.endswith( '.o:'): if simple_prerequisites: o_mtime = yabs.mtime( item[:-1], state.mtimecache) #print yabs.place(), 'o_mtime=', o_mtime, item[:-1] continue # ignore the .o file mentioned in the dep file. if item=='\\': continue if state.debug>4: print 'yabs3: found dep ' + i if simple_prerequisites and ( o_mtime is not None): item_mtime = yabs.mtime( item, state.mtimecache) if item_mtime > o_mtime: #print yabs.place(), 'item_mtime newer:', item_mtime, item deps.append( item) else: #print yabs.place(), 'ommiting gcc dep:', item pass else: deps.append( item) if not dep_file_ok: assert deps == [] deps = [ None] return deps def add_toolcommand( tool, command, tooldict, suffixes=None, state=None): ''' Adds a particular command to yabs3. is a yabs3-specific name for a tool. Currently supported values are: gcc, g++, msvc (which means the microsoft vc++ compiler, usually called `cl.exe'). is something that can be passed to os.system(), e.g. something like: /my/custom/installation/directory/gcc-2.95.3/bin/gcc Version information is obtained for gcc and g++ tools, but not currently for vc++. ''' if not tooldict.has_key( 'os'): tooldict['os'] = yabs.uname_os if not tooldict.has_key( 'osv'): tooldict['osv'] = yabs.uname_osv if not tooldict.has_key( 'cpu'): tooldict['cpu'] = yabs.uname_cpu if state is None: state=yabs.default_state assert isinstance( state, yabs.State) assert type( command)==type( '') version = '' tooldict[tool] = None if tool=='gcc' or tool=='g++': c = command + ' -dumpversion' e, version = state.subprocessfn( c, echo=False) if e: raise Exception( 'failed to get gcc version\n' ' gcc command: ' + c + '\n' ' output: ' + version + '\n' ) else: tooldict[tool] = version.strip() state.tools.append( (tooldict, command)) if suffixes is None: if tool=='gcc': suffixes = ( ( '.c', '.o'), ( '.c', '.c'), ( '.c', '.s'), ( '.S', '.s'), ( '.S', '.o'), ( '', '.exe'), ( '', '.so'), ( '', '.a'), ( '', '.o'), ) elif tool=='g++': suffixes = ( ( '.cpp', '.o'), ( '.cpp', '.cpp'), ( '.cpp', '.s')) if suffixes: for pre, suf in suffixes: if not state.tools_reverse.has_key( ( pre, suf)): state.tools_reverse[ ( pre, suf)] = [] state.tools_reverse[ ( pre, suf)].append( ( tooldict, command)) def _find_command_from_tool_version( tooldict, approx=False, tools=None, state=None): ''' Returns a command that is with specified version, by searching the items previously registered with calls to add_toolcommand(). Raises an exception if the requested combination has not been registered. ''' assert type( tooldict)==dict if state is None: state=yabs.default_state assert isinstance( state, yabs.State) assert approx in ( True, False), ' param should be True or False, but is ' + repr(tools) if tools is None: tools = state.tools class error( yabserrors.error): def __init__( self): pass def __str__( self): ret = 'unknown tool: ' + str( tooldict) + '\n' ret += 'known tools are:\n' for i in state.tools: ret += str( i) + '\n' return ret compatible_tools = [] for td, command in tools: compatible = True for n,v in td.iteritems(): vv = tooldict.get( n, None) if ( approx and (vv is None)) or v==vv: pass else: #print yabs.place(), 'incompatible: approx=', approx, 'n=', n, 'v=', v, 'vv=', vv compatible = False break if compatible: compatible_tools.append( ( td, command)) if len(compatible_tools)==1: # override input values with the ones in the toolset we found: ret = tooldict for n, v in compatible_tools[0][0].iteritems(): ret[n] = v return ret, compatible_tools[0][1] elif len(compatible_tools)==0: if 1: print yabs.place(), 'no matching tool: approx=', approx, ':' print ' ' + str( tooldict) print yabs.place(), 'available tools are:' for i in tools: print ' ', i return None, None else: if 1: print yabs.place(), 'ambiguous tool (approx='+str(approx)+'):' print ' ' + str( tooldict) print yabs.place(), 'compatible tools are:' for i in compatible_tools: print ' ' + str( i) print yabs.callers() return None, None def setup( state=None): if state is None: state = yabs.default_state state.tools = [] state.tools_reverse = {} try: state.default_gccversion = yabs.subprocess_text( 'gcc -dumpversion', echo=False).strip() except yabserrors.command_error: state.default_gccversion = '' state.build_subdir = '_yabs' state.extra_params = [] setup() # Add default tools. if os.name=='nt': add_toolcommand( 'g++', 'g++') add_toolcommand( 'gcc', 'gcc') add_toolcommand( 'vc++', 'cl') else: import glob #print '$PATH=', os.getenv( 'PATH'), os.getenv( 'PATH').split( ':') def add_tools( leafnames): paths = os.getenv( 'PATH').split( ':') # scan path in reverse, because later calls to add_toolcommand # override earlier ones. paths.reverse() for path in paths: for leaf in leafnames: for f in glob.glob( os.path.join( path, leaf)): add_toolcommand( leaf, f, {}) if 0: # add all gcc* and g++ commands. this can cause problems if the same # gcc is in different places in $PATH. add_tools( [ 'gcc', 'g++']) else: add_toolcommand( 'gcc', 'gcc', {}) add_toolcommand( 'g++', 'g++', {}) def _infix2dict( infix, state=None): ''' converts a filename portion to a dict. ''' if state is None: state=yabs.default_state ret = {} if infix is None or infix=='': return ret items = infix.split( yabs2.separator) for item in items: if item=='': continue eq = item.find( '=') if eq>=0: ret[ item[:eq]] = item[eq+1:] else: ret[item] = None return ret def _dict2infix( d, state=None): #print 'd=', d if state is None: state = yabs.default_state items = [] for n, v in d.iteritems(): if v is None: items.append( n) else: items.append( n + '=' + v) items.sort() ret = string.join( items, yabs2.separator) #print 'ret=', ret return ret def _find_suffixes( leaf): ''' Returns list of pairs of indexes into , with each pair delimiting a suffix. E.g. _find_suffixes( 'foo.c,debug,gcc.o') returns [(3, 5), (15, 17)]. ''' suffixes = [] end = 0 while 1: best = len(leaf) best_suffix = '' for suffix in 'o', 'cpp', 'c', 'a', 'so', 'exe', 'S', 's': s = (leaf+',').find( '.'+suffix, end) if s!=-1 and s < best: if (leaf+',')[s+len(suffix)+1] in '.,': best = s best_suffix = '.'+suffix if best==len(leaf): break end = best+len(best_suffix) suffixes.append( (best, end)) return suffixes def _transform( path, state, extra=None): ''' Expands build specification in leaf of to match a tool registered with add_toolcommand. Can fail if the incoming specification is ambiguous, or doesn't match a registered tool. ''' #print yabs.place(), 'path=', path d, leaf = os.path.split(path) suffixes = _find_suffixes( leaf) #print yabs.place(), suffixes ret = leaf for i in range(len(suffixes)): #print yabs.place(), 'looking at suffix', suffixes[i], ': ', leaf[suffixes[i][0]:suffixes[i][1]] if i==0: build_begin = leaf.find( ',', 0, suffixes[i][1]) if build_begin==-1: build_begin = suffixes[i][0] ret = leaf[:build_begin] prefix = '' #print yabs.place(), 'ret=', ret else: build_begin = suffixes[i-1][1] prefix = leaf[suffixes[i-1][0]:suffixes[i-1][1]] suffix = leaf[suffixes[i][0]:suffixes[i][1]] build_end = suffixes[i][0] build = leaf[build_begin:build_end] tools = state.tools if extra: # note that infix2dict() overwrites early items with later items, # so we put first so that items in will be # overwriten by similar items in : build = extra + ',' + build tools = state.tools_reverse.get( ( prefix, suffix), None) #print yabs.place(), prefix, suffix, tools if tools is None: builddict = None else: builddict, command = _find_command_from_tool_version( _infix2dict(build), tools=tools, approx=True) if builddict is None: print yabs.place(), 'no matching command for:', build if builddict is not None: if builddict.has_key('g++'): # a little hacky - we remove any 'gcc' from build dict. would # prefer to do `builddict.pop('gcc', None)' but this doesn't # work on python-2.2. try: del builddict['gcc'] except KeyError: pass ret += ',' + _dict2infix(builddict) ret += leaf[ suffixes[i][0]:suffixes[i][1]] #print yabs.place(), 'ret=', ret ret = os.path.join( d, ret) if 0: print yabs.place(), 'path=', path print yabs.place(), 'ret=', ret return ret def _transform_test(): a = _transform( 'foo,debug,z=1.exe', yabs.default_state) b = _transform( 'foo,debug.exe', yabs.default_state, extra='z=1') c = _transform( 'foo,debug,z=2.exe', yabs.default_state, extra='z=1') assert ',z=1.exe' in a, a assert ',z=1.exe' in b, b assert ',z=2.exe' in c, c assert ',z=1.exe' not in c, c if 0: _transform_test() def convenience_compile( target, state): ''' Makes things like bar/foo[,].o depend on bar/_yabs/foo[,].o. ''' if ( 0 or target.endswith( '.a') or target.endswith( '.o') or target.endswith( '.c') or target.endswith( '.s') or target.endswith( '.cpp') or target.endswith( '.obj') ): elements = target.split( os.sep) else: return if elements[-1].count('.') < 2: return #print yabs.place(), 'target=', target if len(elements)>=2 and elements[-2]==state.build_subdir: return if state is None: state = yabs.default_state p = os.sep + os.path.join( *elements[:-1]) p = os.path.join( p, state.build_subdir, elements[-1]) pp = _transform( p, state) if 0: print yabs.place(), ' p=', p print yabs.place(), 'pp=', pp if pp is None: return return '', [ pp], yabs.add_rule( convenience_compile, phony=True) def default_target( target, state=None, build=None, quiet=False): ''' Takes names such as src/foo.exe, src/bar.c.o and converts them into src/_yabs/foo,gcc,debug.exe, src/_yabs/bar.c,gcc,debug.o (insert_subdir=True) or src/foo,gcc,debug.exe, src/bar,gcc,debug.c.o (insert_subdir=False). This function is useful if you want to force an executable to use a particular build of a particular source file. For example, to force the use of a debug build of the file foo.c, do: yabs3.add_exe( 'myapp', 'main,release.c ' + yabs3.default_target( 'foo.c,gcc,debug.o')) Elements inside itself override those in . Note that any default build passed as part of in call to add_exe/so() will be ignored by this function. Returns unchanged if target is not in form that implies a default target. If is already inside _yabs/, returns if quiet is True, else raises an exception. ''' if state is None: state = yabs.default_state elements = target.split( os.sep) if len(elements)>=2 and elements[-2]==state.build_subdir: if quiet: return target raise Exception( 'yabs3.default_target() does not accept name within _yabs: ' + target) p = '' if len(elements) > 1: p = os.sep.join( elements[:-1]) p = os.path.join( p, state.build_subdir, elements[-1]) pp = _transform( p, state, extra=build) if pp is None: return return pp if 0: print default_target( 'foo.c.o') print default_target( 'foo.c.c.o') print default_target( 'foo.c,gcc,debug.o', build='release') print default_target( 'foo.c,release.o') print default_target( 'foo.exe') print default_target( 'foo,gcc,debug.exe') print default_target( 'foo.exe') print default_target( 'foo.so') print default_target( '_yabs/foo.c.o') print default_target( '/yabs/example-1/foo.exe') print default_target( '/yabs/example-1/_yabs/foo,cpu=i686,debug,gcc4.1.2,os=Linux,osv=2.6.18-4-k7.exe') #sys.exit() #print expand( 'foo.exe') def _singlebuild( target, state): '''rule for copying build-specific files to build-agnostic filenames. not added as a rule by default. ''' #print '_singlebuild: target=', target a, b = yabs2.split_dir( target, state.build_subdir) #print '_singlebuild: a,b=', a, b if len(b)!=1: return None if b[0].endswith( '.cmds'): return None prereq = default_target( target, state, False) if prereq==target or prereq is None: return None #print '_singlebuild: target=', target, 'prereq return ( 'cp ' + prereq + ' ' + target, [prereq] ) def _cmmrule( target, state): ''' Rule for processing Cmm source into C++ source. ''' tail = '.cmm-s.cpp' if not target.endswith( tail): return None pre = target[:len(target)-len(tail)] return ( 'cmm -detailedparse -embeddedfns -s ' + pre + ' ' + target, [pre], [target+'.yabs-cmd'] ) yabs.add_rule( _cmmrule) def cygwin_to_win32( paths): ''' Converts cygwin path into form accepted by the vc++ compiler. Should probably do this in python, but at the moment we invoke cygwin's `cygpath' utility. ''' ret = yabs.subprocess_text( 'cygpath -m ' + paths) ret = ret.replace( '\n', ' ') return ret add_gccdep_versioncache = dict() def _get_gccversion( command): g = command.split( ' ', 1)[0] if add_gccdep_versioncache.has_key( g): return add_gccdep_versioncache[ g] else: # we are careful to specify prefix='', otherwise the output from the # gcc command will contain whatever prefix is in yabs.defaultstate. version_text = yabs.subprocess_text( g + ' -dumpversion', fn=yabs._subprocess_popen4, echo=False, prefix='').strip() version = yabs2.make_version_numbers( version_text) add_gccdep_versioncache[ g] = version return version def add_gcc_dep( cmd, sourcefile, target, dependencies): ''' Takes a gcc command that already includes source/target filenames and , and adds flags and post-command to create a dependency file called . ''' version = _get_gccversion( cmd) head, tail = cmd.split( ' ', 1) if yabs2.compare_version_numbers( version, (3,0,0)) < 0: d_file = os.path.splitext( os.path.basename( sourcefile))[0] + '.d' cmd = head + ' -MD ' + tail + '\n' cmd += 'mv \'' + d_file + '\' \'' + dependencies + '\'' # The dynamic dependencies will have been put in a file # in current directory, with name being .d. We move this to a more useful # place for use in subsequent builds, where it will be # parsed into semi-prerequisites. return cmd else: # gcc 3 can be told exactly where to put the .d file. # For info on the -M switches, see: # http://gcc.gnu.org/onlinedocs/gcc-3.2/gcc/Preprocessor-Options.html#Preprocessor%20Options # # -MF sets the output .d filename (default would # be foo.d, and we want foo.o.d), -MD turns on the # dependencies generation. cmd = head + ' -MF \'' + dependencies + '\' -MD ' + tail return cmd def add_gcc_pp( cmd, sourcefile, target): ''' Takes a raw gcc command, and adds the source/target filenames plus various flags necessary to make the command preprocess the source into the target. ''' target = target.replace( '\\', '\\\\') version = _get_gccversion( cmd) if yabs2.compare_version_numbers( version, (3,0,0)) < 0: # gcc 2.x.y # g++'s -dD claims to restore # #defines but it has a bug - it doesn't output #undef # statements, resulting in errors if a macro is # #defined and then #undefined. So we generate the # active macros sparately, using g++'s -dM, which # seems to work correctly. See # http://gcc.gnu.org/onlinedocs/gcc_3.html#SEC13 . # # The sed regex removes `4\n' from end of #line lines. # Gcc's preprocessor appends ` 4' to indicate that a # file is a C file, but they cause subsequent compiles # to complain about `warning: badly nested C headers # from preprocessor' and template code occuring in C # files. # # For example, it converts: # # # 1 "/usr/include/machine/cdefs.h" 1 3 4 # # into: # # # 1 "/usr/include/machine/cdefs.h" 1 3 cmd += ' ' + sourcefile cmd2 = cmd + ' -E -dM >> ' + target + '-tmp\n' cmd += ' -E > ' + target + '-tmp\n' cmd += cmd2 cmd += 'cat ' + target\ + '-tmp | sed -e \'s|\(^# [0-9][0-9]* "[^"]*"[ 0-9]*\) 4$$|\1|g\' > '\ + target + '-tmp2\n' cmd += 'rm ' + target + '-tmp\n' cmd += 'grep -v "#define _POSIX_THREADS" ' + target + '-tmp2 > '\ + target + '\n' cmd += 'rm ' + target + '-tmp2' return cmd else: cmd += ' -E -dD ' cmd += ' ' + sourcefile\ + " | sed -e 's|\(^# [0-9][0-9]* *.*\) 4$|\\1|g'" cmd += " -e 's|^#define __STDC__ 1$||g'" cmd += " -e 's|^#define __STDC_HOSTED__ 1$||g'" cmd += " > " + target return cmd def add_custom_params( prefix, includes=(), defines=(), fn=None): includes2 = [] for include in includes: if os.path.isabs( include): includes2.append( include) else: includes2.append( os.path.abspath( os.path.join( prefix, include))) yabs.default_state.extra_params.append( ( prefix, includes2, defines, fn)) def _customise_command( cmd, target, sourcefile, state): for prefix, extra_includes, extra_defines, fn in state.extra_params: if sourcefile.startswith( prefix): for extra_include in extra_includes: cmd += ' -I' + extra_include for extra_define in extra_defines: cmd += ' -D' + extra_define if fn: cmd = fn( cmd) return cmd def _compile_rule( target, state): ''' Rule for compiling/preprocessing C/C++ source files using gcc or msvc. This rule accepts target filenames that look like: `/_yabs/...o', where is .cpp or .c and contains gcc or msvc. Depending on , we look for a gcc, g++ or cl command that has the matching version number. This function also works for preprocessing C/C++ source files - such targets have leafnames like `foo.cpp.cpp' with gcc/g++. It can also generate the special command-files, that contain the text of commands used to compile/preprocess C/C++ files. ''' assert isinstance( state, yabs.State) #print yabs.place(), 'target=', target if not target.endswith( '.o')\ and not target.endswith( '.c')\ and not target.endswith( '.s')\ and not target.endswith( '.cpp')\ and not target.endswith( '.obj')\ and not target.endswith( '.S')\ : #print yabs.place(), 'returning None' return None head, tail = os.path.split( target) src_dir, build_subdir = os.path.split( head) if build_subdir != state.build_subdir: if state.debug>2: print yabs.place(), 'build_subdir != state.build_subdir' print yabs.place(), 'src_dir:', src_dir print yabs.place(), 'build_subdir:', build_subdir print yabs.place(), 'state.build_subdir:', state.build_subdir return None # find last two suffixes, surrounding the build. e.g. # foo.c,debug,release.o. suffixes = _find_suffixes( tail) if len(suffixes) < 2: return s0 = suffixes[-2] s1 = suffixes[-1] src_leaf = tail[ : s0[0]] suffix_text = tail[ s0[0]: s0[1]] outsuffix_text = tail[ s1[0]: s1[1]] #print yabs.place(), 'suffix_text=', suffix_text, 'outsuffix_text=', outsuffix_text if ( 1 and outsuffix_text != suffix_text and outsuffix_text != '.o' and outsuffix_text != '.obj' and outsuffix_text != '.s' ): if state.debug>0: print yabs.place(), 'unrecognised output suffix: ' + outsuffix_text return None build = tail[ s0[1]: s1[0]] sourcefile = os.path.join( src_dir, tail[ : s0[1]]) cmd_file = src_dir + os.sep + state.build_subdir + os.sep\ + suffix_text + build + outsuffix_text tooldict = _infix2dict(build) if tooldict.has_key('gcc') or tooldict.has_key('g++'): out_suffixes = '.o', '.cpp', '.c', '.cmm', '.s', if outsuffix_text not in out_suffixes: if state.debug>2: print 'Unrecognised output suffix for gcc build: `' + outsuffix_text + '\'' print ' - expecting one of:', out_suffixes return tooldict2, gcc = _find_command_from_tool_version( tooldict, state=state) if tooldict2 is None: print 'no match for tool spec:' print ' ' + str(tooldict) print 'known tools are:' for i in state.tools: print ' ' + str(i) return def gccrule_command( context): if suffix_text=='.cpp' or suffix_text=='.cmm': gccv = tooldict2['g++'] else: gccv = tooldict2['gcc'] cmd = '' cmd += gcc for n, v in tooldict2.iteritems(): if n=='gcc': continue elif n=='g++': continue elif n=='sp': continue elif n=='static': continue elif n == 'debug': cmd += ' -g' elif n == 'release': cmd += ' -O2 -DNDEBUG' elif n == 'threads': if yabs.uname_os=='OpenBSD': cmd += ' -pthread' else: cmd += ' -D_REENTRANT' elif n == 'gtk': cmd += ' `pkg-config --cflags gtk+-2.0`' elif n == 'motif': cmd += ' -I /usr/local/include -I/usr/X11R6/include ' elif n == 'boost': cmd += ' -I ' + context.state.boost_include_dir elif n.startswith( '-'): cmd += ' ' + n.replace( '\\', '/') elif n=='os' or n=='osv' or n=='cpu': pass else: raise Exception( 'Unrecognised compiler flag `' + n + '\'') #print yabs.place(), 'tooldict=', tooldict2 if yabs2.compare_version_numbers( yabs2.make_version_numbers( gccv), (3,0,0)) < 0: # gcc 2.x.y cmd += \ ' -Wall -W -Wundef -Wpointer-arith'\ ' -Wno-bad-function-cast -Wno-cast-qual -Wcast-align'\ ' -Wwrite-strings -Wmissing-declarations '\ ' -Wno-redundant-decls -Wnested-externs -Wno-inline'\ ' -Wformat' else: cmd += \ ' -Wall -W -Wundef -Wpointer-arith'\ ' -Wno-cast-qual -Wcast-align -Wwrite-strings'\ ' -Wmissing-format-attribute'\ ' -Wredundant-decls -Wno-unreachable-code'\ ' -Wno-inline -Wno-undef' # -Wunreachable-code seems to get things plain wrong... # -Wshadow is not helpful because it warns against # shadowing previous locals. the warning against # shadowing enclosing scopes' names is useful though. if suffix_text != '.cpp': cmd += ' -Wstrict-prototypes -Wmissing-prototypes' cmd = _customise_command( cmd, target, sourcefile, state) if outsuffix_text==suffix_text: # pre-process, not compile cmd = add_gcc_pp( cmd, sourcefile, target) elif outsuffix_text=='.s': cmd += ' -S -o ' + target.replace( '\\', '\\\\') + ' ' + sourcefile else: cmd += ' -c -o ' + target.replace( '\\', '\\\\') + ' ' + sourcefile if not sourcefile.endswith( '.s'): cmd = add_gcc_dep( cmd, sourcefile, target, target+'.d') cmd = 'mkdir -p ' + os.path.dirname( target) + '\n' + cmd if src_leaf=='': # target is something like `.cpp.o'. This is the # `cmd_file' from this rule when applied to a conventional # .o file target. We regenerate this file if it has # changed, and return an empty command. if context.state.debug>4: print 'yabs3: handling compile-rule comand file: ' + target if yabs.updatediff( target, cmd): if context.state.debug>4: print 'yabs3: updating compile-rule command file ' + target else: if context.state.debug>4: print 'yabs3: compile-rule command file unchanged: ' + target return '' else: # return the compilation/preprocessing command. return cmd if src_leaf=='': return gccrule_command, [], [ None], None, 1 # Our target is the special command file, of the form # `.cpp.o', used to detect changes to build commands. # a semi-prerequisite None ensures that this target is # always rebuilt. else: semi_prerequisites = lambda state : _gcc_extradeps( target + '.d', state=state, simple_prerequisites=tooldict2.has_key('sp')) return gccrule_command, [ sourcefile, cmd_file], semi_prerequisites elif tooldict.has_key('msvc'): if outsuffix_text!='.obj': if state.debug>0: print 'yabs: unrecognised suffix for msvc build `.' + outsuffix_text + '\'' print 'yabs: - expecting `.obj\'' return None def msvcrule_command( state): vcpp = _find_command_from_tool_version( 'vc++', toolversion, state) target_win32 = cygwin_to_win32( target) sourcefile_win32 = cygwin_to_win32( sourcefile) cmd = 'mkdir -p ' + os.path.dirname( target) + '\n' cmd += vcpp + ' /c /Fdmsvc.pdb /FD' cmd += ' /Fo' + target_win32 cmd += ' /GR /GX /nologo /W3' for i in build.split( yabs2.separator): if i.startswith('msvc'): continue elif i == 'debug': cmd += ' /Zi' elif i == 'release': cmd += ' /Ox /DNDEBUG' elif i == 'threads': pass elif i == 'gtk': raise Exception( 'can\'t handle gtk/msvc') elif i == 'boost': raise Exception( 'can\'t handle boost/msvc') elif i.startswith( 'os='): pass elif i.startswith( 'osv='): pass elif i.startswith( 'cpu='): pass else: raise 'Unrecognised compiler flag `' + i + '\'' if src_leaf=='': # target is something like `.cpp.obj'. This is the # `cmd_file' from this rule when applied to a conventional # .o file target. We regenerate this file if it has # changed, and return an empty command. if state.debug>4: print 'handling special comand file: ' + target if yabs.updatediff( target, cmd): if state.debug>4: print 'updating command file ' + target else: if state.debug>4: print 'command file unchanged: ' + target return '' else: cmd += ' ' + sourcefile_win32 # return the compilation/preprocessing command. return cmd if src_leaf=='': return msvcrule_command, [], [ None], None, 1 # a semi-prerequisite None ensures that this target is # always rebuilt. Our target is the special command file, # eg `.cpp.o', used to detect changes to build # commands. return msvcrule_command, [ sourcefile, cmd_file] else: if state.debug>0: print yabs.place(), 'unrecognised tool,version:', tool, toolversion, 'build=', build return None yabs.add_rule( _compile_rule) def _add_exe_transformfn( src, build, state): ''' Default transformation function, converting source filenames into object filenames. Returns unchanged if is already within _yabs/. ''' if src.find( os.sep + state.build_subdir + os.sep) >= 0: return src suffix = os.path.splitext( src)[1] if suffix in ( '.cpp', '.c', '.S', '.s',): src += '.o' elif suffix in ( '.o',): pass elif suffix in ( '.so', '.a',): pass return default_target( src, state, build=build, quiet=True) ret = _transform( src, state, extra=build) if 0: print yabs.place(), ( src, build, ret) return ret if 0: print _add_exe_transformfn( 'foo.c', 'debug,gcc,release', yabs.default_state) print _add_exe_transformfn( 'foo.c,debug.o', 'debug,gcc,release', yabs.default_state) print _add_exe_transformfn( 'foo.cpp.o', 'debug,gcc', yabs.default_state) def add_exe( target_base, source, linkextra = None, transform_fn = _add_exe_transformfn, prereq_extra = [], state = yabs.default_state): ''' Specify an executable, from a base name and list of source files. The actual executable name will be: _yabs/.exe can be either a list of filenames, or a string containing a space-separated list of filenames. should be a function that takes the same parameters as the default add_exe_transformfn() - a source file, a build type and a Yabs state. It should return the filename of what the source file should be converted into - e.g. it should convert foo.c into an object file. It is up to caller to ensure that the returned file can be created from the source file, by registering other rules with Yabs. It can return None if the source file should be ignored. Embed default build parameters into , e.g.: add_exe( 'foo,threads', [ 'foo.c', 'bar.cpp']) - will always make a threads build. ''' if target_base.endswith( '.exe'): raise Exception( 'target name should not end with .exe') return _add_composite( target_base + '.exe', source, linkextra, transform_fn = transform_fn, prereq_extra = prereq_extra, state = yabs.default_state) def add_lib( target_base, source, linkextra = None, transform_fn = _add_exe_transformfn, prereq_extra = [], state = yabs.default_state): if target_base.endswith( '.a'): raise Exception( 'target name should not end with .a') return _add_composite( target_base + '.a', source, linkextra, transform_fn = transform_fn, prereq_extra = prereq_extra, state = yabs.default_state) def add_so( target_base, source, linkextra = None, transform_fn = _add_exe_transformfn, prereq_extra = [], state = yabs.default_state): ''' Specify a shared library, from a base name and list of source files. The actual shared library name will be: _yabs/.so See add_exe() for details. ''' if target_base.endswith( '.so'): raise Exception( 'target name should not end with .so') return _add_composite( target_base + '.so', source, linkextra, transform_fn = transform_fn, prereq_extra = prereq_extra, state = yabs.default_state) def add_composite( target_base, source, linkextra = None, transform_fn = _add_exe_transformfn, prereq_extra = [], state = yabs.default_state): return _add_composite( target_base, source, linkextra, transform_fn = transform_fn, prereq_extra = prereq_extra, state = yabs.default_state) def _add_composite( target_base, source, linkextra = None, transform_fn = _add_exe_transformfn, prereq_extra = [], state = yabs.default_state): ''' Don't call this directly - it uses yabs.get_callerdirectory() and expects to be called via an intermediate such as yabs3.add_exe(). ''' assert type( target_base)==type( '') assert callable( transform_fn) assert isinstance( state, yabs.State) target_base_root, target_base_ext = os.path.splitext( target_base) comma = target_base_root.find(',') if comma!=-1: basebuild = target_base_root[comma:] target_base_root = target_base_root[:comma] else: basebuild='' if os.path.isabs( target_base): root = None elif target_base.startswith( '..'): print 'yabs3.add_exe: target starts with ..' root = None target_base = os.path.abspath( os.path.join( yabs.get_caller_directory( 2), target_base)) else: root = yabs.get_caller_directory( 3) #print 'composite rule: root=', root, 'target_base=', target_base if type( source)==str: source = string.split( source) if type( prereq_extra)==str: prereq_extra = string.split( prereq_extra) # our rule (composite_rule, below) only uses the regex if the target # extension matches, so we don't compile the regex. the module will # compile it as needed. this avoids a slight overhead when rules are first # registered. regex = ( '^' + yabs2.insert_leafsubdir( target_base_root, state.build_subdir) + yabs2.separator + '(?P.*)' + target_base_ext + '$' ) regex_convenience = ( '^' + target_base_root + '(?P(,.*)?)' + target_base_ext + '$' ) def convenience_rule( target, state): if not target.endswith( target_base_ext): return None if state.debug>3: print 'yabs3.add_exe_rule: root=', root, 'target=', target # check for convenience build - foo/bar/qwerty[,].exe, not inside _yabs: build = None match = re.match( regex_convenience, target) if match: build = basebuild + match.groupdict()[ 'build'] p = os.path.join( os.path.dirname( target), state.build_subdir, target_base_root+build+target_base_ext) pp = _transform( p, state) if pp: return '', [pp] yabs.add_rule( convenience_rule, phony=True, root=root, state=state, targetinfo=regex_convenience) def composite_rule( target, state): if not target.endswith( target_base_ext): return None if state.debug>3: print 'yabs3.add_exe_rule: root=', root, 'target=', target if root is None: assert( os.path.isabs( target)) else: if os.path.isabs( target): #print 'yabs3: target=', target, 'root=', root assert not target.startswith( root) # these asserts check that yabs.make is calling us correctly. match = re.match( regex, target) if match==None: if state.debug>2 and target.endswith( target_base_ext): print yabs.place(), 'composite rule doesn\'t match:' print yabs.place(), ' root =' + str( root) print yabs.place(), ' regex =' + regex print yabs.place(), ' target=' + target return None build = match.groupdict()[ 'build'] if state.debug>3: print 'yabs: build=`' + build + '\'' #print yabs.place(), 'target is:', target #print yabs.place(), 'build is:', build #print yabs.place(), 'composite_rule: build=', build if build=='default': if state.debug>2: print 'yabs: using default build ' + build tooldict, command = _find_command_from_tool_version( _infix2dict( build)) if tooldict is None: if state.debug>0: print 'yabs3.add_exe_rule: can\'t find tool for build:', build return None if state.debug>2: print 'link rule, target ', target,\ ' considering tooldict, command:', tooldict, command o_files = list() o_files_text = '' compile_build = build #print yabs.place(), tool, target_base_ext if ( yabs.uname_cpu=='x86_64' and (tooldict.has_key('gcc') or tooldict.has_key('g++')) and target_base_ext=='.so' ): #print yabs.place(), 'adding -fPIC to compile_build' compile_build = '-fPIC,' + compile_build # 64-bit linux appears to require -fPIC for shared objects. i used # to think this was necessary for x86_32 too, but it isn't required # on linux, and slows things down by about 20%. use_gpp = False if callable(source): source2 = source() else: source2 = source for i in source2: if not os.path.isabs( i): if root is None: print 'yabs: executable rule error' print ' target_base is absolute: ' + target_base print ' but object file is relative: ' + i return None i = os.path.join( root, i) o_file = transform_fn( i, compile_build, state) if o_file: o_files.append( o_file) o_files_text += ' ' + o_file.replace( '\\', '\\\\') if i.endswith( '.cpp'): use_gpp = True if root is not None: target = os.path.join( root, target) #print yabs.place(), 'o_files=', o_files prereqs = o_files prereqs.append( target + '.yabs-cmd') # Add command-file to list of prerequisites, so that the # executable will be remade if the flags defined above are # changed. if os.path.isabs( target): target_absolute = target else: target_absolute = os.path.join( root, target) #print yabs.place(), 'tooldict=', tooldict if ( tooldict.has_key('gcc') or tooldict.has_key('g++')) and target_base_ext=='.a': cmd = 'ar -rcs ' + target_absolute.replace( '\\', '\\\\') + ' ' + o_files_text elif tooldict.has_key('gcc') or tooldict.has_key('g++'): #if state.debug>0: print 'considering gcc link rule' tooldict_temp = tooldict.copy() if use_gpp: if not tooldict_temp.has_key( 'g++'): tooldict_temp['g++'] = tooldict_temp['gcc'] del tooldict_temp['gcc'] else: if not tooldict_temp.has_key( 'gcc'): tooldict_temp['gcc'] = tooldict_temp['g++'] del tooldict_temp['g++'] tooldict2, linker = _find_command_from_tool_version( tooldict_temp, state=state) cmd = 'mkdir -p ' + os.path.dirname( target) + '\n' cmd += linker + ' -W -Wall -o ' + target_absolute.replace( '\\', '\\\\') cmd += o_files_text if ( linkextra): cmd += ' ' + linkextra if target_base_ext=='.so': #cmd += ' -shared -fpic' cmd += ' -shared' for i in build.split( yabs2.separator): if i.startswith('gcc=') or i.startswith('g++='): continue # we can ignore this. elif i.startswith('sp=') or i=='sp': continue elif i == 'debug': cmd += ' -g' elif i == 'release': cmd += ' -O2' elif i == 'threads': if yabs.uname_os == 'OpenBSD': cmd += ' -pthread' else: cmd += ' -lpthread' elif i == 'gtk': cmd += ' `pkg-config --libs gtk+-2.0` -lXt -lgthread-2.0' elif i == 'motif': cmd += ' -L/usr/local/lib -L/usr/X11R6/lib -lXm -lXmu -lXext -lXt -lX11' # used to have -lXmu. restored for use with openbsd-3.8 - can'y # build xsel for example. elif i == 'boost': pass elif i.startswith( 'z='): cmd += ' -Wl,-z,' + i[2:] elif i.startswith( 'e='): cmd += ' -Wl,-e,' + i[2:] elif i.startswith( 'init='): cmd += ' -Wl,-init=' + i[5:] elif i == 'static': cmd += ' -static' elif i.startswith( '-'): cmd += ' ' + i.replace( '\\', '\\\\') elif i.startswith( 'os=') or i.startswith( 'osv=') or i.startswith( 'cpu='): pass elif i=='default': pass else: raise Exception( 'Unrecognised linker flag: ' + i) elif tool=='msvc' and taget_base_ext=='.exe': target_absolute_win32 = cygwin_to_win32( target_absolute) o_files_text_win32 = cygwin_to_win32( o_files_text) cmd = 'mkdir -p ' + os.path.dirname( target) + '\n' cmd += 'cl /nologo $3 /Fdmsvc.pdb /FD /Fm' + target_absolute_win32 + '.map' cmd += ' /GR /W3 /Fe' + target_absolute_win32 cmd += ' ' + o_files_text_win32 for i in build.split( yabs2.separator): if i.startswith( 'msvc'): continue # we can ignore this. elif i == 'debug': cmd += ' /Zi' elif i == 'release': cmd += ' /Ox' elif i == 'threads': pass elif i == 'gtk': pass elif i == 'boost': pass else: raise 'Unrecognised msvc linker flag `' + i + '\'' else: print 'unrecognised tool name: '\ + tool + ', version ' + toolversion\ + '. extension=' + target_base_ext return None return cmd, prereqs + prereq_extra, [target+'.yabs-cmd'] yabs.add_rule( composite_rule, phony=False, root=root, state=state, autocmds='.yabs-cmd', targetinfo=regex)