#! /usr/bin/env python # encoding: utf-8 # Thomas Nagy, 2005 (ita) "base for all c/c++ programs and libraries" import os, types, sys, re, md5 import Action, Object, Params, Scan, Common, Utils from Params import error, debug, fatal, warning from Params import hash_sig_weak g_prio_link=101 """default priority for link tasks: an even number means link tasks may be parallelized an odd number is probably the thing to do""" g_src_file_ext = ['.c', '.cpp', '.cc'] "default extensions for source files" cregexp1 = re.compile(r'^[ \t]*#[ \t]*(?:include)[ \t]*(?:/\*.*?\*/)?[ \t]*(<|")([^>"]+)(>|")', re.M) "regexp for computing dependencies (when not using the preprocessor)" class c_scanner(Scan.scanner): "scanner for c/c++ files" def __init__(self): Scan.scanner.__init__(self) # re-scan a node, update the tree def do_scan(self, node, env, hashparams): debug("do_scan(self, node, env, hashparams)", 'ccroot') variant = node.variant(env) if not node: error("BUG rescanning a null node") return (nodes, names) = self.scan(node, env, **hashparams) if Params.g_verbose: if Params.g_zones: debug('scanner for %s returned %s %s' % (node.m_name, str(nodes), str(names)), 'deps') tree = Params.g_build tree.m_depends_on[variant][node] = nodes tree.m_raw_deps[variant][node] = names tree.m_deps_tstamp[variant][node] = tree.m_tstamp_variants[variant][node] if Params.g_preprocess: for n in nodes: try: # FIXME some tools do not behave properly and this part fails # it should not be allowed to scan ahead of time vv = n.variant(env) tree.m_deps_tstamp[vv][n] = tree.m_tstamp_variants[vv][n] except KeyError: pass def get_signature(self, task): debug("get_signature(self, task)", 'ccroot') if Params.g_preprocess: if Params.g_strong_hash: return self._get_signature_preprocessor(task) else: return self._get_signature_preprocessor_weak(task) else: if Params.g_strong_hash: return self._get_signature_regexp_strong(task) else: return self._get_signature_regexp_weak(task) def scan(self, node, env, path_lst, defines={}): debug("scan", 'ccroot') if Params.g_preprocess: return self._scan_preprocessor(node, env, path_lst, defines) else: # the regular scanner does not use the define values return self._scan_default(node, env, path_lst) #return self._scan_default(node, env, path_lst) def _scan_default(self, node, env, path_lst): debug("_scan_default(self, node, env, path_lst)", 'ccroot') variant = node.variant(env) file = open(node.abspath(env), 'rb') found = cregexp1.findall( file.read() ) file.close() nodes = [] names = [] if not node: return (nodes, names) for (_, name, _) in found: #print 'boo', name # quite a few nested 'for' loops, looking suspicious found = None for dir in path_lst: found = dir.get_file(name) if found: break if found: nodes.append(found) else: names.append(name) #print "-S ", nodes, names return (nodes, names) def _scan_preprocessor(self, node, env, path_lst, defines={}): debug("_scan_preprocessor(self, node, env, path_lst)", 'croot') import preproc gruik = preproc.cparse(nodepaths = path_lst, defines=defines) gruik.start2(node, env) if Params.g_verbose: debug("nodes found for %s %s %s" % (str(node), str(gruik.m_nodes), str(gruik.m_names)), 'deps') return (gruik.m_nodes, gruik.m_names) def _get_signature_regexp_strong(self, task): m = md5.new() tree = Params.g_build seen = [] env = task.m_env def add_node_sig(node): if not node: warning("null node in get_node_sig") if node in seen: return # TODO - using the variant each time is stupid variant = node.variant(env) seen.append(node) # rescan if necessary, and add the signatures of the nodes it depends on if tree.needs_rescan(node, task.m_env): self.do_scan(node, task.m_env, task.m_scanner_params) lst = tree.m_depends_on[variant][node] for dep in lst: add_node_sig(dep) m.update(tree.m_tstamp_variants[variant][node]) # add the signatures of the input nodes for node in task.m_inputs: add_node_sig(node) # add the signatures of the task it depends on for task in task.m_run_after: m.update(task.signature()) return m.digest() def _get_signature_regexp_weak(self, task): msum = 0 tree = Params.g_build seen = [] env = task.m_env def add_node_sig(node): if not node: warning("null node in get_node_sig") if node in seen: return 0 sum = 0 # TODO - using the variant each time is stupid variant = node.variant(env) seen.append(node) sum += tree.m_tstamp_variants[variant][node] # rescan if necessary, and add the signatures of the nodes it depends on if tree.needs_rescan(node, task.m_env): self.do_scan(node, task.m_env, task.m_scanner_params) lst = tree.m_depends_on[variant][node] for dep in lst: sum += add_node_sig(dep) return sum # add the signatures of the input nodes for node in task.m_inputs: msum = hash_sig_weak(msum, add_node_sig(node)) # add the signatures of the task it depends on for task in task.m_run_after: msum = hash_sig_weak(msum, task.signature()) return int(msum) def _get_signature_preprocessor_weak(self, task): msum = 0 tree = Params.g_build rescan = 0 env=task.m_env seen=[] def add_node_sig(n): if not n: warning("null node in get_node_sig") if n.m_name in seen: return 0 # TODO - using the variant each time is stupid variant = n.variant(env) seen.append(n.m_name) return tree.m_tstamp_variants[variant][n] # there is only one c/cpp file as input node = task.m_inputs[0] variant = node.variant(env) if not variant == 0: fatal('variant is not 0') if tree.needs_rescan(node, task.m_env): rescan = 1 if not rescan: for anode in tree.m_depends_on[variant][node]: if tree.needs_rescan(anode, task.m_env): rescan = 1 # rescan the cpp file if necessary if rescan: #print "rescanning ", node self.do_scan(node, task.m_env, task.m_scanner_params) # print "rescan for ", task.m_inputs[0], " is ", rescan, " and deps ", \ # tree.m_depends_on[variant][node], tree.m_raw_deps[variant][node] # we are certain that the files have been scanned - compute the signature msum = hash_sig_weak(msum, add_node_sig(node)) for n in tree.m_depends_on[variant][node]: msum = hash_sig_weak(msum, add_node_sig(n)) # and now xor the signature with the other tasks for task in task.m_run_after: msum = hash_sig_weak(msum, task.signature()) #debug("signature of the task %d is %s" % (task.m_idx, Params.vsig(sig)), 'ccroot') return int(msum) def _get_signature_preprocessor(self, task): # assumption: there is only one cpp file to compile in a task tree = Params.g_build rescan = 0 m = md5.new() seen=[] env = task.m_env def add_node_sig(n): if not n: warning("warning: null node in get_node_sig") if n.m_name in seen: return # TODO - using the variant each time is stupid variant = n.variant(env) seen.append(n.m_name) m.update(tree.m_tstamp_variants[variant][n]) # there is only one c/cpp file as input node = task.m_inputs[0] variant = node.variant(env) if not variant == 0: fatal('variant is not 0') if tree.needs_rescan(node, task.m_env): rescan = 1 #if rescan: print "node has changed, a rescan is req ", node if not rescan: #print node, "depends on ", tree.m_depends_on[variant][node] for anode in tree.m_depends_on[variant][node]: if tree.needs_rescan(anode, task.m_env): #print "rescanning because of ", anode rescan = 1 # rescan the cpp file if necessary if rescan: #print "rescanning ", node self.do_scan(node, task.m_env, task.m_scanner_params) # DEBUG #print "rescan for ", task.m_inputs[0], " is ", rescan, " and deps ", \ # tree.m_depends_on[variant][node], tree.m_raw_deps[variant][node] # we are certain that the files have been scanned - compute the signature add_node_sig(node) for n in tree.m_depends_on[variant][node]: add_node_sig(n) #for task in task.m_run_after: m.update(task.signature()) return m.digest() g_c_scanner = c_scanner() "scanner for c programs" def read_la_file(path): sp = re.compile(r'^([^=]+)=\'(.*)\'$') dc={} file = open(path, "r") for line in file.readlines(): try: #print sp.split(line.strip()) _, left, right, _ = sp.split(line.strip()) dc[left]=right except: pass file.close() return dc # fake libtool files fakelibtool_vardeps = ['CXX', 'PREFIX'] def fakelibtool_build(task): # Writes a .la file, used by libtool dest = open(task.m_outputs[0].abspath(task.m_env), 'w') sname = task.m_inputs[0].m_name fu = dest.write fu("# Generated by ltmain.sh - GNU libtool 1.5.18 - (pwn3d by BKsys II code name WAF)\n") if task.m_env['vnum']: nums=task.m_env['vnum'].split('.') libname = task.m_inputs[0].m_name name3 = libname+'.'+task.m_env['vnum'] name2 = libname+'.'+nums[0] name1 = libname fu("dlname='%s'\n" % name2) strn = " ".join([name3, name2, name1]) fu("library_names='%s'\n" % (strn) ) else: fu("dlname='%s'\n" % sname) fu("library_names='%s %s %s'\n" % (sname, sname, sname) ) fu("old_library=''\n") vars = ' '.join(task.m_env['libtoolvars']+task.m_env['LINKFLAGS']) fu("dependency_libs='%s'\n" % vars) fu("current=0\n") fu("age=0\nrevision=0\ninstalled=yes\nshouldnotlink=no\n") fu("dlopen=''\ndlpreopen=''\n") fu("libdir='%s/lib'\n" % task.m_env['PREFIX']) dest.close() return 0 # TODO move this line out Action.Action('fakelibtool', vars=fakelibtool_vardeps, func=fakelibtool_build, color='BLUE') class ccroot(Object.genobj): "Parent class for programs and libraries in languages c, c++ and moc (Qt)" s_default_ext = [] def __init__(self, type='program'): Object.genobj.__init__(self, type) self.env = Params.g_build.m_allenvs['default'].copy() if not self.env['tools']: fatal('no tool selected') # installation directory, to override PREFIX/bin or PREFIX/lib self.install_in = '' # includes, seen from the current directory self.includes='' self.defines='' self.rpaths='' self.uselib='' # new scheme: provide the names of the local libraries to link with # the objects found will be post()-ed self.uselib_local='' # add .o files produced by another Object subclass self.add_objects = '' self.m_linktask=None # libtool emulation self.want_libtool=0 # -1: fake; 1: real self.vnum='' self.p_compiletasks=[] # do not forget to set the following variables in a subclass self.p_flag_vars = [] self.p_type_vars = [] # TODO ??? self.m_type_initials = '' self.chmod = 0755 # these are kind of private, do not touch self._incpaths_lst=[] self.scanner_defines = {} self._bld_incpaths_lst=[] def create_task(self, type, env=None, nice=10): "overrides Object.create_task to catch the creation of cpp tasks" task = Object.genobj.create_task(self, type, env, nice) if type == self.m_type_initials: self.p_compiletasks.append(task) return task def get_valid_types(self): "subclass me" fatal('subclass method get_valid_types of ccroot') def apply(self): "adding some kind of genericity is tricky subclass this method if it does not suit your needs" debug("apply called for "+self.m_type_initials, 'ccroot') if not self.m_type in self.get_valid_types(): fatal('Invalid type for object: '+self.m_type_initials) self.apply_type_vars() self.apply_incpaths() self.apply_defines() self.apply_core() self.apply_lib_vars() self.apply_obj_vars() if self.m_type != 'objects': self.apply_objdeps() def apply_core(self): if self.want_libtool and self.want_libtool>0: self.apply_libtool() if self.m_type == 'objects': type = 'program' else: type = self.m_type obj_ext = self.env[type+'_obj_ext'][0] pre = self.m_type_initials # get the list of folders to use by the scanners # all our objects share the same include paths anyway tree = Params.g_build dir_lst = { 'path_lst' : self._incpaths_lst, 'defines' : self.scanner_defines } lst = self.to_list(self.source) find_source_lst = self.path.find_source_lst for filename in lst: node = find_source_lst(Utils.split_path(filename)) if not node: fatal("source not found: %s in %s" % (filename, str(self.path))) # Extract the extension and look for a handler hook. k = max(0, filename.rfind('.')) try: self.get_hook(filename[k:])(self, node) continue except TypeError: pass # create the compilation task: cpp or cc task = self.create_task(self.m_type_initials, self.env) task.m_scanner = g_c_scanner task.m_scanner_params = dir_lst task.m_inputs = [node] task.m_outputs = [node.change_ext(obj_ext)] # if we are only building .o files, tell which ones we built if self.m_type=='objects': outputs = [] app = outputs.append for t in self.p_compiletasks: app(t.m_outputs[0]) self.out_nodes = outputs return # and after the objects, the remaining is the link step # link in a lower priority (101) so it runs alone (default is 10) global g_prio_link if self.m_type=='staticlib': linktask = self.create_task(pre+'_link_static', self.env, g_prio_link) else: linktask = self.create_task(pre+'_link', self.env, g_prio_link) outputs = [] app = outputs.append for t in self.p_compiletasks: app(t.m_outputs[0]) linktask.set_inputs(outputs) linktask.set_outputs(self.path.find_build(self.get_target_name())) self.m_linktask = linktask if self.m_type != 'program' and self.want_libtool: latask = self.create_task('fakelibtool', self.env, 200) latask.set_inputs(linktask.m_outputs) latask.set_outputs(linktask.m_outputs[0].change_ext('.la')) self.m_latask = latask def get_target_name(self, ext=None): return self.get_library_name(self.target, self.m_type, ext) def get_library_name(self, name, type, ext=None): prefix = self.env[type+'_PREFIX'] suffix = self.env[type+'_SUFFIX'] if ext: suffix = ext if not prefix: prefix='' if not suffix: suffix='' return ''.join([prefix, name, suffix]) def apply_defines(self): "subclass me" pass def apply_incpaths(self): lst = [] for i in self.to_list(self.uselib): if self.env['CPPPATH_'+i]: lst += self.to_list(self.env['CPPPATH_'+i]) inc_lst = self.to_list(self.includes) + lst lst = self._incpaths_lst # add the build directory self._incpaths_lst.append(Params.g_build.m_bldnode) self._incpaths_lst.append(Params.g_build.m_srcnode) # now process the include paths tree = Params.g_build for dir in inc_lst: if os.path.isabs(dir[0]) or (len(dir) > 1 and dir[1] == ':'): self.env.append_value('CPPPATH', dir) continue node = self.path.find_dir_lst(Utils.split_path(dir)) if not node: debug("node not found in ccroot:apply_incpaths "+str(dir), 'ccroot') continue if not node in lst: lst.append(node) Params.g_build.rescan(node) self._bld_incpaths_lst.append(node) # now the nodes are added to self._incpaths_lst def apply_type_vars(self): debug('apply_type_vars called', 'ccroot') for var in self.p_type_vars: # each compiler defines variables like 'shlib_CXXFLAGS', 'shlib_LINKFLAGS', etc # so when we make a cppobj of the type shlib, CXXFLAGS are modified accordingly compvar = '_'.join([self.m_type, var]) #print compvar value = self.env[compvar] if value: self.env.append_value(var, value) def apply_obj_vars(self): debug('apply_obj_vars called for cppobj', 'ccroot') cpppath_st = self.env['CPPPATH_ST'] lib_st = self.env['LIB_ST'] staticlib_st = self.env['STATICLIB_ST'] libpath_st = self.env['LIBPATH_ST'] staticlibpath_st = self.env['STATICLIBPATH_ST'] self.addflags('CXXFLAGS', self.cxxflags) self.addflags('CPPFLAGS', self.cppflags) app = self.env.append_unique # local flags come first # set the user-defined includes paths if not self._incpaths_lst: self.apply_incpaths() for i in self._bld_incpaths_lst: app('_CXXINCFLAGS', cpppath_st % i.bldpath(self.env)) app('_CXXINCFLAGS', cpppath_st % i.srcpath(self.env)) # set the library include paths for i in self.env['CPPPATH']: app('_CXXINCFLAGS', cpppath_st % i) #print self.env['_CXXINCFLAGS'] #print " appending include ",i # this is usually a good idea app('_CXXINCFLAGS', cpppath_st % '.') app('_CXXINCFLAGS', cpppath_st % self.env.variant()) tmpnode = Params.g_build.m_curdirnode app('_CXXINCFLAGS', cpppath_st % tmpnode.bldpath(self.env)) app('_CXXINCFLAGS', cpppath_st % tmpnode.srcpath(self.env)) for i in self.env['RPATH']: app('LINKFLAGS', i) for i in self.env['LIBPATH']: app('LINKFLAGS', libpath_st % i) for i in self.env['LIBPATH']: app('LINKFLAGS', staticlibpath_st % i) if self.env['STATICLIB']: app('LINKFLAGS', self.env['STATICLIB_MARKER']) for i in self.env['STATICLIB']: app('LINKFLAGS', staticlib_st % i) # fully static binaries ? if not self.env['FULLSTATIC']: if self.env['STATICLIB'] or self.env['LIB']: app('LINKFLAGS', self.env['SHLIB_MARKER']) if self.env['LIB']: for i in self.env['LIB']: app('LINKFLAGS', lib_st % i) def install(self): if not (Params.g_commands['install'] or Params.g_commands['uninstall']): return if self.install_in is 0: return dest_var = '' dest_subdir = '' if self.install_in: dest_var = self.install_in dest_subdir = '' else: if self.m_type == 'program': dest_var = 'PREFIX' dest_subdir = 'bin' else: dest_var = 'PREFIX' dest_subdir = 'lib' if self.m_type == 'program': self.install_results(dest_var, dest_subdir, self.m_linktask, chmod=self.chmod) elif self.m_type == 'shlib' or self.m_type == 'plugin': if self.want_libtool: self.install_results(dest_var, dest_subdir, self.m_latask) if sys.platform=='win32' or not self.vnum: self.install_results(dest_var, dest_subdir, self.m_linktask) else: libname = self.m_linktask.m_outputs[0].m_name nums=self.vnum.split('.') name3 = libname+'.'+self.vnum name2 = libname+'.'+nums[0] name1 = libname filename = self.m_linktask.m_outputs[0].relpath_gen(Params.g_build.m_curdirnode) Common.install_as(dest_var, dest_subdir+'/'+name3, filename) #print 'lib/'+name2, '->', name3 #print 'lib/'+name1, '->', name2 Common.symlink_as(dest_var, name3, dest_subdir+'/'+name2) Common.symlink_as(dest_var, name2, dest_subdir+'/'+name1) elif self.m_type == 'staticlib': self.install_results(dest_var, dest_subdir, self.m_linktask, chmod=0644) # This piece of code must suck, no one uses it def apply_libtool(self): self.env['vnum']=self.vnum paths=[] libs=[] libtool_files=[] libtool_vars=[] for l in self.env['LINKFLAGS']: if l[:2]=='-L': paths.append(l[2:]) elif l[:2]=='-l': libs.append(l[2:]) for l in libs: for p in paths: try: dict = read_la_file(p+'/lib'+l+'.la') linkflags2 = dict['dependency_libs'] for v in linkflags2.split(): if v[-3:] == '.la': libtool_files.append(v) libtool_vars.append(v) continue self.env.append_unique('LINKFLAGS', v) break except: pass self.env['libtoolvars']=libtool_vars while libtool_files: file = libtool_files.pop() dict = read_la_file(file) for v in dict['dependency_libs'].split(): if v[-3:] == '.la': libtool_files.append(v) continue self.env.append_unique('LINKFLAGS', v) def apply_lib_vars(self): debug('apply_lib_vars called', 'ccroot') # 0. override if necessary self.process_vnum() # 1. the case of the libs defined in the project names = self.uselib_local.split() env=self.env htbl = Params.g_build.m_depends_on for name in names: for obj in Object.g_allobjs: if obj.name != name: continue if not obj.m_posted: obj.post() if obj.m_type == 'shlib': env.append_value('LIB', obj.target) elif obj.m_type == 'plugin': if platform == 'darwin': env.append_value('PLUGIN', obj.target) else: env.append_value('LIB', obj.target) elif obj.m_type == 'staticlib': env.append_value('STATICLIB', obj.target) else: error('unknown object type %s in apply_lib_vars' % obj.name) # add the path too tmp_path = obj.path.bldpath(self.env) if not tmp_path in env['LIBPATH']: env.prepend_value('LIBPATH', tmp_path) # set the dependency over the link task self.m_linktask.m_run_after.append(obj.m_linktask) # make sure to rebuild our link task if obj.m_linktask is re-run try: lst = htbl[self.m_linktask.m_outputs[0]] for a in obj.m_linktask: if not a in lst: lst.append(a) except: htbl[self.m_linktask.m_outputs[0]] = obj.m_linktask.m_outputs # do not continue on all objects, we have found the interesting one break # 2. the case of the libs defined outside libs = self.uselib.split() for l in libs: for v in self.p_flag_vars: val=self.env[v+'_'+l] if val: self.env.append_value(v, val) def process_vnum(self): if self.vnum and sys.platform != 'darwin' and sys.platform != 'win32': nums=self.vnum.split('.') # this is very unix-specific try: name3 = self.soname except: name3 = self.m_linktask.m_outputs[0].m_name+'.'+self.vnum.split('.')[0] self.env.append_value('LINKFLAGS', '-Wl,-h,'+name3) def apply_objdeps(self): "add the .o files produced by some other object files in the same manner as uselib_local" lst = self.add_objects.split() if not lst: return for obj in Object.g_allobjs: if obj.target in lst or obj.name in lst: if not obj.m_posted: obj.post() self.m_linktask.m_inputs += obj.out_nodes def addflags(self, var, value): "utility function for cc.py and ccroot.py: add self.cxxflags to CXXFLAGS" if type(var) is types.StringType: for i in value.split(): self.env.append_value(var, i) else: # TODO: double-check self.env[var] += value