''' 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): ''' Returns list of filenames in a makefile-style dependency file. We return [None] if we can't open the dependecy file, or if it doesn't contain any items. This ensures that we default to a rebuild if the dependency file is faulty. ''' 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 = [] for i in yabs2.names_in_file( filename, '[ \t\n\\\\:]*'): if i.endswith( '.o'): continue # ignore the .o file mentioned in the dep file. if state.debug>4: print 'yabs3: found dep ' + i deps.append( i) if deps == []: deps = [ None] return deps def add_toolcommand( tool, command, 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 state is None: state=yabs.default_state #print 'add_toolcommand: tool=', tool, ', command=', command, 'state=', state assert isinstance( state, yabs.State) assert type( tool)==type( '') assert type( command)==type( '') #assert tool=='gcc' or tool=='g++' or tool=='vc++' if tool=='gcc' or tool=='g++': e, version = state.subprocessfn( command + ' -dumpversion', echo=False) version = version.strip() if e: version = '' else: version = '' state.tools[ (tool, version)] = command def _find_command_from_tool_version( tool, version, state): ''' 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( tool)==type( '') assert isinstance( state, yabs.State) class error( yabserrors.error): def __init__( self, state, name, version): self.name = name self.state = state self.version= version def __str__( self): ret = '`' + self.name + '\' version `' + self.version + '\' is not known. ' #ret += 'state=' + str( state) + '. ' ret += 'known tools are:\n' for name, version in self.state.tools.iteritems(): ret += ' ' + str( name) + ' ' + '(' + str( version) + ')\n' return ret if not state.tools.has_key( ( tool, version)): #if state.debug > 0: # print 'tool `' + tool + '\': version `' + version + '\' not recognised' raise error( state, tool, version) return state.tools[ ( tool, version)] def _get_toolset_version_from_build( build, state): ''' Looks for a flag starting with msvc or gcc, and extracts version number. Returns None if not found. This version number can then be passed to _find_command_from_tool_version() to find a matching gcc, g++ or msvc command. ''' assert type( build)==type( '') assert isinstance( state, yabs.State) if build=='default': build = _get_defaultbuild( None, state) for i in build.split( yabs2.separator): if i.startswith( 'gcc'): return 'gcc', i[3:] if i.startswith( 'msvc'): return 'msvc', i[4:] return None, None def _envstring( state=None): return yabs2._env_to_string( yabs2._make_env()) def _get_canonical_build( build, state): #print '_get_canonical_build: state=', state #print '_get_canonical_build: state=', state.env if state is None: state=yabs.default_state items = list() for i in build.split( yabs2.separator): if i=='gcc': items.append( 'gcc' + state.default_gccversion) else: items.append( i) items.sort() #build = yabs2.separator\ #print '_get_canonical_build: state=', state, 'build=', build build =\ string.join( items, yabs2.separator)\ + _envstring( state) #print 'returning:', build return build def _get_defaultbuild( build=None, state=None): ''' Returns default build as a string. Converts .defaultbuild (specified on command line with -b) into a more detailed build string, with a version number appended to `gcc'. Also sorts the individual items in the resulting build string, so that the user doesn't have to use a consistent ordering. ''' #print '_get_defaultbuild, build=', build, ', state=', state if state is None: state=yabs.default_state if build is None: assert isinstance( state, yabs.State) if state.defaultbuild2 == '': state.defaultbuild2 = _get_canonical_build( state.defaultbuild, state) return state.defaultbuild2 else: return _get_canonical_build( build, state) def setup( state=None): if state is None: state = yabs.default_state state.tools = dict() try: state.default_gccversion = yabs.subprocess_text( 'gcc -dumpversion', echo=False).strip() except yabserrors.command_error: state.default_gccversion = '' state.defaultbuild = 'gcc,debug' state.defaultbuild2 = '' state.build_subdir = '_yabs' 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) add_tools( [ 'gcc', 'g++', 'gcc-*', 'g++-*']) def _defaultbuild_internal( target, state, insert_subdir): ''' A phony rule for the following target names: foo.c.c foo.cpp.cpp foo.c.o foo.cpp.o foo.exe foo.so foo.S.o foo.a Occurrencies of these pairs of suffixes in target are modified by inserting the default build string in-between the suffixes and also inserting the build sub-directory. The resulting target names match the rules for preprocessing/compiling source files and creating executables. The substitution is done repeatedly, so you can preprocess and then compile a file by specifying a target `foo.cpp.cpp.o'. Also handles embedded build types, such as: foo.c,gcc,-O5.o In this case, the build string `gcc,-O5' is used instead of the default build string. ''' target2 = default_target( target, state, insert_subdir) if target2 == None: return None if target2 == target: return None return '', [ target2] def default_target( target, state=None, insert_subdir=True, infix=None): ''' 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). is converted to a full build specification using yabs3._get_canonical_build. If is None, and contains a build starting with a comma, then this build is used for . 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.c ' + yabs3.default_target( 'foo.c.o', infix='gcc,debug')) or: yabs3.add_exe( 'myapp', 'main.c ' + yabs3.default_target( 'foo.c,gcc,debug.o')) returns None if target is not in form that implies a default target. ''' if state is None: state = yabs.default_state assert isinstance( state, yabs.State) assert insert_subdir is False or insert_subdir is True if target.find( os.sep + state.build_subdir + os.sep)>=0: return None if target.find( ',default.') >= 0: if insert_subdir: return yabs2.insert_leafsubdir( target, state.build_subdir) else: return None if infix is None: comma = target.find( yabs2.separator) if comma>=0: dot = target.rfind( '.') if dot>=0 and dot>comma: # infer infix from target filename. this allows # user to build targets like `foo.c,gcc,-O3.o'. infix = target[comma+1:dot] target = target[:comma] + target[dot:] if infix is None: infix = _get_defaultbuild( state=state) else: infix = _get_canonical_build( infix, state) infix = yabs2.separator + infix #print yabs.place(), 'target=', target target2 = target def replace( s, a, b): #print s, a+b c = s.find( a+b) if c<0: return s if state.debug>=3: print yabs.place(), 'target=', target print yabs.place(), 'yabs3._defaultbuild_internal(): found ', a, b, 'in ', s next_char_pos = c+len(a)+len(b) if next_char_pos < len( s): if s[ next_char_pos] != ',' and s[ next_char_pos] != '.': print yabs.place(), 'not at end, and next isn\'t a comma' return s s = s[:c] + a + infix + b + s[c+len(a)+len(b):] if insert_subdir: s = yabs2.insert_leafsubdir( s, state.build_subdir) #print yabs.place(), s return s while True: target3 = target2 target3 = replace( target3, '.cpp', '.cpp') target3 = replace( target3, '.cpp', '.o') target3 = replace( target3, '.c', '.c') target3 = replace( target3, '.c', '.o') target3 = replace( target3, '.c', '.s') target3 = replace( target3, '.S', '.o') if target3==target2: break target2 = target3 #print yabs.place(), 'target2=', target2, 'infix=', infix if ( target2.endswith( '.exe') or target.endswith( '.so') or target.endswith( '.a') or target.endswith( '.o') ): if ( os.path.splitext( target2)[0].endswith( infix)): #print yabs.place(), 'returning None' #return None pass else: #if target2.find( infix)>=0: return None root, ext = os.path.splitext( target2) target2 = root + infix + ext if insert_subdir: target2 = yabs2.insert_leafsubdir( target2, state.build_subdir) #print yabs.place(), 'returning:', target2 return target2 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] ) #yabs.add_rule( _singlebuild, autocmds='.cmds') def _default_build( target, state): return _defaultbuild_internal( target, state, False) def _default_build_subdir( target, state): return _defaultbuild_internal( target, state, True) yabs.add_rule( _default_build_subdir, True, internal=1) # This adds a rule which simply makes any file that doesn't already # contain the default build, depend on the filename with the default # build appended. The rule is marked as phony. 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_popen, 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. ''' version = _get_gccversion( cmd) if yabs2.compare_version_numbers( version, (3,0,0)) < 0: #if yabs2.compare_version_numbers( yabs2.make_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 _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) 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'): 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 # Should probably remove state.envstring from this to make it a # constant regex, so it could be a global and compiled only once. regex = ( '(?P.*)\\' + os.extsep + '(?P[a-zA-Z]*)' + yabs2.separator + '(?P.*)' #+ state.envstring + '\\' + os.extsep + '(?P[a-zA-Z]*)$' ) match = re.match( regex, tail) if match is None: if state.debug>2: print yabs.place(), 'regex doesn\'t match' print yabs.place(), 'regex is: ', regex print yabs.place(), 'target is:', tail print return None src_leaf = match.groupdict()[ 'src_leaf'] suffix = match.groupdict()[ 'suffix'] build = match.groupdict()[ 'build'] outsuffix = match.groupdict()[ 'outsuffix'] if outsuffix!=suffix and outsuffix!='o' and outsuffix!='obj' and outsuffix!='s': if state.debug>0: print 'yabs3 compile rule: unrecognised output suffix: ' + outsuffix return None sourcefile = os.path.join( src_dir, src_leaf + os.extsep + suffix) if build.count( ',osv=') > 1: if state.debug>1: print 'yabs3: build contains double build?', build return None cmd_file = src_dir + os.sep + state.build_subdir + os.sep\ + os.extsep + suffix + yabs2.separator + build\ + os.extsep + outsuffix #print yabs.place(), 'build=', build, 'cmd_file=', cmd_file if build=='default': build = _get_defaultbuild( None, state) if state.debug>2: print 'yabs: using default build ' + build elif not build.endswith( _envstring( state)): if state.debug>1: print 'build should be either `default\' or end with '\ + _envstring( state) + ': ' + build return None #+ state.envstring\ tool, toolversion = _get_toolset_version_from_build( build, state) if tool=='gcc': if outsuffix!='o' and outsuffix!='cpp' and outsuffix!='c' and outsuffix!='cmm'\ and outsuffix!='s': if state.debug>2: print 'Unrecognised output suffix for gcc build: `.' + outsuffix + '\'' print ' - expecting one of: .o .cpp .c .cmm' return None def gccrule_command( context): if suffix=='cpp' or suffix=='cmm': gcc = _find_command_from_tool_version( 'g++', toolversion, context.state) else: gcc = _find_command_from_tool_version( 'gcc', toolversion, context.state) cmd = '' cmd += gcc for i in build.split( yabs2.separator): if i.startswith( 'gcc'): continue elif i == 'debug': cmd += ' -g' elif i == 'release': cmd += ' -O2 -DNDEBUG' elif i == 'threads': if yabs.uname_os=='OpenBSD': cmd += ' -pthread' else: cmd += ' -D_REENTRANT' elif i == 'gtk': cmd += ' `pkg-config --cflags gtk+-2.0`' elif i == 'motif': cmd += ' -I /usr/local/include -I/usr/X11R6/include ' elif i == 'boost': cmd += ' -I ' + context.state.boost_include_dir elif i.startswith( 'os=') or i.startswith( 'osv=')\ or i.startswith( 'cpu='): pass elif i.startswith( '-'): cmd += ' ' + i else: raise 'Unrecognised compiler flag `' + i + '\'' if yabs2.compare_version_numbers( yabs2.make_version_numbers( toolversion), (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 != 'cpp': cmd += ' -Wstrict-prototypes -Wmissing-prototypes' if outsuffix==suffix: # pre-process, not compile cmd = add_gcc_pp( cmd, sourcefile, target) elif outsuffix=='s': cmd += ' -S -o ' + target + ' ' + sourcefile else: cmd += ' -c -o ' + target + ' ' + 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) return gccrule_command, [ sourcefile, cmd_file], semi_prerequisites elif tool=='msvc': if outsuffix!='obj': if state.debug>0: print 'yabs: unrecognised suffix for msvc build `.' + outsuffix + '\'' 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 'unrecognised tool,version ', tool, toolversion return None yabs.add_rule( _compile_rule) def _source_rule( target, state): ''' Rule for non-generated files, i.e. human-written files, without environment in their filename. ''' assert isinstance( state, yabs.State) envstring = _envstring( state) if string.find( target, envstring)>=0: return None if yabs.mtime( target, state.mtimecache)==0: return None return [], [], '' def _add_exe_transformfn( src, build, state): ''' Default transformation function, converting source filenames into object filenames. ''' if build.find( ',msvc')>=0 or build.startswith( 'msvc'): object_suffix='.obj' else: object_suffix='.o' if state.debug>3: print '_add_exe_transformfn: build=', build print '_add_exe_transformfn: object_suffix=', object_suffix root, suffix = os.path.splitext( src) # remove the flags that are for the linker. build0 = build build = '' i = 0 for j in build0.split( yabs2.separator): if j.startswith( 'z=') or j.startswith( 'e=') or j.startswith( 'init='): pass elif j=='static': pass else: if i>0: build = build + yabs2.separator build = build + j i += 1 #if build != 'default': build += state.envstring if suffix=='.cpp' or suffix=='.c' or suffix=='.s' or suffix=='.S': return yabs2.insert_leafsubdir( src + yabs2.separator + build + object_suffix, state.build_subdir) elif suffix=='.cmm': return yabs2.insert_leafsubdir( yabs2.insert_leafsubdir( src + yabs2.separator + build + '.cmm', state.build_subdir) + '.cmm-s.cpp' + yabs2.separator + build + object_suffix, state.build_subdir) else: return src 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. ''' 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 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 filename. It is up to caller to ensure that the returned file can be created from the source file, by registering other rules with Yabs. ''' 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) 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.*)' #+ state.envstring + target_base_ext + '$' ) 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(), 'executable 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 'composite_rule: build=', build if build=='default': #build = _get_defaultbuild( None, state) if state.debug>2: print 'yabs: using default build ' + build elif not build.endswith( _envstring( state)): if state.debug>1: print 'build should be either `default\' or end with '\ + _envstring( state) + ': ' + build return None tool, toolversion = _get_toolset_version_from_build( build, state) if tool 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 tool,version=', tool, toolversion o_files = list() o_files_text = '' compile_build = build if yabs.uname_cpu=='x86_64' and tool=='gcc' and target_base_ext=='.so': 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 for i in source: 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) o_files.append( o_file) o_files_text += ' ' + o_file if i.endswith( '.cpp'): use_gpp = True if root is not None: target = os.path.join( root, target) #print '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) if tool=='gcc' and target_base_ext=='.a': cmd = 'ar -rcs ' + target_absolute + ' ' + o_files_text elif tool=='gcc': #if state.debug>0: print 'considering gcc link rule' if use_gpp: linker = _find_command_from_tool_version( 'g++', toolversion, state) else: linker = _find_command_from_tool_version( 'gcc', toolversion, state) #gxx = _find_command_from_tool_version( 'g++', toolversion, state) cmd = 'mkdir -p ' + os.path.dirname( target) + '\n' cmd += linker + ' -W -Wall -o ' + target_absolute cmd += o_files_text if ( linkextra): cmd += ' ' + linkextra if target_base_ext=='.so': #cmd += ' -shared -fpic' cmd += ' -shared' #print '_add_compoiste: build=\'' + build + '\'' for i in build.split( yabs2.separator): if i.startswith( 'gcc'): continue # we can ignore this. elif i == 'debug': cmd += ' -g' elif i == 'release': cmd += ' -O2' elif i == 'threads': #if state.env[ 'os'] == 'OpenBSD': cmd += ' -pthread' 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 elif i.startswith( 'os=') or i.startswith( 'osv=') or i.startswith( 'cpu='): pass elif i=='default': pass else: raise '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')