diff options
author | Ryan Dahl <ry@tinyclouds.org> | 2009-10-02 17:05:42 +0200 |
---|---|---|
committer | Ryan Dahl <ry@tinyclouds.org> | 2009-10-02 17:05:42 +0200 |
commit | 84d2966377a757fca9760d3b8c57d5f2d74975e5 (patch) | |
tree | 27f978306f4dda48b96866ed23887c48d466dfbe /tools | |
parent | 763137e705376039f12d3927cfeb48e1dd7f2b40 (diff) | |
download | nodejs-84d2966377a757fca9760d3b8c57d5f2d74975e5.tar.gz nodejs-84d2966377a757fca9760d3b8c57d5f2d74975e5.tar.bz2 nodejs-84d2966377a757fca9760d3b8c57d5f2d74975e5.zip |
Use waf-light instead of waf
This way there won't be strange tools/.waf-12343 directories hanging about.
All that waf needs to run is inside the tools/wafadmin directory.
Diffstat (limited to 'tools')
63 files changed, 15736 insertions, 0 deletions
diff --git a/tools/waf b/tools/waf Binary files differdeleted file mode 100755 index 2bf38ea0a..000000000 --- a/tools/waf +++ /dev/null diff --git a/tools/waf-light b/tools/waf-light new file mode 100755 index 000000000..a506f8235 --- /dev/null +++ b/tools/waf-light @@ -0,0 +1,150 @@ +#!/usr/bin/env python +# encoding: utf-8 +# Thomas Nagy, 2005-2009 + +""" +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + +1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + +2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + +3. The name of the author may not be used to endorse or promote products + derived from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE AUTHOR "AS IS" AND ANY EXPRESS OR +IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, +INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) +HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, +STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING +IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. +""" + +import os, sys +if sys.hexversion<0x203000f: raise ImportError("Waf requires Python >= 2.3") + +if 'PSYCOWAF' in os.environ: + try:import psyco;psyco.full() + except:pass + +VERSION="1.5.9" +REVISION="x" +INSTALL="x" +C1='x' +C2='x' +cwd = os.getcwd() +join = os.path.join + +WAF='waf' +def b(x): + return x + +if sys.hexversion>0x300000f: + WAF='waf3' + def b(x): + return x.encode() + +def err(m): + print(('\033[91mError: %s\033[0m' % m)) + sys.exit(1) + +def unpack_wafdir(dir): + f = open(sys.argv[0],'rb') + c = "corrupted waf (%d)" + while 1: + line = f.readline() + if not line: err("run waf-light from a folder containing wafadmin") + if line == b('#==>\n'): + txt = f.readline() + if not txt: err(c % 1) + if f.readline()!=b('#<==\n'): err(c % 2) + break + if not txt: err(c % 3) + txt = txt[1:-1].replace(b(C1), b('\n')).replace(b(C2), b('\r')) + + import shutil, tarfile + try: shutil.rmtree(dir) + except OSError: pass + try: os.makedirs(join(dir, 'wafadmin', 'Tools')) + except OSError: err("Cannot unpack waf lib into %s\nMove waf into a writeable directory" % dir) + + os.chdir(dir) + tmp = 't.tbz2' + t = open(tmp,'wb') + t.write(txt) + t.close() + + try: + t = tarfile.open(tmp) + for x in t: t.extract(x) + t.close() + except: + os.chdir(cwd) + try: shutil.rmtree(dir) + except OSError: pass + err("Waf cannot be unpacked, check that bzip2 support is present") + + os.chmod(join('wafadmin','Tools'), 493) + + os.unlink(tmp) + + if sys.hexversion>0x300000f: + sys.path = [join(dir, 'wafadmin')] + sys.path + import py3kfixes + py3kfixes.fixdir(dir) + + os.chdir(cwd) + +def test(dir): + try: os.stat(join(dir, 'wafadmin')); return os.path.abspath(dir) + except OSError: pass + +def find_lib(): + name = sys.argv[0] + base = os.path.dirname(os.path.abspath(name)) + + #devs use $WAFDIR + w=test(os.environ.get('WAFDIR', '')) + if w: return w + + #waf-light + if name.endswith('waf-light'): + w = test(base) + if w: return w + err("waf-light requires wafadmin -> export WAFDIR=/folder") + + dir = "/lib/%s-%s-%s/" % (WAF, VERSION, REVISION) + for i in [INSTALL,'/usr','/usr/local','/opt']: + w = test(i+dir) + if w: return w + + #waf-local + s = '.%s-%s-%s' + if sys.platform == 'win32': s = s[1:] + dir = join(base, s % (WAF, VERSION, REVISION)) + w = test(dir) + if w: return w + + #unpack + unpack_wafdir(dir) + return dir + +wafdir = find_lib() +w = join(wafdir, 'wafadmin') +t = join(w, 'Tools') +sys.path = [w, t] + sys.path + +import Scripting +Scripting.prepare(t, cwd, VERSION, wafdir) +sys.exit(0) + diff --git a/tools/wafadmin/Build.py b/tools/wafadmin/Build.py new file mode 100644 index 000000000..98c22abbb --- /dev/null +++ b/tools/wafadmin/Build.py @@ -0,0 +1,963 @@ +#!/usr/bin/env python +# encoding: utf-8 +# Thomas Nagy, 2005 (ita) + +""" +Dependency tree holder + +The class Build holds all the info related to a build: +* file system representation (tree of Node instances) +* various cached objects (task signatures, file scan results, ..) + +There is only one Build object at a time (bld singleton) +""" + +import os, sys, errno, re, glob, gc, datetime, shutil +try: import cPickle +except: import pickle as cPickle +import Runner, TaskGen, Node, Scripting, Utils, Environment, Task, Logs, Options +from Logs import debug, error, info +from Constants import * + +SAVED_ATTRS = 'root srcnode bldnode node_sigs node_deps raw_deps task_sigs id_nodes'.split() +"Build class members to save" + +bld = None +"singleton - safe to use when Waf is not used as a library" + +class BuildError(Utils.WafError): + def __init__(self, b=None, t=[]): + self.bld = b + self.tasks = t + self.ret = 1 + Utils.WafError.__init__(self, self.format_error()) + + def format_error(self): + lst = ['Build failed'] + for tsk in self.tasks: + txt = tsk.format_error() + if txt: lst.append(txt) + return '\n'.join(lst) + +def group_method(fun): + """ + sets a build context method to execute after the current group has finished executing + this is useful for installing build files: + * calling install_files/install_as will fail if called too early + * people do not want to define install method in their task classes + + TODO: try it + """ + def f(*k, **kw): + if not k[0].is_install: + return False + + postpone = True + if 'postpone' in kw: + postpone = kw['postpone'] + del kw['postpone'] + + if postpone: + m = k[0].task_manager + m.groups[m.current_group].post_funs.append((fun, k, kw)) + kw['cwd'] = k[0].path + else: + fun(*k, **kw) + return f + +class BuildContext(Utils.Context): + "holds the dependency tree" + def __init__(self): + + # not a singleton, but provided for compatibility + global bld + bld = self + + self.task_manager = Task.TaskManager() + + # instead of hashing the nodes, we assign them a unique id when they are created + self.id_nodes = 0 + self.idx = {} + + # map names to environments, the 'default' must be defined + self.all_envs = {} + + # ======================================= # + # code for reading the scripts + + # project build directory - do not reset() from load_dirs() + self.bdir = '' + + # the current directory from which the code is run + # the folder changes everytime a wscript is read + self.path = None + + # Manual dependencies. + self.deps_man = Utils.DefaultDict(list) + + # ======================================= # + # cache variables + + # local cache for absolute paths - cache_node_abspath[variant][node] + self.cache_node_abspath = {} + + # list of folders that are already scanned + # so that we do not need to stat them one more time + self.cache_scanned_folders = {} + + # list of targets to uninstall for removing the empty folders after uninstalling + self.uninstall = [] + + # ======================================= # + # tasks and objects + + # build dir variants (release, debug, ..) + for v in 'cache_node_abspath task_sigs node_deps raw_deps node_sigs'.split(): + var = {} + setattr(self, v, var) + + self.cache_dir_contents = {} + + self.all_task_gen = [] + self.task_gen_cache_names = {} + self.cache_sig_vars = {} + self.log = None + + self.root = None + self.srcnode = None + self.bldnode = None + + # bind the build context to the nodes in use + # this means better encapsulation and no build context singleton + class node_class(Node.Node): + pass + self.node_class = node_class + self.node_class.__module__ = "Node" + self.node_class.__name__ = "Nodu" + self.node_class.bld = self + + self.is_install = None + + def __copy__(self): + "nodes are not supposed to be copied" + raise Utils.WafError('build contexts are not supposed to be cloned') + + def load(self): + "load the cache from the disk" + try: + env = Environment.Environment(os.path.join(self.cachedir, 'build.config.py')) + except (IOError, OSError): + pass + else: + if env['version'] < HEXVERSION: + raise Utils.WafError('Version mismatch! reconfigure the project') + for t in env['tools']: + self.setup(**t) + + try: + gc.disable() + f = data = None + + Node.Nodu = self.node_class + + try: + f = open(os.path.join(self.bdir, DBFILE), 'rb') + except (IOError, EOFError): + # handle missing file/empty file + pass + + try: + if f: data = cPickle.load(f) + except AttributeError: + # handle file of an old Waf version + # that has an attribute which no longer exist + # (e.g. AttributeError: 'module' object has no attribute 'BuildDTO') + if Logs.verbose > 1: raise + + if data: + for x in SAVED_ATTRS: setattr(self, x, data[x]) + else: + debug('build: Build cache loading failed') + + finally: + if f: f.close() + gc.enable() + + def save(self): + "store the cache on disk, see self.load" + gc.disable() + self.root.__class__.bld = None + + # some people are very nervous with ctrl+c so we have to make a temporary file + Node.Nodu = self.node_class + db = os.path.join(self.bdir, DBFILE) + file = open(db + '.tmp', 'wb') + data = {} + for x in SAVED_ATTRS: data[x] = getattr(self, x) + cPickle.dump(data, file, -1) + file.close() + + # do not use shutil.move + try: os.unlink(db) + except OSError: pass + os.rename(db + '.tmp', db) + self.root.__class__.bld = self + gc.enable() + + # ======================================= # + + def clean(self): + debug('build: clean called') + + # does not clean files created during the configuration + precious = set([]) + for env in self.all_envs.values(): + for x in env[CFG_FILES]: + node = self.srcnode.find_resource(x) + if node: + precious.add(node.id) + + def clean_rec(node): + for x in list(node.childs.keys()): + nd = node.childs[x] + + tp = nd.id & 3 + if tp == Node.DIR: + clean_rec(nd) + elif tp == Node.BUILD: + if nd.id in precious: continue + for env in self.all_envs.values(): + try: os.remove(nd.abspath(env)) + except OSError: pass + node.childs.__delitem__(x) + + clean_rec(self.srcnode) + + for v in 'node_sigs node_deps task_sigs raw_deps cache_node_abspath'.split(): + setattr(self, v, {}) + + def compile(self): + """The cache file is not written if nothing was build at all (build is up to date)""" + debug('build: compile called') + + """ + import cProfile, pstats + cProfile.run("import Build\nBuild.bld.flush()", 'profi.txt') + p = pstats.Stats('profi.txt') + p.sort_stats('cumulative').print_stats(80) + """ + self.flush() + #""" + + self.generator = Runner.Parallel(self, Options.options.jobs) + + def dw(on=True): + if Options.options.progress_bar: + if on: sys.stderr.write(Logs.colors.cursor_on) + else: sys.stderr.write(Logs.colors.cursor_off) + + debug('build: executor starting') + + back = os.getcwd() + os.chdir(self.bldnode.abspath()) + + try: + try: + dw(on=False) + self.generator.start() + except KeyboardInterrupt: + dw() + if self.generator.consumers: + self.save() + raise + except Exception: + dw() + # do not store anything, for something bad happened + raise + else: + dw() + if self.generator.consumers: + self.save() + + if self.generator.error: + raise BuildError(self, self.task_manager.tasks_done) + + finally: + os.chdir(back) + + def install(self): + "this function is called for both install and uninstall" + debug('build: install called') + + self.flush() + + # remove empty folders after uninstalling + if self.is_install < 0: + lst = [] + for x in self.uninstall: + dir = os.path.dirname(x) + if not dir in lst: lst.append(dir) + lst.sort() + lst.reverse() + + nlst = [] + for y in lst: + x = y + while len(x) > 4: + if not x in nlst: nlst.append(x) + x = os.path.dirname(x) + + nlst.sort() + nlst.reverse() + for x in nlst: + try: os.rmdir(x) + except OSError: pass + + def new_task_gen(self, *k, **kw): + kw['bld'] = self + if len(k) == 0: + ret = TaskGen.task_gen(*k, **kw) + else: + cls_name = k[0] + + try: cls = TaskGen.task_gen.classes[cls_name] + except KeyError: raise Utils.WscriptError('%s is not a valid task generator -> %s' % + (cls_name, [x for x in TaskGen.task_gen.classes])) + ret = cls(*k, **kw) + return ret + + def load_envs(self): + try: + lst = Utils.listdir(self.cachedir) + except OSError, e: + if e.errno == errno.ENOENT: + raise Utils.WafError('The project was not configured: run "waf configure" first!') + else: + raise + + if not lst: + raise Utils.WafError('The cache directory is empty: reconfigure the project') + + for file in lst: + if file.endswith(CACHE_SUFFIX): + env = Environment.Environment(os.path.join(self.cachedir, file)) + name = file[:-len(CACHE_SUFFIX)] + + self.all_envs[name] = env + + self.init_variants() + + for env in self.all_envs.values(): + for f in env[CFG_FILES]: + newnode = self.path.find_or_declare(f) + try: + hash = Utils.h_file(newnode.abspath(env)) + except (IOError, AttributeError): + error("cannot find "+f) + hash = SIG_NIL + self.node_sigs[env.variant()][newnode.id] = hash + + # TODO: hmmm, these nodes are removed from the tree when calling rescan() + self.bldnode = self.root.find_dir(self.bldnode.abspath()) + self.path = self.srcnode = self.root.find_dir(self.srcnode.abspath()) + self.cwd = self.bldnode.abspath() + + def setup(self, tool, tooldir=None, funs=None): + "setup tools for build process" + if isinstance(tool, list): + for i in tool: self.setup(i, tooldir) + return + + if not tooldir: tooldir = Options.tooldir + + module = Utils.load_tool(tool, tooldir) + if hasattr(module, "setup"): module.setup(self) + + def init_variants(self): + debug('build: init variants') + + lstvariants = [] + for env in self.all_envs.values(): + if not env.variant() in lstvariants: + lstvariants.append(env.variant()) + self.lst_variants = lstvariants + + debug('build: list of variants is %r' % lstvariants) + + for name in lstvariants+[0]: + for v in 'node_sigs cache_node_abspath'.split(): + var = getattr(self, v) + if not name in var: + var[name] = {} + + # ======================================= # + # node and folder handling + + # this should be the main entry point + def load_dirs(self, srcdir, blddir, load_cache=1): + "this functions should be the start of everything" + + assert(os.path.isabs(srcdir)) + assert(os.path.isabs(blddir)) + + self.cachedir = os.path.join(blddir, CACHE_DIR) + + if srcdir == blddir: + raise Utils.WafError("build dir must be different from srcdir: %s <-> %s " % (srcdir, blddir)) + + self.bdir = blddir + + # try to load the cache file, if it does not exist, nothing happens + self.load() + + if not self.root: + Node.Nodu = self.node_class + self.root = Node.Nodu('', None, Node.DIR) + + if not self.srcnode: + self.srcnode = self.root.ensure_dir_node_from_path(srcdir) + debug('build: srcnode is %s and srcdir %s' % (self.srcnode.name, srcdir)) + + self.path = self.srcnode + + # create this build dir if necessary + try: os.makedirs(blddir) + except OSError: pass + + if not self.bldnode: + self.bldnode = self.root.ensure_dir_node_from_path(blddir) + + self.init_variants() + + def rescan(self, src_dir_node): + """ + look the contents of a (folder)node and update its list of childs + + The intent is to perform the following steps + * remove the nodes for the files that have disappeared + * remove the signatures for the build files that have disappeared + * cache the results of os.listdir + * create the build folder equivalent (mkdir) for each variant + src/bar -> build/default/src/bar, build/release/src/bar + + when a folder in the source directory is removed, we do not check recursively + to remove the unused nodes. To do that, call 'waf clean' and build again. + """ + + # do not rescan over and over again + # TODO use a single variable in waf 1.6 + if self.cache_scanned_folders.get(src_dir_node.id, None): return + self.cache_scanned_folders[src_dir_node.id] = True + + # TODO remove in waf 1.6 + if hasattr(self, 'repository'): self.repository(src_dir_node) + + if not src_dir_node.name and sys.platform == 'win32': + # the root has no name, contains drive letters, and cannot be listed + return + + + # first, take the case of the source directory + parent_path = src_dir_node.abspath() + try: + lst = set(Utils.listdir(parent_path)) + except OSError: + lst = set([]) + + # TODO move this at the bottom + self.cache_dir_contents[src_dir_node.id] = lst + + # hash the existing source files, remove the others + cache = self.node_sigs[0] + for x in src_dir_node.childs.values(): + if x.id & 3 != Node.FILE: continue + if x.name in lst: + try: + cache[x.id] = Utils.h_file(x.abspath()) + except IOError: + raise Utils.WafError('The file %s is not readable or has become a dir' % x.abspath()) + else: + try: del cache[x.id] + except KeyError: pass + + del src_dir_node.childs[x.name] + + + # first obtain the differences between srcnode and src_dir_node + h1 = self.srcnode.height() + h2 = src_dir_node.height() + + lst = [] + child = src_dir_node + while h2 > h1: + lst.append(child.name) + child = child.parent + h2 -= 1 + lst.reverse() + + # list the files in the build dirs + # remove the existing timestamps if the build files are removed + for variant in self.lst_variants: + sub_path = os.path.join(self.bldnode.abspath(), variant , *lst) + try: + self.listdir_bld(src_dir_node, sub_path, variant) + except OSError: + #debug('build: osError on ' + sub_path) + # listdir failed, remove all sigs of nodes + # TODO more things to remove? + dict = self.node_sigs[variant] + for node in src_dir_node.childs.values(): + if node.id in dict: + dict.__delitem__(node.id) + + # avoid deleting the build dir node + if node.id != self.bldnode.id: + src_dir_node.childs.__delitem__(node.name) + os.makedirs(sub_path) + + # ======================================= # + def listdir_src(self, parent_node): + """do not use, kept for compatibility""" + pass + + def remove_node(self, node): + """do not use, kept for compatibility""" + pass + + def listdir_bld(self, parent_node, path, variant): + """in this method we do not add timestamps but we remove them + when the files no longer exist (file removed in the build dir)""" + + i_existing_nodes = [x for x in parent_node.childs.values() if x.id & 3 == Node.BUILD] + + lst = set(Utils.listdir(path)) + node_names = set([x.name for x in i_existing_nodes]) + remove_names = node_names - lst + + # remove the stamps of the build nodes that no longer exist on the filesystem + ids_to_remove = [x.id for x in i_existing_nodes if x.name in remove_names] + cache = self.node_sigs[variant] + for nid in ids_to_remove: + if nid in cache: + cache.__delitem__(nid) + + def get_env(self): + return self.env_of_name('default') + def set_env(self, name, val): + self.all_envs[name] = val + + env = property(get_env, set_env) + + def add_manual_dependency(self, path, value): + if isinstance(path, Node.Node): + node = path + elif os.path.isabs(path): + node = self.root.find_resource(path) + else: + node = self.path.find_resource(path) + self.deps_man[node.id].append(value) + + def launch_node(self): + """return the launch directory as a node""" + # p_ln is kind of private, but public in case if + try: + return self.p_ln + except AttributeError: + self.p_ln = self.root.find_dir(Options.launch_dir) + return self.p_ln + + def glob(self, pattern, relative=True): + "files matching the pattern, seen from the current folder" + path = self.path.abspath() + files = [self.root.find_resource(x) for x in glob.glob(path+os.sep+pattern)] + if relative: + files = [x.path_to_parent(self.path) for x in files if x] + else: + files = [x.abspath() for x in files if x] + return files + + ## the following methods are candidates for the stable apis ## + + def add_group(self, *k): + self.task_manager.add_group(*k) + + def set_group(self, *k, **kw): + self.task_manager.set_group(*k, **kw) + + def hash_env_vars(self, env, vars_lst): + """hash environment variables + ['CXX', ..] -> [env['CXX'], ..] -> md5()""" + + # ccroot objects use the same environment for building the .o at once + # the same environment and the same variables are used + + idx = str(id(env)) + str(vars_lst) + try: return self.cache_sig_vars[idx] + except KeyError: pass + + lst = [str(env[a]) for a in vars_lst] + ret = Utils.h_list(lst) + debug("envhash: %r %r" % (ret, lst)) + + # next time + self.cache_sig_vars[idx] = ret + return ret + + def name_to_obj(self, name, env): + """retrieve a task generator from its name or its target name + remember that names must be unique""" + cache = self.task_gen_cache_names + if not cache: + # create the index lazily + for x in self.all_task_gen: + vt = x.env.variant() + '_' + if x.name: + cache[vt + x.name] = x + else: + if isinstance(x.target, str): + target = x.target + else: + target = ' '.join(x.target) + v = vt + target + if not cache.get(v, None): + cache[v] = x + return cache.get(env.variant() + '_' + name, None) + + def flush(self, all=1): + """tell the task generators to create the tasks""" + + self.ini = datetime.datetime.now() + # force the initialization of the mapping name->object in flush + # name_to_obj can be used in userland scripts, in that case beware of incomplete mapping + self.task_gen_cache_names = {} + self.name_to_obj('', self.env) + + debug('build: delayed operation TaskGen.flush() called') + + if Options.options.compile_targets: + debug('task_gen: posting objects listed in compile_targets') + + # ensure the target names exist, fail before any post() + target_objects = Utils.DefaultDict(list) + for target_name in Options.options.compile_targets.split(','): + # trim target_name (handle cases when the user added spaces to targets) + target_name = target_name.strip() + for env in self.all_envs.values(): + obj = self.name_to_obj(target_name, env) + if obj: + target_objects[target_name].append(obj) + if not target_name in target_objects and all: + raise Utils.WafError("target '%s' does not exist" % target_name) + + to_compile = [] + for x in target_objects.values(): + for y in x: + to_compile.append(id(y)) + + # tasks must be posted in order of declaration + # we merely apply a filter to discard the ones we are not interested in + for i in xrange(len(self.task_manager.groups)): + g = self.task_manager.groups[i] + self.task_manager.current_group = i + for tg in g.tasks_gen: + if id(tg) in to_compile: + tg.post() + + else: + debug('task_gen: posting objects (normal)') + ln = self.launch_node() + # if the build is started from the build directory, do as if it was started from the top-level + # for the pretty-printing (Node.py), the two lines below cannot be moved to Build::launch_node + if ln.is_child_of(self.bldnode) or not ln.is_child_of(self.srcnode): + ln = self.srcnode + + # if the project file is located under the source directory, build all targets by default + # else 'waf configure build' does nothing + proj_node = self.root.find_dir(os.path.split(Utils.g_module.root_path)[0]) + if proj_node.id != self.srcnode.id: + ln = self.srcnode + + for i in xrange(len(self.task_manager.groups)): + g = self.task_manager.groups[i] + self.task_manager.current_group = i + for tg in g.tasks_gen: + if not tg.path.is_child_of(ln): + continue + tg.post() + + def env_of_name(self, name): + try: + return self.all_envs[name] + except KeyError: + error('no such environment: '+name) + return None + + def progress_line(self, state, total, col1, col2): + n = len(str(total)) + + Utils.rot_idx += 1 + ind = Utils.rot_chr[Utils.rot_idx % 4] + + ini = self.ini + + pc = (100.*state)/total + eta = Utils.get_elapsed_time(ini) + fs = "[%%%dd/%%%dd][%%s%%2d%%%%%%s][%s][" % (n, n, ind) + left = fs % (state, total, col1, pc, col2) + right = '][%s%s%s]' % (col1, eta, col2) + + cols = Utils.get_term_cols() - len(left) - len(right) + 2*len(col1) + 2*len(col2) + if cols < 7: cols = 7 + + ratio = int((cols*state)/total) - 1 + + bar = ('='*ratio+'>').ljust(cols) + msg = Utils.indicator % (left, bar, right) + + return msg + + + # do_install is not used anywhere + def do_install(self, src, tgt, chmod=O644): + """returns true if the file was effectively installed or uninstalled, false otherwise""" + if self.is_install > 0: + if not Options.options.force: + # check if the file is already there to avoid a copy + try: + st1 = os.stat(tgt) + st2 = os.stat(src) + except OSError: + pass + else: + # same size and identical timestamps -> make no copy + if st1.st_mtime >= st2.st_mtime and st1.st_size == st2.st_size: + return False + + srclbl = src.replace(self.srcnode.abspath(None)+os.sep, '') + info("* installing %s as %s" % (srclbl, tgt)) + + # following is for shared libs and stale inodes (-_-) + try: os.remove(tgt) + except OSError: pass + + try: + shutil.copy2(src, tgt) + os.chmod(tgt, chmod) + except IOError: + try: + os.stat(src) + except (OSError, IOError): + error('File %r does not exist' % src) + raise Utils.WafError('Could not install the file %r' % tgt) + return True + + elif self.is_install < 0: + info("* uninstalling %s" % tgt) + + self.uninstall.append(tgt) + + try: + os.remove(tgt) + except OSError, e: + if e.errno != errno.ENOENT: + if not getattr(self, 'uninstall_error', None): + self.uninstall_error = True + Logs.warn('build: some files could not be uninstalled (retry with -vv to list them)') + if Logs.verbose > 1: + Logs.warn('could not remove %s (error code %r)' % (e.filename, e.errno)) + return True + + def get_install_path(self, path, env=None): + "installation path prefixed by the destdir, the variables like in '${PREFIX}/bin' are substituted" + if not env: env = self.env + destdir = env.get_destdir() + path = path.replace('/', os.sep) + destpath = Utils.subst_vars(path, env) + if destdir: + destpath = os.path.join(destdir, destpath.lstrip(os.sep)) + return destpath + + def install_files(self, path, files, env=None, chmod=O644, relative_trick=False, cwd=None): + """To install files only after they have been built, put the calls in a method named + post_build on the top-level wscript + + The files must be a list and contain paths as strings or as Nodes + + The relative_trick flag can be set to install folders, use bld.path.ant_glob() with it + """ + if env: + assert isinstance(env, Environment.Environment), "invalid parameter" + else: + env = self.env + + if not path: return [] + + if not cwd: + cwd = self.path + + if isinstance(files, str) and '*' in files: + gl = cwd.abspath() + os.sep + files + lst = glob.glob(gl) + else: + lst = Utils.to_list(files) + + destpath = self.get_install_path(path, env) + + Utils.check_dir(destpath) + + installed_files = [] + for filename in lst: + if isinstance(filename, str) and os.path.isabs(filename): + alst = Utils.split_path(filename) + destfile = os.path.join(destpath, alst[-1]) + else: + if isinstance(filename, Node.Node): + nd = filename + else: + nd = cwd.find_resource(filename) + if not nd: + raise Utils.WafError("Unable to install the file %r (not found in %s)" % (filename, cwd)) + + if relative_trick: + destfile = os.path.join(destpath, filename) + Utils.check_dir(os.path.dirname(destfile)) + else: + destfile = os.path.join(destpath, nd.name) + + filename = nd.abspath(env) + + if self.do_install(filename, destfile, chmod): + installed_files.append(destfile) + return installed_files + + def install_as(self, path, srcfile, env=None, chmod=O644, cwd=None): + """ + srcfile may be a string or a Node representing the file to install + + returns True if the file was effectively installed, False otherwise + """ + if env: + assert isinstance(env, Environment.Environment), "invalid parameter" + else: + env = self.env + + if not path: + raise Utils.WafError("where do you want to install %r? (%r?)" % (srcfile, path)) + + if not cwd: + cwd = self.path + + destpath = self.get_install_path(path, env) + + dir, name = os.path.split(destpath) + Utils.check_dir(dir) + + # the source path + if isinstance(srcfile, Node.Node): + src = srcfile.abspath(env) + else: + src = srcfile + if not os.path.isabs(srcfile): + node = cwd.find_resource(srcfile) + if not node: + raise Utils.WafError("Unable to install the file %r (not found in %s)" % (srcfile, cwd)) + src = node.abspath(env) + + return self.do_install(src, destpath, chmod) + + def symlink_as(self, path, src, env=None, cwd=None): + """example: bld.symlink_as('${PREFIX}/lib/libfoo.so', 'libfoo.so.1.2.3') """ + + if sys.platform == 'win32': + # well, this *cannot* work + return + + if not path: + raise Utils.WafError("where do you want to install %r? (%r?)" % (src, path)) + + tgt = self.get_install_path(path, env) + + dir, name = os.path.split(tgt) + Utils.check_dir(dir) + + if self.is_install > 0: + link = False + if not os.path.islink(tgt): + link = True + elif os.readlink(tgt) != src: + link = True + try: os.remove(tgt) + except OSError: pass + + if link: + info('* symlink %s (-> %s)' % (tgt, src)) + os.symlink(src, tgt) + return 0 + + else: # UNINSTALL + try: + info('* removing %s' % (tgt)) + os.remove(tgt) + return 0 + except OSError: + return 1 + + def exec_command(self, cmd, **kw): + # 'runner' zone is printed out for waf -v, see wafadmin/Options.py + debug('runner: system command -> %s' % cmd) + if self.log: + self.log.write('%s\n' % cmd) + kw['log'] = self.log + try: + if not kw.get('cwd', None): + kw['cwd'] = self.cwd + except AttributeError: + self.cwd = kw['cwd'] = self.bldnode.abspath() + return Utils.exec_command(cmd, **kw) + + def printout(self, s): + f = self.log or sys.stderr + f.write(s) + f.flush() + + def add_subdirs(self, dirs): + self.recurse(dirs, 'build') + + def pre_recurse(self, name_or_mod, path, nexdir): + if not hasattr(self, 'oldpath'): + self.oldpath = [] + self.oldpath.append(self.path) + self.path = self.root.find_dir(nexdir) + return {'bld': self, 'ctx': self} + + def post_recurse(self, name_or_mod, path, nexdir): + self.path = self.oldpath.pop() + + ###### user-defined behaviour + + def pre_build(self): + if hasattr(self, 'pre_funs'): + for m in self.pre_funs: + m(self) + + def post_build(self): + if hasattr(self, 'post_funs'): + for m in self.post_funs: + m(self) + + def add_pre_fun(self, meth): + try: self.pre_funs.append(meth) + except AttributeError: self.pre_funs = [meth] + + def add_post_fun(self, meth): + try: self.post_funs.append(meth) + except AttributeError: self.post_funs = [meth] + + def use_the_magic(self): + Task.algotype = Task.MAXPARALLEL + Task.file_deps = Task.extract_deps + + install_as = group_method(install_as) + install_files = group_method(install_files) + symlink_as = group_method(symlink_as) + diff --git a/tools/wafadmin/Configure.py b/tools/wafadmin/Configure.py new file mode 100644 index 000000000..78346925a --- /dev/null +++ b/tools/wafadmin/Configure.py @@ -0,0 +1,336 @@ +#!/usr/bin/env python +# encoding: utf-8 +# Thomas Nagy, 2005-2008 (ita) + +""" +Configuration system + +A configuration instance is created when "waf configure" is called, it is used to: +* create data dictionaries (Environment instances) +* store the list of modules to import + +The old model (copied from Scons) was to store logic (mapping file extensions to functions) +along with the data. In Waf a way was found to separate that logic by adding an indirection +layer (storing the names in the Environment instances) + +In the new model, the logic is more object-oriented, and the user scripts provide the +logic. The data files (Environments) must contain configuration data only (flags, ..). + +Note: the c/c++ related code is in the module config_c +""" + +import os, shlex, sys, time +try: import cPickle +except ImportError: import pickle as cPickle +import Environment, Utils, Options +from Logs import warn +from Constants import * + +conf_template = '''# project %(app)s configured on %(now)s by +# waf %(wafver)s (abi %(abi)s, python %(pyver)x on %(systype)s) +# using %(args)s +# +''' + +class ConfigurationError(Utils.WscriptError): + pass + +autoconfig = False +"reconfigure the project automatically" + +def find_file(filename, path_list): + """find a file in a list of paths + @param filename: name of the file to search for + @param path_list: list of directories to search + @return: the first occurrence filename or '' if filename could not be found +""" + for directory in Utils.to_list(path_list): + if os.path.exists(os.path.join(directory, filename)): + return directory + return '' + +def find_program_impl(env, filename, path_list=[], var=None, environ=None): + """find a program in folders path_lst, and sets env[var] + @param env: environment + @param filename: name of the program to search for + @param path_list: list of directories to search for filename + @param var: environment value to be checked for in env or os.environ + @return: either the value that is referenced with [var] in env or os.environ + or the first occurrence filename or '' if filename could not be found +""" + + if not environ: + environ = os.environ + + try: path_list = path_list.split() + except AttributeError: pass + + if var: + if env[var]: return env[var] + if var in environ: env[var] = environ[var] + + if not path_list: path_list = environ.get('PATH', '').split(os.pathsep) + + ext = (Options.platform == 'win32') and '.exe,.com,.bat,.cmd' or '' + for y in [filename+x for x in ext.split(',')]: + for directory in path_list: + x = os.path.join(directory, y) + if os.path.isfile(x): + if var: env[var] = x + return x + return '' + +class ConfigurationContext(Utils.Context): + tests = {} + error_handlers = [] + def __init__(self, env=None, blddir='', srcdir=''): + self.env = None + self.envname = '' + + self.environ = dict(os.environ) + + self.line_just = 40 + + self.blddir = blddir + self.srcdir = srcdir + self.all_envs = {} + + # curdir: necessary for recursion + self.cwd = self.curdir = os.getcwd() + + self.tools = [] # tools loaded in the configuration, and that will be loaded when building + + self.setenv(DEFAULT) + + self.lastprog = '' + + self.hash = 0 + self.files = [] + + self.tool_cache = [] + + if self.blddir: + self.post_init() + + def post_init(self): + + self.cachedir = os.path.join(self.blddir, CACHE_DIR) + + path = os.path.join(self.blddir, WAF_CONFIG_LOG) + try: os.unlink(path) + except (OSError, IOError): pass + + try: + self.log = open(path, 'w') + except (OSError, IOError): + self.fatal('could not open %r for writing' % path) + + app = getattr(Utils.g_module, 'APPNAME', '') + if app: + ver = getattr(Utils.g_module, 'VERSION', '') + if ver: + app = "%s (%s)" % (app, ver) + + now = time.ctime() + pyver = sys.hexversion + systype = sys.platform + args = " ".join(sys.argv) + wafver = WAFVERSION + abi = ABI + self.log.write(conf_template % vars()) + + def __del__(self): + """cleanup function: close config.log""" + + # may be ran by the gc, not always after initialization + if hasattr(self, 'log') and self.log: + self.log.close() + + def fatal(self, msg): + raise ConfigurationError(msg) + + def check_tool(self, input, tooldir=None, funs=None): + "load a waf tool" + + tools = Utils.to_list(input) + if tooldir: tooldir = Utils.to_list(tooldir) + for tool in tools: + tool = tool.replace('++', 'xx') + if tool == 'java': tool = 'javaw' + # avoid loading the same tool more than once with the same functions + # used by composite projects + + mag = (tool, id(self.env), funs) + if mag in self.tool_cache: + continue + self.tool_cache.append(mag) + + module = Utils.load_tool(tool, tooldir) + func = getattr(module, 'detect', None) + if func: + if type(func) is type(find_file): func(self) + else: self.eval_rules(funs or func) + + self.tools.append({'tool':tool, 'tooldir':tooldir, 'funs':funs}) + + def sub_config(self, k): + "executes the configure function of a wscript module" + self.recurse(k, name='configure') + + def pre_recurse(self, name_or_mod, path, nexdir): + return {'conf': self, 'ctx': self} + + def post_recurse(self, name_or_mod, path, nexdir): + if not autoconfig: + return + self.hash = hash((self.hash, getattr(name_or_mod, 'waf_hash_val', name_or_mod))) + self.files.append(path) + + def store(self, file=''): + "save the config results into the cache file" + if not os.path.isdir(self.cachedir): + os.makedirs(self.cachedir) + + if not file: + file = open(os.path.join(self.cachedir, 'build.config.py'), 'w') + file.write('version = 0x%x\n' % HEXVERSION) + file.write('tools = %r\n' % self.tools) + file.close() + + if not self.all_envs: + self.fatal('nothing to store in the configuration context!') + for key in self.all_envs: + tmpenv = self.all_envs[key] + tmpenv.store(os.path.join(self.cachedir, key + CACHE_SUFFIX)) + + def set_env_name(self, name, env): + "add a new environment called name" + self.all_envs[name] = env + return env + + def retrieve(self, name, fromenv=None): + "retrieve an environment called name" + try: + env = self.all_envs[name] + except KeyError: + env = Environment.Environment() + env['PREFIX'] = os.path.abspath(os.path.expanduser(Options.options.prefix)) + self.all_envs[name] = env + else: + if fromenv: warn("The environment %s may have been configured already" % name) + return env + + def setenv(self, name): + "enable the environment called name" + self.env = self.retrieve(name) + self.envname = name + + def add_os_flags(self, var, dest=None): + # do not use 'get' to make certain the variable is not defined + try: self.env.append_value(dest or var, Utils.to_list(self.environ[var])) + except KeyError: pass + + def check_message_1(self, sr): + self.line_just = max(self.line_just, len(sr)) + self.log.write(sr + '\n\n') + Utils.pprint('NORMAL', "%s :" % sr.ljust(self.line_just), sep='') + + def check_message_2(self, sr, color='GREEN'): + Utils.pprint(color, sr) + + def check_message(self, th, msg, state, option=''): + sr = 'Checking for %s %s' % (th, msg) + self.check_message_1(sr) + p = self.check_message_2 + if state: p('ok ' + option) + else: p('not found', 'YELLOW') + + # FIXME remove in waf 1.6 + # the parameter 'option' is not used (kept for compatibility) + def check_message_custom(self, th, msg, custom, option='', color='PINK'): + sr = 'Checking for %s %s' % (th, msg) + self.check_message_1(sr) + self.check_message_2(custom, color) + + def find_program(self, filename, path_list=[], var=None, mandatory=False): + "wrapper that adds a configuration message" + + ret = None + if var: + if self.env[var]: + ret = self.env[var] + elif var in os.environ: + ret = os.environ[var] + + if not isinstance(filename, list): filename = [filename] + if not ret: + for x in filename: + ret = find_program_impl(self.env, x, path_list, var, environ=self.environ) + if ret: break + + self.check_message('program', ','.join(filename), ret, ret) + self.log.write('find program=%r paths=%r var=%r -> %r\n\n' % (filename, path_list, var, ret)) + if not ret and mandatory: + self.fatal('The program %r could not be found' % filename) + if var: + self.env[var] = ret + return ret + + def cmd_to_list(self, cmd): + "commands may be written in pseudo shell like 'ccache g++'" + if isinstance(cmd, str) and cmd.find(' '): + try: + os.stat(cmd) + except OSError: + return shlex.split(cmd) + else: + return [cmd] + return cmd + + def __getattr__(self, name): + r = self.__class__.__dict__.get(name, None) + if r: return r + if name and name.startswith('require_'): + + for k in ['check_', 'find_']: + n = name.replace('require_', k) + ret = self.__class__.__dict__.get(n, None) + if ret: + def run(*k, **kw): + r = ret(self, *k, **kw) + if not r: + self.fatal('requirement failure') + return r + return run + self.fatal('No such method %r' % name) + + def eval_rules(self, rules): + self.rules = Utils.to_list(rules) + for x in self.rules: + f = getattr(self, x) + if not f: self.fatal("No such method '%s'." % x) + try: + f() + except Exception, e: + ret = self.err_handler(x, e) + if ret == BREAK: + break + elif ret == CONTINUE: + continue + else: + self.fatal(e) + + def err_handler(self, fun, error): + pass + +def conf(f): + "decorator: attach new configuration functions" + setattr(ConfigurationContext, f.__name__, f) + return f + +def conftest(f): + "decorator: attach new configuration tests (registered as strings)" + ConfigurationContext.tests[f.__name__] = f + return conf(f) + + diff --git a/tools/wafadmin/Constants.py b/tools/wafadmin/Constants.py new file mode 100644 index 000000000..4df0b9b36 --- /dev/null +++ b/tools/wafadmin/Constants.py @@ -0,0 +1,76 @@ +#!/usr/bin/env python +# encoding: utf-8 +# Yinon dot me gmail 2008 + +""" +these constants are somewhat public, try not to mess them + +maintainer: the version number is updated from the top-level wscript file +""" + +# do not touch these three lines, they are updated automatically +HEXVERSION = 0x10509 +WAFVERSION="1.5.9" +WAFREVISION = "6626:6639M" +ABI = 7 + +# permissions +O644 = 420 +O755 = 493 + +MAXJOBS = 99999999 + +CACHE_DIR = 'c4che' +CACHE_SUFFIX = '.cache.py' +DBFILE = '.wafpickle-%d' % ABI +WSCRIPT_FILE = 'wscript' +WSCRIPT_BUILD_FILE = 'wscript_build' +WAF_CONFIG_LOG = 'config.log' +WAF_CONFIG_H = 'config.h' + +SIG_NIL = 'iluvcuteoverload' + +VARIANT = '_VARIANT_' +DEFAULT = 'default' + +SRCDIR = 'srcdir' +BLDDIR = 'blddir' +APPNAME = 'APPNAME' +VERSION = 'VERSION' + +DEFINES = 'defines' +UNDEFINED = () + +BREAK = "break" +CONTINUE = "continue" + +# task scheduler options +JOBCONTROL = "JOBCONTROL" +MAXPARALLEL = "MAXPARALLEL" +NORMAL = "NORMAL" + +# task state +NOT_RUN = 0 +MISSING = 1 +CRASHED = 2 +EXCEPTION = 3 +SKIPPED = 8 +SUCCESS = 9 + +ASK_LATER = -1 +SKIP_ME = -2 +RUN_ME = -3 + + +LOG_FORMAT = "%(asctime)s %(c1)s%(zone)s%(c2)s %(message)s" +HOUR_FORMAT = "%H:%M:%S" + +TEST_OK = True + +CFG_FILES = 'cfg_files' + +# positive '->' install +# negative '<-' uninstall +INSTALL = 1337 +UNINSTALL = -1337 + diff --git a/tools/wafadmin/Environment.py b/tools/wafadmin/Environment.py new file mode 100644 index 000000000..7838bd149 --- /dev/null +++ b/tools/wafadmin/Environment.py @@ -0,0 +1,205 @@ +#!/usr/bin/env python +# encoding: utf-8 +# Thomas Nagy, 2005 (ita) + +"""Environment representation + +There is one gotcha: getitem returns [] if the contents evals to False +This means env['foo'] = {}; print env['foo'] will print [] not {} +""" + +import os, copy, re +import Logs, Options, Utils +from Constants import * +re_imp = re.compile('^(#)*?([^#=]*?)\ =\ (.*?)$', re.M) + +class Environment(object): + """A safe-to-use dictionary, but do not attach functions to it please (break cPickle) + An environment instance can be stored into a file and loaded easily + """ + __slots__ = ("table", "parent") + def __init__(self, filename=None): + self.table = {} + #self.parent = None + + if filename: + self.load(filename) + + def __contains__(self, key): + if key in self.table: return True + try: return self.parent.__contains__(key) + except AttributeError: return False # parent may not exist + + def __str__(self): + keys = set() + cur = self + while cur: + keys.update(cur.table.keys()) + cur = getattr(cur, 'parent', None) + keys = list(keys) + keys.sort() + return "\n".join(["%r %r" % (x, self.__getitem__(x)) for x in keys]) + + def __getitem__(self, key): + try: + while 1: + x = self.table.get(key, None) + if not x is None: + return x + self = self.parent + except AttributeError: + return [] + + def __setitem__(self, key, value): + self.table[key] = value + + def __delitem__(self, key, value): + del self.table[key] + + def set_variant(self, name): + self.table[VARIANT] = name + + def variant(self): + try: + while 1: + x = self.table.get(VARIANT, None) + if not x is None: + return x + self = self.parent + except AttributeError: + return DEFAULT + + def copy(self): + # TODO waf 1.6 rename this method derive, #368 + newenv = Environment() + newenv.parent = self + return newenv + + def detach(self): + """TODO try it + modifying the original env will not change the copy""" + tbl = self.get_merged_dict() + try: + delattr(self, 'parent') + except AttributeError: + pass + else: + keys = tbl.keys() + for x in keys: + tbl[x] = copy.copy(tbl[x]) + self.table = tbl + + def get_flat(self, key): + s = self[key] + if isinstance(s, str): return s + return ' '.join(s) + + def _get_list_value_for_modification(self, key): + """Gets a value that must be a list for further modification. The + list may be modified inplace and there is no need to + "self.table[var] = value" afterwards. + """ + try: + value = self.table[key] + except KeyError: + try: value = self.parent[key] + except AttributeError: value = [] + if isinstance(value, list): + value = value[:] + else: + value = [value] + else: + if not isinstance(value, list): + value = [value] + self.table[key] = value + return value + + def append_value(self, var, value): + current_value = self._get_list_value_for_modification(var) + + if isinstance(value, list): + current_value.extend(value) + else: + current_value.append(value) + + def prepend_value(self, var, value): + current_value = self._get_list_value_for_modification(var) + + if isinstance(value, list): + current_value = value + current_value + # a new list: update the dictionary entry + self.table[var] = current_value + else: + current_value.insert(0, value) + + # prepend unique would be ambiguous + def append_unique(self, var, value): + current_value = self._get_list_value_for_modification(var) + + if isinstance(value, list): + for value_item in value: + if value_item not in current_value: + current_value.append(value_item) + else: + if value not in current_value: + current_value.append(value) + + def get_merged_dict(self): + """compute a merged table""" + table_list = [] + env = self + while 1: + table_list.insert(0, env.table) + try: env = env.parent + except AttributeError: break + merged_table = {} + for table in table_list: + merged_table.update(table) + return merged_table + + def store(self, filename): + "Write the variables into a file" + file = open(filename, 'w') + merged_table = self.get_merged_dict() + keys = list(merged_table.keys()) + keys.sort() + for k in keys: file.write('%s = %r\n' % (k, merged_table[k])) + file.close() + + def load(self, filename): + "Retrieve the variables from a file" + tbl = self.table + code = Utils.readf(filename) + for m in re_imp.finditer(code): + g = m.group + tbl[g(2)] = eval(g(3)) + Logs.debug('env: %s' % str(self.table)) + + def get_destdir(self): + "return the destdir, useful for installing" + if self.__getitem__('NOINSTALL'): return '' + return Options.options.destdir + + def update(self, d): + for k, v in d.iteritems(): + self[k] = v + + + def __getattr__(self, name): + if name in self.__slots__: + return object.__getattr__(self, name) + else: + return self[name] + + def __setattr__(self, name, value): + if name in self.__slots__: + object.__setattr__(self, name, value) + else: + self[name] = value + + def __detattr__(self, name): + if name in self.__slots__: + object.__detattr__(self, name) + else: + del self[name] + diff --git a/tools/wafadmin/Logs.py b/tools/wafadmin/Logs.py new file mode 100644 index 000000000..0d55e1fe2 --- /dev/null +++ b/tools/wafadmin/Logs.py @@ -0,0 +1,128 @@ +#!/usr/bin/env python +# encoding: utf-8 +# Thomas Nagy, 2005 (ita) + +import os, re, logging, traceback, sys +from Constants import * + +zones = '' +verbose = 0 + +colors_lst = { +'USE' : True, +'BOLD' :'\x1b[01;1m', +'RED' :'\x1b[01;91m', +'GREEN' :'\x1b[32m', +'YELLOW':'\x1b[33m', +'PINK' :'\x1b[35m', +'BLUE' :'\x1b[01;34m', +'CYAN' :'\x1b[36m', +'NORMAL':'\x1b[0m', +'cursor_on' :'\x1b[?25h', +'cursor_off' :'\x1b[?25l', +} + +got_tty = not os.environ.get('TERM', 'dumb') in ['dumb', 'emacs'] +if got_tty: + try: + got_tty = sys.stderr.isatty() + except AttributeError: + got_tty = False + +import Utils + +if not got_tty or sys.platform == 'win32' or 'NOCOLOR' in os.environ: + colors_lst['USE'] = False + +def get_color(cl): + if not colors_lst['USE']: return '' + return colors_lst.get(cl, '') + +class foo(object): + def __getattr__(self, a): + return get_color(a) + def __call__(self, a): + return get_color(a) + +colors = foo() + +re_log = re.compile(r'(\w+): (.*)', re.M) +class log_filter(logging.Filter): + def __init__(self, name=None): + pass + + def filter(self, rec): + rec.c1 = colors.PINK + rec.c2 = colors.NORMAL + rec.zone = rec.module + if rec.levelno >= logging.INFO: + if rec.levelno >= logging.ERROR: + rec.c1 = colors.RED + elif rec.levelno >= logging.WARNING: + rec.c1 = colors.YELLOW + else: + rec.c1 = colors.GREEN + return True + + zone = '' + m = re_log.match(rec.msg) + if m: + zone = rec.zone = m.group(1) + rec.msg = m.group(2) + + if zones: + return getattr(rec, 'zone', '') in zones or '*' in zones + elif not verbose > 2: + return False + return True + +class formatter(logging.Formatter): + def __init__(self): + logging.Formatter.__init__(self, LOG_FORMAT, HOUR_FORMAT) + + def format(self, rec): + if rec.levelno >= logging.WARNING or rec.levelno == logging.INFO: + try: + return '%s%s%s' % (rec.c1, rec.msg.decode('utf-8'), rec.c2) + except: + return rec.c1+rec.msg+rec.c2 + return logging.Formatter.format(self, rec) + +def debug(msg): + if verbose: + # FIXME why does it eat the newlines???? + msg = msg.replace('\n', ' ') + logging.debug(msg) + +def error(msg): + logging.error(msg) + if verbose > 1: + if isinstance(msg, Utils.WafError): + st = msg.stack + else: + st = traceback.extract_stack() + if st: + st = st[:-1] + buf = [] + for filename, lineno, name, line in st: + buf.append(' File "%s", line %d, in %s' % (filename, lineno, name)) + if line: + buf.append(' %s' % line.strip()) + if buf: logging.error("\n".join(buf)) + +warn = logging.warn +info = logging.info + +def init_log(): + log = logging.getLogger() + log.handlers = [] + log.filters = [] + hdlr = logging.StreamHandler() + hdlr.setFormatter(formatter()) + log.addHandler(hdlr) + log.addFilter(log_filter()) + log.setLevel(logging.DEBUG) + +# may be initialized more than once +init_log() + diff --git a/tools/wafadmin/Node.py b/tools/wafadmin/Node.py new file mode 100644 index 000000000..ba17ab434 --- /dev/null +++ b/tools/wafadmin/Node.py @@ -0,0 +1,660 @@ +#!/usr/bin/env python +# encoding: utf-8 +# Thomas Nagy, 2005 (ita) + +""" +Node: filesystem structure, contains lists of nodes + +IMPORTANT: +1. Each file/folder is represented by exactly one node + +2. Most would-be class properties are stored in Build: nodes to depend on, signature, flags, .. +unused class members increase the .wafpickle file size sensibly with lots of objects + +3. The build is launched from the top of the build dir (for example, in _build_/) + +4. Node should not be instantiated directly. +Each instance of Build.BuildContext has a Node sublass. +(aka: 'Nodu', see BuildContext initializer) +The BuildContext is referenced here as self.__class__.bld +Its Node class is referenced here as self.__class__ + +The public and advertised apis are the following: +${TGT} -> dir/to/file.ext +${TGT[0].base()} -> dir/to/file +${TGT[0].dir(env)} -> dir/to +${TGT[0].file()} -> file.ext +${TGT[0].file_base()} -> file +${TGT[0].suffix()} -> .ext +${TGT[0].abspath(env)} -> /path/to/dir/to/file.ext + +""" + +import os, sys, fnmatch, re +import Utils + +UNDEFINED = 0 +DIR = 1 +FILE = 2 +BUILD = 3 + +type_to_string = {UNDEFINED: "unk", DIR: "dir", FILE: "src", BUILD: "bld"} + +# These fnmatch expressions are used by default to prune the directory tree +# while doing the recursive traversal in the find_iter method of the Node class. +prune_pats = '.git .bzr .hg .svn _MTN _darcs CVS SCCS'.split() + +# These fnmatch expressions are used by default to exclude files and dirs +# while doing the recursive traversal in the find_iter method of the Node class. +exclude_pats = prune_pats + '*~ #*# .#* %*% ._* .gitignore .cvsignore vssver.scc .DS_Store'.split() + +# These Utils.jar_regexp expressions are used by default to exclude files and dirs and also prune the directory tree +# while doing the recursive traversal in the ant_glob method of the Node class. +exclude_regs = ''' +**/*~ +**/#*# +**/.#* +**/%*% +**/._* +**/CVS +**/CVS/** +**/.cvsignore +**/SCCS +**/SCCS/** +**/vssver.scc +**/.svn +**/.svn/** +**/.git +**/.git/** +**/.gitignore +**/.bzr +**/.bzr/** +**/.hg +**/.hg/** +**/_MTN +**/_MTN/** +**/_darcs +**/_darcs/** +**/.DS_Store''' + +class Node(object): + __slots__ = ("name", "parent", "id", "childs") + def __init__(self, name, parent, node_type = UNDEFINED): + self.name = name + self.parent = parent + + # assumption: one build object at a time + self.__class__.bld.id_nodes += 4 + self.id = self.__class__.bld.id_nodes + node_type + + if node_type == DIR: self.childs = {} + + # We do not want to add another type attribute (memory) + # use the id to find out: type = id & 3 + # for setting: new type = type + x - type & 3 + + if parent and name in parent.childs: + raise Utils.WafError('node %s exists in the parent files %r already' % (name, parent)) + + if parent: parent.childs[name] = self + + def __setstate__(self, data): + if len(data) == 4: + (self.parent, self.name, self.id, self.childs) = data + else: + (self.parent, self.name, self.id) = data + + def __getstate__(self): + if getattr(self, 'childs', None) is None: + return (self.parent, self.name, self.id) + else: + return (self.parent, self.name, self.id, self.childs) + + def __str__(self): + if not self.parent: return '' + return "%s://%s" % (type_to_string[self.id & 3], self.abspath()) + + def __repr__(self): + return self.__str__() + + def __hash__(self): + "expensive, make certain it is not used" + raise Utils.WafError('nodes, you are doing it wrong') + + def __copy__(self): + "nodes are not supposed to be copied" + raise Utils.WafError('nodes are not supposed to be cloned') + + def get_type(self): + return self.id & 3 + + def set_type(self, t): + self.id = self.id + t - self.id & 3 + + def dirs(self): + return [x for x in self.childs.values() if x.id & 3 == DIR] + + def files(self): + return [x for x in self.childs.values() if x.id & 3 == FILE] + + def get_dir(self, name, default=None): + node = self.childs.get(name, None) + if not node or node.id & 3 != DIR: return default + return node + + def get_file(self, name, default=None): + node = self.childs.get(name, None) + if not node or node.id & 3 != FILE: return default + return node + + def get_build(self, name, default=None): + node = self.childs.get(name, None) + if not node or node.id & 3 != BUILD: return default + return node + + def find_resource(self, lst): + "Find an existing input file: either a build node declared previously or a source node" + if isinstance(lst, str): + lst = Utils.split_path(lst) + + if len(lst) == 1: + parent = self + else: + parent = self.find_dir(lst[:-1]) + if not parent: return None + self.__class__.bld.rescan(parent) + + name = lst[-1] + node = parent.childs.get(name, None) + if node: + tp = node.id & 3 + if tp == FILE or tp == BUILD: + return node + else: + return None + + tree = self.__class__.bld + if not name in tree.cache_dir_contents[parent.id]: + return None + + path = parent.abspath() + os.sep + name + try: + st = Utils.h_file(path) + except IOError: + return None + + child = self.__class__(name, parent, FILE) + tree.node_sigs[0][child.id] = st + return child + + def find_or_declare(self, lst): + "Used for declaring a build node representing a file being built" + if isinstance(lst, str): + lst = Utils.split_path(lst) + + if len(lst) == 1: + parent = self + else: + parent = self.find_dir(lst[:-1]) + if not parent: return None + self.__class__.bld.rescan(parent) + + name = lst[-1] + node = parent.childs.get(name, None) + if node: + tp = node.id & 3 + if tp != BUILD: + raise Utils.WafError("find_or_declare returns a build node, not a source nor a directory %r" % lst) + return node + node = self.__class__(name, parent, BUILD) + return node + + def find_dir(self, lst): + "search a folder in the filesystem" + + if isinstance(lst, str): + lst = Utils.split_path(lst) + + current = self + for name in lst: + self.__class__.bld.rescan(current) + prev = current + + if not current.parent and name == current.name: + continue + elif not name: + continue + elif name == '.': + continue + elif name == '..': + current = current.parent or current + else: + current = prev.childs.get(name, None) + if current is None: + dir_cont = self.__class__.bld.cache_dir_contents + if prev.id in dir_cont and name in dir_cont[prev.id]: + if not prev.name: + if os.sep == '/': + # cygwin //machine/share + dirname = os.sep + name + else: + # windows c: + dirname = name + else: + # regular path + dirname = prev.abspath() + os.sep + name + if not os.path.isdir(dirname): + return None + current = self.__class__(name, prev, DIR) + elif (not prev.name and len(name) == 2 and name[1] == ':') or name.startswith('\\\\'): + # drive letter or \\ path for windows + current = self.__class__(name, prev, DIR) + else: + return None + else: + if current.id & 3 != DIR: + return None + return current + + # FIXME: remove in waf 1.6 ? + def ensure_dir_node_from_path(self, lst): + "used very rarely, force the construction of a branch of node instance for representing folders" + + if isinstance(lst, str): + lst = Utils.split_path(lst) + + current = self + for name in lst: + if not name: + continue + elif name == '.': + continue + elif name == '..': + current = current.parent or current + else: + prev = current + current = prev.childs.get(name, None) + if current is None: + current = self.__class__(name, prev, DIR) + return current + + # FIXME: remove in waf 1.6 + def exclusive_build_node(self, path): + """ + create a hierarchy in the build dir (no source folders) for ill-behaving compilers + the node is not hashed, so you must do it manually + + after declaring such a node, find_dir and find_resource should work as expected + """ + lst = Utils.split_path(path) + name = lst[-1] + if len(lst) > 1: + parent = None + try: + parent = self.find_dir(lst[:-1]) + except OSError: + pass + if not parent: + parent = self.ensure_dir_node_from_path(lst[:-1]) + self.__class__.bld.rescan(parent) + else: + try: + self.__class__.bld.rescan(parent) + except OSError: + pass + else: + parent = self + + node = parent.childs.get(name, None) + if not node: + node = self.__class__(name, parent, BUILD) + + return node + + def path_to_parent(self, parent): + "path relative to a direct ancestor, as string" + lst = [] + p = self + h1 = parent.height() + h2 = p.height() + while h2 > h1: + h2 -= 1 + lst.append(p.name) + p = p.parent + if lst: + lst.reverse() + ret = os.path.join(*lst) + else: + ret = '' + return ret + + def find_ancestor(self, node): + "find a common ancestor for two nodes - for the shortest path in hierarchy" + dist = self.height() - node.height() + if dist < 0: return node.find_ancestor(self) + # now the real code + cand = self + while dist > 0: + cand = cand.parent + dist -= 1 + if cand == node: return cand + cursor = node + while cand.parent: + cand = cand.parent + cursor = cursor.parent + if cand == cursor: return cand + + def relpath_gen(self, from_node): + "string representing a relative path between self to another node" + + if self == from_node: return '.' + if from_node.parent == self: return '..' + + # up_path is '../../../' and down_path is 'dir/subdir/subdir/file' + ancestor = self.find_ancestor(from_node) + lst = [] + cand = self + while not cand.id == ancestor.id: + lst.append(cand.name) + cand = cand.parent + cand = from_node + while not cand.id == ancestor.id: + lst.append('..') + cand = cand.parent + lst.reverse() + return os.sep.join(lst) + + def nice_path(self, env=None): + "printed in the console, open files easily from the launch directory" + tree = self.__class__.bld + ln = tree.launch_node() + + if self.id & 3 == FILE: return self.relpath_gen(ln) + else: return os.path.join(tree.bldnode.relpath_gen(ln), env.variant(), self.relpath_gen(tree.srcnode)) + + def is_child_of(self, node): + "does this node belong to the subtree node" + p = self + diff = self.height() - node.height() + while diff > 0: + diff -= 1 + p = p.parent + return p.id == node.id + + def variant(self, env): + "variant, or output directory for this node, a source has for variant 0" + if not env: return 0 + elif self.id & 3 == FILE: return 0 + else: return env.variant() + + def height(self): + "amount of parents" + # README a cache can be added here if necessary + d = self + val = -1 + while d: + d = d.parent + val += 1 + return val + + # helpers for building things + + def abspath(self, env=None): + """ + absolute path + @param env [Environment]: + * obligatory for build nodes: build/variant/src/dir/bar.o + * optional for dirs: get either src/dir or build/variant/src/dir + * excluded for source nodes: src/dir/bar.c + + Instead of computing the absolute path each time again, + store the already-computed absolute paths in one of (variants+1) dictionaries: + bld.cache_node_abspath[0] holds absolute paths for source nodes. + bld.cache_node_abspath[variant] holds the absolute path for the build nodes + which reside in the variant given by env. + """ + ## absolute path - hot zone, so do not touch + + # less expensive + variant = (env and (self.id & 3 != FILE) and env.variant()) or 0 + + ret = self.__class__.bld.cache_node_abspath[variant].get(self.id, None) + if ret: return ret + + if not variant: + # source directory + if not self.parent: + val = os.sep == '/' and os.sep or '' + elif not self.parent.name: # root + val = (os.sep == '/' and os.sep or '') + self.name + else: + val = self.parent.abspath() + os.sep + self.name + else: + # build directory + val = os.sep.join((self.__class__.bld.bldnode.abspath(), variant, self.path_to_parent(self.__class__.bld.srcnode))) + self.__class__.bld.cache_node_abspath[variant][self.id] = val + return val + + def change_ext(self, ext): + "node of the same path, but with a different extension - hot zone so do not touch" + name = self.name + k = name.rfind('.') + if k >= 0: + name = name[:k] + ext + else: + name = name + ext + + return self.parent.find_or_declare([name]) + + def src_dir(self, env): + "src path without the file name" + return self.parent.srcpath(env) + + def bld_dir(self, env): + "build path without the file name" + return self.parent.bldpath(env) + + def bld_base(self, env): + "build path without the extension: src/dir/foo(.cpp)" + s = os.path.splitext(self.name)[0] + return os.path.join(self.bld_dir(env), s) + + def bldpath(self, env=None): + "path seen from the build dir default/src/foo.cpp" + if self.id & 3 == FILE: + return self.relpath_gen(self.__class__.bld.bldnode) + if self.path_to_parent(self.__class__.bld.srcnode) is not '': + return os.path.join(env.variant(), self.path_to_parent(self.__class__.bld.srcnode)) + return env.variant() + + def srcpath(self, env=None): + "path in the srcdir from the build dir ../src/foo.cpp" + if self.id & 3 == BUILD: + return self.bldpath(env) + return self.relpath_gen(self.__class__.bld.bldnode) + + def read(self, env): + "get the contents of a file, it is not used anywhere for the moment" + return Utils.readf(self.abspath(env)) + + def dir(self, env): + "scons-like" + return self.parent.abspath(env) + + def file(self): + "scons-like" + return self.name + + def file_base(self): + "scons-like" + return os.path.splitext(self.name)[0] + + def suffix(self): + "scons-like - hot zone so do not touch" + k = max(0, self.name.rfind('.')) + return self.name[k:] + + def find_iter_impl(self, src=True, bld=True, dir=True, accept_name=None, is_prune=None, maxdepth=25): + "find nodes in the filesystem hierarchy, try to instanciate the nodes passively" + bld_ctx = self.__class__.bld + bld_ctx.rescan(self) + for name in bld_ctx.cache_dir_contents[self.id]: + if accept_name(self, name): + node = self.find_resource(name) + if node: + if src and node.id & 3 == FILE: + yield node + else: + node = self.find_dir(name) + if node and node.id != bld_ctx.bldnode.id: + if dir: + yield node + if not is_prune(self, name): + if maxdepth: + for k in node.find_iter_impl(src, bld, dir, accept_name, is_prune, maxdepth=maxdepth - 1): + yield k + else: + if not is_prune(self, name): + node = self.find_resource(name) + if not node: + # not a file, it is a dir + node = self.find_dir(name) + if node and node.id != bld_ctx.bldnode.id: + if maxdepth: + for k in node.find_iter_impl(src, bld, dir, accept_name, is_prune, maxdepth=maxdepth - 1): + yield k + + if bld: + for node in self.childs.values(): + if node.id == bld_ctx.bldnode.id: + continue + if node.id & 3 == BUILD: + if accept_name(self, node.name): + yield node + raise StopIteration + + def find_iter(self, in_pat=['*'], ex_pat=exclude_pats, prune_pat=prune_pats, src=True, bld=True, dir=False, maxdepth=25, flat=False): + "find nodes recursively, this returns everything but folders by default" + + if not (src or bld or dir): + raise StopIteration + + if self.id & 3 != DIR: + raise StopIteration + + in_pat = Utils.to_list(in_pat) + ex_pat = Utils.to_list(ex_pat) + prune_pat = Utils.to_list(prune_pat) + + def accept_name(node, name): + for pat in ex_pat: + if fnmatch.fnmatchcase(name, pat): + return False + for pat in in_pat: + if fnmatch.fnmatchcase(name, pat): + return True + return False + + def is_prune(node, name): + for pat in prune_pat: + if fnmatch.fnmatchcase(name, pat): + return True + return False + + ret = self.find_iter_impl(src, bld, dir, accept_name, is_prune, maxdepth=maxdepth) + if flat: + return " ".join([x.relpath_gen(self) for x in ret]) + + return ret + + def ant_glob(self, *k, **kw): + + src=kw.get('src', 1) + bld=kw.get('bld', 1) + dir=kw.get('dir', 0) + excl = kw.get('excl', exclude_regs) + incl = k and k[0] or kw.get('incl', '**') + + def to_pat(s): + lst = Utils.to_list(s) + ret = [] + for x in lst: + x = x.replace('//', '/') + if x.endswith('/'): + x += '**' + lst2 = x.split('/') + accu = [] + for k in lst2: + if k == '**': + accu.append(k) + else: + k = k.replace('.', '[.]').replace('*', '.*').replace('?', '.') + k = '^%s$' % k + #print "pattern", k + accu.append(re.compile(k)) + ret.append(accu) + return ret + + def filtre(name, nn): + ret = [] + for lst in nn: + if not lst: + pass + elif lst[0] == '**': + ret.append(lst) + if len(lst) > 1: + if lst[1].match(name): + ret.append(lst[2:]) + else: + ret.append([]) + elif lst[0].match(name): + ret.append(lst[1:]) + return ret + + def accept(name, pats): + nacc = filtre(name, pats[0]) + nrej = filtre(name, pats[1]) + if [] in nrej: + nacc = [] + return [nacc, nrej] + + def ant_iter(nodi, maxdepth=25, pats=[]): + nodi.__class__.bld.rescan(nodi) + for name in nodi.__class__.bld.cache_dir_contents[nodi.id]: + npats = accept(name, pats) + if npats and npats[0]: + accepted = [] in npats[0] + #print accepted, nodi, name + + node = nodi.find_resource(name) + if node and accepted: + if src and node.id & 3 == FILE: + yield node + else: + node = nodi.find_dir(name) + if node and node.id != nodi.__class__.bld.bldnode.id: + if accepted and dir: + yield node + if maxdepth: + for k in ant_iter(node, maxdepth=maxdepth - 1, pats=npats): + yield k + if bld: + for node in nodi.childs.values(): + if node.id == nodi.__class__.bld.bldnode.id: + continue + if node.id & 3 == BUILD: + npats = accept(node.name, pats) + if npats and npats[0] and [] in npats[0]: + yield node + raise StopIteration + + ret = [x for x in ant_iter(self, pats=[to_pat(incl), to_pat(excl)])] + + if kw.get('flat', True): + return " ".join([x.relpath_gen(self) for x in ret]) + + return ret + +class Nodu(Node): + pass + diff --git a/tools/wafadmin/Options.py b/tools/wafadmin/Options.py new file mode 100644 index 000000000..bbb53a8c6 --- /dev/null +++ b/tools/wafadmin/Options.py @@ -0,0 +1,273 @@ +#!/usr/bin/env python +# encoding: utf-8 +# Scott Newton, 2005 (scottn) +# Thomas Nagy, 2006 (ita) + +"Custom command-line options" + +import os, sys, imp, types, tempfile, optparse +import Logs, Utils +from Constants import * + +cmds = 'distclean configure build install clean uninstall check dist distcheck'.split() + +# TODO remove in waf 1.6 the following two +commands = {} +is_install = False + +options = {} +arg_line = [] +launch_dir = '' +tooldir = '' +lockfile = os.environ.get('WAFLOCK', '.lock-wscript') +try: cache_global = os.path.abspath(os.environ['WAFCACHE']) +except KeyError: cache_global = '' +platform = Utils.unversioned_sys_platform() +conf_file = 'conf-runs-%s-%d.pickle' % (platform, ABI) + +# Such a command-line should work: JOBS=4 PREFIX=/opt/ DESTDIR=/tmp/ahoj/ waf configure +default_prefix = os.environ.get('PREFIX') +if not default_prefix: + if platform == 'win32': default_prefix = tempfile.gettempdir() + else: default_prefix = '/usr/local/' + +default_jobs = os.environ.get('JOBS', -1) +if default_jobs < 1: + try: + if 'SC_NPROCESSORS_ONLN' in os.sysconf_names: + default_jobs = os.sysconf('SC_NPROCESSORS_ONLN') + else: + default_jobs = int(Utils.cmd_output(['sysctl', '-n', 'hw.ncpu'])) + except: + if os.name == 'java': # platform.system() == 'Java' + from java.lang import Runtime + default_jobs = Runtime.getRuntime().availableProcessors() + else: + # environment var defined on win32 + default_jobs = int(os.environ.get('NUMBER_OF_PROCESSORS', 1)) + +default_destdir = os.environ.get('DESTDIR', '') + +def get_usage(self): + cmds_str = [] + module = Utils.g_module + if module: + # create the help messages for commands + tbl = module.__dict__ + keys = list(tbl.keys()) + keys.sort() + + if 'build' in tbl: + if not module.build.__doc__: + module.build.__doc__ = 'builds the project' + if 'configure' in tbl: + if not module.configure.__doc__: + module.configure.__doc__ = 'configures the project' + + ban = ['set_options', 'init', 'shutdown'] + + optlst = [x for x in keys if not x in ban + and type(tbl[x]) is type(parse_args_impl) + and tbl[x].__doc__ + and not x.startswith('_')] + + just = max([len(x) for x in optlst]) + + for x in optlst: + cmds_str.append(' %s: %s' % (x.ljust(just), tbl[x].__doc__)) + ret = '\n'.join(cmds_str) + else: + ret = ' '.join(cmds) + return '''waf [command] [options] + +Main commands (example: ./waf build -j4) +%s +''' % ret + + +setattr(optparse.OptionParser, 'get_usage', get_usage) + +def create_parser(module=None): + Logs.debug('options: create_parser is called') + parser = optparse.OptionParser(conflict_handler="resolve", version = 'waf %s (%s)' % (WAFVERSION, WAFREVISION)) + + parser.formatter.width = Utils.get_term_cols() + p = parser.add_option + + p('-j', '--jobs', + type = 'int', + default = default_jobs, + help = 'amount of parallel jobs (%r)' % default_jobs, + dest = 'jobs') + + p('-k', '--keep', + action = 'store_true', + default = False, + help = 'keep running happily on independent task groups', + dest = 'keep') + + p('-v', '--verbose', + action = 'count', + default = 0, + help = 'verbosity level -v -vv or -vvv [default: 0]', + dest = 'verbose') + + p('--nocache', + action = 'store_true', + default = False, + help = 'ignore the WAFCACHE (if set)', + dest = 'nocache') + + p('--zones', + action = 'store', + default = '', + help = 'debugging zones (task_gen, deps, tasks, etc)', + dest = 'zones') + + p('-p', '--progress', + action = 'count', + default = 0, + help = '-p: progress bar; -pp: ide output', + dest = 'progress_bar') + + p('--targets', + action = 'store', + default = '', + help = 'build given task generators, e.g. "target1,target2"', + dest = 'compile_targets') + + gr = optparse.OptionGroup(parser, 'configuration options') + parser.add_option_group(gr) + gr.add_option('-b', '--blddir', + action = 'store', + default = '', + help = 'build dir for the project (configuration)', + dest = 'blddir') + gr.add_option('-s', '--srcdir', + action = 'store', + default = '', + help = 'src dir for the project (configuration)', + dest = 'srcdir') + gr.add_option('--prefix', + help = 'installation prefix (configuration) [default: %r]' % default_prefix, + default = default_prefix, + dest = 'prefix') + + gr = optparse.OptionGroup(parser, 'installation options') + parser.add_option_group(gr) + gr.add_option('--destdir', + help = 'installation root [default: %r]' % default_destdir, + default = default_destdir, + dest = 'destdir') + gr.add_option('-f', '--force', + action = 'store_true', + default = False, + help = 'force file installation', + dest = 'force') + + return parser + +def parse_args_impl(parser, _args=None): + global options, commands, arg_line + (options, args) = parser.parse_args(args=_args) + + arg_line = args + #arg_line = args[:] # copy + + # By default, 'waf' is equivalent to 'waf build' + commands = {} + for var in cmds: commands[var] = 0 + if not args: + commands['build'] = 1 + args.append('build') + + # Parse the command arguments + for arg in args: + commands[arg] = True + + # the check thing depends on the build + if 'check' in args: + idx = args.index('check') + try: + bidx = args.index('build') + if bidx > idx: + raise ValueError('build before check') + except ValueError, e: + args.insert(idx, 'build') + + if args[0] != 'init': + args.insert(0, 'init') + + # TODO -k => -j0 + if options.keep: options.jobs = 1 + if options.jobs < 1: options.jobs = 1 + + if 'install' in sys.argv or 'uninstall' in sys.argv: + # absolute path only if set + options.destdir = options.destdir and os.path.abspath(os.path.expanduser(options.destdir)) + + Logs.verbose = options.verbose + Logs.init_log() + + if options.zones: + Logs.zones = options.zones.split(',') + if not Logs.verbose: Logs.verbose = 1 + elif Logs.verbose > 0: + Logs.zones = ['runner'] + if Logs.verbose > 2: + Logs.zones = ['*'] + +# TODO waf 1.6 +# 1. rename the class to OptionsContext +# 2. instead of a class attribute, use a module (static 'parser') +# 3. parse_args_impl was made in times when we did not know about binding new methods to classes + +class Handler(Utils.Context): + """loads wscript modules in folders for adding options + This class should be named 'OptionsContext' + A method named 'recurse' is bound when used by the module Scripting""" + + parser = None + # make it possible to access the reference, like Build.bld + + def __init__(self, module=None): + self.parser = create_parser(module) + self.cwd = os.getcwd() + Handler.parser = self + + def add_option(self, *k, **kw): + self.parser.add_option(*k, **kw) + + def add_option_group(self, *k, **kw): + return self.parser.add_option_group(*k, **kw) + + def get_option_group(self, opt_str): + return self.parser.get_option_group(opt_str) + + def sub_options(self, *k, **kw): + if not k: raise Utils.WscriptError('folder expected') + self.recurse(k[0], name='set_options') + + def tool_options(self, *k, **kw): + Utils.python_24_guard() + + if not k[0]: + raise Utils.WscriptError('invalid tool_options call %r %r' % (k, kw)) + tools = Utils.to_list(k[0]) + + # TODO waf 1.6 remove the global variable tooldir + path = Utils.to_list(kw.get('tdir', kw.get('tooldir', tooldir))) + + for tool in tools: + tool = tool.replace('++', 'xx') + module = Utils.load_tool(tool, path) + try: + fun = module.set_options + except AttributeError: + pass + else: + fun(kw.get('option_group', self)) + + def parse_args(self, args=None): + parse_args_impl(self.parser, args) + diff --git a/tools/wafadmin/Runner.py b/tools/wafadmin/Runner.py new file mode 100644 index 000000000..8a5b6b9a7 --- /dev/null +++ b/tools/wafadmin/Runner.py @@ -0,0 +1,219 @@ +#!/usr/bin/env python +# encoding: utf-8 +# Thomas Nagy, 2005-2008 (ita) + +"Execute the tasks" + +import sys, random, time, threading, traceback +try: from Queue import Queue +except ImportError: from queue import Queue +import Build, Utils, Logs, Options +from Logs import debug, error +from Constants import * + +GAP = 15 + +run_old = threading.Thread.run +def run(*args, **kwargs): + try: + run_old(*args, **kwargs) + except (KeyboardInterrupt, SystemExit): + raise + except: + sys.excepthook(*sys.exc_info()) +threading.Thread.run = run + +class TaskConsumer(threading.Thread): + def __init__(self, m): + threading.Thread.__init__(self) + self.setDaemon(1) + self.master = m + self.start() + + def run(self): + try: + self.loop() + except: + pass + + def loop(self): + m = self.master + while 1: + tsk = m.ready.get() + if m.stop: + m.out.put(tsk) + continue + + try: + tsk.generator.bld.printout(tsk.display()) + if tsk.__class__.stat: ret = tsk.__class__.stat(tsk) + # actual call to task's run() function + else: ret = tsk.call_run() + except Exception, e: + tsk.err_msg = Utils.ex_stack() + tsk.hasrun = EXCEPTION + + # TODO cleanup + m.error_handler(tsk) + m.out.put(tsk) + continue + + if ret: + tsk.err_code = ret + tsk.hasrun = CRASHED + else: + try: + tsk.post_run() + except Utils.WafError: + pass + except Exception: + tsk.err_msg = Utils.ex_stack() + tsk.hasrun = EXCEPTION + else: + tsk.hasrun = SUCCESS + if tsk.hasrun != SUCCESS: + m.error_handler(tsk) + + m.out.put(tsk) + +class Parallel(object): + """ + keep the consumer threads busy, and avoid consuming cpu cycles + when no more tasks can be added (end of the build, etc) + """ + def __init__(self, bld, j=2): + + # number of consumers + self.numjobs = j + + self.manager = bld.task_manager + self.manager.current_group = 0 + + self.total = self.manager.total() + + # tasks waiting to be processed - IMPORTANT + self.outstanding = [] + self.maxjobs = MAXJOBS + + # tasks that are awaiting for another task to complete + self.frozen = [] + + # tasks waiting to be run by the consumers + self.ready = Queue(0) + self.out = Queue(0) + + self.count = 0 # tasks not in the producer area + + self.processed = 1 # progress indicator + + self.consumers = None # the consumer threads, created lazily + + self.stop = False # error condition to stop the build + self.error = False # error flag + + def get_next(self): + "override this method to schedule the tasks in a particular order" + if not self.outstanding: + return None + return self.outstanding.pop(0) + + def postpone(self, tsk): + "override this method to schedule the tasks in a particular order" + # TODO consider using a deque instead + if random.randint(0, 1): + self.frozen.insert(0, tsk) + else: + self.frozen.append(tsk) + + def refill_task_list(self): + "called to set the next group of tasks" + + while self.count > self.numjobs + GAP or self.count >= self.maxjobs: + self.get_out() + + while not self.outstanding: + if self.count: + self.get_out() + + if self.frozen: + self.outstanding += self.frozen + self.frozen = [] + elif not self.count: + (jobs, tmp) = self.manager.get_next_set() + if jobs != None: self.maxjobs = jobs + if tmp: self.outstanding += tmp + break + + def get_out(self): + "the tasks that are put to execute are all collected using get_out" + ret = self.out.get() + self.manager.add_finished(ret) + if not self.stop and getattr(ret, 'more_tasks', None): + self.outstanding += ret.more_tasks + self.total += len(ret.more_tasks) + self.count -= 1 + + def error_handler(self, tsk): + "by default, errors make the build stop (not thread safe so be careful)" + if not Options.options.keep: + self.stop = True + self.error = True + + def start(self): + "execute the tasks" + + while not self.stop: + + self.refill_task_list() + + # consider the next task + tsk = self.get_next() + if not tsk: + if self.count: + # tasks may add new ones after they are run + continue + else: + # no tasks to run, no tasks running, time to exit + break + + if tsk.hasrun: + # if the task is marked as "run", just skip it + self.processed += 1 + self.manager.add_finished(tsk) + continue + + try: + st = tsk.runnable_status() + except Exception, e: + tsk.err_msg = Utils.ex_stack() + tsk.hasrun = EXCEPTION + self.processed += 1 + self.error_handler(tsk) + self.manager.add_finished(tsk) + continue + + if st == ASK_LATER: + self.postpone(tsk) + elif st == SKIP_ME: + self.processed += 1 + tsk.hasrun = SKIPPED + self.manager.add_finished(tsk) + else: + # run me: put the task in ready queue + tsk.position = (self.processed, self.total) + self.count += 1 + self.ready.put(tsk) + self.processed += 1 + + # create the consumer threads only if there is something to consume + if not self.consumers: + self.consumers = [TaskConsumer(self) for i in xrange(self.numjobs)] + + # self.count represents the tasks that have been made available to the consumer threads + # collect all the tasks after an error else the message may be incomplete + while self.error and self.count: + self.get_out() + + #print loop + assert (self.count == 0 or self.stop) + diff --git a/tools/wafadmin/Scripting.py b/tools/wafadmin/Scripting.py new file mode 100644 index 000000000..0aee26d2a --- /dev/null +++ b/tools/wafadmin/Scripting.py @@ -0,0 +1,561 @@ +#!/usr/bin/env python +# encoding: utf-8 +# Thomas Nagy, 2005 (ita) + +"Module called for configuring, compiling and installing targets" + +import os, sys, shutil, traceback, datetime, inspect, errno + +import Utils, Configure, Build, Logs, Options, Environment, Task +from Logs import error, warn, info +from Constants import * + +g_gz = 'bz2' +commands = [] + +def prepare_impl(t, cwd, ver, wafdir): + Options.tooldir = [t] + Options.launch_dir = cwd + + # some command-line options can be processed immediately + if '--version' in sys.argv: + opt_obj = Options.Handler() + opt_obj.curdir = cwd + opt_obj.parse_args() + sys.exit(0) + + # now find the wscript file + msg1 = 'Waf: Please run waf from a directory containing a file named "%s" or run distclean' % WSCRIPT_FILE + + # in theory projects can be configured in a gcc manner: + # mkdir build && cd build && ../waf configure && ../waf + build_dir_override = None + candidate = None + + lst = os.listdir(cwd) + + search_for_candidate = True + if WSCRIPT_FILE in lst: + candidate = cwd + + elif 'configure' in sys.argv and not WSCRIPT_BUILD_FILE in lst: + # gcc-like configuration + calldir = os.path.abspath(os.path.dirname(sys.argv[0])) + if WSCRIPT_FILE in os.listdir(calldir): + candidate = calldir + search_for_candidate = False + else: + error('arg[0] directory does not contain a wscript file') + sys.exit(1) + build_dir_override = cwd + + # climb up to find a script if it is not found + while search_for_candidate: + if len(cwd) <= 3: + break # stop at / or c: + dirlst = os.listdir(cwd) + if WSCRIPT_FILE in dirlst: + candidate = cwd + if 'configure' in sys.argv and candidate: + break + if Options.lockfile in dirlst: + env = Environment.Environment() + env.load(os.path.join(cwd, Options.lockfile)) + try: + os.stat(env['cwd']) + except: + candidate = cwd + else: + candidate = env['cwd'] + break + cwd = os.path.dirname(cwd) # climb up + + if not candidate: + # check if the user only wanted to display the help + if '-h' in sys.argv or '--help' in sys.argv: + warn('No wscript file found: the help message may be incomplete') + opt_obj = Options.Handler() + opt_obj.curdir = cwd + opt_obj.parse_args() + else: + error(msg1) + sys.exit(0) + + # We have found wscript, but there is no guarantee that it is valid + try: + os.chdir(candidate) + except OSError: + raise Utils.WafError("the folder %r is unreadable" % candidate) + + # define the main module containing the functions init, shutdown, .. + Utils.set_main_module(os.path.join(candidate, WSCRIPT_FILE)) + + if build_dir_override: + d = getattr(Utils.g_module, BLDDIR, None) + if d: + # test if user has set the blddir in wscript. + msg = ' Overriding build directory %s with %s' % (d, build_dir_override) + warn(msg) + Utils.g_module.blddir = build_dir_override + + # bind a few methods and classes by default + + def set_def(obj, name=''): + n = name or obj.__name__ + if not n in Utils.g_module.__dict__: + setattr(Utils.g_module, n, obj) + + for k in [dist, distclean, distcheck, clean, install, uninstall]: + set_def(k) + + set_def(Configure.ConfigurationContext, 'configure_context') + + for k in ['build', 'clean', 'install', 'uninstall']: + set_def(Build.BuildContext, k + '_context') + + # now parse the options from the user wscript file + opt_obj = Options.Handler(Utils.g_module) + opt_obj.curdir = candidate + try: + f = Utils.g_module.set_options + except AttributeError: + pass + else: + opt_obj.sub_options(['']) + opt_obj.parse_args() + + if not 'init' in Utils.g_module.__dict__: + Utils.g_module.init = Utils.nada + if not 'shutdown' in Utils.g_module.__dict__: + Utils.g_module.shutdown = Utils.nada + + main() + +def prepare(t, cwd, ver, wafdir): + if WAFVERSION != ver: + msg = 'Version mismatch: waf %s <> wafadmin %s (wafdir %s)' % (ver, WAFVERSION, wafdir) + print('\033[91mError: %s\033[0m' % msg) + sys.exit(1) + + #""" + try: + prepare_impl(t, cwd, ver, wafdir) + except Utils.WafError, e: + error(str(e)) + sys.exit(1) + except KeyboardInterrupt: + Utils.pprint('RED', 'Interrupted') + sys.exit(68) + """ + import cProfile, pstats + cProfile.runctx("import Scripting; Scripting.prepare_impl(t, cwd, ver, wafdir)", {}, + {'t': t, 'cwd':cwd, 'ver':ver, 'wafdir':wafdir}, + 'profi.txt') + p = pstats.Stats('profi.txt') + p.sort_stats('time').print_stats(45) + #""" + +def main(): + global commands + commands = Options.arg_line[:] + + while commands: + x = commands.pop(0) + + ini = datetime.datetime.now() + if x == 'configure': + fun = configure + elif x == 'build': + fun = build + else: + fun = getattr(Utils.g_module, x, None) + + if not fun: + raise Utils.WscriptError('No such command %r' % x) + + ctx = getattr(Utils.g_module, x + '_context', Utils.Context)() + + if x in ['init', 'shutdown', 'dist', 'distclean', 'distcheck']: + # compatibility TODO remove in waf 1.6 + try: + fun(ctx) + except TypeError: + fun() + else: + fun(ctx) + + ela = '' + if not Options.options.progress_bar: + ela = ' (%s)' % Utils.get_elapsed_time(ini) + + if x != 'init' and x != 'shutdown': + info('%r finished successfully%s' % (x, ela)) + + if not commands and x != 'shutdown': + commands.append('shutdown') + +def configure(conf): + + src = getattr(Options.options, SRCDIR, None) + if not src: src = getattr(Utils.g_module, SRCDIR, None) + if not src: + src = '.' + incomplete_src = 1 + src = os.path.abspath(src) + + bld = getattr(Options.options, BLDDIR, None) + if not bld: + bld = getattr(Utils.g_module, BLDDIR, None) + if bld == '.': + raise Utils.WafError('Setting blddir="." may cause distclean problems') + if not bld: + bld = 'build' + incomplete_bld = 1 + bld = os.path.abspath(bld) + + try: os.makedirs(bld) + except OSError: pass + + # It is not possible to compile specific targets in the configuration + # this may cause configuration errors if autoconfig is set + targets = Options.options.compile_targets + Options.options.compile_targets = None + Options.is_install = False + + conf.srcdir = src + conf.blddir = bld + conf.post_init() + + if 'incomplete_src' in vars(): + conf.check_message_1('Setting srcdir to') + conf.check_message_2(src) + if 'incomplete_bld' in vars(): + conf.check_message_1('Setting blddir to') + conf.check_message_2(bld) + + # calling to main wscript's configure() + conf.sub_config(['']) + + conf.store() + + # this will write a configure lock so that subsequent builds will + # consider the current path as the root directory (see prepare_impl). + # to remove: use 'waf distclean' + env = Environment.Environment() + env[BLDDIR] = bld + env[SRCDIR] = src + env['argv'] = sys.argv + env['commands'] = Options.commands + env['options'] = Options.options.__dict__ + + # conf.hash & conf.files hold wscript files paths and hash + # (used only by Configure.autoconfig) + env['hash'] = conf.hash + env['files'] = conf.files + env['environ'] = dict(conf.environ) + env['cwd'] = os.path.split(Utils.g_module.root_path)[0] + + if Utils.g_module.root_path != src: + # in case the source dir is somewhere else + env.store(os.path.join(src, Options.lockfile)) + + env.store(Options.lockfile) + + Options.options.compile_targets = targets + +def clean(bld): + '''removes the build files''' + try: + proj = Environment.Environment(Options.lockfile) + except IOError: + raise Utils.WafError('Nothing to clean (project not configured)') + + bld.load_dirs(proj[SRCDIR], proj[BLDDIR]) + bld.load_envs() + + bld.is_install = 0 # False + + # read the scripts - and set the path to the wscript path (useful for srcdir='/foo/bar') + bld.add_subdirs([os.path.split(Utils.g_module.root_path)[0]]) + + try: + bld.clean() + finally: + bld.save() + +def check_configured(bld): + if not Configure.autoconfig: + return bld + + conf_cls = getattr(Utils.g_module, 'configure_context', Utils.Context) + bld_cls = getattr(Utils.g_module, 'build_context', Utils.Context) + + def reconf(proj): + back = (Options.commands, Options.options.__dict__, Logs.zones, Logs.verbose) + + Options.commands = proj['commands'] + Options.options.__dict__ = proj['options'] + conf = conf_cls() + conf.environ = proj['environ'] + configure(conf) + + (Options.commands, Options.options.__dict__, Logs.zones, Logs.verbose) = back + + try: + proj = Environment.Environment(Options.lockfile) + except IOError: + conf = conf_cls() + configure(conf) + else: + try: + bld = bld_cls() + bld.load_dirs(proj[SRCDIR], proj[BLDDIR]) + bld.load_envs() + except Utils.WafError: + reconf(proj) + return bld_cls() + + try: + proj = Environment.Environment(Options.lockfile) + except IOError: + raise Utils.WafError('Auto-config: project does not configure (bug)') + + h = 0 + try: + for file in proj['files']: + if file.endswith('configure'): + h = hash((h, Utils.readf(file))) + else: + mod = Utils.load_module(file) + h = hash((h, mod.waf_hash_val)) + except (OSError, IOError): + warn('Reconfiguring the project: a file is unavailable') + reconf(proj) + else: + if (h != proj['hash']): + warn('Reconfiguring the project: the configuration has changed') + reconf(proj) + + return bld_cls() + +def install(bld): + '''installs the build files''' + bld = check_configured(bld) + + Options.commands['install'] = True + Options.commands['uninstall'] = False + Options.is_install = True + + bld.is_install = INSTALL + + build_impl(bld) + bld.install() + +def uninstall(bld): + '''removes the installed files''' + Options.commands['install'] = False + Options.commands['uninstall'] = True + Options.is_install = True + + bld.is_install = UNINSTALL + + try: + def runnable_status(self): + return SKIP_ME + setattr(Task.Task, 'runnable_status_back', Task.Task.runnable_status) + setattr(Task.Task, 'runnable_status', runnable_status) + + build_impl(bld) + bld.install() + finally: + setattr(Task.Task, 'runnable_status', Task.Task.runnable_status_back) + +def build(bld): + bld = check_configured(bld) + + Options.commands['install'] = False + Options.commands['uninstall'] = False + Options.is_install = False + + bld.is_install = 0 # False + + return build_impl(bld) + +def build_impl(bld): + # compile the project and/or install the files + try: + proj = Environment.Environment(Options.lockfile) + except IOError: + raise Utils.WafError("Project not configured (run 'waf configure' first)") + + bld.load_dirs(proj[SRCDIR], proj[BLDDIR]) + bld.load_envs() + + info("Waf: Entering directory `%s'" % bld.bldnode.abspath()) + bld.add_subdirs([os.path.split(Utils.g_module.root_path)[0]]) + + # execute something immediately before the build starts + bld.pre_build() + + try: + bld.compile() + finally: + if Options.options.progress_bar: print('') + info("Waf: Leaving directory `%s'" % bld.bldnode.abspath()) + + # execute something immediately after a successful build + bld.post_build() + + bld.install() + +excludes = '.bzr .bzrignore .git .gitignore .svn CVS .cvsignore .arch-ids {arch} SCCS BitKeeper .hg _MTN _darcs Makefile Makefile.in config.log'.split() +dist_exts = '~ .rej .orig .pyc .pyo .bak .tar.bz2 tar.gz .zip .swp'.split() +def dont_dist(name, src, build_dir): + global excludes, dist_exts + + if (name.startswith(',,') + or name.startswith('++') + or name.startswith('.waf-1.') + or (src == '.' and name == Options.lockfile) + or name in excludes + or name == build_dir + ): + return True + + for ext in dist_exts: + if name.endswith(ext): + return True + + return False + +# like shutil.copytree +# exclude files and to raise exceptions immediately +def copytree(src, dst, build_dir): + names = os.listdir(src) + os.makedirs(dst) + for name in names: + srcname = os.path.join(src, name) + dstname = os.path.join(dst, name) + + if dont_dist(name, src, build_dir): + continue + + if os.path.isdir(srcname): + copytree(srcname, dstname, build_dir) + else: + shutil.copy2(srcname, dstname) + +# TODO in waf 1.6, change this method if "srcdir == blddir" is allowed +def distclean(ctx=None): + '''removes the build directory''' + lst = os.listdir('.') + for f in lst: + if f == Options.lockfile: + try: + proj = Environment.Environment(f) + except: + Logs.warn('could not read %r' % f) + continue + + try: + shutil.rmtree(proj[BLDDIR]) + except IOError: + pass + except OSError, e: + if e.errno != errno.ENOENT: + Logs.warn('project %r cannot be removed' % proj[BLDDIR]) + + try: + os.remove(f) + except OSError, e: + if e.errno != errno.ENOENT: + Logs.warn('file %r cannot be removed' % f) + + # remove the local waf cache + if f.startswith('.waf-'): + shutil.rmtree(f, ignore_errors=True) + +# FIXME waf 1.6 a unique ctx parameter, and remove the optional appname and version +def dist(appname='', version=''): + '''makes a tarball for redistributing the sources''' + # return return (distdirname, tarballname) + import tarfile + + if not appname: appname = getattr(Utils.g_module, APPNAME, 'noname') + if not version: version = getattr(Utils.g_module, VERSION, '1.0') + + tmp_folder = appname + '-' + version + arch_name = tmp_folder+'.tar.'+g_gz + + # remove the previous dir + try: + shutil.rmtree(tmp_folder) + except (OSError, IOError): + pass + + # remove the previous archive + try: + os.remove(arch_name) + except (OSError, IOError): + pass + + # copy the files into the temporary folder + copytree('.', tmp_folder, getattr(Utils.g_module, BLDDIR, None)) + + # undocumented hook for additional cleanup + dist_hook = getattr(Utils.g_module, 'dist_hook', None) + if dist_hook: + back = os.getcwd() + os.chdir(tmp_folder) + try: + dist_hook() + finally: + # go back to the root directory + os.chdir(back) + + tar = tarfile.open(arch_name, 'w:' + g_gz) + tar.add(tmp_folder) + tar.close() + + try: from hashlib import sha1 as sha + except ImportError: from sha import sha + try: + digest = " (sha=%r)" % sha(Utils.readf(arch_name)).hexdigest() + except: + digest = '' + + info('New archive created: %s%s' % (arch_name, digest)) + + if os.path.exists(tmp_folder): shutil.rmtree(tmp_folder) + return arch_name + +# FIXME waf 1.6 a unique ctx parameter, and remove the optional appname and version +def distcheck(appname='', version=''): + '''checks if the sources compile (tarball from 'dist')''' + import tempfile, tarfile + + if not appname: appname = getattr(Utils.g_module, APPNAME, 'noname') + if not version: version = getattr(Utils.g_module, VERSION, '1.0') + + waf = os.path.abspath(sys.argv[0]) + tarball = dist(appname, version) + t = tarfile.open(tarball) + for x in t: t.extract(x) + t.close() + + path = appname + '-' + version + + instdir = tempfile.mkdtemp('.inst', '%s-%s' % (appname, version)) + ret = Utils.pproc.Popen([waf, 'configure', 'install', 'uninstall', '--destdir=' + instdir], cwd=path).wait() + if ret: + raise Utils.WafError('distcheck failed with code %i' % ret) + + if os.path.exists(instdir): + raise Utils.WafError('distcheck succeeded, but files were left in %s' % instdir) + + shutil.rmtree(path) + +# FIXME remove in Waf 1.6 (kept for compatibility) +def add_subdir(dir, bld): + bld.recurse(dir, 'build') + diff --git a/tools/wafadmin/Task.py b/tools/wafadmin/Task.py new file mode 100644 index 000000000..79b37b04c --- /dev/null +++ b/tools/wafadmin/Task.py @@ -0,0 +1,1078 @@ +#!/usr/bin/env python +# encoding: utf-8 +# Thomas Nagy, 2005-2008 (ita) + +""" +Running tasks in parallel is a simple problem, but in practice it is more complicated: +* dependencies discovered during the build (dynamic task creation) +* dependencies discovered after files are compiled +* the amount of tasks and dependencies (graph size) can be huge + +This is why the dependency management is split on three different levels: +1. groups of tasks that run all after another group of tasks +2. groups of tasks that can be run in parallel +3. tasks that can run in parallel, but with possible unknown ad-hoc dependencies + +The point #1 represents a strict sequential order between groups of tasks, for example a compiler is produced +and used to compile the rest, whereas #2 and #3 represent partial order constraints where #2 applies to the kind of task +and #3 applies to the task instances. + +#1 is held by the task manager: ordered list of TaskGroups (see bld.add_group) +#2 is held by the task groups and the task types: precedence after/before (topological sort), + and the constraints extracted from file extensions +#3 is held by the tasks individually (attribute run_after), + and the scheduler (Runner.py) use Task::runnable_status to reorder the tasks + +-- + +To try, use something like this in your code: +import Constants, Task +Task.algotype = Constants.MAXPARALLEL + +-- + +There are two concepts with the tasks (individual units of change): +* dependency (if 1 is recompiled, recompile 2) +* order (run 2 after 1) + +example 1: if t1 depends on t2 and t2 depends on t3 it is not necessary to make t1 depend on t3 (dependency is transitive) +example 2: if t1 depends on a node produced by t2, it is not immediately obvious that t1 must run after t2 (order is not obvious) + +The role of the Task Manager is to give the tasks in order (groups of task that may be run in parallel one after the other) + +""" + +import os, shutil, sys, re, random, datetime +from Utils import md5 +import Build, Runner, Utils, Node, Logs, Options +from Logs import debug, warn, error +from Constants import * + +algotype = NORMAL +#algotype = JOBCONTROL +#algotype = MAXPARALLEL + +COMPILE_TEMPLATE_SHELL = ''' +def f(task): + env = task.env + wd = getattr(task, 'cwd', None) + p = env.get_flat + cmd = \'\'\' %s \'\'\' % s + return task.exec_command(cmd, cwd=wd) +''' + +COMPILE_TEMPLATE_NOSHELL = ''' +def f(task): + env = task.env + wd = getattr(task, 'cwd', None) + def to_list(xx): + if isinstance(xx, str): return [xx] + return xx + lst = [] + %s + lst = [x for x in lst if x] + return task.exec_command(lst, cwd=wd) +''' + + +""" +Enable different kind of dependency algorithms: +1 make groups: first compile all cpps and then compile all links (NORMAL) +2 parallelize all (each link task run after its dependencies) (MAXPARALLEL) +3 like 1 but provide additional constraints for the parallelization (MAXJOBS) + +In theory 1. will be faster than 2 for waf, but might be slower for builds +The scheme 2 will not allow for running tasks one by one so it can cause disk thrashing on huge builds +""" + +file_deps = Utils.nada +""" +Additional dependency pre-check may be added by replacing the function file_deps. +e.g. extract_outputs, extract_deps below. +""" + +class TaskManager(object): + """The manager is attached to the build object, it holds a list of TaskGroup""" + def __init__(self): + self.groups = [] + self.tasks_done = [] + self.current_group = 0 + self.groups_names = {} + + def get_next_set(self): + """return the next set of tasks to execute + the first parameter is the maximum amount of parallelization that may occur""" + ret = None + while not ret and self.current_group < len(self.groups): + ret = self.groups[self.current_group].get_next_set() + if ret: return ret + else: + self.groups[self.current_group].process_install() + self.current_group += 1 + return (None, None) + + def add_group(self, name=None, set=True): + #if self.groups and not self.groups[0].tasks: + # error('add_group: an empty group is already present') + g = TaskGroup() + + if name and name in self.groups_names: + error('add_group: name %s already present' % name) + self.groups_names[name] = g + self.groups.append(g) + if set: + self.current_group = len(self.groups) - 1 + + def set_group(self, idx): + if isinstance(idx, str): + g = self.groups_names[idx] + for x in xrange(len(self.groups)): + if id(g) == id(self.groups[x]): + self.current_group = x + else: + self.current_group = idx + + def add_task_gen(self, tgen): + if not self.groups: self.add_group() + self.groups[self.current_group].tasks_gen.append(tgen) + + def add_task(self, task): + if not self.groups: self.add_group() + self.groups[self.current_group].tasks.append(task) + + def total(self): + total = 0 + if not self.groups: return 0 + for group in self.groups: + total += len(group.tasks) + return total + + def add_finished(self, tsk): + self.tasks_done.append(tsk) + bld = tsk.generator.bld + if bld.is_install: + f = None + if 'install' in tsk.__dict__: + f = tsk.__dict__['install'] + # install=0 to prevent installation + if f: f(tsk) + else: + tsk.install() + +class TaskGroup(object): + "the compilation of one group does not begin until the previous group has finished (in the manager)" + def __init__(self): + self.tasks = [] # this list will be consumed + self.tasks_gen = [] + + self.cstr_groups = Utils.DefaultDict(list) # tasks having equivalent constraints + self.cstr_order = Utils.DefaultDict(set) # partial order between the cstr groups + self.temp_tasks = [] # tasks put on hold + self.ready = 0 + self.post_funs = [] + + def reset(self): + "clears the state of the object (put back the tasks into self.tasks)" + for x in self.cstr_groups: + self.tasks += self.cstr_groups[x] + self.tasks = self.temp_tasks + self.tasks + self.temp_tasks = [] + self.cstr_groups = Utils.DefaultDict(list) + self.cstr_order = Utils.DefaultDict(set) + self.ready = 0 + + def process_install(self): + for (f, k, kw) in self.post_funs: + f(*k, **kw) + + def prepare(self): + "prepare the scheduling" + self.ready = 1 + file_deps(self.tasks) + self.make_cstr_groups() + self.extract_constraints() + + def get_next_set(self): + "next list of tasks to execute using max job settings, returns (maxjobs, task_list)" + global algotype + if algotype == NORMAL: + tasks = self.tasks_in_parallel() + maxj = MAXJOBS + elif algotype == JOBCONTROL: + (maxj, tasks) = self.tasks_by_max_jobs() + elif algotype == MAXPARALLEL: + tasks = self.tasks_with_inner_constraints() + maxj = MAXJOBS + else: + raise Utils.WafError("unknown algorithm type %s" % (algotype)) + + if not tasks: return () + return (maxj, tasks) + + def make_cstr_groups(self): + "unite the tasks that have similar constraints" + self.cstr_groups = Utils.DefaultDict(list) + for x in self.tasks: + h = x.hash_constraints() + self.cstr_groups[h].append(x) + + def set_order(self, a, b): + self.cstr_order[a].add(b) + + def compare_exts(self, t1, t2): + "extension production" + x = "ext_in" + y = "ext_out" + in_ = t1.attr(x, ()) + out_ = t2.attr(y, ()) + for k in in_: + if k in out_: + return -1 + in_ = t2.attr(x, ()) + out_ = t1.attr(y, ()) + for k in in_: + if k in out_: + return 1 + return 0 + + def compare_partial(self, t1, t2): + "partial relations after/before" + m = "after" + n = "before" + name = t2.__class__.__name__ + if name in Utils.to_list(t1.attr(m, ())): return -1 + elif name in Utils.to_list(t1.attr(n, ())): return 1 + name = t1.__class__.__name__ + if name in Utils.to_list(t2.attr(m, ())): return 1 + elif name in Utils.to_list(t2.attr(n, ())): return -1 + return 0 + + def extract_constraints(self): + "extract the parallelization constraints from the tasks with different constraints" + keys = self.cstr_groups.keys() + max = len(keys) + # hopefully the length of this list is short + for i in xrange(max): + t1 = self.cstr_groups[keys[i]][0] + for j in xrange(i + 1, max): + t2 = self.cstr_groups[keys[j]][0] + + # add the constraints based on the comparisons + val = (self.compare_exts(t1, t2) + or self.compare_partial(t1, t2) + ) + if val > 0: + self.set_order(keys[i], keys[j]) + elif val < 0: + self.set_order(keys[j], keys[i]) + + def tasks_in_parallel(self): + "(NORMAL) next list of tasks that may be executed in parallel" + + if not self.ready: self.prepare() + + keys = self.cstr_groups.keys() + + unconnected = [] + remainder = [] + + for u in keys: + for k in self.cstr_order.values(): + if u in k: + remainder.append(u) + break + else: + unconnected.append(u) + + toreturn = [] + for y in unconnected: + toreturn.extend(self.cstr_groups[y]) + + # remove stuff only after + for y in unconnected: + try: self.cstr_order.__delitem__(y) + except KeyError: pass + self.cstr_groups.__delitem__(y) + + if not toreturn and remainder: + raise Utils.WafError("circular order constraint detected %r" % remainder) + + return toreturn + + def tasks_by_max_jobs(self): + "(JOBCONTROL) returns the tasks that can run in parallel with the max amount of jobs" + if not self.ready: self.prepare() + if not self.temp_tasks: self.temp_tasks = self.tasks_in_parallel() + if not self.temp_tasks: return (None, None) + + maxjobs = MAXJOBS + ret = [] + remaining = [] + for t in self.temp_tasks: + m = getattr(t, "maxjobs", getattr(self.__class__, "maxjobs", MAXJOBS)) + if m > maxjobs: + remaining.append(t) + elif m < maxjobs: + remaining += ret + ret = [t] + maxjobs = m + else: + ret.append(t) + self.temp_tasks = remaining + return (maxjobs, ret) + + def tasks_with_inner_constraints(self): + """(MAXPARALLEL) returns all tasks in this group, but add the constraints on each task instance + as an optimization, it might be desirable to discard the tasks which do not have to run""" + if not self.ready: self.prepare() + + if getattr(self, "done", None): return None + + for p in self.cstr_order: + for v in self.cstr_order[p]: + for m in self.cstr_groups[p]: + for n in self.cstr_groups[v]: + n.set_run_after(m) + self.cstr_order = Utils.DefaultDict(set) + self.cstr_groups = Utils.DefaultDict(list) + self.done = 1 + return self.tasks[:] # make a copy + +class store_task_type(type): + "store the task types that have a name ending in _task into a map (remember the existing task types)" + def __init__(cls, name, bases, dict): + super(store_task_type, cls).__init__(name, bases, dict) + name = cls.__name__ + + if name.endswith('_task'): + name = name.replace('_task', '') + TaskBase.classes[name] = cls + +class TaskBase(object): + """Base class for all Waf tasks + + The most important methods are (by usual order of call): + 1 runnable_status: ask the task if it should be run, skipped, or if we have to ask later + 2 __str__: string to display to the user + 3 run: execute the task + 4 post_run: after the task is run, update the cache about the task + + This class should be seen as an interface, it provides the very minimum necessary for the scheduler + so it does not do much. + + For illustration purposes, TaskBase instances try to execute self.fun (if provided) + """ + + __metaclass__ = store_task_type + + color = "GREEN" + maxjobs = MAXJOBS + classes = {} + stat = None + + def __init__(self, *k, **kw): + self.hasrun = NOT_RUN + + try: + self.generator = kw['generator'] + except KeyError: + self.generator = self + self.bld = Build.bld + + if kw.get('normal', 1): + self.generator.bld.task_manager.add_task(self) + + def __repr__(self): + "used for debugging" + return '\n\t{task: %s %s}' % (self.__class__.__name__, str(getattr(self, "fun", ""))) + + def __str__(self): + "string to display to the user" + if hasattr(self, 'fun'): + return 'executing: %s\n' % self.fun.__name__ + return self.__class__.__name__ + '\n' + + def exec_command(self, *k, **kw): + "use this for executing commands from tasks" + return self.generator.bld.exec_command(*k, **kw) + + def runnable_status(self): + "RUN_ME SKIP_ME or ASK_LATER" + return RUN_ME + + def can_retrieve_cache(self): + return False + + def call_run(self): + if self.can_retrieve_cache(): + return 0 + return self.run() + + def run(self): + "called if the task must run" + if hasattr(self, 'fun'): + return self.fun(self) + return 0 + + def post_run(self): + "update the dependency tree (node stats)" + pass + + def display(self): + "print either the description (using __str__) or the progress bar or the ide output" + col1 = Logs.colors(self.color) + col2 = Logs.colors.NORMAL + + if Options.options.progress_bar == 1: + return self.generator.bld.progress_line(self.position[0], self.position[1], col1, col2) + + if Options.options.progress_bar == 2: + ela = Utils.get_elapsed_time(self.generator.bld.ini) + try: + ins = ','.join([n.name for n in self.inputs]) + except AttributeError: + ins = '' + try: + outs = ','.join([n.name for n in self.outputs]) + except AttributeError: + outs = '' + return '|Total %s|Current %s|Inputs %s|Outputs %s|Time %s|\n' % (self.position[1], self.position[0], ins, outs, ela) + + total = self.position[1] + n = len(str(total)) + fs = '[%%%dd/%%%dd] %%s%%s%%s' % (n, n) + return fs % (self.position[0], self.position[1], col1, str(self), col2) + + def attr(self, att, default=None): + "retrieve an attribute from the instance or from the class (microoptimization here)" + ret = getattr(self, att, self) + if ret is self: return getattr(self.__class__, att, default) + return ret + + def hash_constraints(self): + "identify a task type for all the constraints relevant for the scheduler: precedence, file production" + a = self.attr + sum = hash((self.__class__.__name__, + str(a('before', '')), + str(a('after', '')), + str(a('ext_in', '')), + str(a('ext_out', '')), + self.__class__.maxjobs)) + return sum + + def format_error(self): + "error message to display to the user (when a build fails)" + if getattr(self, "err_msg", None): + return self.err_msg + elif self.hasrun == CRASHED: + try: + return " -> task failed (err #%d): %r" % (self.err_code, self) + except AttributeError: + return " -> task failed: %r" % self + elif self.hasrun == MISSING: + return " -> missing files: %r" % self + else: + return '' + + def install(self): + """ + installation is performed by looking at the task attributes: + * install_path: installation path like "${PREFIX}/bin" + * filename: install the first node in the outputs as a file with a particular name, be certain to give os.sep + * chmod: permissions + """ + bld = self.generator.bld + d = self.attr('install') + + if self.attr('install_path'): + lst = [a.relpath_gen(bld.srcnode) for a in self.outputs] + perm = self.attr('chmod', O644) + if self.attr('src'): + # if src is given, install the sources too + lst += [a.relpath_gen(bld.srcnode) for a in self.inputs] + if self.attr('filename'): + dir = self.install_path.rstrip(os.sep) + os.sep + self.attr('filename') + bld.install_as(dir, lst[0], self.env, perm) + else: + bld.install_files(self.install_path, lst, self.env, perm) + +class Task(TaskBase): + """The parent class is quite limited, in this version: + * file system interaction: input and output nodes + * persistence: do not re-execute tasks that have already run + * caching: same files can be saved and retrieved from a cache directory + * dependencies: + implicit, like .c files depending on .h files + explicit, like the input nodes or the dep_nodes + environment variables, like the CXXFLAGS in self.env + """ + vars = [] + def __init__(self, env, **kw): + TaskBase.__init__(self, **kw) + self.env = env + + # inputs and outputs are nodes + # use setters when possible + self.inputs = [] + self.outputs = [] + + self.deps_nodes = [] + self.run_after = [] + + # Additionally, you may define the following + #self.dep_vars = 'PREFIX DATADIR' + + def __str__(self): + "string to display to the user" + env = self.env + src_str = ' '.join([a.nice_path(env) for a in self.inputs]) + tgt_str = ' '.join([a.nice_path(env) for a in self.outputs]) + if self.outputs: sep = ' -> ' + else: sep = '' + return '%s: %s%s%s\n' % (self.__class__.__name__.replace('_task', ''), src_str, sep, tgt_str) + + def __repr__(self): + return "".join(['\n\t{task: ', self.__class__.__name__, " ", ",".join([x.name for x in self.inputs]), " -> ", ",".join([x.name for x in self.outputs]), '}']) + + def unique_id(self): + "get a unique id: hash the node paths, the variant, the class, the function" + try: + return self.uid + except AttributeError: + "this is not a real hot zone, but we want to avoid surprizes here" + m = md5() + up = m.update + up(self.__class__.__name__) + up(self.env.variant()) + p = None + for x in self.inputs + self.outputs: + if p != x.parent.id: + p = x.parent.id + up(x.parent.abspath()) + up(x.name) + self.uid = m.digest() + return self.uid + + def set_inputs(self, inp): + if isinstance(inp, list): self.inputs += inp + else: self.inputs.append(inp) + + def set_outputs(self, out): + if isinstance(out, list): self.outputs += out + else: self.outputs.append(out) + + def set_run_after(self, task): + "set (scheduler) order on another task" + # TODO: handle list or object + assert isinstance(task, TaskBase) + self.run_after.append(task) + + def add_file_dependency(self, filename): + "TODO user-provided file dependencies" + node = self.generator.bld.current.find_resource(filename) + self.deps_nodes.append(node) + + def signature(self): + # compute the result one time, and suppose the scan_signature will give the good result + try: return self.cache_sig[0] + except AttributeError: pass + + m = md5() + + # explicit deps + exp_sig = self.sig_explicit_deps() + m.update(exp_sig) + + # implicit deps + imp_sig = self.scan and self.sig_implicit_deps() or SIG_NIL + m.update(imp_sig) + + # env vars + var_sig = self.sig_vars() + m.update(var_sig) + + # we now have the signature (first element) and the details (for debugging) + ret = m.digest() + self.cache_sig = (ret, exp_sig, imp_sig, var_sig) + return ret + + def runnable_status(self): + "SKIP_ME RUN_ME or ASK_LATER" + #return 0 # benchmarking + + if self.inputs and (not self.outputs): + if not getattr(self.__class__, 'quiet', None): + warn("invalid task (no inputs OR outputs): override in a Task subclass or set the attribute 'quiet' %r" % self) + + for t in self.run_after: + if not t.hasrun: + return ASK_LATER + + env = self.env + bld = self.generator.bld + + # first compute the signature + try: + new_sig = self.signature() + except KeyError: + debug("task: something is wrong, computing the task %r signature failed" % self) + return RUN_ME + + # compare the signature to a signature computed previously + key = self.unique_id() + try: + prev_sig = bld.task_sigs[key][0] + except KeyError: + debug("task: task %r must run as it was never run before or the task code changed" % self) + return RUN_ME + + # compare the signatures of the outputs + for node in self.outputs: + variant = node.variant(env) + try: + if bld.node_sigs[variant][node.id] != new_sig: + return RUN_ME + except KeyError: + debug("task: task %r must run as the output nodes do not exist" % self) + return RUN_ME + + # debug if asked to + if Logs.verbose: self.debug_why(bld.task_sigs[key]) + + if new_sig != prev_sig: + return RUN_ME + return SKIP_ME + + def post_run(self): + "called after a successful task run" + bld = self.generator.bld + env = self.env + sig = self.signature() + + cnt = 0 + variant = env.variant() + for node in self.outputs: + # check if the node exists .. + try: + os.stat(node.abspath(env)) + except OSError: + self.has_run = MISSING + self.err_msg = '-> missing file: %r' % node.abspath(env) + raise Utils.WafError + + # important, store the signature for the next run + bld.node_sigs[variant][node.id] = sig + + # We could re-create the signature of the task with the signature of the outputs + # in practice, this means hashing the output files + # this is unnecessary + if Options.cache_global: + ssig = sig.encode('hex') + dest = os.path.join(Options.cache_global, '%s_%d_%s' % (ssig, cnt, node.name)) + try: shutil.copy2(node.abspath(env), dest) + except IOError: warn('Could not write the file to the cache') + cnt += 1 + + bld.task_sigs[self.unique_id()] = self.cache_sig + + def can_retrieve_cache(self): + """Retrieve build nodes from the cache - the file time stamps are updated + for cleaning the least used files from the cache dir - be careful when overridding""" + if not Options.cache_global: return None + if Options.options.nocache: return None + if not self.outputs: return None + + env = self.env + sig = self.signature() + + cnt = 0 + for node in self.outputs: + variant = node.variant(env) + + ssig = sig.encode('hex') + orig = os.path.join(Options.cache_global, '%s_%d_%s' % (ssig, cnt, node.name)) + try: + shutil.copy2(orig, node.abspath(env)) + # mark the cache file as used recently (modified) + os.utime(orig, None) + except (OSError, IOError): + debug('task: failed retrieving file') + return None + else: + cnt += 1 + + for node in self.outputs: + self.generator.bld.node_sigs[variant][node.id] = sig + self.generator.bld.printout('restoring from cache %r\n' % node.bldpath(env)) + + return 1 + + def debug_why(self, old_sigs): + "explains why a task is run" + + new_sigs = self.cache_sig + def v(x): + return x.encode('hex') + + debug("Task %r" % self) + msgs = ['Task must run', '* Source file or manual dependency', '* Implicit dependency', '* Environment variable'] + tmp = 'task: -> %s: %s %s' + for x in xrange(len(msgs)): + if (new_sigs[x] != old_sigs[x]): + debug(tmp % (msgs[x], v(old_sigs[x]), v(new_sigs[x]))) + + def sig_explicit_deps(self): + bld = self.generator.bld + m = md5() + + # the inputs + for x in self.inputs + getattr(self, 'dep_nodes', []): + if not x.parent.id in bld.cache_scanned_folders: + bld.rescan(x.parent) + + variant = x.variant(self.env) + m.update(bld.node_sigs[variant][x.id]) + + # manual dependencies, they can slow down the builds + if bld.deps_man: + additional_deps = bld.deps_man + for x in self.inputs + self.outputs: + try: + d = additional_deps[x.id] + except KeyError: + continue + + for v in d: + if isinstance(v, Node.Node): + bld.rescan(v.parent) + variant = v.variant(self.env) + try: + v = bld.node_sigs[variant][v.id] + except KeyError: # make it fatal? + v = '' + elif hasattr(v, '__call__'): + v = v() # dependency is a function, call it + m.update(v) + + for x in self.deps_nodes: + v = bld.node_sigs[x.variant(self.env)][x.id] + m.update(v) + + return m.digest() + + def sig_vars(self): + m = md5() + bld = self.generator.bld + env = self.env + + # dependencies on the environment vars + act_sig = bld.hash_env_vars(env, self.__class__.vars) + m.update(act_sig) + + # additional variable dependencies, if provided + dep_vars = getattr(self, 'dep_vars', None) + if dep_vars: + m.update(bld.hash_env_vars(env, dep_vars)) + + return m.digest() + + #def scan(self, node): + # """this method returns a tuple containing: + # * a list of nodes corresponding to real files + # * a list of names for files not found in path_lst + # the input parameters may have more parameters that the ones used below + # """ + # return ((), ()) + scan = None + + # compute the signature, recompute it if there is no match in the cache + def sig_implicit_deps(self): + "the signature obtained may not be the one if the files have changed, we do it in two steps" + + bld = self.generator.bld + + # get the task signatures from previous runs + key = self.unique_id() + prev_sigs = bld.task_sigs.get(key, ()) + if prev_sigs: + try: + # for issue #379 + if prev_sigs[2] == self.compute_sig_implicit_deps(): + return prev_sigs[2] + except (KeyError, OSError): + pass + + # no previous run or the signature of the dependencies has changed, rescan the dependencies + (nodes, names) = self.scan() + if Logs.verbose: + debug('deps: scanner for %s returned %s %s' % (str(self), str(nodes), str(names))) + + # store the dependencies in the cache + bld.node_deps[key] = nodes + bld.raw_deps[key] = names + + # recompute the signature and return it + sig = self.compute_sig_implicit_deps() + + return sig + + def compute_sig_implicit_deps(self): + """it is intended for .cpp and inferred .h files + there is a single list (no tree traversal) + this is the hot spot so ... do not touch""" + m = md5() + upd = m.update + + bld = self.generator.bld + tstamp = bld.node_sigs + env = self.env + + for k in bld.node_deps.get(self.unique_id(), []): + # unlikely but necessary if it happens + if not k.parent.id in bld.cache_scanned_folders: + # if the parent folder is removed, an OSError may be thrown + bld.rescan(k.parent) + + # if the parent folder is removed, a KeyError will be thrown + if k.id & 3 == 2: # Node.FILE: + upd(tstamp[0][k.id]) + else: + upd(tstamp[env.variant()][k.id]) + + return m.digest() + +def funex(c): + dc = {} + exec(c, dc) + return dc['f'] + +reg_act = re.compile(r"(?P<backslash>\\)|(?P<dollar>\$\$)|(?P<subst>\$\{(?P<var>\w+)(?P<code>.*?)\})", re.M) +def compile_fun_shell(name, line): + """Compiles a string (once) into a function, eg: + simple_task_type('c++', '${CXX} -o ${TGT[0]} ${SRC} -I ${SRC[0].parent.bldpath()}') + + The env variables (CXX, ..) on the task must not hold dicts (order) + The reserved keywords TGT and SRC represent the task input and output nodes + + quick test: + bld.new_task_gen(source='wscript', rule='echo "foo\\${SRC[0].name}\\bar"') + """ + + extr = [] + def repl(match): + g = match.group + if g('dollar'): return "$" + elif g('backslash'): return '\\\\' + elif g('subst'): extr.append((g('var'), g('code'))); return "%s" + return None + + line = reg_act.sub(repl, line) + + parm = [] + dvars = [] + app = parm.append + for (var, meth) in extr: + if var == 'SRC': + if meth: app('task.inputs%s' % meth) + else: app('" ".join([a.srcpath(env) for a in task.inputs])') + elif var == 'TGT': + if meth: app('task.outputs%s' % meth) + else: app('" ".join([a.bldpath(env) for a in task.outputs])') + else: + if not var in dvars: dvars.append(var) + app("p('%s')" % var) + if parm: parm = "%% (%s) " % (',\n\t\t'.join(parm)) + else: parm = '' + + c = COMPILE_TEMPLATE_SHELL % (line, parm) + + debug('action: %s' % c) + return (funex(c), dvars) + +def compile_fun_noshell(name, line): + + extr = [] + def repl(match): + g = match.group + if g('dollar'): return "$" + elif g('subst'): extr.append((g('var'), g('code'))); return "<<|@|>>" + return None + + line2 = reg_act.sub(repl, line) + params = line2.split('<<|@|>>') + + buf = [] + dvars = [] + app = buf.append + for x in xrange(len(extr)): + params[x] = params[x].strip() + if params[x]: + app("lst.extend(%r)" % params[x].split()) + (var, meth) = extr[x] + if var == 'SRC': + if meth: app('lst.append(task.inputs%s)' % meth) + else: app("lst.extend([a.srcpath(env) for a in task.inputs])") + elif var == 'TGT': + if meth: app('lst.append(task.outputs%s)' % meth) + else: app("lst.extend([a.bldpath(env) for a in task.outputs])") + else: + app('lst.extend(to_list(env[%r]))' % var) + if not var in dvars: dvars.append(var) + + if extr: + if params[-1]: + app("lst.extend(%r)" % params[-1].split()) + + fun = COMPILE_TEMPLATE_NOSHELL % "\n\t".join(buf) + debug('action: %s' % fun) + return (funex(fun), dvars) + +def compile_fun(name, line, shell=None): + "commands can be launched by the shell or not" + if line.find('<') > 0 or line.find('>') > 0 or line.find('&&') > 0: + shell = True + #else: + # shell = False + + if shell is None: + if sys.platform == 'win32': + shell = False + else: + shell = True + + if shell: + return compile_fun_shell(name, line) + else: + return compile_fun_noshell(name, line) + +def simple_task_type(name, line, color='GREEN', vars=[], ext_in=[], ext_out=[], before=[], after=[], shell=None): + """return a new Task subclass with the function run compiled from the line given""" + (fun, dvars) = compile_fun(name, line, shell) + fun.code = line + return task_type_from_func(name, fun, vars or dvars, color, ext_in, ext_out, before, after) + +def task_type_from_func(name, func, vars=[], color='GREEN', ext_in=[], ext_out=[], before=[], after=[]): + """return a new Task subclass with the function run compiled from the line given""" + params = { + 'run': func, + 'vars': vars, + 'color': color, + 'name': name, + 'ext_in': Utils.to_list(ext_in), + 'ext_out': Utils.to_list(ext_out), + 'before': Utils.to_list(before), + 'after': Utils.to_list(after), + } + + cls = type(Task)(name, (Task,), params) + TaskBase.classes[name] = cls + return cls + +def always_run(cls): + """Set all task instances of this class to be executed whenever a build is started + The task signature is calculated, but the result of the comparation between + task signatures is bypassed + """ + old = cls.runnable_status + def always(self): + old(self) + return RUN_ME + cls.runnable_status = always + +def update_outputs(cls): + """When a command is always run, it is possible that the output only change + sometimes. By default the build node have as a hash the signature of the task + which may not change. With this, the output nodes (produced) are hashed, + and the hashes are set to the build nodes + + This may avoid unnecessary recompilations, but it uses more resources + (hashing the output files) so it is not used by default + """ + old_post_run = cls.post_run + def post_run(self): + old_post_run(self) + bld = self.outputs[0].__class__.bld + bld.node_sigs[self.env.variant()][self.outputs[0].id] = \ + Utils.h_file(self.outputs[0].abspath(self.env)) + cls.post_run = post_run + +def extract_outputs(tasks): + """file_deps: Infer additional dependencies from task input and output nodes + """ + v = {} + for x in tasks: + try: + (ins, outs) = v[x.env.variant()] + except KeyError: + ins = {} + outs = {} + v[x.env.variant()] = (ins, outs) + + for a in getattr(x, 'inputs', []): + try: ins[a.id].append(x) + except KeyError: ins[a.id] = [x] + for a in getattr(x, 'outputs', []): + try: outs[a.id].append(x) + except KeyError: outs[a.id] = [x] + + for (ins, outs) in v.values(): + links = set(ins.iterkeys()).intersection(outs.iterkeys()) + for k in links: + for a in ins[k]: + for b in outs[k]: + a.set_run_after(b) + +def extract_deps(tasks): + """file_deps: Infer additional dependencies from task input and output nodes and from implicit dependencies + returned by the scanners - that will only work if all tasks are created + + this is aimed at people who have pathological builds and who do not care enough + to implement the build dependencies properly + + with two loops over the list of tasks, do not expect this to be really fast + """ + + # first reuse the function above + extract_outputs(tasks) + + # map the output nodes to the tasks producing them + out_to_task = {} + for x in tasks: + v = x.env.variant() + try: + lst = x.outputs + except AttributeError: + pass + else: + for node in lst: + out_to_task[(v, node.id)] = x + + # map the dependencies found to the tasks compiled + dep_to_task = {} + for x in tasks: + try: + x.signature() + except: # this is on purpose + pass + + variant = x.env.variant() + key = x.unique_id() + for k in x.generator.bld.node_deps.get(x.unique_id(), []): + try: dep_to_task[(v, k.id)].append(x) + except KeyError: dep_to_task[(v, k.id)] = [x] + + # now get the intersection + deps = set(dep_to_task.keys()).intersection(set(out_to_task.keys())) + + # and add the dependencies from task to task + for idx in deps: + for k in dep_to_task[idx]: + k.set_run_after(out_to_task[idx]) + + # cleanup, remove the signatures + for x in tasks: + try: + delattr(x, 'cache_sig') + except AttributeError: + pass + diff --git a/tools/wafadmin/TaskGen.py b/tools/wafadmin/TaskGen.py new file mode 100644 index 000000000..07a6e958b --- /dev/null +++ b/tools/wafadmin/TaskGen.py @@ -0,0 +1,576 @@ +#!/usr/bin/env python +# encoding: utf-8 +# Thomas Nagy, 2005-2008 (ita) + +""" +The class task_gen encapsulates the creation of task objects (low-level code) +The instances can have various parameters, but the creation of task nodes (Task.py) +is delayed. To achieve this, various methods are called from the method "apply" + +The class task_gen contains lots of methods, and a configuration table: +* the methods to call (self.meths) can be specified dynamically (removing, adding, ..) +* the order of the methods (self.prec or by default task_gen.prec) is configurable +* new methods can be inserted dynamically without pasting old code + +Additionally, task_gen provides the method apply_core +* file extensions are mapped to methods: def meth(self, name_or_node) +* if a mapping is not found in self.mappings, it is searched in task_gen.mappings +* when called, the functions may modify self.allnodes to re-add source to process +* the mappings can map an extension or a filename (see the code below) + +WARNING: subclasses must reimplement the clone method +""" + +import os, traceback, copy +import Build, Task, Utils, Logs, Options +from Logs import debug, error, warn +from Constants import * + +typos = { +'sources':'source', +'targets':'target', +'include':'includes', +'define':'defines', +'importpath':'importpaths', +'install_var':'install_path', +'install_subdir':'install_path', +'inst_var':'install_path', +'inst_dir':'install_path', +'feature':'features', +} + +class register_obj(type): + """no decorators for classes, so we use a metaclass + we store into task_gen.classes the classes that inherit task_gen + and whose names end in '_taskgen' + """ + def __init__(cls, name, bases, dict): + super(register_obj, cls).__init__(name, bases, dict) + name = cls.__name__ + suffix = '_taskgen' + if name.endswith(suffix): + task_gen.classes[name.replace(suffix, '')] = cls + +class task_gen(object): + """ + Most methods are of the form 'def meth(self):' without any parameters + there are many of them, and they do many different things: + * task creation + * task results installation + * environment modification + * attribute addition/removal + + The inheritance approach is complicated + * mixing several languages at once + * subclassing is needed even for small changes + * inserting new methods is complicated + + This new class uses a configuration table: + * adding new methods easily + * obtaining the order in which to call the methods + * postponing the method calls (post() -> apply) + + Additionally, a 'traits' static attribute is provided: + * this list contains methods + * the methods can remove or add methods from self.meths + Example1: the attribute 'staticlib' is set on an instance + a method set in the list of traits is executed when the + instance is posted, it finds that flag and adds another method for execution + Example2: a method set in the list of traits finds the msvc + compiler (from self.env['MSVC']==1); more methods are added to self.meths + """ + + __metaclass__ = register_obj + mappings = {} + mapped = {} + prec = Utils.DefaultDict(list) + traits = Utils.DefaultDict(set) + classes = {} + + def __init__(self, *kw, **kwargs): + self.prec = Utils.DefaultDict(list) + "map precedence of function names to call" + # so we will have to play with directed acyclic graphs + # detect cycles, etc + + self.source = '' + self.target = '' + + # list of methods to execute - does not touch it by hand unless you know + self.meths = [] + + # list of mappings extension -> function + self.mappings = {} + + # list of features (see the documentation on traits) + self.features = list(kw) + + # not always a good idea + self.tasks = [] + + self.default_chmod = O644 + self.default_install_path = None + + # kind of private, beware of what you put in it, also, the contents are consumed + self.allnodes = [] + + self.bld = kwargs.get('bld', Build.bld) + self.env = self.bld.env.copy() + + self.path = self.bld.path # emulate chdir when reading scripts + self.name = '' # give a name to the target (static+shlib with the same targetname ambiguity) + + # provide a unique id + self.idx = self.bld.idx[self.path.id] = self.bld.idx.get(self.path.id, 0) + 1 + + for key, val in kwargs.iteritems(): + setattr(self, key, val) + + self.bld.task_manager.add_task_gen(self) + self.bld.all_task_gen.append(self) + + def __str__(self): + return ("<task_gen '%s' of type %s defined in %s>" + % (self.name or self.target, self.__class__.__name__, str(self.path))) + + def __setattr__(self, name, attr): + real = typos.get(name, name) + if real != name: + warn('typo %s -> %s' % (name, real)) + if Logs.verbose > 0: + traceback.print_stack() + object.__setattr__(self, real, attr) + + def to_list(self, value): + "helper: returns a list" + if isinstance(value, str): return value.split() + else: return value + + def apply(self): + "order the methods to execute using self.prec or task_gen.prec" + keys = set(self.meths) + + # add the methods listed in the features + self.features = Utils.to_list(self.features) + for x in self.features + ['*']: + st = task_gen.traits[x] + if not st: + warn('feature %r does not exist - bind at least one method to it' % x) + keys.update(st) + + # copy the precedence table + prec = {} + prec_tbl = self.prec or task_gen.prec + for x in prec_tbl: + if x in keys: + prec[x] = prec_tbl[x] + + # elements disconnected + tmp = [] + for a in keys: + for x in prec.values(): + if a in x: break + else: + tmp.append(a) + + # topological sort + out = [] + while tmp: + e = tmp.pop() + if e in keys: out.append(e) + try: + nlst = prec[e] + except KeyError: + pass + else: + del prec[e] + for x in nlst: + for y in prec: + if x in prec[y]: + break + else: + tmp.append(x) + + if prec: raise Utils.WafError("graph has a cycle %s" % str(prec)) + out.reverse() + self.meths = out + + # then we run the methods in order + debug('task_gen: posting %s %d' % (self, id(self))) + for x in out: + try: + v = getattr(self, x) + except AttributeError: + raise Utils.WafError("tried to retrieve %s which is not a valid method" % x) + debug('task_gen: -> %s (%d)' % (x, id(self))) + v() + + def post(self): + "runs the code to create the tasks, do not subclass" + if not self.name: + if isinstance(self.target, list): + self.name = ' '.join(self.target) + else: + self.name = self.target + + if getattr(self, 'posted', None): + #error("OBJECT ALREADY POSTED" + str( self)) + return + self.apply() + debug('task_gen: posted %s' % self.name) + self.posted = True + + def get_hook(self, ext): + try: return self.mappings[ext] + except KeyError: + try: return task_gen.mappings[ext] + except KeyError: return None + + # TODO waf 1.6: always set the environment + # TODO waf 1.6: create_task(self, name, inputs, outputs) + def create_task(self, name, env=None): + env = env or self.env + task = Task.TaskBase.classes[name](env.copy(), generator=self) + self.tasks.append(task) + return task + + def name_to_obj(self, name): + return self.bld.name_to_obj(name, self.env) + + def find_sources_in_dirs(self, dirnames, excludes=[], exts=[]): + """ + The attributes "excludes" and "exts" must be lists to avoid the confusion + find_sources_in_dirs('a', 'b', 'c') <-> find_sources_in_dirs('a b c') + + do not use absolute paths + do not use paths outside of the source tree + the files or folder beginning by . are not returned + + # TODO: remove in Waf 1.6 + """ + + err_msg = "'%s' attribute must be a list" + if not isinstance(excludes, list): + raise Utils.WscriptError(err_msg % 'excludes') + if not isinstance(exts, list): + raise Utils.WscriptError(err_msg % 'exts') + + lst = [] + + #make sure dirnames is a list helps with dirnames with spaces + dirnames = self.to_list(dirnames) + + ext_lst = exts or self.mappings.keys() + task_gen.mappings.keys() + + for name in dirnames: + anode = self.path.find_dir(name) + + if not anode or not anode.is_child_of(self.bld.srcnode): + raise Utils.WscriptError("Unable to use '%s' - either because it's not a relative path" \ + ", or it's not child of '%s'." % (name, self.bld.srcnode)) + + self.bld.rescan(anode) + for name in self.bld.cache_dir_contents[anode.id]: + + # ignore hidden files + if name.startswith('.'): + continue + + (base, ext) = os.path.splitext(name) + if ext in ext_lst and not name in lst and not name in excludes: + lst.append((anode.relpath_gen(self.path) or '.') + os.path.sep + name) + + lst.sort() + self.source = self.to_list(self.source) + if not self.source: self.source = lst + else: self.source += lst + + def clone(self, env): + "" + newobj = task_gen(bld=self.bld) + for x in self.__dict__: + if x in ['env', 'bld']: + continue + elif x in ["path", "features"]: + setattr(newobj, x, getattr(self, x)) + else: + setattr(newobj, x, copy.copy(getattr(self, x))) + + newobj.__class__ = self.__class__ + if isinstance(env, str): + newobj.env = self.bld.all_envs[env].copy() + else: + newobj.env = env.copy() + + return newobj + + def get_inst_path(self): + return getattr(self, '_install_path', getattr(self, 'default_install_path', '')) + + def set_inst_path(self, val): + self._install_path = val + + install_path = property(get_inst_path, set_inst_path) + + + def get_chmod(self): + return getattr(self, '_chmod', getattr(self, 'default_chmod', O644)) + + def set_chmod(self, val): + self._chmod = val + + chmod = property(get_chmod, set_chmod) + +def declare_extension(var, func): + try: + for x in Utils.to_list(var): + task_gen.mappings[x] = func + except: + raise Utils.WscriptError('declare_extension takes either a list or a string %r' % var) + task_gen.mapped[func.__name__] = func + +def declare_order(*k): + assert(len(k) > 1) + n = len(k) - 1 + for i in xrange(n): + f1 = k[i] + f2 = k[i+1] + if not f1 in task_gen.prec[f2]: + task_gen.prec[f2].append(f1) + +def declare_chain(name='', action='', ext_in='', ext_out='', reentrant=1, color='BLUE', + install=0, before=[], after=[], decider=None, rule=None, scan=None): + """ + see Tools/flex.py for an example + while i do not like such wrappers, some people really do + """ + + action = action or rule + if isinstance(action, str): + act = Task.simple_task_type(name, action, color=color) + else: + act = Task.task_type_from_func(name, action, color=color) + act.ext_in = tuple(Utils.to_list(ext_in)) + act.ext_out = tuple(Utils.to_list(ext_out)) + act.before = Utils.to_list(before) + act.after = Utils.to_list(after) + act.scan = scan + + def x_file(self, node): + if decider: + ext = decider(self, node) + elif isinstance(ext_out, str): + ext = ext_out + + if isinstance(ext, str): + out_source = node.change_ext(ext) + if reentrant: + self.allnodes.append(out_source) + elif isinstance(ext, list): + out_source = [node.change_ext(x) for x in ext] + if reentrant: + for i in xrange(reentrant): + self.allnodes.append(out_source[i]) + else: + # XXX: useless: it will fail on Utils.to_list above... + raise Utils.WafError("do not know how to process %s" % str(ext)) + + tsk = self.create_task(name) + tsk.set_inputs(node) + tsk.set_outputs(out_source) + + if node.__class__.bld.is_install == INSTALL: + tsk.install = install + + declare_extension(act.ext_in, x_file) + +def bind_feature(name, methods): + lst = Utils.to_list(methods) + task_gen.traits[name].update(lst) + +""" +All the following decorators are registration decorators, i.e add an attribute to current class + (task_gen and its derivatives), with same name as func, which points to func itself. +For example: + @taskgen + def sayHi(self): + print("hi") +Now taskgen.sayHi() may be called + +If python were really smart, it could infer itself the order of methods by looking at the +attributes. A prerequisite for execution is to have the attribute set before. +Intelligent compilers binding aspect-oriented programming and parallelization, what a nice topic for studies. +""" +def taskgen(func): + setattr(task_gen, func.__name__, func) + +def feature(*k): + def deco(func): + setattr(task_gen, func.__name__, func) + for name in k: + task_gen.traits[name].update([func.__name__]) + return func + return deco + +def before(*k): + def deco(func): + setattr(task_gen, func.__name__, func) + for fun_name in k: + if not func.__name__ in task_gen.prec[fun_name]: + task_gen.prec[fun_name].append(func.__name__) + return func + return deco + +def after(*k): + def deco(func): + setattr(task_gen, func.__name__, func) + for fun_name in k: + if not fun_name in task_gen.prec[func.__name__]: + task_gen.prec[func.__name__].append(fun_name) + return func + return deco + +def extension(var): + def deco(func): + setattr(task_gen, func.__name__, func) + try: + for x in Utils.to_list(var): + task_gen.mappings[x] = func + except: + raise Utils.WafError('extension takes either a list or a string %r' % var) + task_gen.mapped[func.__name__] = func + return func + return deco + +# TODO make certain the decorators may be used here + +def apply_core(self): + """Process the attribute source + transform the names into file nodes + try to process the files by name first, later by extension""" + # get the list of folders to use by the scanners + # all our objects share the same include paths anyway + find_resource = self.path.find_resource + + for filename in self.to_list(self.source): + # if self.mappings or task_gen.mappings contains a file of the same name + x = self.get_hook(filename) + if x: + x(self, filename) + else: + node = find_resource(filename) + if not node: raise Utils.WafError("source not found: '%s' in '%s'" % (filename, str(self.path))) + self.allnodes.append(node) + + for node in self.allnodes: + # self.mappings or task_gen.mappings map the file extension to a function + x = self.get_hook(node.suffix()) + + if not x: + raise Utils.WafError("Cannot guess how to process %s (got mappings %r in %r) -> try conf.check_tool(..)?" % \ + (str(node), self.__class__.mappings.keys(), self.__class__)) + x(self, node) +feature('*')(apply_core) + +def exec_rule(self): + """Process the attribute rule, when provided the method apply_core will be disabled + """ + if not getattr(self, 'rule', None): + return + + # someone may have removed it already + try: + self.meths.remove('apply_core') + except ValueError: + pass + + # get the function and the variables + func = self.rule + vars2 = [] + if isinstance(func, str): + # use the shell by default for user-defined commands + (func, vars2) = Task.compile_fun('', self.rule, shell=getattr(self, 'shell', True)) + func.code = self.rule + vars = getattr(self, 'vars', vars2) + if not vars: + if isinstance(self.rule, str): + vars = self.rule + else: + vars = Utils.h_fun(self.rule) + + # create the task class + name = getattr(self, 'name', None) or self.target or self.rule + cls = Task.task_type_from_func(name, func, vars) + + # now create one instance + tsk = self.create_task(name) + + # we assume that the user knows that without inputs or outputs + #if not getattr(self, 'target', None) and not getattr(self, 'source', None): + # cls.quiet = True + + if getattr(self, 'target', None): + cls.quiet = True + tsk.outputs=[self.path.find_or_declare(x) for x in self.to_list(self.target)] + + if getattr(self, 'source', None): + cls.quiet = True + tsk.inputs = [] + for x in self.to_list(self.source): + y = self.path.find_resource(x) + if not y: + raise Utils.WafError('input file %r could not be found (%r)' % (x, self.path.abspath())) + tsk.inputs.append(y) + + if getattr(self, 'always', None): + Task.always_run(cls) + + if getattr(self, 'scan', None): + cls.scan = self.scan + + if getattr(self, 'install_path', None): + tsk.install_path = self.install_path + + if getattr(self, 'cwd', None): + tsk.cwd = self.cwd + + if getattr(self, 'on_results', None): + Task.update_outputs(cls) + + for x in ['after', 'before']: + setattr(cls, x, getattr(self, x, [])) +feature('*')(exec_rule) +before('apply_core')(exec_rule) + +def sequence_order(self): + """ + add a strict sequential constraint between the tasks generated by task generators + it uses the fact that task generators are posted in order + it will not post objects which belong to other folders + there is also an awesome trick for executing the method in last position + + to use: + bld.new_task_gen(features='javac seq') + bld.new_task_gen(features='jar seq') + + to start a new sequence, set the attribute seq_start, for example: + obj.seq_start = True + """ + if self.meths and self.meths[-1] != 'sequence_order': + self.meths.append('sequence_order') + return + + if getattr(self, 'seq_start', None): + return + + # all the tasks previously declared must be run before these + if getattr(self.bld, 'prev', None): + self.bld.prev.post() + for x in self.bld.prev.tasks: + for y in self.tasks: + y.set_run_after(x) + + self.bld.prev = self + +feature('seq')(sequence_order) + diff --git a/tools/wafadmin/Tools/UnitTest.py b/tools/wafadmin/Tools/UnitTest.py new file mode 100644 index 000000000..4c41a59eb --- /dev/null +++ b/tools/wafadmin/Tools/UnitTest.py @@ -0,0 +1,266 @@ +#!/usr/bin/env python +# encoding: utf-8 +# Carlos Rafael Giani, 2006 + +""" +Unit tests run in the shutdown() method, and for c/c++ programs + +One should NOT have to give parameters to programs to execute + +In the shutdown method, add the following code: + + >>> def shutdown(): + ... ut = UnitTest.unit_test() + ... ut.run() + ... ut.print_results() + + +Each object to use as a unit test must be a program and must have X{obj.unit_test=1} +""" +import os, sys +import Build, TaskGen, Utils, Options, Logs, Task +from Constants import * + +class unit_test(object): + "Unit test representation" + def __init__(self): + self.returncode_ok = 0 # Unit test returncode considered OK. All returncodes differing from this one + # will cause the unit test to be marked as "FAILED". + + # The following variables are filled with data by run(). + + # print_results() uses these for printing the unit test summary, + # but if there is need for direct access to the results, + # they can be retrieved here, after calling run(). + + self.num_tests_ok = 0 # Number of successful unit tests + self.num_tests_failed = 0 # Number of failed unit tests + self.num_tests_err = 0 # Tests that have not even run + self.total_num_tests = 0 # Total amount of unit tests + self.max_label_length = 0 # Maximum label length (pretty-print the output) + + self.unit_tests = Utils.ordered_dict() # Unit test dictionary. Key: the label (unit test filename relative + # to the build dir), value: unit test filename with absolute path + self.unit_test_results = {} # Dictionary containing the unit test results. + # Key: the label, value: result (true = success false = failure) + self.unit_test_erroneous = {} # Dictionary indicating erroneous unit tests. + # Key: the label, value: true = unit test has an error false = unit test is ok + self.change_to_testfile_dir = False #True if the test file needs to be executed from the same dir + self.want_to_see_test_output = False #True to see the stdout from the testfile (for example check suites) + self.want_to_see_test_error = False #True to see the stderr from the testfile (for example check suites) + self.run_if_waf_does = 'check' #build was the old default + + def run(self): + "Run the unit tests and gather results (note: no output here)" + + self.num_tests_ok = 0 + self.num_tests_failed = 0 + self.num_tests_err = 0 + self.total_num_tests = 0 + self.max_label_length = 0 + + self.unit_tests = Utils.ordered_dict() + self.unit_test_results = {} + self.unit_test_erroneous = {} + + ld_library_path = [] + + # If waf is not building, don't run anything + if not Options.commands[self.run_if_waf_does]: return + + # Get the paths for the shared libraries, and obtain the unit tests to execute + for obj in Build.bld.all_task_gen: + try: + link_task = obj.link_task + except AttributeError: + pass + else: + lib_path = link_task.outputs[0].parent.abspath(obj.env) + if lib_path not in ld_library_path: + ld_library_path.append(lib_path) + + unit_test = getattr(obj, 'unit_test', '') + if unit_test and 'cprogram' in obj.features: + try: + output = obj.path + filename = os.path.join(output.abspath(obj.env), obj.target) + srcdir = output.abspath() + label = os.path.join(output.bldpath(obj.env), obj.target) + self.max_label_length = max(self.max_label_length, len(label)) + self.unit_tests[label] = (filename, srcdir) + except KeyError: + pass + self.total_num_tests = len(self.unit_tests) + # Now run the unit tests + Utils.pprint('GREEN', 'Running the unit tests') + count = 0 + result = 1 + + for label in self.unit_tests.allkeys: + file_and_src = self.unit_tests[label] + filename = file_and_src[0] + srcdir = file_and_src[1] + count += 1 + line = Build.bld.progress_line(count, self.total_num_tests, Logs.colors.GREEN, Logs.colors.NORMAL) + if Options.options.progress_bar and line: + sys.stderr.write(line) + sys.stderr.flush() + try: + kwargs = {} + kwargs['env'] = os.environ.copy() + if self.change_to_testfile_dir: + kwargs['cwd'] = srcdir + if not self.want_to_see_test_output: + kwargs['stdout'] = Utils.pproc.PIPE # PIPE for ignoring output + if not self.want_to_see_test_error: + kwargs['stderr'] = Utils.pproc.PIPE # PIPE for ignoring output + if ld_library_path: + v = kwargs['env'] + def add_path(dct, path, var): + dct[var] = os.pathsep.join(Utils.to_list(path) + [os.environ.get(var, '')]) + if sys.platform == 'win32': + add_path(v, ld_library_path, 'PATH') + elif sys.platform == 'darwin': + add_path(v, ld_library_path, 'DYLD_LIBRARY_PATH') + add_path(v, ld_library_path, 'LD_LIBRARY_PATH') + else: + add_path(v, ld_library_path, 'LD_LIBRARY_PATH') + + pp = Utils.pproc.Popen(filename, **kwargs) + pp.wait() + + result = int(pp.returncode == self.returncode_ok) + + if result: + self.num_tests_ok += 1 + else: + self.num_tests_failed += 1 + + self.unit_test_results[label] = result + self.unit_test_erroneous[label] = 0 + except OSError: + self.unit_test_erroneous[label] = 1 + self.num_tests_err += 1 + except KeyboardInterrupt: + pass + if Options.options.progress_bar: sys.stdout.write(Logs.colors.cursor_on) + + def print_results(self): + "Pretty-prints a summary of all unit tests, along with some statistics" + + # If waf is not building, don't output anything + if not Options.commands[self.run_if_waf_does]: return + + p = Utils.pprint + # Early quit if no tests were performed + if self.total_num_tests == 0: + p('YELLOW', 'No unit tests present') + return + + for label in self.unit_tests.allkeys: + filename = self.unit_tests[label] + err = 0 + result = 0 + + try: err = self.unit_test_erroneous[label] + except KeyError: pass + + try: result = self.unit_test_results[label] + except KeyError: pass + + n = self.max_label_length - len(label) + if err: n += 4 + elif result: n += 7 + else: n += 3 + + line = '%s %s' % (label, '.' * n) + + if err: p('RED', '%sERROR' % line) + elif result: p('GREEN', '%sOK' % line) + else: p('YELLOW', '%sFAILED' % line) + + percentage_ok = float(self.num_tests_ok) / float(self.total_num_tests) * 100.0 + percentage_failed = float(self.num_tests_failed) / float(self.total_num_tests) * 100.0 + percentage_erroneous = float(self.num_tests_err) / float(self.total_num_tests) * 100.0 + + p('NORMAL', ''' +Successful tests: %i (%.1f%%) +Failed tests: %i (%.1f%%) +Erroneous tests: %i (%.1f%%) + +Total number of tests: %i +''' % (self.num_tests_ok, percentage_ok, self.num_tests_failed, percentage_failed, + self.num_tests_err, percentage_erroneous, self.total_num_tests)) + p('GREEN', 'Unit tests finished') + + +############################################################################################ + +""" +New unit test system + +The targets with feature 'test' are executed after they are built +bld.new_task_gen(features='cprogram cc test', ...) + +To display the results: +import UnitTest +bld.add_post_fun(UnitTest.summary) +""" + +import threading +testlock = threading.Lock() + +@TaskGen.feature('test') +@TaskGen.after('apply_link') +def make_test(self): + if not 'cprogram' in self.features: + Logs.error('test cannot be executed %s' % self) + return + + tsk = self.create_task('utest') + tsk.set_inputs(self.link_task.outputs) + +def exec_test(self): + fail = False + try: + testlock.acquire() + + filename = self.inputs[0].abspath(self.env) + try: + ret = Utils.cmd_output(filename, cwd='/cygdrive/c/home/waf-1.5.8/demos/unit_test/tests/test0') + except Exception, e: + fail = True + ret = "" + else: + pass + + stats = getattr(self.generator.bld, 'utest_results', []) + stats.append((filename, fail, ret)) + self.generator.bld.utest_results = stats + + testlock.release() + except Exception, e: + print e + +cls = Task.task_type_from_func('utest', func=exec_test, color='RED', ext_in='.bin') + +old = cls.runnable_status +def test_status(self): + if getattr(Options.options, 'all_tests', False): + return RUN_ME + return old(self) + +cls.runnable_status = test_status +cls.quiet = 1 + +def summary(bld): + lst = getattr(bld, 'utest_results', []) + if lst: + Utils.pprint('CYAN', 'execution summary') + for (f, fail, ret) in lst: + col = fail and 'RED' or 'GREEN' + Utils.pprint(col, (fail and 'FAIL' or 'ok') + " " + f) + +def set_options(opt): + opt.add_option('--alltests', action='store_true', default=False, help='Exec all unit tests', dest='all_tests') + diff --git a/tools/wafadmin/Tools/__init__.py b/tools/wafadmin/Tools/__init__.py new file mode 100644 index 000000000..bc6ca230b --- /dev/null +++ b/tools/wafadmin/Tools/__init__.py @@ -0,0 +1,4 @@ +#!/usr/bin/env python +# encoding: utf-8 +# Thomas Nagy, 2006 (ita) + diff --git a/tools/wafadmin/Tools/ar.py b/tools/wafadmin/Tools/ar.py new file mode 100644 index 000000000..626f8e95a --- /dev/null +++ b/tools/wafadmin/Tools/ar.py @@ -0,0 +1,36 @@ +#!/usr/bin/env python +# encoding: utf-8 +# Thomas Nagy, 2006-2008 (ita) +# Ralf Habacker, 2006 (rh) + +"ar and ranlib" + +import os, sys +import Task, Utils +from Configure import conftest + +ar_str = '${AR} ${ARFLAGS} ${AR_TGT_F}${TGT} ${AR_SRC_F}${SRC}' +cls = Task.simple_task_type('static_link', ar_str, color='YELLOW', ext_in='.o', shell=False) +cls.maxjobs = 1 +cls.install = Utils.nada + +# remove the output in case it already exists +old = cls.run +def wrap(self): + try: os.remove(self.outputs[0].abspath(self.env)) + except OSError: pass + return old(self) +setattr(cls, 'run', wrap) + +def detect(conf): + conf.find_program('ar', var='AR') + conf.find_program('ranlib', var='RANLIB') + conf.env.ARFLAGS = 'rcs' + +@conftest +def find_ar(conf): + v = conf.env + conf.check_tool('ar') + if not v['AR']: conf.fatal('ar is required for static libraries - not found') + + diff --git a/tools/wafadmin/Tools/bison.py b/tools/wafadmin/Tools/bison.py new file mode 100644 index 000000000..d56fc706d --- /dev/null +++ b/tools/wafadmin/Tools/bison.py @@ -0,0 +1,42 @@ +#!/usr/bin/env python +# encoding: utf-8 +# John O'Meara, 2006 +# Thomas Nagy 2009 + +"Bison processing" + +import Task +from TaskGen import extension + +bison = '${BISON} ${BISONFLAGS} ${SRC[0].abspath()} -o ${TGT[0].name}' +cls = Task.simple_task_type('bison', bison, 'GREEN', ext_in='.yc .y .yy', ext_out='.c .cxx .h .l', before='cxx', shell=False) + +@extension(['.y', '.yc', '.yy']) +def big_bison(self, node): + """when it becomes complicated (unlike flex), the old recipes work better (cwd)""" + tsk = self.create_task('bison') + tsk.set_inputs(node) + + has_h = '-d' in self.env['BISONFLAGS'] + + outs = [] + if node.name.endswith('.yc'): + outs.append(node.change_ext('.tab.cc')) + if has_h: + outs.append(node.change_ext('.tab.hh')) + else: + outs.append(node.change_ext('.tab.c')) + if has_h: + outs.append(node.change_ext('.tab.h')) + + tsk.set_outputs(outs) + tsk.cwd = node.bld_dir(tsk.env) + + # and the c/cxx file must be compiled too + self.allnodes.append(outs[0]) + +def detect(conf): + bison = conf.find_program('bison', var='BISON', mandatory=True) + v = conf.env + v['BISONFLAGS'] = '-d' + diff --git a/tools/wafadmin/Tools/boost.py b/tools/wafadmin/Tools/boost.py new file mode 100644 index 000000000..47e4500d2 --- /dev/null +++ b/tools/wafadmin/Tools/boost.py @@ -0,0 +1,335 @@ +#!/usr/bin/env python +# encoding: utf-8 +# +# partially based on boost.py written by Gernot Vormayr +# written by Ruediger Sonderfeld <ruediger@c-plusplus.de>, 2008 +# modified by Bjoern Michaelsen, 2008 +# modified by Luca Fossati, 2008 +# rewritten for waf 1.5.1, Thomas Nagy, 2008 +# +#def set_options(opt): +# opt.tool_options('boost') +# # ... +# +#def configure(conf): +# # ... (e.g. conf.check_tool('g++')) +# conf.check_tool('boost') +# conf.check_boost(lib='signals filesystem', static='onlystatic', score_version=(-1000, 1000), tag_minscore=1000) +# +#def build(bld): +# bld.new_task_gen(source='main.c', target='bar', uselib="BOOST BOOST_SYSTEM") +# +#ISSUES: +# * find_includes should be called only once! +# * support mandatory + +######## boost update ########### +## ITA: * the method get_boost_version_number does work +## * the rest of the code has not really been tried +# * make certain a demo is provided (in demos/adv for example) + +# TODO: boost.py will be removed in waf 1.6 + +import os.path, glob, types, re, sys +import Configure, config_c, Options, Utils, Logs +from Logs import warn +from Configure import conf + +boost_code = ''' +#include <iostream> +#include <boost/version.hpp> +int main() { std::cout << BOOST_VERSION << std::endl; } +''' + +boost_libpath = ['/usr/lib', '/usr/local/lib', '/opt/local/lib', '/sw/lib', '/lib'] +boost_cpppath = ['/usr/include', '/usr/local/include', '/opt/local/include', '/sw/include'] + +STATIC_NOSTATIC = 'nostatic' +STATIC_BOTH = 'both' +STATIC_ONLYSTATIC = 'onlystatic' + +is_versiontag = re.compile('^\d+_\d+_?\d*$') +is_threadingtag = re.compile('^mt$') +is_abitag = re.compile('^[sgydpn]+$') +is_toolsettag = re.compile('^(acc|borland|como|cw|dmc|darwin|gcc|hp_cxx|intel|kylix|vc|mgw|qcc|sun|vacpp)\d*$') + +def set_options(opt): + opt.add_option('--boost-includes', type='string', default='', dest='boostincludes', help='path to the boost directory where the includes are e.g. /usr/local/include/boost-1_35') + opt.add_option('--boost-libs', type='string', default='', dest='boostlibs', help='path to the directory where the boost libs are e.g. /usr/local/lib') + +def string_to_version(s): + version = s.split('.') + if len(version) < 3: return 0 + return int(version[0])*100000 + int(version[1])*100 + int(version[2]) + +def version_string(version): + major = version / 100000 + minor = version / 100 % 1000 + minor_minor = version % 100 + if minor_minor == 0: + return "%d_%d" % (major, minor) + else: + return "%d_%d_%d" % (major, minor, minor_minor) + +def libfiles(lib, pattern, lib_paths): + result = [] + for lib_path in lib_paths: + libname = pattern % ('boost_' + lib + '*') + result += glob.glob(lib_path + '/' + libname) + return result + +@conf +def get_boost_version_number(self, dir): + """silently retrieve the boost version number""" + try: + return self.run_c_code(compiler='cxx', code=boost_code, includes=dir, execute=1, env=self.env.copy(), type='cprogram', compile_mode='cxx', compile_filename='test.cpp') + except Configure.ConfigurationError, e: + return -1 + +def set_default(kw, var, val): + if not var in kw: + kw[var] = val + +def tags_score(tags, kw): + """ + checks library tags + + see http://www.boost.org/doc/libs/1_35_0/more/getting_started/unix-variants.html 6.1 + """ + score = 0 + needed_tags = { + 'threading': kw['tag_threading'], + 'abi': kw['tag_abi'], + 'toolset': kw['tag_toolset'], + 'version': kw['tag_version'] + } + + if kw['tag_toolset'] is None: + v = kw['env'] + toolset = v['CXX_NAME'] + if v['CXX_VERSION']: + version_no = v['CXX_VERSION'].split('.') + toolset += version_no[0] + if len(version_no) > 1: + toolset += version_no[1] + needed_tags['toolset'] = toolset + + found_tags = {} + for tag in tags: + if is_versiontag.match(tag): found_tags['version'] = tag + if is_threadingtag.match(tag): found_tags['threading'] = tag + if is_abitag.match(tag): found_tags['abi'] = tag + if is_toolsettag.match(tag): found_tags['toolset'] = tag + + for tagname in needed_tags.iterkeys(): + if needed_tags[tagname] is not None and tagname in found_tags: + if re.compile(needed_tags[tagname]).match(found_tags[tagname]): + score += kw['score_' + tagname][0] + else: + score += kw['score_' + tagname][1] + return score + +@conf +def validate_boost(self, kw): + ver = kw.get('version', '') + + for x in 'min_version max_version version'.split(): + set_default(kw, x, ver) + + set_default(kw, 'lib', '') + kw['lib'] = Utils.to_list(kw['lib']) + + set_default(kw, 'env', self.env) + + set_default(kw, 'libpath', boost_libpath) + set_default(kw, 'cpppath', boost_cpppath) + + for x in 'tag_threading tag_version tag_toolset'.split(): + set_default(kw, x, None) + set_default(kw, 'tag_abi', '^[^d]*$') + + set_default(kw, 'score_threading', (10, -10)) + set_default(kw, 'score_abi', (10, -10)) + set_default(kw, 'score_toolset', (1, -1)) + set_default(kw, 'score_version', (100, -100)) + + set_default(kw, 'score_min', 0) + set_default(kw, 'static', STATIC_NOSTATIC) + set_default(kw, 'found_includes', False) + set_default(kw, 'min_score', 0) + + set_default(kw, 'errmsg', 'not found') + set_default(kw, 'okmsg', 'ok') + +@conf +def find_boost_includes(self, kw): + """ + check every path in kw['cpppath'] for subdir + that either starts with boost- or is named boost. + + Then the version is checked and selected accordingly to + min_version/max_version. The highest possible version number is + selected! + + If no versiontag is set the versiontag is set accordingly to the + selected library and CPPPATH_BOOST is set. + """ + boostPath = getattr(Options.options, 'boostincludes', '') + if boostPath: + boostPath = [os.path.normpath(os.path.expandvars(os.path.expanduser(boostPath)))] + else: + boostPath = Utils.to_list(kw['cpppath']) + + min_version = string_to_version(kw.get('min_version', '')) + max_version = string_to_version(kw.get('max_version', '')) or (sys.maxint - 1) + + version = 0 + for include_path in boostPath: + boost_paths = glob.glob(os.path.join(include_path, 'boost*')) + for path in boost_paths: + pathname = os.path.split(path)[-1] + ret = -1 + if pathname == 'boost': + path = include_path + ret = self.get_boost_version_number(path) + elif pathname.startswith('boost-'): + ret = self.get_boost_version_number(path) + ret = int(ret) + + if ret != -1 and ret >= min_version and ret <= max_version and ret > version: + boost_path = path + version = ret + if not version: + self.fatal('boost headers not found! (required version min: %s max: %s)' + % (kw['min_version'], kw['max_version'])) + return False + + found_version = version_string(version) + versiontag = '^' + found_version + '$' + if kw['tag_version'] is None: + kw['tag_version'] = versiontag + elif kw['tag_version'] != versiontag: + warn('boost header version %r and tag_version %r do not match!' % (versiontag, kw['tag_version'])) + env = self.env + env['CPPPATH_BOOST'] = boost_path + env['BOOST_VERSION'] = found_version + self.found_includes = 1 + ret = 'Version %s (%s)' % (found_version, boost_path) + return ret + +@conf +def find_boost_library(self, lib, kw): + + def find_library_from_list(lib, files): + lib_pattern = re.compile('.*boost_(.*?)\..*') + result = (None, None) + resultscore = kw['min_score'] - 1 + for file in files: + m = lib_pattern.search(file, 1) + if m: + libname = m.group(1) + libtags = libname.split('-')[1:] + currentscore = tags_score(libtags, kw) + if currentscore > resultscore: + result = (libname, file) + resultscore = currentscore + return result + + lib_paths = getattr(Options.options, 'boostlibs', '') + if lib_paths: + lib_paths = [os.path.normpath(os.path.expandvars(os.path.expanduser(lib_paths)))] + else: + lib_paths = Utils.to_list(kw['libpath']) + + v = kw.get('env', self.env) + + (libname, file) = (None, None) + if kw['static'] in [STATIC_NOSTATIC, STATIC_BOTH]: + st_env_prefix = 'LIB' + files = libfiles(lib, v['shlib_PATTERN'], lib_paths) + (libname, file) = find_library_from_list(lib, files) + if libname is None and kw['static'] in [STATIC_ONLYSTATIC, STATIC_BOTH]: + st_env_prefix = 'STATICLIB' + staticLibPattern = v['staticlib_PATTERN'] + if self.env['CC_NAME'] == 'msvc': + staticLibPattern = 'lib' + staticLibPattern + files = libfiles(lib, staticLibPattern, lib_paths) + (libname, file) = find_library_from_list(lib, files) + if libname is not None: + v['LIBPATH_BOOST_' + lib.upper()] = [os.path.split(file)[0]] + if self.env['CC_NAME'] == 'msvc' and os.path.splitext(file)[1] == '.lib': + v[st_env_prefix + '_BOOST_' + lib.upper()] = ['libboost_'+libname] + else: + v[st_env_prefix + '_BOOST_' + lib.upper()] = ['boost_'+libname] + return + self.fatal('lib boost_' + lib + ' not found!') + +@conf +def check_boost(self, *k, **kw): + """ + This should be the main entry point + +- min_version +- max_version +- version +- include_path +- lib_path +- lib +- toolsettag - None or a regexp +- threadingtag - None or a regexp +- abitag - None or a regexp +- versiontag - WARNING: you should rather use version or min_version/max_version +- static - look for static libs (values: + 'nostatic' or STATIC_NOSTATIC - ignore static libs (default) + 'both' or STATIC_BOTH - find static libs, too + 'onlystatic' or STATIC_ONLYSTATIC - find only static libs +- score_version +- score_abi +- scores_threading +- score_toolset + * the scores are tuples (match_score, nomatch_score) + match_score is the added to the score if the tag is matched + nomatch_score is added when a tag is found and does not match +- min_score + """ + + if not self.env['CXX']: + self.fatal('load a c++ compiler tool first, for example conf.check_tool("g++")') + self.validate_boost(kw) + ret = None + try: + if not kw.get('found_includes', None): + self.check_message_1(kw.get('msg_includes', 'boost headers')) + ret = self.find_boost_includes(kw) + + except Configure.ConfigurationError, e: + if 'errmsg' in kw: + self.check_message_2(kw['errmsg'], 'YELLOW') + if 'mandatory' in kw: + if Logs.verbose > 1: + raise + else: + self.fatal('the configuration failed (see %r)' % self.log.name) + else: + if 'okmsg' in kw: + self.check_message_2(kw.get('okmsg_includes', ret)) + + for lib in kw['lib']: + self.check_message_1('library boost_'+lib) + try: + self.find_boost_library(lib, kw) + except Configure.ConfigurationError, e: + ret = False + if 'errmsg' in kw: + self.check_message_2(kw['errmsg'], 'YELLOW') + if 'mandatory' in kw: + if Logs.verbose > 1: + raise + else: + self.fatal('the configuration failed (see %r)' % self.log.name) + else: + if 'okmsg' in kw: + self.check_message_2(kw['okmsg']) + + return ret + diff --git a/tools/wafadmin/Tools/cc.py b/tools/wafadmin/Tools/cc.py new file mode 100644 index 000000000..0d6f037b1 --- /dev/null +++ b/tools/wafadmin/Tools/cc.py @@ -0,0 +1,102 @@ +#!/usr/bin/env python +# encoding: utf-8 +# Thomas Nagy, 2006 (ita) + +"Base for c programs/libraries" + +import os +import TaskGen, Build, Utils, Task +from Logs import debug +import ccroot +from TaskGen import feature, before, extension, after + +g_cc_flag_vars = [ +'CCDEPS', 'FRAMEWORK', 'FRAMEWORKPATH', +'STATICLIB', 'LIB', 'LIBPATH', 'LINKFLAGS', 'RPATH', +'CCFLAGS', 'CPPPATH', 'CPPFLAGS', 'CCDEFINES'] + +EXT_CC = ['.c'] + +g_cc_type_vars = ['CCFLAGS', 'LINKFLAGS'] + +# TODO remove in waf 1.6 +class cc_taskgen(ccroot.ccroot_abstract): + pass + +@feature('cc') +@before('apply_type_vars') +@after('default_cc') +def init_cc(self): + self.p_flag_vars = set(self.p_flag_vars).union(g_cc_flag_vars) + self.p_type_vars = set(self.p_type_vars).union(g_cc_type_vars) + + if not self.env['CC_NAME']: + raise Utils.WafError("At least one compiler (gcc, ..) must be selected") + +@feature('cc') +@after('apply_incpaths') +def apply_obj_vars_cc(self): + """after apply_incpaths for INC_PATHS""" + env = self.env + app = env.append_unique + cpppath_st = env['CPPPATH_ST'] + + # local flags come first + # set the user-defined includes paths + for i in env['INC_PATHS']: + app('_CCINCFLAGS', cpppath_st % i.bldpath(env)) + app('_CCINCFLAGS', cpppath_st % i.srcpath(env)) + + # set the library include paths + for i in env['CPPPATH']: + app('_CCINCFLAGS', cpppath_st % i) + +@feature('cc') +@after('apply_lib_vars') +def apply_defines_cc(self): + """after uselib is set for CCDEFINES""" + self.defines = getattr(self, 'defines', []) + lst = self.to_list(self.defines) + self.to_list(self.env['CCDEFINES']) + milst = [] + + # now process the local defines + for defi in lst: + if not defi in milst: + milst.append(defi) + + # CCDEFINES_ + libs = self.to_list(self.uselib) + for l in libs: + val = self.env['CCDEFINES_'+l] + if val: milst += val + self.env['DEFLINES'] = ["%s %s" % (x[0], Utils.trimquotes('='.join(x[1:]))) for x in [y.split('=') for y in milst]] + y = self.env['CCDEFINES_ST'] + self.env['_CCDEFFLAGS'] = [y%x for x in milst] + +@extension(EXT_CC) +def c_hook(self, node): + # create the compilation task: cpp or cc + task = self.create_task('cc') + if getattr(self, 'obj_ext', None): + obj_ext = self.obj_ext + else: + obj_ext = '_%d.o' % self.idx + + task.inputs = [node] + task.outputs = [node.change_ext(obj_ext)] + try: + self.compiled_tasks.append(task) + except AttributeError: + raise Utils.WafError('Have you forgotten to set the feature "cc" on %s?' % str(self)) + return task + +cc_str = '${CC} ${CCFLAGS} ${CPPFLAGS} ${_CCINCFLAGS} ${_CCDEFFLAGS} ${CC_SRC_F}${SRC} ${CC_TGT_F}${TGT}' +cls = Task.simple_task_type('cc', cc_str, 'GREEN', ext_out='.o', ext_in='.c', shell=False) +cls.scan = ccroot.scan +cls.vars.append('CCDEPS') + +link_str = '${LINK_CC} ${CCLNK_SRC_F}${SRC} ${CCLNK_TGT_F}${TGT[0].abspath(env)} ${LINKFLAGS}' +cls = Task.simple_task_type('cc_link', link_str, color='YELLOW', ext_in='.o', ext_out='.bin', shell=False) +cls.maxjobs = 1 +cls.install = Utils.nada + diff --git a/tools/wafadmin/Tools/ccroot.py b/tools/wafadmin/Tools/ccroot.py new file mode 100644 index 000000000..2f71e0c0a --- /dev/null +++ b/tools/wafadmin/Tools/ccroot.py @@ -0,0 +1,613 @@ +#!/usr/bin/env python +# encoding: utf-8 +# Thomas Nagy, 2005-2008 (ita) + +"base for all c/c++ programs and libraries" + +import os, sys, re +import TaskGen, Task, Utils, preproc, Logs, Build, Options +from Logs import error, debug, warn +from Utils import md5 +from TaskGen import taskgen, after, before, feature +from Constants import * +try: + from cStringIO import StringIO +except ImportError: + from io import StringIO + +import config_c # <- necessary for the configuration, do not touch + +USE_TOP_LEVEL = False + +def get_cc_version(conf, cc, gcc=False, icc=False): + + cmd = cc + ['-dM', '-E', '-'] + try: + p = Utils.pproc.Popen(cmd, stdin=Utils.pproc.PIPE, stdout=Utils.pproc.PIPE, stderr=Utils.pproc.PIPE) + p.stdin.write('\n') + out = p.communicate()[0] + except: + conf.fatal('could not determine the compiler version %r' % cmd) + + # PY3K: do not touch + out = str(out) + + if gcc: + if out.find('__INTEL_COMPILER') >= 0: + conf.fatal('The intel compiler pretends to be gcc') + if out.find('__GNUC__') < 0: + conf.fatal('Could not determine the compiler type') + + if icc and out.find('__INTEL_COMPILER') < 0: + conf.fatal('Not icc/icpc') + + k = {} + if icc or gcc: + out = out.split('\n') + import shlex + + for line in out: + lst = shlex.split(line) + if len(lst)>2: + key = lst[1] + val = lst[2] + k[key] = val + + def isD(var): + return var in k + + def isT(var): + return var in k and k[var] != '0' + + # Some documentation is available at http://predef.sourceforge.net + # The names given to DEST_OS must match what Utils.unversioned_sys_platform() returns. + mp1 = { + '__linux__' : 'linux', + '__GNU__' : 'hurd', + '__FreeBSD__' : 'freebsd', + '__NetBSD__' : 'netbsd', + '__OpenBSD__' : 'openbsd', + '__sun' : 'sunos', + '__hpux' : 'hpux', + '__sgi' : 'irix', + '_AIX' : 'aix', + '__CYGWIN__' : 'cygwin', + '__MSYS__' : 'msys', + '_UWIN' : 'uwin', + '_WIN64' : 'win32', + '_WIN32' : 'win32', + } + + for i in mp1: + if isD(i): + conf.env.DEST_OS = mp1[i] + break + else: + if isD('__APPLE__') and isD('__MACH__'): + conf.env.DEST_OS = 'darwin' + elif isD('__unix__'): # unix must be tested last as it's a generic fallback + conf.env.DEST_OS = 'generic' + + if isD('__ELF__'): + conf.env.DEST_BINFMT = 'elf' + + mp2 = { + '__x86_64__' : 'x86_64', + '__i386__' : 'x86', + '__ia64__' : 'ia', + '__mips__' : 'mips', + '__sparc__' : 'sparc', + '__alpha__' : 'alpha', + '__arm__' : 'arm', + '__hppa__' : 'hppa', + '__powerpc__' : 'powerpc', + } + for i in mp2: + if isD(i): + conf.env.DEST_CPU = mp2[i] + break + + debug('ccroot: dest platform: ' + ' '.join([conf.env[x] or '?' for x in ('DEST_OS', 'DEST_BINFMT', 'DEST_CPU')])) + conf.env['CC_VERSION'] = (k['__GNUC__'], k['__GNUC_MINOR__'], k['__GNUC_PATCHLEVEL__']) + return k + +class DEBUG_LEVELS: + """Will disappear in waf 1.6""" + ULTRADEBUG = "ultradebug" + DEBUG = "debug" + RELEASE = "release" + OPTIMIZED = "optimized" + CUSTOM = "custom" + + ALL = [ULTRADEBUG, DEBUG, RELEASE, OPTIMIZED, CUSTOM] + +def scan(self): + "look for .h the .cpp need" + debug('ccroot: _scan_preprocessor(self, node, env, path_lst)') + + # TODO waf 1.6 - assume the default input has exactly one file + + if len(self.inputs) == 1: + node = self.inputs[0] + (nodes, names) = preproc.get_deps(node, self.env, nodepaths = self.env['INC_PATHS']) + if Logs.verbose: + debug('deps: deps for %s: %r; unresolved %r' % (str(node), nodes, names)) + return (nodes, names) + + all_nodes = [] + all_names = [] + seen = [] + for node in self.inputs: + (nodes, names) = preproc.get_deps(node, self.env, nodepaths = self.env['INC_PATHS']) + if Logs.verbose: + debug('deps: deps for %s: %r; unresolved %r' % (str(node), nodes, names)) + for x in nodes: + if id(x) in seen: continue + seen.append(id(x)) + all_nodes.append(x) + for x in names: + if not x in all_names: + all_names.append(x) + return (all_nodes, all_names) + +class ccroot_abstract(TaskGen.task_gen): + "Parent class for programs and libraries in languages c, c++ and moc (Qt)" + def __init__(self, *k, **kw): + # COMPAT remove in waf 1.6 TODO + if len(k) > 1: + k = list(k) + if k[1][0] != 'c': + k[1] = 'c' + k[1] + TaskGen.task_gen.__init__(self, *k, **kw) + +def get_target_name(self): + tp = 'program' + for x in self.features: + if x in ['cshlib', 'cstaticlib']: + tp = x.lstrip('c') + + pattern = self.env[tp + '_PATTERN'] + if not pattern: pattern = '%s' + + dir, name = os.path.split(self.target) + + if self.env.DEST_BINFMT == 'pe' and getattr(self, 'vnum', None) and 'cshlib' in self.features: + # include the version in the dll file name, + # the import lib file name stays unversionned. + name = name + '-' + self.vnum.split('.')[0] + + return os.path.join(dir, pattern % name) + +@feature('cc', 'cxx') +@before('apply_core') +def default_cc(self): + """compiled_tasks attribute must be set before the '.c->.o' tasks can be created""" + Utils.def_attrs(self, + includes = '', + defines= '', + rpaths = '', + uselib = '', + uselib_local = '', + add_objects = '', + p_flag_vars = [], + p_type_vars = [], + compiled_tasks = [], + link_task = None) + + # The only thing we need for cross-compilation is DEST_BINFMT. + # At some point, we may reach a case where DEST_BINFMT is not enough, but for now it's sufficient. + # Currently, cross-compilation is auto-detected only for the gnu and intel compilers. + if not self.env.DEST_BINFMT: + # Infer the binary format from the os name. + self.env.DEST_BINFMT = Utils.unversioned_sys_platform_to_binary_format( + self.env.DEST_OS or Utils.unversioned_sys_platform()) + + if not self.env.BINDIR: self.env.BINDIR = Utils.subst_vars('${PREFIX}/bin', self.env) + if not self.env.LIBDIR: self.env.LIBDIR = Utils.subst_vars('${PREFIX}/lib${LIB_EXT}', self.env) + +@feature('cprogram', 'dprogram', 'cstaticlib', 'dstaticlib', 'cshlib', 'dshlib') +def apply_verif(self): + """no particular order, used for diagnostic""" + if not (self.source or getattr(self, 'add_objects', None)): + raise Utils.WafError('no source files specified for %s' % self) + if not self.target: + raise Utils.WafError('no target for %s' % self) + +# TODO reference the d programs, shlibs in d.py, not here + +@feature('cprogram', 'dprogram') +@after('default_cc') +@before('apply_core') +def vars_target_cprogram(self): + self.default_install_path = self.env.BINDIR + self.default_chmod = O755 + +@after('default_cc') +@feature('cshlib', 'dshlib') +@before('apply_core') +def vars_target_cshlib(self): + if self.env.DEST_BINFMT == 'pe': + # set execute bit on libs to avoid 'permission denied' (issue 283) + self.default_chmod = O755 + self.default_install_path = self.env.BINDIR + else: + self.default_install_path = self.env.LIBDIR + +@feature('cprogram', 'dprogram', 'cstaticlib', 'dstaticlib', 'cshlib', 'dshlib') +@after('apply_link', 'vars_target_cprogram', 'vars_target_cshlib') +def default_link_install(self): + """you may kill this method to inject your own installation for the first element + any other install should only process its own nodes and not those from the others""" + if self.install_path: + self.bld.install_files(self.install_path, [self.link_task.outputs[0]], env=self.env, chmod=self.chmod) + +@feature('cc', 'cxx') +@after('apply_type_vars', 'apply_lib_vars', 'apply_core') +def apply_incpaths(self): + """used by the scanner + after processing the uselib for CPPPATH + after apply_core because some processing may add include paths + """ + lst = [] + # TODO move the uselib processing out of here + for lib in self.to_list(self.uselib): + for path in self.env['CPPPATH_' + lib]: + if not path in lst: + lst.append(path) + if preproc.go_absolute: + for path in preproc.standard_includes: + if not path in lst: + lst.append(path) + + for path in self.to_list(self.includes): + if not path in lst: + if preproc.go_absolute or not os.path.isabs(path): + lst.append(path) + else: + self.env.prepend_value('CPPPATH', path) + + for path in lst: + node = None + if os.path.isabs(path): + if preproc.go_absolute: + node = self.bld.root.find_dir(path) + elif path[0] == '#': + node = self.bld.srcnode + if len(path) > 1: + node = node.find_dir(path[1:]) + else: + node = self.path.find_dir(path) + + if node: + self.env.append_value('INC_PATHS', node) + + # TODO WAF 1.6 + if USE_TOP_LEVEL: + self.env.append_value('INC_PATHS', self.bld.srcnode) + +@feature('cc', 'cxx') +@after('init_cc', 'init_cxx') +@before('apply_lib_vars') +def apply_type_vars(self): + """before apply_lib_vars because we modify uselib + after init_cc and init_cxx because web need p_type_vars + """ + for x in self.features: + if not x in ['cprogram', 'cstaticlib', 'cshlib']: + continue + x = x.lstrip('c') + + # if the type defines uselib to add, add them + st = self.env[x + '_USELIB'] + if st: self.uselib = self.uselib + ' ' + st + + # each compiler defines variables like 'shlib_CXXFLAGS', 'shlib_LINKFLAGS', etc + # so when we make a task generator of the type shlib, CXXFLAGS are modified accordingly + for var in self.p_type_vars: + compvar = '%s_%s' % (x, var) + #print compvar + value = self.env[compvar] + if value: self.env.append_value(var, value) + +@feature('cprogram', 'cshlib', 'cstaticlib') +@after('apply_core') +def apply_link(self): + """executes after apply_core for collecting 'compiled_tasks' + use a custom linker if specified (self.link='name-of-custom-link-task')""" + link = getattr(self, 'link', None) + if not link: + if 'cstaticlib' in self.features: link = 'static_link' + elif 'cxx' in self.features: link = 'cxx_link' + else: link = 'cc_link' + + tsk = self.create_task(link) + outputs = [t.outputs[0] for t in self.compiled_tasks] + tsk.set_inputs(outputs) + tsk.set_outputs(self.path.find_or_declare(get_target_name(self))) + + self.link_task = tsk + +@feature('cc', 'cxx') +@after('apply_link', 'init_cc', 'init_cxx') +def apply_lib_vars(self): + """after apply_link because of 'link_task' + after default_cc because of the attribute 'uselib'""" + env = self.env + + # 1. the case of the libs defined in the project (visit ancestors first) + # the ancestors external libraries (uselib) will be prepended + uselib = self.to_list(self.uselib) + names = self.to_list(self.uselib_local) + + seen = [] + tmp = names[:] # consume a copy of the list of names + while tmp: + lib_name = tmp.pop(0) + # visit dependencies only once + if lib_name in seen: + continue + + y = self.name_to_obj(lib_name) + if not y: + raise Utils.WafError('object %r was not found in uselib_local (required by %r)' % (lib_name, self.name)) + y.post() + seen.append(lib_name) + + # object has ancestors to process (shared libraries): add them to the end of the list + if getattr(y, 'uselib_local', None): + lst = y.to_list(y.uselib_local) + if 'cshlib' in y.features or 'cprogram' in y.features: + lst = [x for x in lst if not 'cstaticlib' in self.name_to_obj(x).features] + tmp.extend(lst) + + # link task and flags + if getattr(y, 'link_task', None): + + link_name = y.target[y.target.rfind(os.sep) + 1:] + if 'cstaticlib' in y.features: + env.append_value('STATICLIB', link_name) + elif 'cshlib' in y.features or 'cprogram' in y.features: + # WARNING some linkers can link against programs + env.append_value('LIB', link_name) + + # the order + self.link_task.set_run_after(y.link_task) + + # for the recompilation + dep_nodes = getattr(self.link_task, 'dep_nodes', []) + self.link_task.dep_nodes = dep_nodes + y.link_task.outputs + + # add the link path too + tmp_path = y.link_task.outputs[0].parent.bldpath(self.env) + if not tmp_path in env['LIBPATH']: env.prepend_value('LIBPATH', tmp_path) + + # add ancestors uselib too + # WARNING providing STATICLIB_FOO in env will result in broken builds + # TODO waf 1.6 prevent this behaviour somehow + for v in self.to_list(y.uselib): + if v in uselib: continue + uselib = [v] + uselib + + # if the library task generator provides 'export_incdirs', add to the include path + # the export_incdirs must be a list of paths relative to the other library + if getattr(y, 'export_incdirs', None): + for x in self.to_list(y.export_incdirs): + node = y.path.find_dir(x) + if not node: + raise Utils.WafError('object %r: invalid folder %r in export_incdirs' % (y.target, x)) + self.env.append_unique('INC_PATHS', node) + + # 2. the case of the libs defined outside + for x in uselib: + for v in self.p_flag_vars: + val = self.env[v + '_' + x] + if val: self.env.append_value(v, val) + +@feature('cprogram', 'cstaticlib', 'cshlib') +@after('apply_link') +def apply_objdeps(self): + "add the .o files produced by some other object files in the same manner as uselib_local" + if not getattr(self, 'add_objects', None): return + + seen = [] + names = self.to_list(self.add_objects) + while names: + x = names[0] + + # visit dependencies only once + if x in seen: + names = names[1:] + continue + + # object does not exist ? + y = self.name_to_obj(x) + if not y: + raise Utils.WafError('object %r was not found in uselib_local (required by add_objects %r)' % (x, self.name)) + + # object has ancestors to process first ? update the list of names + if getattr(y, 'add_objects', None): + added = 0 + lst = y.to_list(y.add_objects) + lst.reverse() + for u in lst: + if u in seen: continue + added = 1 + names = [u]+names + if added: continue # list of names modified, loop + + # safe to process the current object + y.post() + seen.append(x) + + for t in y.compiled_tasks: + self.link_task.inputs.extend(t.outputs) + +@feature('cprogram', 'cshlib', 'cstaticlib') +@after('apply_lib_vars') +def apply_obj_vars(self): + """after apply_lib_vars for uselib""" + v = self.env + lib_st = v['LIB_ST'] + staticlib_st = v['STATICLIB_ST'] + libpath_st = v['LIBPATH_ST'] + staticlibpath_st = v['STATICLIBPATH_ST'] + rpath_st = v['RPATH_ST'] + + app = v.append_unique + + if v['FULLSTATIC']: + v.append_value('LINKFLAGS', v['FULLSTATIC_MARKER']) + + for i in v['RPATH']: + if i and rpath_st: + app('LINKFLAGS', rpath_st % i) + + for i in v['LIBPATH']: + app('LINKFLAGS', libpath_st % i) + app('LINKFLAGS', staticlibpath_st % i) + + if v['STATICLIB']: + v.append_value('LINKFLAGS', v['STATICLIB_MARKER']) + k = [(staticlib_st % i) for i in v['STATICLIB']] + app('LINKFLAGS', k) + + # fully static binaries ? + if not v['FULLSTATIC']: + if v['STATICLIB'] or v['LIB']: + v.append_value('LINKFLAGS', v['SHLIB_MARKER']) + + app('LINKFLAGS', [lib_st % i for i in v['LIB']]) + +@after('apply_link') +def process_obj_files(self): + if not hasattr(self, 'obj_files'): return + for x in self.obj_files: + node = self.path.find_resource(x) + self.link_task.inputs.append(node) + +@taskgen +def add_obj_file(self, file): + """Small example on how to link object files as if they were source + obj = bld.create_obj('cc') + obj.add_obj_file('foo.o')""" + if not hasattr(self, 'obj_files'): self.obj_files = [] + if not 'process_obj_files' in self.meths: self.meths.append('process_obj_files') + self.obj_files.append(file) + +c_attrs = { +'cxxflag' : 'CXXFLAGS', +'cflag' : 'CCFLAGS', +'ccflag' : 'CCFLAGS', +'linkflag' : 'LINKFLAGS', +'ldflag' : 'LINKFLAGS', +'lib' : 'LIB', +'libpath' : 'LIBPATH', +'staticlib': 'STATICLIB', +'staticlibpath': 'STATICLIBPATH', +'rpath' : 'RPATH', +'framework' : 'FRAMEWORK', +'frameworkpath' : 'FRAMEWORKPATH' +} + +@feature('cc', 'cxx') +@before('init_cxx', 'init_cc') +@before('apply_lib_vars', 'apply_obj_vars', 'apply_incpaths', 'init_cc') +def add_extra_flags(self): + """case and plural insensitive + before apply_obj_vars for processing the library attributes + """ + for x in self.__dict__.keys(): + y = x.lower() + if y[-1] == 's': + y = y[:-1] + if c_attrs.get(y, None): + self.env.append_unique(c_attrs[y], getattr(self, x)) + +# ============ the code above must not know anything about import libs ========== + +@feature('cshlib') +@after('apply_link', 'default_cc') +@before('apply_lib_vars', 'apply_objdeps', 'default_link_install') +def apply_implib(self): + """On mswindows, handle dlls and their import libs + the .dll.a is the import lib and it is required for linking so it is installed too + """ + if not self.env.DEST_BINFMT == 'pe': + return + + self.meths.remove('default_link_install') + + bindir = self.install_path + if not bindir: return + + # install the dll in the bin dir + dll = self.link_task.outputs[0] + self.bld.install_files(bindir, [dll], self.env, self.chmod) + + # add linker flags to generate the import lib + implib = self.env['implib_PATTERN'] % os.path.split(self.target)[1] + + implib = dll.parent.find_or_declare(implib) + self.link_task.outputs.append(implib) + self.bld.install_as('${LIBDIR}/%s' % implib.name, implib, self.env) + + self.env.append_value('LINKFLAGS', (self.env['IMPLIB_ST'] % implib.bldpath(self.env)).split()) + +# ============ the code above must not know anything about vnum processing on unix platforms ========= + +@feature('cshlib') +@after('apply_link') +@before('apply_lib_vars', 'default_link_install') +def apply_vnum(self): + """ + libfoo.so is installed as libfoo.so.1.2.3 + """ + if not getattr(self, 'vnum', '') or not 'cshlib' in self.features or os.name != 'posix' or self.env.DEST_BINFMT != 'elf': + return + + self.meths.remove('default_link_install') + + link = self.link_task + nums = self.vnum.split('.') + node = link.outputs[0] + + libname = node.name + if libname.endswith('.dylib'): + name3 = libname.replace('.dylib', '.%s.dylib' % task.vnum) + name2 = libname.replace('.dylib', '.%s.dylib' % nums[0]) + else: + name3 = libname + '.' + self.vnum + name2 = libname + '.' + nums[0] + + self.env.append_value('LINKFLAGS', (self.env['SONAME_ST'] % name2).split()) + + bld = self.bld + nums = self.vnum.split('.') + + path = self.install_path + if not path: return + + bld.install_as(path + os.sep + name3, node, env=self.env) + bld.symlink_as(path + os.sep + name2, name3) + bld.symlink_as(path + os.sep + libname, name3) + + # the following task is just to enable execution from the build dir :-/ + tsk = self.create_task('vnum') + tsk.set_inputs([node]) + tsk.set_outputs(node.parent.find_or_declare(name2)) + +def exec_vnum_link(self): + path = self.inputs[0].parent.abspath(self.env) + try: + os.remove(self.outputs[0].abspath()) + except OSError, e: + pass + + try: + os.symlink(self.inputs[0].name, self.outputs[0].abspath(self.env)) + except Exception, e: + return 1 + +cls = Task.task_type_from_func('vnum', func=exec_vnum_link, ext_in='.bin', color='CYAN') +cls.quiet = 1 + diff --git a/tools/wafadmin/Tools/compiler_cc.py b/tools/wafadmin/Tools/compiler_cc.py new file mode 100644 index 000000000..3f96feab8 --- /dev/null +++ b/tools/wafadmin/Tools/compiler_cc.py @@ -0,0 +1,67 @@ +#!/usr/bin/env python +# encoding: utf-8 +# Matthias Jahn jahn dôt matthias ât freenet dôt de, 2007 (pmarat) + +import os, sys, imp, types, ccroot +import optparse +import Utils, Configure, Options +from Logs import debug + +c_compiler = { + 'win32': ['msvc', 'gcc'], + 'cygwin': ['gcc'], + 'darwin': ['gcc'], + 'aix5': ['gcc'], + 'linux': ['gcc', 'icc', 'suncc'], + 'sunos': ['gcc', 'suncc'], + 'irix': ['gcc'], + 'hpux': ['gcc'], + 'default': ['gcc'] +} + +def __list_possible_compiler(platform): + try: + return c_compiler[platform] + except KeyError: + return c_compiler["default"] + +def detect(conf): + """ + for each compiler for the platform, try to configure the compiler + in theory the tools should raise a configuration error if the compiler + pretends to be something it is not (setting CC=icc and trying to configure gcc) + """ + try: test_for_compiler = Options.options.check_c_compiler + except AttributeError: conf.fatal("Add set_options(opt): opt.tool_options('compiler_cc')") + for compiler in test_for_compiler.split(): + try: + conf.check_tool(compiler) + except Configure.ConfigurationError, e: + debug('compiler_cc: %r' % e) + else: + if conf.env['CC']: + conf.check_message(compiler, '', True) + conf.env['COMPILER_CC'] = compiler + break + conf.check_message(compiler, '', False) + +def set_options(opt): + build_platform = Utils.unversioned_sys_platform() + possible_compiler_list = __list_possible_compiler(build_platform) + test_for_compiler = ' '.join(possible_compiler_list) + cc_compiler_opts = opt.add_option_group("C Compiler Options") + cc_compiler_opts.add_option('--check-c-compiler', default="%s" % test_for_compiler, + help='On this platform (%s) the following C-Compiler will be checked by default: "%s"' % (build_platform, test_for_compiler), + dest="check_c_compiler") + + for c_compiler in test_for_compiler.split(): + opt.tool_options('%s' % c_compiler, option_group=cc_compiler_opts) + + """opt.add_option('-d', '--debug-level', + action = 'store', + default = ccroot.DEBUG_LEVELS.RELEASE, + help = "Specify the debug level, does nothing if CFLAGS is set in the environment. [Allowed Values: '%s']" % "', '".join(ccroot.DEBUG_LEVELS.ALL), + choices = ccroot.DEBUG_LEVELS.ALL, + dest = 'debug_level')""" + + diff --git a/tools/wafadmin/Tools/compiler_cxx.py b/tools/wafadmin/Tools/compiler_cxx.py new file mode 100644 index 000000000..38288e058 --- /dev/null +++ b/tools/wafadmin/Tools/compiler_cxx.py @@ -0,0 +1,61 @@ +#!/usr/bin/env python +# encoding: utf-8 +# Matthias Jahn jahn dôt matthias ât freenet dôt de 2007 (pmarat) + +import os, sys, imp, types, ccroot +import optparse +import Utils, Configure, Options +from Logs import debug + +cxx_compiler = { +'win32': ['msvc', 'g++'], +'cygwin': ['g++'], +'darwin': ['g++'], +'aix': ['g++'], +'linux': ['g++', 'icpc', 'sunc++'], +'sunos': ['g++', 'sunc++'], +'irix': ['g++'], +'hpux': ['g++'], +'default': ['g++'] +} + +def __list_possible_compiler(platform): + try: + return cxx_compiler[platform] + except KeyError: + return cxx_compiler["default"] + +def detect(conf): + try: test_for_compiler = Options.options.check_cxx_compiler + except AttributeError: raise Configure.ConfigurationError("Add set_options(opt): opt.tool_options('compiler_cxx')") + for compiler in test_for_compiler.split(): + try: + conf.check_tool(compiler) + except Configure.ConfigurationError, e: + debug('compiler_cxx: %r' % e) + else: + if conf.env['CXX']: + conf.check_message(compiler, '', True) + conf.env['COMPILER_CXX'] = compiler + break + conf.check_message(compiler, '', False) + +def set_options(opt): + build_platform = Utils.unversioned_sys_platform() + possible_compiler_list = __list_possible_compiler(build_platform) + test_for_compiler = ' '.join(possible_compiler_list) + cxx_compiler_opts = opt.add_option_group('C++ Compiler Options') + cxx_compiler_opts.add_option('--check-cxx-compiler', default="%s" % test_for_compiler, + help='On this platform (%s) the following C++ Compiler will be checked by default: "%s"' % (build_platform, test_for_compiler), + dest="check_cxx_compiler") + + for cxx_compiler in test_for_compiler.split(): + opt.tool_options('%s' % cxx_compiler, option_group=cxx_compiler_opts) + + """opt.add_option('-d', '--debug-level', + action = 'store', + default = ccroot.DEBUG_LEVELS.RELEASE, + help = "Specify the debug level, does nothing if CXXFLAGS is set in the environment. [Allowed Values: '%s']" % "', '".join(ccroot.DEBUG_LEVELS.ALL), + choices = ccroot.DEBUG_LEVELS.ALL, + dest = 'debug_level')""" + diff --git a/tools/wafadmin/Tools/compiler_d.py b/tools/wafadmin/Tools/compiler_d.py new file mode 100644 index 000000000..1ea5efa30 --- /dev/null +++ b/tools/wafadmin/Tools/compiler_d.py @@ -0,0 +1,33 @@ +#!/usr/bin/env python +# encoding: utf-8 +# Carlos Rafael Giani, 2007 (dv) + +import os, sys, imp, types +import Utils, Configure, Options + +def detect(conf): + if getattr(Options.options, 'check_dmd_first', None): + test_for_compiler = ['dmd', 'gdc'] + else: + test_for_compiler = ['gdc', 'dmd'] + + for d_compiler in test_for_compiler: + try: + conf.check_tool(d_compiler) + except: + pass + else: + break + else: + conf.fatal('no suitable d compiler was found') + +def set_options(opt): + d_compiler_opts = opt.add_option_group('D Compiler Options') + d_compiler_opts.add_option('--check-dmd-first', action='store_true', + help='checks for the gdc compiler before dmd (default is the other way round)', + dest='check_dmd_first', + default=False) + + for d_compiler in ['gdc', 'dmd']: + opt.tool_options('%s' % d_compiler, option_group=d_compiler_opts) + diff --git a/tools/wafadmin/Tools/config_c.py b/tools/wafadmin/Tools/config_c.py new file mode 100644 index 000000000..0e293d8e4 --- /dev/null +++ b/tools/wafadmin/Tools/config_c.py @@ -0,0 +1,680 @@ +#!/usr/bin/env python +# encoding: utf-8 +# Thomas Nagy, 2005-2008 (ita) + +""" +c/c++ configuration routines +""" + +import os, imp, sys, shlex, shutil +from Utils import md5 +import Build, Utils, Configure, Task, Options, Logs, TaskGen +from Constants import * +from Configure import conf, conftest + +cfg_ver = { + 'atleast-version': '>=', + 'exact-version': '==', + 'max-version': '<=', +} + +SNIP1 = ''' + int main() { + void *p; + p=(void*)(%s); + return 0; +} +''' + +SNIP2 = ''' +int main() { + if ((%(type_name)s *) 0) return 0; + if (sizeof (%(type_name)s)) return 0; +} +''' + +SNIP3 = ''' +int main() { + return 0; +} +''' + +def parse_flags(line, uselib, env): + """pkg-config still has bugs on some platforms, and there are many -config programs, parsing flags is necessary :-/""" + + lst = shlex.split(line) + while lst: + x = lst.pop(0) + st = x[:2] + ot = x[2:] + + if st == '-I' or st == '/I': + if not ot: ot = lst.pop(0) + env.append_unique('CPPPATH_' + uselib, ot) + elif st == '-D': + if not ot: ot = lst.pop(0) + env.append_unique('CXXDEFINES_' + uselib, ot) + env.append_unique('CCDEFINES_' + uselib, ot) + elif st == '-l': + if not ot: ot = lst.pop(0) + env.append_unique('LIB_' + uselib, ot) + elif st == '-L': + if not ot: ot = lst.pop(0) + env.append_unique('LIBPATH_' + uselib, ot) + elif x == '-pthread' or x.startswith('+'): + env.append_unique('CCFLAGS_' + uselib, x) + env.append_unique('CXXFLAGS_' + uselib, x) + env.append_unique('LINKFLAGS_' + uselib, x) + elif x == '-framework': + framework = lst.pop(0) + env.append_unique('FRAMEWORK_' + uselib, framework) + elif x.startswith('-std'): + env.append_unique('CCFLAGS_' + uselib, x) + env.append_unique('LINKFLAGS_' + uselib, x) + elif x.startswith('-Wl'): + env.append_unique('LINKFLAGS_' + uselib, x) + elif x.startswith('-m') or x.startswith('-f'): + env.append_unique('CCFLAGS_' + uselib, x) + env.append_unique('CXXFLAGS_' + uselib, x) + +@conf +def ret_msg(self, f, kw): + """execute a function, when provided""" + if isinstance(f, str): + return f + return f(kw) + +@conf +def validate_cfg(self, kw): + if not 'path' in kw: + kw['path'] = 'pkg-config --errors-to-stdout --print-errors' + + # pkg-config version + if 'atleast_pkgconfig_version' in kw: + if not 'msg' in kw: + kw['msg'] = 'Checking for pkg-config version >= %s' % kw['atleast_pkgconfig_version'] + return + + # pkg-config --modversion + if 'modversion' in kw: + return + + # checking for the version of a module, for the moment, one thing at a time + for x in cfg_ver.keys(): + y = x.replace('-', '_') + if y in kw: + if not 'package' in kw: + raise ValueError('%s requires a package' % x) + + if not 'msg' in kw: + kw['msg'] = 'Checking for %s %s %s' % (kw['package'], cfg_ver[x], kw[y]) + return + + if not 'msg' in kw: + kw['msg'] = 'Checking for %s' % kw['package'] + if not 'okmsg' in kw: + kw['okmsg'] = 'ok' + if not 'errmsg' in kw: + kw['errmsg'] = 'not found' + +@conf +def cmd_and_log(self, cmd, kw): + Logs.debug('runner: %s\n' % cmd) + if self.log: self.log.write('%s\n' % cmd) + + try: + p = Utils.pproc.Popen(cmd, stdout=Utils.pproc.PIPE, shell=True) + output = p.communicate()[0] + except OSError: + self.fatal('fail') + + if p.returncode: + if not kw.get('errmsg', ''): + if kw.get('mandatory', False): + kw['errmsg'] = output.strip() + else: + kw['errmsg'] = 'fail' + self.fatal('fail') + return output + +@conf +def exec_cfg(self, kw): + + # pkg-config version + if 'atleast_pkgconfig_version' in kw: + cmd = '%s --atleast-pkgconfig-version=%s' % (kw['path'], kw['atleast_pkgconfig_version']) + self.cmd_and_log(cmd, kw) + if not 'okmsg' in kw: + kw['okmsg'] = 'ok' + return + + # checking for the version of a module + for x in cfg_ver: + y = x.replace('-', '_') + if y in kw: + self.cmd_and_log('%s --%s=%s %s' % (kw['path'], x, kw[y], kw['package']), kw) + if not 'okmsg' in kw: + kw['okmsg'] = 'ok' + self.define(self.have_define(kw.get('uselib_store', kw['package'])), 1, 0) + break + + # retrieving the version of a module + if 'modversion' in kw: + version = self.cmd_and_log('%s --modversion %s' % (kw['path'], kw['modversion']), kw).strip() + self.define('%s_VERSION' % Utils.quote_define_name(kw.get('uselib_store', kw['modversion'])), version) + return version + + lst = [kw['path']] + for key, val in kw.get('define_variable', {}).iteritems(): + lst.append('--define-variable=%s=%s' % (key, val)) + + lst.append(kw.get('args', '')) + lst.append(kw['package']) + + # so we assume the command-line will output flags to be parsed afterwards + cmd = ' '.join(lst) + ret = self.cmd_and_log(cmd, kw) + if not 'okmsg' in kw: + kw['okmsg'] = 'ok' + + self.define(self.have_define(kw.get('uselib_store', kw['package'])), 1, 0) + parse_flags(ret, kw.get('uselib_store', kw['package'].upper()), kw.get('env', self.env)) + return ret + +@conf +def check_cfg(self, *k, **kw): + self.validate_cfg(kw) + if 'msg' in kw: + self.check_message_1(kw['msg']) + ret = None + try: + ret = self.exec_cfg(kw) + except Configure.ConfigurationError, e: + if 'errmsg' in kw: + self.check_message_2(kw['errmsg'], 'YELLOW') + if 'mandatory' in kw and kw['mandatory']: + if Logs.verbose > 1: + raise + else: + self.fatal('the configuration failed (see %r)' % self.log.name) + else: + kw['success'] = ret + if 'okmsg' in kw: + self.check_message_2(self.ret_msg(kw['okmsg'], kw)) + + return ret + +# the idea is the following: now that we are certain +# that all the code here is only for c or c++, it is +# easy to put all the logic in one function +# +# this should prevent code duplication (ita) + +# env: an optional environment (modified -> provide a copy) +# compiler: cc or cxx - it tries to guess what is best +# type: program, shlib, staticlib, objects +# code: a c code to execute +# uselib_store: where to add the variables +# uselib: parameters to use for building +# define: define to set, like FOO in #define FOO, if not set, add /* #undef FOO */ +# execute: True or False - will return the result of the execution + +@conf +def validate_c(self, kw): + """validate the parameters for the test method""" + + if not 'env' in kw: + kw['env'] = self.env.copy() + + env = kw['env'] + if not 'compiler' in kw: + kw['compiler'] = 'cc' + if env['CXX_NAME'] and Task.TaskBase.classes.get('cxx', None): + kw['compiler'] = 'cxx' + if not self.env['CXX']: + self.fatal('a c++ compiler is required') + else: + if not self.env['CC']: + self.fatal('a c compiler is required') + + if not 'type' in kw: + kw['type'] = 'cprogram' + + assert not(kw['type'] != 'cprogram' and kw.get('execute', 0)), 'can only execute programs' + + + #if kw['type'] != 'program' and kw.get('execute', 0): + # raise ValueError, 'can only execute programs' + + def to_header(dct): + if 'header_name' in dct: + dct = Utils.to_list(dct['header_name']) + return ''.join(['#include <%s>\n' % x for x in dct]) + return '' + + # set the file name + if not 'compile_mode' in kw: + kw['compile_mode'] = (kw['compiler'] == 'cxx') and 'cxx' or 'cc' + + if not 'compile_filename' in kw: + kw['compile_filename'] = 'test.c' + ((kw['compile_mode'] == 'cxx') and 'pp' or '') + + #OSX + if 'framework_name' in kw: + try: TaskGen.task_gen.create_task_macapp + except AttributeError: self.fatal('frameworks require the osx tool') + + fwkname = kw['framework_name'] + if not 'uselib_store' in kw: + kw['uselib_store'] = fwkname.upper() + + if not kw.get('no_header', False): + if not 'header_name' in kw: + kw['header_name'] = [] + fwk = '%s/%s.h' % (fwkname, fwkname) + if kw.get('remove_dot_h', None): + fwk = fwk[:-2] + kw['header_name'] = Utils.to_list(kw['header_name']) + [fwk] + + kw['msg'] = 'Checking for framework %s' % fwkname + kw['framework'] = fwkname + #kw['frameworkpath'] = set it yourself + + if 'function_name' in kw: + fu = kw['function_name'] + if not 'msg' in kw: + kw['msg'] = 'Checking for function %s' % fu + kw['code'] = to_header(kw) + SNIP1 % fu + if not 'uselib_store' in kw: + kw['uselib_store'] = fu.upper() + if not 'define_name' in kw: + kw['define_name'] = self.have_define(fu) + + elif 'type_name' in kw: + tu = kw['type_name'] + if not 'msg' in kw: + kw['msg'] = 'Checking for type %s' % tu + if not 'header_name' in kw: + kw['header_name'] = 'stdint.h' + kw['code'] = to_header(kw) + SNIP2 % {'type_name' : tu} + if not 'define_name' in kw: + kw['define_name'] = self.have_define(tu.upper()) + + elif 'header_name' in kw: + if not 'msg' in kw: + kw['msg'] = 'Checking for header %s' % kw['header_name'] + + l = Utils.to_list(kw['header_name']) + assert len(l)>0, 'list of headers in header_name is empty' + + kw['code'] = to_header(kw) + SNIP3 + + if not 'uselib_store' in kw: + kw['uselib_store'] = l[0].upper() + + if not 'define_name' in kw: + kw['define_name'] = self.have_define(l[0]) + + if 'lib' in kw: + if not 'msg' in kw: + kw['msg'] = 'Checking for library %s' % kw['lib'] + if not 'uselib_store' in kw: + kw['uselib_store'] = kw['lib'].upper() + + if 'staticlib' in kw: + if not 'msg' in kw: + kw['msg'] = 'Checking for static library %s' % kw['staticlib'] + if not 'uselib_store' in kw: + kw['uselib_store'] = kw['staticlib'].upper() + + if 'fragment' in kw: + # an additional code fragment may be provided to replace the predefined code + # in custom headers + kw['code'] = kw['fragment'] + if not 'msg' in kw: + kw['msg'] = 'Checking for custom code' + if not 'errmsg' in kw: + kw['errmsg'] = 'fail' + + for (flagsname,flagstype) in [('cxxflags','compiler'), ('cflags','compiler'), ('linkflags','linker')]: + if flagsname in kw: + if not 'msg' in kw: + kw['msg'] = 'Checking for %s flags %s' % (flagstype, kw[flagsname]) + if not 'errmsg' in kw: + kw['errmsg'] = 'fail' + + if not 'execute' in kw: + kw['execute'] = False + + if not 'errmsg' in kw: + kw['errmsg'] = 'not found' + + if not 'okmsg' in kw: + kw['okmsg'] = 'ok' + + if not 'code' in kw: + kw['code'] = SNIP3 + + if not kw.get('success'): kw['success'] = None + + assert 'msg' in kw, 'invalid parameters, read http://freehackers.org/~tnagy/wafbook/single.html#config_helpers_c' + +@conf +def post_check(self, *k, **kw): + "set the variables after a test was run successfully" + + is_success = 0 + if kw['execute']: + if kw['success']: + is_success = kw['success'] + else: + is_success = (kw['success'] == 0) + + def define_or_stuff(): + nm = kw['define_name'] + if kw['execute'] and kw.get('define_ret', None) and isinstance(is_success, str): + self.define(kw['define_name'], is_success, quote=kw.get('quote', 1)) + else: + self.define_cond(kw['define_name'], is_success) + + if 'define_name' in kw: + if 'header_name' in kw or 'function_name' in kw or 'type_name' in kw or 'fragment' in kw: + define_or_stuff() + + if is_success and 'uselib_store' in kw: + import cc, cxx + for k in set(cc.g_cc_flag_vars).union(cxx.g_cxx_flag_vars): + lk = k.lower() + # inconsistency: includes -> CPPPATH + if k == 'CPPPATH': lk = 'includes' + if k == 'CXXDEFINES': lk = 'defines' + if k == 'CCDEFINES': lk = 'defines' + if lk in kw: + val = kw[lk] + # remove trailing slash + if isinstance(val, str): + val = val.rstrip(os.path.sep) + self.env.append_unique(k + '_' + kw['uselib_store'], val) + +@conf +def check(self, *k, **kw): + # so this will be the generic function + # it will be safer to use check_cxx or check_cc + self.validate_c(kw) + self.check_message_1(kw['msg']) + ret = None + try: + ret = self.run_c_code(*k, **kw) + except Configure.ConfigurationError, e: + self.check_message_2(kw['errmsg'], 'YELLOW') + if 'mandatory' in kw and kw['mandatory']: + if Logs.verbose > 1: + raise + else: + self.fatal('the configuration failed (see %r)' % self.log.name) + else: + kw['success'] = ret + self.check_message_2(self.ret_msg(kw['okmsg'], kw)) + + self.post_check(*k, **kw) + if not kw.get('execute', False): + return ret == 0 + return ret + +@conf +def run_c_code(self, *k, **kw): + test_f_name = kw['compile_filename'] + + k = 0 + while k < 10000: + # make certain to use a fresh folder - necessary for win32 + dir = os.path.join(self.blddir, '.conf_check_%d' % k) + + # if the folder already exists, remove it + try: + shutil.rmtree(dir) + except OSError: + pass + + try: + os.stat(dir) + except OSError: + break + + k += 1 + + try: + os.makedirs(dir) + except: + self.fatal('cannot create a configuration test folder %r' % dir) + + try: + os.stat(dir) + except: + self.fatal('cannot use the configuration test folder %r' % dir) + + bdir = os.path.join(dir, 'testbuild') + + if not os.path.exists(bdir): + os.makedirs(bdir) + + env = kw['env'] + + dest = open(os.path.join(dir, test_f_name), 'w') + dest.write(kw['code']) + dest.close() + + back = os.path.abspath('.') + + bld = Build.BuildContext() + bld.log = self.log + bld.all_envs.update(self.all_envs) + bld.all_envs['default'] = env + bld.lst_variants = bld.all_envs.keys() + bld.load_dirs(dir, bdir) + + os.chdir(dir) + + bld.rescan(bld.srcnode) + + o = bld.new_task_gen(features=[kw['compile_mode'], kw['type']], source=test_f_name, target='testprog') + + for k, v in kw.iteritems(): + setattr(o, k, v) + + self.log.write("==>\n%s\n<==\n" % kw['code']) + + # compile the program + try: + bld.compile() + except Utils.WafError: + ret = Utils.ex_stack() + else: + ret = 0 + + # chdir before returning + os.chdir(back) + + if ret: + self.log.write('command returned %r' % ret) + self.fatal(str(ret)) + + # keep the name of the program to execute + if kw['execute']: + lastprog = o.link_task.outputs[0].abspath(env) + + # if we need to run the program, try to get its result + if kw['execute']: + args = Utils.to_list(kw.get('exec_args', [])) + try: + data = Utils.cmd_output([lastprog] + args).strip() + except ValueError, e: + self.fatal(Utils.ex_stack()) + ret = data + + return ret + +@conf +def check_cxx(self, *k, **kw): + kw['compiler'] = 'cxx' + return self.check(*k, **kw) + +@conf +def check_cc(self, *k, **kw): + kw['compiler'] = 'cc' + return self.check(*k, **kw) + +@conf +def define(self, define, value, quote=1): + """store a single define and its state into an internal list for later + writing to a config header file. Value can only be + a string or int; other types not supported. String + values will appear properly quoted in the generated + header file.""" + assert define and isinstance(define, str) + + # ordered_dict is for writing the configuration header in order + tbl = self.env[DEFINES] or Utils.ordered_dict() + + # the user forgot to tell if the value is quoted or not + if isinstance(value, str): + if quote: + tbl[define] = '"%s"' % str(value) + else: + tbl[define] = value + elif isinstance(value, int): + tbl[define] = value + else: + raise TypeError('define %r -> %r must be a string or an int' % (define, value)) + + # add later to make reconfiguring faster + self.env[DEFINES] = tbl + self.env[define] = value # <- not certain this is necessary + +@conf +def undefine(self, define): + """store a single define and its state into an internal list + for later writing to a config header file""" + assert define and isinstance(define, str) + + tbl = self.env[DEFINES] or Utils.ordered_dict() + + value = UNDEFINED + tbl[define] = value + + # add later to make reconfiguring faster + self.env[DEFINES] = tbl + self.env[define] = value + +@conf +def define_cond(self, name, value): + """Conditionally define a name. + Formally equivalent to: if value: define(name, 1) else: undefine(name)""" + if value: + self.define(name, 1) + else: + self.undefine(name) + +@conf +def is_defined(self, key): + defines = self.env[DEFINES] + if not defines: + return False + try: + value = defines[key] + except KeyError: + return False + else: + return value != UNDEFINED + +@conf +def get_define(self, define): + "get the value of a previously stored define" + try: return self.env[DEFINES][define] + except KeyError: return None + +@conf +def have_define(self, name): + "prefix the define with 'HAVE_' and make sure it has valid characters." + return self.__dict__.get('HAVE_PAT', 'HAVE_%s') % Utils.quote_define_name(name) + +@conf +def write_config_header(self, configfile='', env='', guard='', top=False): + "save the defines into a file" + if not configfile: configfile = WAF_CONFIG_H + waf_guard = guard or '_%s_WAF' % Utils.quote_define_name(configfile) + + # configfile -> absolute path + # there is a good reason to concatenate first and to split afterwards + if not env: env = self.env + if top: + diff = '' + else: + diff = Utils.diff_path(self.srcdir, self.curdir) + full = os.sep.join([self.blddir, env.variant(), diff, configfile]) + full = os.path.normpath(full) + (dir, base) = os.path.split(full) + + try: os.makedirs(dir) + except: pass + + dest = open(full, 'w') + dest.write('/* Configuration header created by Waf - do not edit */\n') + dest.write('#ifndef %s\n#define %s\n\n' % (waf_guard, waf_guard)) + + dest.write(self.get_config_header()) + + # config files are not removed on "waf clean" + env.append_value(CFG_FILES, os.path.join(diff, configfile)) + + dest.write('\n#endif /* %s */\n' % waf_guard) + dest.close() + +@conf +def get_config_header(self): + """Fill-in the contents of the config header. Override when you need to write your own config header.""" + config_header = [] + + tbl = self.env[DEFINES] or Utils.ordered_dict() + for key in tbl.allkeys: + value = tbl[key] + if value is None: + config_header.append('#define %s' % key) + elif value is UNDEFINED: + config_header.append('/* #undef %s */' % key) + else: + config_header.append('#define %s %s' % (key, value)) + + return "\n".join(config_header) + +@conftest +def find_cpp(conf): + v = conf.env + cpp = None + if v['CPP']: cpp = v['CPP'] + elif 'CPP' in conf.environ: cpp = conf.environ['CPP'] + if not cpp: cpp = conf.find_program('cpp', var='CPP') + if not cpp: cpp = v['CC'] + if not cpp: cpp = v['CXX'] + v['CPP'] = cpp + +@conftest +def cc_add_flags(conf): + conf.add_os_flags('CFLAGS', 'CCFLAGS') + conf.add_os_flags('CPPFLAGS') + conf.add_os_flags('LINKFLAGS') + +@conftest +def cxx_add_flags(conf): + conf.add_os_flags('CXXFLAGS') + conf.add_os_flags('CPPFLAGS') + conf.add_os_flags('LINKFLAGS') + +@conftest +def cc_load_tools(conf): + conf.check_tool('cc') + +@conftest +def cxx_load_tools(conf): + conf.check_tool('cxx') + diff --git a/tools/wafadmin/Tools/cs.py b/tools/wafadmin/Tools/cs.py new file mode 100644 index 000000000..a54e8d328 --- /dev/null +++ b/tools/wafadmin/Tools/cs.py @@ -0,0 +1,69 @@ +#!/usr/bin/env python +# encoding: utf-8 +# Thomas Nagy, 2006 (ita) + +"C# support" + +import TaskGen, Utils, Task +from Logs import error +from TaskGen import before, after, taskgen, feature + +flag_vars= ['FLAGS', 'ASSEMBLIES'] + +@feature('cs') +def init_cs(self): + Utils.def_attrs(self, + flags = '', + assemblies = '', + resources = '', + uselib = '') + +@feature('cs') +@after('init_cs') +def apply_uselib_cs(self): + if not self.uselib: + return + global flag_vars + for var in self.to_list(self.uselib): + for v in self.flag_vars: + val = self.env[v+'_'+var] + if val: self.env.append_value(v, val) + +@feature('cs') +@after('apply_uselib_cs') +@before('apply_core') +def apply_cs(self): + try: self.meths.remove('apply_core') + except ValueError: pass + + # process the flags for the assemblies + assemblies_flags = [] + for i in self.to_list(self.assemblies) + self.env['ASSEMBLIES']: + assemblies_flags += '/r:'+i + self.env['_ASSEMBLIES'] += assemblies_flags + + # process the flags for the resources + for i in self.to_list(self.resources): + self.env['_RESOURCES'].append('/resource:'+i) + + # additional flags + self.env['_FLAGS'] += self.to_list(self.flags) + self.env['FLAGS'] + + curnode = self.path + + # process the sources + nodes = [] + for i in self.to_list(self.source): + nodes.append(curnode.find_resource(i)) + + # create the task + task = self.create_task('mcs') + task.inputs = nodes + task.set_outputs(self.path.find_or_declare(self.target)) + +Task.simple_task_type('mcs', '${MCS} ${SRC} /out:${TGT} ${_FLAGS} ${_ASSEMBLIES} ${_RESOURCES}', color='YELLOW') + +def detect(conf): + mcs = conf.find_program('mcs', var='MCS') + if not mcs: mcs = conf.find_program('gmcs', var='MCS') + diff --git a/tools/wafadmin/Tools/cxx.py b/tools/wafadmin/Tools/cxx.py new file mode 100644 index 000000000..e82adb2e0 --- /dev/null +++ b/tools/wafadmin/Tools/cxx.py @@ -0,0 +1,106 @@ +#!/usr/bin/env python +# encoding: utf-8 +# Thomas Nagy, 2005 (ita) + +"Base for c++ programs and libraries" + +import TaskGen, Task, Utils +from Logs import debug +import ccroot # <- do not remove +from TaskGen import feature, before, extension, after + +g_cxx_flag_vars = [ +'CXXDEPS', 'FRAMEWORK', 'FRAMEWORKPATH', +'STATICLIB', 'LIB', 'LIBPATH', 'LINKFLAGS', 'RPATH', +'CXXFLAGS', 'CCFLAGS', 'CPPPATH', 'CPPFLAGS', 'CXXDEFINES'] +"main cpp variables" + +EXT_CXX = ['.cpp', '.cc', '.cxx', '.C', '.c++'] + +g_cxx_type_vars=['CXXFLAGS', 'LINKFLAGS'] + +# TODO remove in waf 1.6 +class cxx_taskgen(ccroot.ccroot_abstract): + pass + +@feature('cxx') +@before('apply_type_vars') +@after('default_cc') +def init_cxx(self): + if not 'cc' in self.features: + self.mappings['.c'] = TaskGen.task_gen.mappings['.cxx'] + + self.p_flag_vars = set(self.p_flag_vars).union(g_cxx_flag_vars) + self.p_type_vars = set(self.p_type_vars).union(g_cxx_type_vars) + + if not self.env['CXX_NAME']: + raise Utils.WafError("At least one compiler (g++, ..) must be selected") + +@feature('cxx') +@after('apply_incpaths') +def apply_obj_vars_cxx(self): + """after apply_incpaths for INC_PATHS""" + env = self.env + app = env.append_unique + cxxpath_st = env['CPPPATH_ST'] + + # local flags come first + # set the user-defined includes paths + for i in env['INC_PATHS']: + app('_CXXINCFLAGS', cxxpath_st % i.bldpath(env)) + app('_CXXINCFLAGS', cxxpath_st % i.srcpath(env)) + + # set the library include paths + for i in env['CPPPATH']: + app('_CXXINCFLAGS', cxxpath_st % i) + +@feature('cxx') +@after('apply_lib_vars') +def apply_defines_cxx(self): + """after uselib is set for CXXDEFINES""" + self.defines = getattr(self, 'defines', []) + lst = self.to_list(self.defines) + self.to_list(self.env['CXXDEFINES']) + milst = [] + + # now process the local defines + for defi in lst: + if not defi in milst: + milst.append(defi) + + # CXXDEFINES_USELIB + libs = self.to_list(self.uselib) + for l in libs: + val = self.env['CXXDEFINES_'+l] + if val: milst += self.to_list(val) + + self.env['DEFLINES'] = ["%s %s" % (x[0], Utils.trimquotes('='.join(x[1:]))) for x in [y.split('=') for y in milst]] + y = self.env['CXXDEFINES_ST'] + self.env['_CXXDEFFLAGS'] = [y%x for x in milst] + +@extension(EXT_CXX) +def cxx_hook(self, node): + # create the compilation task: cpp or cc + task = self.create_task('cxx') + if getattr(self, 'obj_ext', None): + obj_ext = self.obj_ext + else: + obj_ext = '_%d.o' % self.idx + + task.inputs = [node] + task.outputs = [node.change_ext(obj_ext)] + try: + self.compiled_tasks.append(task) + except AttributeError: + raise Utils.WafError('Have you forgotten to set the feature "cxx" on %s?' % str(self)) + return task + +cxx_str = '${CXX} ${CXXFLAGS} ${CPPFLAGS} ${_CXXINCFLAGS} ${_CXXDEFFLAGS} ${CXX_SRC_F}${SRC} ${CXX_TGT_F}${TGT}' +cls = Task.simple_task_type('cxx', cxx_str, color='GREEN', ext_out='.o', ext_in='.cxx', shell=False) +cls.scan = ccroot.scan +cls.vars.append('CXXDEPS') + +link_str = '${LINK_CXX} ${CXXLNK_SRC_F}${SRC} ${CXXLNK_TGT_F}${TGT[0].abspath(env)} ${LINKFLAGS}' +cls = Task.simple_task_type('cxx_link', link_str, color='YELLOW', ext_in='.o', ext_out='.bin', shell=False) +cls.maxjobs = 1 +cls.install = Utils.nada + diff --git a/tools/wafadmin/Tools/d.py b/tools/wafadmin/Tools/d.py new file mode 100644 index 000000000..256e33a2a --- /dev/null +++ b/tools/wafadmin/Tools/d.py @@ -0,0 +1,532 @@ +#!/usr/bin/env python +# encoding: utf-8 +# Carlos Rafael Giani, 2007 (dv) +# Thomas Nagy, 2007-2008 (ita) + +import os, sys, re, optparse +import ccroot # <- leave this +import TaskGen, Utils, Task, Configure, Logs, Build +from Logs import debug, error +from TaskGen import taskgen, feature, after, before, extension +from Configure import conftest + +EXT_D = ['.d', '.di', '.D'] +D_METHS = ['apply_core', 'apply_vnum', 'apply_objdeps'] # additional d methods + +def filter_comments(filename): + txt = Utils.readf(filename) + buf = [] + + i = 0 + max = len(txt) + while i < max: + c = txt[i] + # skip a string + if c == '"': + i += 1 + c = '' + while i < max: + p = c + c = txt[i] + i += 1 + if i == max: return buf + if c == '"': + cnt = 0 + while i < cnt and i < max: + #print "cntcnt = ", str(cnt), self.txt[self.i-2-cnt] + if txt[i-2-cnt] == '\\': cnt+=1 + else: break + #print "cnt is ", str(cnt) + if (cnt%2)==0: break + # i -= 1 # <- useless in practice + # skip a char + elif c == "'": + i += 1 + if i == max: return buf + c = txt[i] + if c == '\\': + i += 1 + if i == max: return buf + c = txt[i] + if c == 'x': + i += 2 # skip two chars + elif c == 'u': + i += 4 # skip unicode chars + i += 1 + if i == max: return buf + c = txt[i] + if c != '\'': error("uh-oh, invalid character") + + # skip a comment + elif c == '/': + if i == max: break + c = txt[i+1] + # eat /+ +/ comments + if c == '+': + i += 1 + nesting = 1 + prev = 0 + while i < max: + c = txt[i] + if c == '+': + prev = 1 + elif c == '/': + if prev: + nesting -= 1 + if nesting == 0: break + else: + if i < max: + i += 1 + c = txt[i] + if c == '+': + nesting += 1 + else: + return buf + else: + prev = 0 + i += 1 + # eat /* */ comments + elif c == '*': + i += 1 + while i < max: + c = txt[i] + if c == '*': + prev = 1 + elif c == '/': + if prev: break + else: + prev = 0 + i += 1 + # eat // comments + elif c == '/': + i += 1 + c = txt[i] + while i < max and c != '\n': + i += 1 + c = txt[i] + # a valid char, add it to the buffer + else: + buf.append(c) + i += 1 + return buf + +class d_parser(object): + def __init__(self, env, incpaths): + #self.code = '' + #self.module = '' + #self.imports = [] + + self.allnames = [] + + self.re_module = re.compile("module\s+([^;]+)") + self.re_import = re.compile("import\s+([^;]+)") + self.re_import_bindings = re.compile("([^:]+):(.*)") + self.re_import_alias = re.compile("[^=]+=(.+)") + + self.env = env + + self.nodes = [] + self.names = [] + + self.incpaths = incpaths + + def tryfind(self, filename): + found = 0 + for n in self.incpaths: + found = n.find_resource(filename.replace('.', '/') + '.d') + if found: + self.nodes.append(found) + self.waiting.append(found) + break + if not found: + if not filename in self.names: + self.names.append(filename) + + def get_strings(self, code): + #self.imports = [] + self.module = '' + lst = [] + + # get the module name (if present) + + mod_name = self.re_module.search(code) + if mod_name: + self.module = re.sub('\s+', '', mod_name.group(1)) # strip all whitespaces + + # go through the code, have a look at all import occurrences + + # first, lets look at anything beginning with "import" and ending with ";" + import_iterator = self.re_import.finditer(code) + if import_iterator: + for import_match in import_iterator: + import_match_str = re.sub('\s+', '', import_match.group(1)) # strip all whitespaces + + # does this end with an import bindings declaration? + # (import bindings always terminate the list of imports) + bindings_match = self.re_import_bindings.match(import_match_str) + if bindings_match: + import_match_str = bindings_match.group(1) + # if so, extract the part before the ":" (since the module declaration(s) is/are located there) + + # split the matching string into a bunch of strings, separated by a comma + matches = import_match_str.split(',') + + for match in matches: + alias_match = self.re_import_alias.match(match) + if alias_match: + # is this an alias declaration? (alias = module name) if so, extract the module name + match = alias_match.group(1) + + lst.append(match) + return lst + + def start(self, node): + self.waiting = [node] + # while the stack is not empty, add the dependencies + while self.waiting: + nd = self.waiting.pop(0) + self.iter(nd) + + def iter(self, node): + path = node.abspath(self.env) # obtain the absolute path + code = "".join(filter_comments(path)) # read the file and filter the comments + names = self.get_strings(code) # obtain the import strings + for x in names: + # optimization + if x in self.allnames: continue + self.allnames.append(x) + + # for each name, see if it is like a node or not + self.tryfind(x) + +def scan(self): + "look for .d/.di the .d source need" + env = self.env + gruik = d_parser(env, env['INC_PATHS']) + gruik.start(self.inputs[0]) + + if Logs.verbose: + debug('deps: nodes found for %s: %s %s' % (str(self.inputs[0]), str(gruik.nodes), str(gruik.names))) + #debug("deps found for %s: %s" % (str(node), str(gruik.deps)), 'deps') + return (gruik.nodes, gruik.names) + +def get_target_name(self): + "for d programs and libs" + v = self.env + tp = 'program' + for x in self.features: + if x in ['dshlib', 'dstaticlib']: + tp = x.lstrip('d') + return v['D_%s_PATTERN' % tp] % self.target + +d_params = { +'dflags': '', +'importpaths':'', +'libs':'', +'libpaths':'', +'generate_headers':False, +} + +@feature('d') +@before('apply_type_vars') +def init_d(self): + for x in d_params: + setattr(self, x, getattr(self, x, d_params[x])) + +class d_taskgen(TaskGen.task_gen): + def __init__(self, *k, **kw): + TaskGen.task_gen.__init__(self, *k, **kw) + + # COMPAT + if len(k) > 1: + self.features.append('d' + k[1]) + +# okay, we borrow a few methods from ccroot +TaskGen.bind_feature('d', D_METHS) + +@feature('d') +@before('apply_d_libs') +def init_d(self): + Utils.def_attrs(self, + dflags='', + importpaths='', + libs='', + libpaths='', + uselib='', + uselib_local='', + generate_headers=False, # set to true if you want .di files as well as .o + compiled_tasks=[], + add_objects=[], + link_task=None) + +@feature('d') +@after('apply_d_link') +@before('apply_vnum') +def apply_d_libs(self): + uselib = self.to_list(self.uselib) + seen = [] + local_libs = self.to_list(self.uselib_local) + libs = [] + libpaths = [] + env = self.env + while local_libs: + x = local_libs.pop() + + # visit dependencies only once + if x in seen: + continue + else: + seen.append(x) + + y = self.name_to_obj(x) + if not y: + raise Utils.WafError('object not found in uselib_local: obj %s uselib %s' % (self.name, x)) + + # object has ancestors to process first ? update the list of names + if y.uselib_local: + added = 0 + lst = y.to_list(y.uselib_local) + lst.reverse() + for u in lst: + if u in seen: continue + added = 1 + local_libs = [u]+local_libs + if added: continue # list of names modified, loop + + # safe to process the current object + y.post() + seen.append(x) + + libname = y.target[y.target.rfind(os.sep) + 1:] + if 'dshlib' in y.features or 'dstaticlib' in y.features: + #libs.append(y.target) + env.append_unique('DLINKFLAGS', env['DLIBPATH_ST'] % y.link_task.outputs[0].parent.bldpath(env)) + env.append_unique('DLINKFLAGS', env['DLIB_ST'] % libname) + + # add the link path too + tmp_path = y.path.bldpath(env) + if not tmp_path in libpaths: libpaths = [tmp_path] + libpaths + + # set the dependency over the link task + if y.link_task is not None: + self.link_task.set_run_after(y.link_task) + dep_nodes = getattr(self.link_task, 'dep_nodes', []) + self.link_task.dep_nodes = dep_nodes + y.link_task.outputs + + # add ancestors uselib too + # TODO potential problems with static libraries ? + morelibs = y.to_list(y.uselib) + for v in morelibs: + if v in uselib: continue + uselib = [v]+uselib + self.uselib = uselib + +@feature('dprogram', 'dshlib', 'dstaticlib') +@after('apply_core') +def apply_d_link(self): + link = getattr(self, 'link', None) + if not link: + if 'dstaticlib' in self.features: link = 'static_link' + else: link = 'd_link' + linktask = self.create_task(link) + outputs = [t.outputs[0] for t in self.compiled_tasks] + linktask.set_inputs(outputs) + linktask.set_outputs(self.path.find_or_declare(get_target_name(self))) + + self.link_task = linktask + +@feature('d') +@after('apply_core') +def apply_d_vars(self): + env = self.env + dpath_st = env['DPATH_ST'] + lib_st = env['DLIB_ST'] + libpath_st = env['DLIBPATH_ST'] + + #dflags = [] + importpaths = self.to_list(self.importpaths) + libpaths = [] + libs = [] + uselib = self.to_list(self.uselib) + + for i in uselib: + if env['DFLAGS_' + i]: + env.append_unique('DFLAGS', env['DFLAGS_' + i]) + + for x in self.features: + if not x in ['dprogram', 'dstaticlib', 'dshlib']: + continue + x.lstrip('d') + d_shlib_dflags = env['D_' + x + '_DFLAGS'] + if d_shlib_dflags: + env.append_unique('DFLAGS', d_shlib_dflags) + + # add import paths + for i in uselib: + if env['DPATH_' + i]: + for entry in self.to_list(env['DPATH_' + i]): + if not entry in importpaths: + importpaths.append(entry) + + # now process the import paths + for path in importpaths: + if os.path.isabs(path): + env.append_unique('_DIMPORTFLAGS', dpath_st % path) + else: + node = self.path.find_dir(path) + self.env.append_unique('INC_PATHS', node) + env.append_unique('_DIMPORTFLAGS', dpath_st % node.srcpath(env)) + env.append_unique('_DIMPORTFLAGS', dpath_st % node.bldpath(env)) + + # add library paths + for i in uselib: + if env['LIBPATH_' + i]: + for entry in self.to_list(env['LIBPATH_' + i]): + if not entry in libpaths: + libpaths += [entry] + libpaths = self.to_list(self.libpaths) + libpaths + + # now process the library paths + for path in libpaths: + env.append_unique('DLINKFLAGS', libpath_st % path) + + # add libraries + for i in uselib: + if env['LIB_' + i]: + for entry in self.to_list(env['LIB_' + i]): + if not entry in libs: + libs += [entry] + libs = libs + self.to_list(self.libs) + + # now process the libraries + for lib in libs: + env.append_unique('DLINKFLAGS', lib_st % lib) + + # add linker flags + for i in uselib: + dlinkflags = env['DLINKFLAGS_' + i] + if dlinkflags: + for linkflag in dlinkflags: + env.append_unique('DLINKFLAGS', linkflag) + +@feature('dshlib') +@after('apply_d_vars') +def add_shlib_d_flags(self): + for linkflag in self.env['D_shlib_LINKFLAGS']: + self.env.append_unique('DLINKFLAGS', linkflag) + +@extension(EXT_D) +def d_hook(self, node): + # create the compilation task: cpp or cc + task = self.create_task(self.generate_headers and 'd_with_header' or 'd') + try: obj_ext = self.obj_ext + except AttributeError: obj_ext = '_%d.o' % self.idx + + task.inputs = [node] + task.outputs = [node.change_ext(obj_ext)] + self.compiled_tasks.append(task) + + if self.generate_headers: + header_node = node.change_ext(self.env['DHEADER_ext']) + task.outputs += [header_node] + +d_str = '${D_COMPILER} ${DFLAGS} ${_DIMPORTFLAGS} ${D_SRC_F}${SRC} ${D_TGT_F}${TGT}' +d_with_header_str = '${D_COMPILER} ${DFLAGS} ${_DIMPORTFLAGS} \ +${D_HDR_F}${TGT[1].bldpath(env)} \ +${D_SRC_F}${SRC} \ +${D_TGT_F}${TGT[0].bldpath(env)}' +link_str = '${D_LINKER} ${DLNK_SRC_F}${SRC} ${DLNK_TGT_F}${TGT} ${DLINKFLAGS}' + +def override_exec(cls): + """stupid dmd wants -of stuck to the file name""" + old_exec = cls.exec_command + def exec_command(self, *k, **kw): + if isinstance(k[0], list): + lst = k[0] + for i in xrange(len(lst)): + if lst[i] == '-of': + del lst[i] + lst[i] = '-of' + lst[i] + break + return old_exec(self, *k, **kw) + cls.exec_command = exec_command + +cls = Task.simple_task_type('d', d_str, 'GREEN', before='static_link d_link', shell=False) +cls.scan = scan +override_exec(cls) + +cls = Task.simple_task_type('d_with_header', d_with_header_str, 'GREEN', before='static_link d_link', shell=False) +override_exec(cls) + +cls = Task.simple_task_type('d_link', link_str, color='YELLOW', shell=False) +override_exec(cls) + +# for feature request #104 +@taskgen +def generate_header(self, filename, install_path): + if not hasattr(self, 'header_lst'): self.header_lst = [] + self.meths.append('process_header') + self.header_lst.append([filename, install_path]) + +@before('apply_core') +def process_header(self): + env = self.env + for i in getattr(self, 'header_lst', []): + node = self.path.find_resource(i[0]) + + if not node: + raise Utils.WafError('file not found on d obj '+i[0]) + + task = self.create_task('d_header') + task.set_inputs(node) + task.set_outputs(node.change_ext('.di')) + +d_header_str = '${D_COMPILER} ${D_HEADER} ${SRC}' +Task.simple_task_type('d_header', d_header_str, color='BLUE', shell=False) + +@conftest +def d_platform_flags(conf): + binfmt = conf.env.DEST_BINFMT or Utils.unversioned_sys_platform_to_binary_format( + conf.env.DEST_OS or Utils.unversioned_sys_platform()) + if binfmt == 'pe': + v['D_program_PATTERN'] = '%s.exe' + v['D_shlib_PATTERN'] = 'lib%s.dll' + v['D_staticlib_PATTERN'] = 'lib%s.a' + else: + v['D_program_PATTERN'] = '%s' + v['D_shlib_PATTERN'] = 'lib%s.so' + v['D_staticlib_PATTERN'] = 'lib%s.a' + + +# quick test # +if __name__ == "__main__": + #Logs.verbose = 2 + + try: arg = sys.argv[1] + except IndexError: arg = "file.d" + + print("".join(filter_comments(arg))) + # TODO + paths = ['.'] + + #gruik = filter() + #gruik.start(arg) + + #code = "".join(gruik.buf) + + #print "we have found the following code" + #print code + + #print "now parsing" + #print "-------------------------------------------" + """ + parser_ = d_parser() + parser_.start(arg) + + print "module: %s" % parser_.module + print "imports: ", + for imp in parser_.imports: + print imp + " ", + print +""" + diff --git a/tools/wafadmin/Tools/dbus.py b/tools/wafadmin/Tools/dbus.py new file mode 100644 index 000000000..e454177fd --- /dev/null +++ b/tools/wafadmin/Tools/dbus.py @@ -0,0 +1,37 @@ +#!/usr/bin/env python +# encoding: utf-8 +# Ali Sabil, 2007 + +import Task, Utils +from TaskGen import taskgen, before, after, feature + +@taskgen +def add_dbus_file(self, filename, prefix, mode): + if not hasattr(self, 'dbus_lst'): + self.dbus_lst = [] + self.meths.append('process_dbus') + self.dbus_lst.append([filename, prefix, mode]) + +@before('apply_core') +def process_dbus(self): + for filename, prefix, mode in getattr(self, 'dbus_lst', []): + env = self.env.copy() + node = self.path.find_resource(filename) + + if not node: + raise Utils.WafError('file not found ' + filename) + + env['DBUS_BINDING_TOOL_PREFIX'] = prefix + env['DBUS_BINDING_TOOL_MODE'] = mode + + task = self.create_task('dbus_binding_tool', env) + task.set_inputs(node) + task.set_outputs(node.change_ext('.h')) + +Task.simple_task_type('dbus_binding_tool', + '${DBUS_BINDING_TOOL} --prefix=${DBUS_BINDING_TOOL_PREFIX} --mode=${DBUS_BINDING_TOOL_MODE} --output=${TGT} ${SRC}', + color='BLUE', before='cc') + +def detect(conf): + dbus_binding_tool = conf.find_program('dbus-binding-tool', var='DBUS_BINDING_TOOL') + diff --git a/tools/wafadmin/Tools/dmd.py b/tools/wafadmin/Tools/dmd.py new file mode 100644 index 000000000..9c7490844 --- /dev/null +++ b/tools/wafadmin/Tools/dmd.py @@ -0,0 +1,64 @@ +#!/usr/bin/env python +# encoding: utf-8 +# Carlos Rafael Giani, 2007 (dv) +# Thomas Nagy, 2008 (ita) + +import sys +import Utils, ar +from Configure import conftest + +@conftest +def find_dmd(conf): + conf.find_program(['dmd', 'ldc'], var='D_COMPILER', mandatory=True) + +@conftest +def common_flags_ldc(conf): + v = conf.env + v['DFLAGS'] = ['-d-version=Posix'] + v['DLINKFLAGS'] = [] + v['D_shlib_DFLAGS'] = ['-relocation-model=pic'] + +@conftest +def common_flags_dmd(conf): + v = conf.env + + # _DFLAGS _DIMPORTFLAGS + + # Compiler is dmd so 'gdc' part will be ignored, just + # ensure key is there, so wscript can append flags to it + v['DFLAGS'] = ['-version=Posix'] + + v['D_SRC_F'] = '' + v['D_TGT_F'] = ['-c', '-of'] + v['DPATH_ST'] = '-I%s' # template for adding import paths + + # linker + v['D_LINKER'] = v['D_COMPILER'] + v['DLNK_SRC_F'] = '' + v['DLNK_TGT_F'] = '-of' + + v['DLIB_ST'] = '-L-l%s' # template for adding libs + v['DLIBPATH_ST'] = '-L-L%s' # template for adding libpaths + + # linker debug levels + v['DFLAGS_OPTIMIZED'] = ['-O'] + v['DFLAGS_DEBUG'] = ['-g', '-debug'] + v['DFLAGS_ULTRADEBUG'] = ['-g', '-debug'] + v['DLINKFLAGS'] = ['-quiet'] + + v['D_shlib_DFLAGS'] = ['-fPIC'] + v['D_shlib_LINKFLAGS'] = ['-L-shared'] + + v['DHEADER_ext'] = '.di' + v['D_HDR_F'] = ['-H', '-Hf'] + +def detect(conf): + conf.find_dmd() + conf.check_tool('ar') + conf.check_tool('d') + conf.common_flags_dmd() + conf.d_platform_flags() + + if conf.env.D_COMPILER.find('ldc') > -1: + conf.common_flags_ldc() + diff --git a/tools/wafadmin/Tools/flex.py b/tools/wafadmin/Tools/flex.py new file mode 100644 index 000000000..a0113c04c --- /dev/null +++ b/tools/wafadmin/Tools/flex.py @@ -0,0 +1,26 @@ +#!/usr/bin/env python +# encoding: utf-8 +# John O'Meara, 2006 +# Thomas Nagy, 2006-2008 + +"Flex processing" + +import TaskGen + +def decide_ext(self, node): + if 'cxx' in self.features: return '.lex.cc' + else: return '.lex.c' + +TaskGen.declare_chain( + name = 'flex', + rule = '${FLEX} -o${TGT} ${FLEXFLAGS} ${SRC}', + ext_in = '.l', + decider = decide_ext, + before = 'cc cxx', +) + +def detect(conf): + conf.find_program('flex', var='FLEX', mandatory=True) + v = conf.env + v['FLEXFLAGS'] = '' + diff --git a/tools/wafadmin/Tools/gas.py b/tools/wafadmin/Tools/gas.py new file mode 100644 index 000000000..7484c8af0 --- /dev/null +++ b/tools/wafadmin/Tools/gas.py @@ -0,0 +1,40 @@ +#!/usr/bin/env python +# encoding: utf-8 +# Thomas Nagy, 2008 (ita) + +"as and gas" + +import os, sys +import Task +from TaskGen import extension, taskgen, after, before + +EXT_ASM = ['.s', '.S', '.asm', '.ASM', '.spp', '.SPP'] + +as_str = '${AS} ${ASFLAGS} ${_ASINCFLAGS} ${SRC} -o ${TGT}' +Task.simple_task_type('asm', as_str, 'PINK', ext_out='.o', shell=False) + +@extension(EXT_ASM) +def asm_hook(self, node): + # create the compilation task: cpp or cc + task = self.create_task('asm') + try: obj_ext = self.obj_ext + except AttributeError: obj_ext = '_%d.o' % self.idx + + task.inputs = [node] + task.outputs = [node.change_ext(obj_ext)] + self.compiled_tasks.append(task) + self.meths.append('asm_incflags') + +@taskgen +@after('apply_obj_vars_cc') +@after('apply_obj_vars_cxx') +@before('apply_link') +def asm_incflags(self): + if self.env['ASINCFLAGS']: self.env['_ASINCFLAGS'] = self.env['ASINCFLAGS'] + if 'cxx' in self.features: self.env['_ASINCFLAGS'] = self.env['_CXXINCFLAGS'] + else: self.env['_ASINCFLAGS'] = self.env['_CCINCFLAGS'] + +def detect(conf): + conf.find_program(['gas', 'as'], var='AS') + if not conf.env.AS: conf.env.AS = conf.env.CC + diff --git a/tools/wafadmin/Tools/gcc.py b/tools/wafadmin/Tools/gcc.py new file mode 100644 index 000000000..9e20d9b72 --- /dev/null +++ b/tools/wafadmin/Tools/gcc.py @@ -0,0 +1,121 @@ +#!/usr/bin/env python +# encoding: utf-8 +# Thomas Nagy, 2006-2008 (ita) +# Ralf Habacker, 2006 (rh) +# Yinon Ehrlich, 2009 + +import os, sys +import Configure, Options, Utils +import ccroot, ar +from Configure import conftest + +@conftest +def find_gcc(conf): + cc = conf.find_program(['gcc', 'cc'], var='CC', mandatory=True) + cc = conf.cmd_to_list(cc) + ccroot.get_cc_version(conf, cc, gcc=True) + conf.env.CC_NAME = 'gcc' + conf.env.CC = cc + +@conftest +def gcc_common_flags(conf): + v = conf.env + + # CPPFLAGS CCDEFINES _CCINCFLAGS _CCDEFFLAGS + + v['CC_SRC_F'] = '' + v['CC_TGT_F'] = ['-c', '-o', ''] # shell hack for -MD + v['CPPPATH_ST'] = '-I%s' # template for adding include paths + + # linker + if not v['LINK_CC']: v['LINK_CC'] = v['CC'] + v['CCLNK_SRC_F'] = '' + v['CCLNK_TGT_F'] = ['-o', ''] # shell hack for -MD + + v['LIB_ST'] = '-l%s' # template for adding libs + v['LIBPATH_ST'] = '-L%s' # template for adding libpaths + v['STATICLIB_ST'] = '-l%s' + v['STATICLIBPATH_ST'] = '-L%s' + v['RPATH_ST'] = '-Wl,-rpath,%s' + v['CCDEFINES_ST'] = '-D%s' + + v['SONAME_ST'] = '-Wl,-h,%s' + v['SHLIB_MARKER'] = '-Wl,-Bdynamic' + v['STATICLIB_MARKER'] = '-Wl,-Bstatic' + v['FULLSTATIC_MARKER'] = '-static' + + # program + v['program_PATTERN'] = '%s' + + # shared library + v['shlib_CCFLAGS'] = ['-fPIC', '-DPIC'] # avoid using -DPIC, -fPIC aleady defines the __PIC__ macro + v['shlib_LINKFLAGS'] = ['-shared'] + v['shlib_PATTERN'] = 'lib%s.so' + + # static lib + v['staticlib_LINKFLAGS'] = ['-Wl,-Bstatic'] + v['staticlib_PATTERN'] = 'lib%s.a' + + # osx stuff + v['LINKFLAGS_MACBUNDLE'] = ['-bundle', '-undefined', 'dynamic_lookup'] + v['CCFLAGS_MACBUNDLE'] = ['-fPIC'] + v['macbundle_PATTERN'] = '%s.bundle' + +@conftest +def gcc_modifier_win32(conf): + v = conf.env + v['program_PATTERN'] = '%s.exe' + + v['shlib_PATTERN'] = '%s.dll' + v['implib_PATTERN'] = 'lib%s.dll.a' + v['IMPLIB_ST'] = '-Wl,--out-implib,%s' + v['shlib_CCFLAGS'] = ['-DPIC', '-DDLL_EXPORT'] # TODO 64-bit platforms may need -fPIC + v.append_value('LINKFLAGS', '-Wl,--enable-auto-import') # suppress information messages + +@conftest +def gcc_modifier_cygwin(conf): + gcc_modifier_win32(conf) + v = conf.env + v['shlib_PATTERN'] = 'cyg%s.dll' + v.append_value('shlib_LINKFLAGS', '-Wl,--enable-auto-image-base') + +@conftest +def gcc_modifier_darwin(conf): + v = conf.env + v['shlib_CCFLAGS'] = ['-fPIC', '-compatibility_version', '1', '-current_version', '1'] + v['shlib_LINKFLAGS'] = ['-dynamiclib'] + v['shlib_PATTERN'] = 'lib%s.dylib' + + v['staticlib_LINKFLAGS'] = [] + + v['SHLIB_MARKER'] = '' + v['STATICLIB_MARKER'] = '' + +@conftest +def gcc_modifier_aix(conf): + v = conf.env + v['program_LINKFLAGS'] = ['-Wl,-brtl'] + + v['shlib_LINKFLAGS'] = ['-shared','-Wl,-brtl,-bexpfull'] + + v['SHLIB_MARKER'] = '' + +@conftest +def gcc_modifier_platform(conf): + # * set configurations specific for a platform. + # * the destination platform is detected automatically by looking at the macros the compiler predefines, + # and if it's not recognised, it fallbacks to sys.platform. + dest_os = conf.env['DEST_OS'] or Utils.unversioned_sys_platform() + gcc_modifier_func = globals().get('gcc_modifier_' + dest_os) + if gcc_modifier_func: + gcc_modifier_func(conf) + +def detect(conf): + conf.find_gcc() + conf.find_cpp() + conf.find_ar() + conf.gcc_common_flags() + conf.gcc_modifier_platform() + conf.cc_load_tools() + conf.cc_add_flags() + diff --git a/tools/wafadmin/Tools/gdc.py b/tools/wafadmin/Tools/gdc.py new file mode 100644 index 000000000..4d2a3216e --- /dev/null +++ b/tools/wafadmin/Tools/gdc.py @@ -0,0 +1,52 @@ +#!/usr/bin/env python +# encoding: utf-8 +# Carlos Rafael Giani, 2007 (dv) + +import sys +import Utils, ar +from Configure import conftest + +@conftest +def find_gdc(conf): + conf.find_program('gdc', var='D_COMPILER', mandatory=True) + +@conftest +def common_flags_gdc(conf): + v = conf.env + + # _DFLAGS _DIMPORTFLAGS + + # for mory info about the meaning of this dict see dmd.py + v['DFLAGS'] = [] + + v['D_SRC_F'] = '' + v['D_TGT_F'] = ['-c', '-o', ''] + v['DPATH_ST'] = '-I%s' # template for adding import paths + + # linker + v['D_LINKER'] = v['D_COMPILER'] + v['DLNK_SRC_F'] = '' + v['DLNK_TGT_F'] = ['-o', ''] + + v['DLIB_ST'] = '-l%s' # template for adding libs + v['DLIBPATH_ST'] = '-L%s' # template for adding libpaths + + # debug levels + v['DLINKFLAGS'] = [] + v['DFLAGS_OPTIMIZED'] = ['-O3'] + v['DFLAGS_DEBUG'] = ['-O0'] + v['DFLAGS_ULTRADEBUG'] = ['-O0'] + + v['D_shlib_DFLAGS'] = [] + v['D_shlib_LINKFLAGS'] = ['-shared'] + + v['DHEADER_ext'] = '.di' + v['D_HDR_F'] = '-fintfc -fintfc-file=' + +def detect(conf): + conf.find_gdc() + conf.check_tool('ar') + conf.check_tool('d') + conf.common_flags_gdc() + conf.d_platform_flags() + diff --git a/tools/wafadmin/Tools/glib2.py b/tools/wafadmin/Tools/glib2.py new file mode 100644 index 000000000..a9dc4f075 --- /dev/null +++ b/tools/wafadmin/Tools/glib2.py @@ -0,0 +1,165 @@ +#! /usr/bin/env python +# encoding: utf-8 +# Thomas Nagy, 2006-2008 (ita) + +"GLib2 support" + +import Task, Utils +from TaskGen import taskgen, before, after, feature + +# +# glib-genmarshal +# + +@taskgen +def add_marshal_file(self, filename, prefix): + if not hasattr(self, 'marshal_list'): + self.marshal_list = [] + self.meths.append('process_marshal') + self.marshal_list.append((filename, prefix)) + +@before('apply_core') +def process_marshal(self): + for f, prefix in getattr(self, 'marshal_list', []): + node = self.path.find_resource(f) + + if not node: + raise Utils.WafError('file not found %r' % f) + + h_node = node.change_ext('.h') + c_node = node.change_ext('.c') + + task = self.create_task('glib_genmarshal') + task.set_inputs(node) + task.set_outputs([h_node, c_node]) + task.env['GLIB_GENMARSHAL_PREFIX'] = prefix + self.allnodes.append(c_node) + +def genmarshal_func(self): + + bld = self.inputs[0].__class__.bld + + get = self.env.get_flat + cmd1 = "%s %s --prefix=%s --header > %s" % ( + get('GLIB_GENMARSHAL'), + self.inputs[0].srcpath(self.env), + get('GLIB_GENMARSHAL_PREFIX'), + self.outputs[0].abspath(self.env) + ) + + ret = bld.exec_command(cmd1) + if ret: return ret + + #print self.outputs[1].abspath(self.env) + f = open(self.outputs[1].abspath(self.env), 'wb') + f.write('''#include "%s"\n''' % self.outputs[0].name) + f.close() + + cmd2 = "%s %s --prefix=%s --body >> %s" % ( + get('GLIB_GENMARSHAL'), + self.inputs[0].srcpath(self.env), + get('GLIB_GENMARSHAL_PREFIX'), + self.outputs[1].abspath(self.env) + ) + ret = Utils.exec_command(cmd2) + if ret: return ret + +# +# glib-mkenums +# + +@taskgen +def add_enums_from_template(self, source='', target='', template='', comments=''): + if not hasattr(self, 'enums_list'): + self.enums_list = [] + self.meths.append('process_enums') + self.enums_list.append({'source': source, + 'target': target, + 'template': template, + 'file-head': '', + 'file-prod': '', + 'file-tail': '', + 'enum-prod': '', + 'value-head': '', + 'value-prod': '', + 'value-tail': '', + 'comments': comments}) + +@taskgen +def add_enums(self, source='', target='', + file_head='', file_prod='', file_tail='', enum_prod='', + value_head='', value_prod='', value_tail='', comments=''): + if not hasattr(self, 'enums_list'): + self.enums_list = [] + self.meths.append('process_enums') + self.enums_list.append({'source': source, + 'template': '', + 'target': target, + 'file-head': file_head, + 'file-prod': file_prod, + 'file-tail': file_tail, + 'enum-prod': enum_prod, + 'value-head': value_head, + 'value-prod': value_prod, + 'value-tail': value_tail, + 'comments': comments}) + +@before('apply_core') +def process_enums(self): + for enum in getattr(self, 'enums_list', []): + # temporary + env = self.env.copy() + task = self.create_task('glib_mkenums', env) + inputs = [] + + # process the source + source_list = self.to_list(enum['source']) + if not source_list: + raise Utils.WafError('missing source ' + str(enum)) + source_list = [self.path.find_resource(k) for k in source_list] + inputs += source_list + env['GLIB_MKENUMS_SOURCE'] = [k.srcpath(env) for k in source_list] + + # find the target + if not enum['target']: + raise Utils.WafError('missing target ' + str(enum)) + tgt_node = self.path.find_or_declare(enum['target']) + if tgt_node.name.endswith('.c'): + self.allnodes.append(tgt_node) + env['GLIB_MKENUMS_TARGET'] = tgt_node.abspath(env) + + + options = [] + + if enum['template']: # template, if provided + template_node = self.path.find_resource(enum['template']) + options.append('--template %s' % (template_node.abspath(env))) + inputs.append(template_node) + params = {'file-head' : '--fhead', + 'file-prod' : '--fprod', + 'file-tail' : '--ftail', + 'enum-prod' : '--eprod', + 'value-head' : '--vhead', + 'value-prod' : '--vprod', + 'value-tail' : '--vtail', + 'comments': '--comments'} + for param, option in params.iteritems(): + if enum[param]: + options.append('%s %r' % (option, enum[param])) + + env['GLIB_MKENUMS_OPTIONS'] = ' '.join(options) + + # update the task instance + task.set_inputs(inputs) + task.set_outputs(tgt_node) + +Task.task_type_from_func('glib_genmarshal', func=genmarshal_func, vars=['GLIB_GENMARSHAL_PREFIX', 'GLIB_GENMARSHAL'], + color='BLUE', before='cc') +Task.simple_task_type('glib_mkenums', + '${GLIB_MKENUMS} ${GLIB_MKENUMS_OPTIONS} ${GLIB_MKENUMS_SOURCE} > ${GLIB_MKENUMS_TARGET}', + color='PINK', before='cc') + +def detect(conf): + glib_genmarshal = conf.find_program('glib-genmarshal', var='GLIB_GENMARSHAL') + mk_enums_tool = conf.find_program('glib-mkenums', var='GLIB_MKENUMS') + diff --git a/tools/wafadmin/Tools/gnome.py b/tools/wafadmin/Tools/gnome.py new file mode 100644 index 000000000..77c810406 --- /dev/null +++ b/tools/wafadmin/Tools/gnome.py @@ -0,0 +1,214 @@ +#!/usr/bin/env python +# encoding: utf-8 +# Thomas Nagy, 2006-2008 (ita) + +"Gnome support" + +import os, re +import TaskGen, Utils, Runner, Task, Build, Options, Logs +import cc +from Logs import error +from TaskGen import taskgen, before, after, feature + +n1_regexp = re.compile('<refentrytitle>(.*)</refentrytitle>', re.M) +n2_regexp = re.compile('<manvolnum>(.*)</manvolnum>', re.M) + +def postinstall_schemas(prog_name): + if Build.bld.is_install: + dir = Build.bld.get_install_path('${PREFIX}/etc/gconf/schemas/%s.schemas' % prog_name) + if not Options.options.destdir: + # add the gconf schema + Utils.pprint('YELLOW', 'Installing GConf schema') + command = 'gconftool-2 --install-schema-file=%s 1> /dev/null' % dir + ret = Utils.exec_command(command) + else: + Utils.pprint('YELLOW', 'GConf schema not installed. After install, run this:') + Utils.pprint('YELLOW', 'gconftool-2 --install-schema-file=%s' % dir) + +def postinstall_icons(): + dir = Build.bld.get_install_path('${DATADIR}/icons/hicolor') + if Build.bld.is_install: + if not Options.options.destdir: + # update the pixmap cache directory + Utils.pprint('YELLOW', "Updating Gtk icon cache.") + command = 'gtk-update-icon-cache -q -f -t %s' % dir + ret = Utils.exec_command(command) + else: + Utils.pprint('YELLOW', 'Icon cache not updated. After install, run this:') + Utils.pprint('YELLOW', 'gtk-update-icon-cache -q -f -t %s' % dir) + +def postinstall_scrollkeeper(prog_name): + if Build.bld.is_install: + # now the scrollkeeper update if we can write to the log file + if os.access('/var/log/scrollkeeper.log', os.W_OK): + dir1 = Build.bld.get_install_path('${PREFIX}/var/scrollkeeper') + dir2 = Build.bld.get_install_path('${DATADIR}/omf/%s' % prog_name) + command = 'scrollkeeper-update -q -p %s -o %s' % (dir1, dir2) + ret = Utils.exec_command(command) + +def postinstall(prog_name='myapp', schemas=1, icons=1, scrollkeeper=1): + if schemas: postinstall_schemas(prog_name) + if icons: postinstall_icons() + if scrollkeeper: postinstall_scrollkeeper(prog_name) + +# OBSOLETE +class gnome_doc_taskgen(TaskGen.task_gen): + def __init__(self, *k, **kw): + TaskGen.task_gen.__init__(self, *k, **kw) + +@feature('gnome_doc') +def init_gnome_doc(self): + self.default_install_path = '${PREFIX}/share' + +@feature('gnome_doc') +@after('init_gnome_doc') +def apply_gnome_doc(self): + self.env['APPNAME'] = self.doc_module + lst = self.to_list(self.doc_linguas) + bld = self.bld + for x in lst: + tsk = self.create_task('xml2po') + node = self.path.find_resource(x+'/'+x+'.po') + src = self.path.find_resource('C/%s.xml' % self.doc_module) + out = self.path.find_or_declare('%s/%s.xml' % (x, self.doc_module)) + tsk.set_inputs([node, src]) + tsk.set_outputs(out) + + tsk2 = self.create_task('xsltproc2po') + out2 = self.path.find_or_declare('%s/%s-%s.omf' % (x, self.doc_module, x)) + tsk2.set_outputs(out2) + node = self.path.find_resource(self.doc_module+".omf.in") + tsk2.inputs = [node, out] + + tsk2.run_after.append(tsk) + + if bld.is_install: + path = self.install_path + 'gnome/help/%s/%s' % (self.doc_module, x) + bld.install_files(self.install_path + 'omf', out2, env=self.env) + for y in self.to_list(self.doc_figures): + try: + os.stat(self.path.abspath() + '/' + x + '/' + y) + bld.install_as(path + '/' + y, self.path.abspath() + '/' + x + '/' + y) + except: + bld.install_as(path + '/' + y, self.path.abspath() + '/C/' + y) + bld.install_as(path + '/%s.xml' % self.doc_module, out.abspath(self.env)) + +# OBSOLETE +class xml_to_taskgen(TaskGen.task_gen): + def __init__(self, *k, **kw): + TaskGen.task_gen.__init__(self, *k, **kw) + +@feature('xml_to') +def init_xml_to(self): + Utils.def_attrs(self, + source = 'xmlfile', + xslt = 'xlsltfile', + target = 'hey', + default_install_path = '${PREFIX}', + task_created = None) + +@feature('xml_to') +@after('init_xml_to') +def apply_xml_to(self): + xmlfile = self.path.find_resource(self.source) + xsltfile = self.path.find_resource(self.xslt) + tsk = self.create_task('xmlto') + tsk.set_inputs([xmlfile, xsltfile]) + tsk.set_outputs(xmlfile.change_ext('html')) + tsk.install_path = self.install_path + +def sgml_scan(self): + node = self.inputs[0] + + env = self.env + variant = node.variant(env) + + fi = open(node.abspath(env), 'r') + content = fi.read() + fi.close() + + # we should use a sgml parser :-/ + name = n1_regexp.findall(content)[0] + num = n2_regexp.findall(content)[0] + + doc_name = name+'.'+num + + if not self.outputs: + self.outputs = [self.generator.path.find_or_declare(doc_name)] + + return ([], [doc_name]) + +class gnome_sgml2man_taskgen(TaskGen.task_gen): + def __init__(self, *k, **kw): + TaskGen.task_gen.__init__(self, *k, **kw) + +@feature('gnome_sgml2man') +def apply_gnome_sgml2man(self): + """ + we could make it more complicated, but for now we just scan the document each time + """ + assert(getattr(self, 'appname', None)) + + def install_result(task): + out = task.outputs[0] + name = out.name + ext = name[-1] + env = task.env + self.bld.install_files('${DATADIR}/man/man%s/' % ext, out, env) + + self.bld.rescan(self.path) + for name in self.bld.cache_dir_contents[self.path.id]: + base, ext = os.path.splitext(name) + if ext != '.sgml': continue + + task = self.create_task('sgml2man') + task.set_inputs(self.path.find_resource(name)) + task.task_generator = self + if self.bld.is_install: task.install = install_result + # no outputs, the scanner does it + # no caching for now, this is not a time-critical feature + # in the future the scanner can be used to do more things (find dependencies, etc) + task.scan() + +cls = Task.simple_task_type('sgml2man', '${SGML2MAN} -o ${TGT[0].bld_dir(env)} ${SRC} > /dev/null', color='BLUE') +cls.scan = sgml_scan +cls.quiet = 1 + +Task.simple_task_type('xmlto', '${XMLTO} html -m ${SRC[1].abspath(env)} ${SRC[0].abspath(env)}') + +Task.simple_task_type('xml2po', '${XML2PO} ${XML2POFLAGS} ${SRC} > ${TGT}', color='BLUE') + +# how do you expect someone to understand this?! +xslt_magic = """${XSLTPROC2PO} -o ${TGT[0].abspath(env)} \ +--stringparam db2omf.basename ${APPNAME} \ +--stringparam db2omf.format docbook \ +--stringparam db2omf.lang C \ +--stringparam db2omf.dtd '-//OASIS//DTD DocBook XML V4.3//EN' \ +--stringparam db2omf.omf_dir ${PREFIX}/share/omf \ +--stringparam db2omf.help_dir ${PREFIX}/share/gnome/help \ +--stringparam db2omf.omf_in ${SRC[0].abspath(env)} \ +--stringparam db2omf.scrollkeeper_cl ${SCROLLKEEPER_DATADIR}/Templates/C/scrollkeeper_cl.xml \ +${DB2OMF} ${SRC[1].abspath(env)}""" + +#--stringparam db2omf.dtd '-//OASIS//DTD DocBook XML V4.3//EN' \ +Task.simple_task_type('xsltproc2po', xslt_magic, color='BLUE') + +def detect(conf): + conf.check_tool('gnu_dirs glib2 dbus') + sgml2man = conf.find_program('docbook2man', var='SGML2MAN') + + def getstr(varname): + return getattr(Options.options, varname, '') + + # addefine also sets the variable to the env + conf.define('GNOMELOCALEDIR', os.path.join(conf.env['DATADIR'], 'locale')) + + xml2po = conf.find_program('xml2po', var='XML2PO') + xsltproc2po = conf.find_program('xsltproc', var='XSLTPROC2PO') + conf.env['XML2POFLAGS'] = '-e -p' + conf.env['SCROLLKEEPER_DATADIR'] = Utils.cmd_output("scrollkeeper-config --pkgdatadir", silent=1).strip() + conf.env['DB2OMF'] = Utils.cmd_output("/usr/bin/pkg-config --variable db2omf gnome-doc-utils", silent=1).strip() + +def set_options(opt): + opt.add_option('--want-rpath', type='int', default=1, dest='want_rpath', help='set rpath to 1 or 0 [Default 1]') + diff --git a/tools/wafadmin/Tools/gnu_dirs.py b/tools/wafadmin/Tools/gnu_dirs.py new file mode 100644 index 000000000..b4882127b --- /dev/null +++ b/tools/wafadmin/Tools/gnu_dirs.py @@ -0,0 +1,111 @@ +#!/usr/bin/env python +# encoding: utf-8 +# Ali Sabil, 2007 + +""" +To use this module do not forget to call +opt.tool_options('gnu_dirs') +AND +conf.check_tool('gnu_dirs') + +Add options for the standard GNU directories, this tool will add the options +found in autotools, and will update the environment with the following +installation variables: + + * PREFIX : architecture-independent files [/usr/local] + * EXEC_PREFIX : architecture-dependent files [PREFIX] + * BINDIR : user executables [EXEC_PREFIX/bin] + * SBINDIR : user executables [EXEC_PREFIX/sbin] + * LIBEXECDIR : program executables [EXEC_PREFIX/libexec] + * SYSCONFDIR : read-only single-machine data [PREFIX/etc] + * SHAREDSTATEDIR : modifiable architecture-independent data [PREFIX/com] + * LOCALSTATEDIR : modifiable single-machine data [PREFIX/var] + * LIBDIR : object code libraries [EXEC_PREFIX/lib] + * INCLUDEDIR : C header files [PREFIX/include] + * OLDINCLUDEDIR : C header files for non-gcc [/usr/include] + * DATAROOTDIR : read-only arch.-independent data root [PREFIX/share] + * DATADIR : read-only architecture-independent data [DATAROOTDIR] + * INFODIR : info documentation [DATAROOTDIR/info] + * LOCALEDIR : locale-dependent data [DATAROOTDIR/locale] + * MANDIR : man documentation [DATAROOTDIR/man] + * DOCDIR : documentation root [DATAROOTDIR/doc/telepathy-glib] + * HTMLDIR : html documentation [DOCDIR] + * DVIDIR : dvi documentation [DOCDIR] + * PDFDIR : pdf documentation [DOCDIR] + * PSDIR : ps documentation [DOCDIR] +""" + +import Utils, Options + +_options = [x.split(', ') for x in ''' +bindir, user executables, ${EXEC_PREFIX}/bin +sbindir, system admin executables, ${EXEC_PREFIX}/sbin +libexecdir, program executables, ${EXEC_PREFIX}/libexec +sysconfdir, read-only single-machine data, ${PREFIX}/etc +sharedstatedir, modifiable architecture-independent data, ${PREFIX}/com +localstatedir, modifiable single-machine data, ${PREFIX}/var +libdir, object code libraries, ${EXEC_PREFIX}/lib +includedir, C header files, ${PREFIX}/include +oldincludedir, C header files for non-gcc, /usr/include +datarootdir, read-only arch.-independent data root, ${PREFIX}/share +datadir, read-only architecture-independent data, ${DATAROOTDIR} +infodir, info documentation, ${DATAROOTDIR}/info +localedir, locale-dependent data, ${DATAROOTDIR}/locale +mandir, man documentation, ${DATAROOTDIR}/man +docdir, documentation root, ${DATAROOTDIR}/doc/${PACKAGE} +htmldir, html documentation, ${DOCDIR} +dvidir, dvi documentation, ${DOCDIR} +pdfdir, pdf documentation, ${DOCDIR} +psdir, ps documentation, ${DOCDIR} +'''.split('\n') if x] + +def detect(conf): + def get_param(varname, default): + return getattr(Options.options, varname, '') or default + + env = conf.env + env['EXEC_PREFIX'] = get_param('EXEC_PREFIX', env['PREFIX']) + env['PACKAGE'] = Utils.g_module.APPNAME or env['PACKAGE'] + + complete = False + iter = 0 + while not complete and iter < len(_options) + 1: + iter += 1 + complete = True + for name, help, default in _options: + name = name.upper() + if not env[name]: + try: + env[name] = Utils.subst_vars(get_param(name, default), env) + except TypeError: + complete = False + if not complete: + lst = [name for name, _, _ in _options if not env[name.upper()]] + raise Utils.WafError('Variable substitution failure %r' % lst) + +def set_options(opt): + + inst_dir = opt.add_option_group('Installation directories', +'By default, "waf install" will put the files in\ + "/usr/local/bin", "/usr/local/lib" etc. An installation prefix other\ + than "/usr/local" can be given using "--prefix", for example "--prefix=$HOME"') + + for k in ('--prefix', '--destdir'): + option = opt.parser.get_option(k) + if option: + opt.parser.remove_option(k) + inst_dir.add_option(option) + + inst_dir.add_option('--exec-prefix', + help = 'installation prefix [Default: ${PREFIX}]', + default = '', + dest = 'EXEC_PREFIX') + + dirs_options = opt.add_option_group('Pre-defined installation directories', '') + + for name, help, default in _options: + option_name = '--' + name + str_default = default + str_help = '%s [Default: %s]' % (help, str_default) + dirs_options.add_option(option_name, help=str_help, default='', dest=name.upper()) + diff --git a/tools/wafadmin/Tools/gob2.py b/tools/wafadmin/Tools/gob2.py new file mode 100644 index 000000000..00aaa32ac --- /dev/null +++ b/tools/wafadmin/Tools/gob2.py @@ -0,0 +1,18 @@ +#!/usr/bin/env python +# encoding: utf-8 +# Ali Sabil, 2007 + +import TaskGen + +TaskGen.declare_chain( + name = 'gob2', + rule = '${GOB2} -o ${TGT[0].bld_dir(env)} ${GOB2FLAGS} ${SRC}', + ext_in = '.gob', + ext_out = '.c' +) + +def detect(conf): + gob2 = conf.find_program('gob2', var='GOB2', mandatory=True) + conf.env['GOB2'] = gob2 + conf.env['GOB2FLAGS'] = '' + diff --git a/tools/wafadmin/Tools/gxx.py b/tools/wafadmin/Tools/gxx.py new file mode 100644 index 000000000..ccec9999d --- /dev/null +++ b/tools/wafadmin/Tools/gxx.py @@ -0,0 +1,125 @@ +#!/usr/bin/env python +# encoding: utf-8 +# Thomas Nagy, 2006 (ita) +# Ralf Habacker, 2006 (rh) +# Yinon Ehrlich, 2009 + +import os, sys +import Configure, Options, Utils +import ccroot, ar +from Configure import conftest + +@conftest +def find_gxx(conf): + cxx = conf.find_program(['g++', 'c++'], var='CXX', mandatory=True) + cxx = conf.cmd_to_list(cxx) + ccroot.get_cc_version(conf, cxx, gcc=True) + conf.env.CXX_NAME = 'gcc' + conf.env.CXX = cxx + +@conftest +def gxx_common_flags(conf): + v = conf.env + + # CPPFLAGS CXXDEFINES _CXXINCFLAGS _CXXDEFFLAGS + + v['CXX_SRC_F'] = '' + v['CXX_TGT_F'] = ['-c', '-o', ''] # shell hack for -MD + v['CPPPATH_ST'] = '-I%s' # template for adding include paths + + # linker + if not v['LINK_CXX']: v['LINK_CXX'] = v['CXX'] + v['CXXLNK_SRC_F'] = '' + v['CXXLNK_TGT_F'] = ['-o', ''] # shell hack for -MD + + v['LIB_ST'] = '-l%s' # template for adding libs + v['LIBPATH_ST'] = '-L%s' # template for adding libpaths + v['STATICLIB_ST'] = '-l%s' + v['STATICLIBPATH_ST'] = '-L%s' + v['RPATH_ST'] = '-Wl,-rpath,%s' + v['CXXDEFINES_ST'] = '-D%s' + + v['SONAME_ST'] = '-Wl,-h,%s' + v['SHLIB_MARKER'] = '-Wl,-Bdynamic' + v['STATICLIB_MARKER'] = '-Wl,-Bstatic' + v['FULLSTATIC_MARKER'] = '-static' + + # program + v['program_PATTERN'] = '%s' + + # shared library + v['shlib_CXXFLAGS'] = ['-fPIC', '-DPIC'] # avoid using -DPIC, -fPIC aleady defines the __PIC__ macro + v['shlib_LINKFLAGS'] = ['-shared'] + v['shlib_PATTERN'] = 'lib%s.so' + + # static lib + v['staticlib_LINKFLAGS'] = ['-Wl,-Bstatic'] + v['staticlib_PATTERN'] = 'lib%s.a' + + # osx stuff + v['LINKFLAGS_MACBUNDLE'] = ['-bundle', '-undefined', 'dynamic_lookup'] + v['CCFLAGS_MACBUNDLE'] = ['-fPIC'] + v['macbundle_PATTERN'] = '%s.bundle' + +@conftest +def gxx_modifier_win32(conf): + v = conf.env + v['program_PATTERN'] = '%s.exe' + + v['shlib_PATTERN'] = '%s.dll' + v['implib_PATTERN'] = 'lib%s.dll.a' + v['IMPLIB_ST'] = '-Wl,--out-implib,%s' + v['shlib_CXXFLAGS'] = ['-DPIC', '-DDLL_EXPORT'] # TODO 64-bit platforms may need -fPIC + + # Auto-import is enabled by default even without this option, + # but enabling it explicitly has the nice effect of suppressing the rather boring, debug-level messages + # that the linker emits otherwise. + v.append_value('LINKFLAGS', '-Wl,--enable-auto-import') + +@conftest +def gxx_modifier_cygwin(conf): + gxx_modifier_win32(conf) + v = conf.env + v['shlib_PATTERN'] = 'cyg%s.dll' + v.append_value('shlib_LINKFLAGS', '-Wl,--enable-auto-image-base') + +@conftest +def gxx_modifier_darwin(conf): + v = conf.env + v['shlib_CXXFLAGS'] = ['-fPIC', '-compatibility_version', '1', '-current_version', '1'] + v['shlib_LINKFLAGS'] = ['-dynamiclib'] + v['shlib_PATTERN'] = 'lib%s.dylib' + + v['staticlib_LINKFLAGS'] = [] + + v['SHLIB_MARKER'] = '' + v['STATICLIB_MARKER'] = '' + +@conftest +def gxx_modifier_aix(conf): + v = conf.env + v['program_LINKFLAGS'] = ['-Wl,-brtl'] + + v['shlib_LINKFLAGS'] = ['-shared', '-Wl,-brtl,-bexpfull'] + + v['SHLIB_MARKER'] = '' + +@conftest +def gxx_modifier_platform(conf): + # * set configurations specific for a platform. + # * the destination platform is detected automatically by looking at the macros the compiler predefines, + # and if it's not recognised, it fallbacks to sys.platform. + dest_os = conf.env['DEST_OS'] or Utils.unversioned_sys_platform() + gxx_modifier_func = globals().get('gxx_modifier_' + dest_os) + if gxx_modifier_func: + gxx_modifier_func(conf) + +def detect(conf): + conf.find_gxx() + conf.find_cpp() + conf.find_ar() + conf.gxx_common_flags() + conf.gxx_modifier_platform() + conf.cxx_load_tools() + conf.cxx_add_flags() + diff --git a/tools/wafadmin/Tools/icc.py b/tools/wafadmin/Tools/icc.py new file mode 100644 index 000000000..56712a3df --- /dev/null +++ b/tools/wafadmin/Tools/icc.py @@ -0,0 +1,36 @@ +#!/usr/bin/env python +# encoding: utf-8 +# Stian Selnes, 2008 +# Thomas Nagy 2009 + +import os, sys +import Configure, Options, Utils +import ccroot, ar, gcc +from Configure import conftest + +@conftest +def find_icc(conf): + if sys.platform == 'cygwin': + conf.fatal('The Intel compiler does not work on Cygwin') + + v = conf.env + cc = None + if v['CC']: cc = v['CC'] + elif 'CC' in conf.environ: cc = conf.environ['CC'] + if not cc: cc = conf.find_program('icc', var='CC') + if not cc: cc = conf.find_program('ICL', var='CC') + if not cc: conf.fatal('Intel C Compiler (icc) was not found') + cc = conf.cmd_to_list(cc) + + ccroot.get_cc_version(conf, cc, icc=True) + v['CC'] = cc + v['CC_NAME'] = 'icc' + +detect = ''' +find_icc +find_ar +gcc_common_flags +gcc_modifier_platform +cc_load_tools +cc_add_flags +''' diff --git a/tools/wafadmin/Tools/icpc.py b/tools/wafadmin/Tools/icpc.py new file mode 100644 index 000000000..726e3a497 --- /dev/null +++ b/tools/wafadmin/Tools/icpc.py @@ -0,0 +1,34 @@ +#!/usr/bin/env python +# encoding: utf-8 +# Thomas Nagy 2009 + +import os, sys +import Configure, Options, Utils +import ccroot, ar, gxx +from Configure import conftest + +@conftest +def find_icpc(conf): + if sys.platform == 'cygwin': + conf.fatal('The Intel compiler does not work on Cygwin') + + v = conf.env + cxx = None + if v['CXX']: cxx = v['CXX'] + elif 'CXX' in conf.environ: cxx = conf.environ['CXX'] + if not cxx: cxx = conf.find_program('icpc', var='CXX') + if not cxx: conf.fatal('Intel C++ Compiler (icpc) was not found') + cxx = conf.cmd_to_list(cxx) + + ccroot.get_cc_version(conf, cxx, icc=True) + v['CXX'] = cxx + v['CXX_NAME'] = 'icc' + +detect = ''' +find_icpc +find_ar +gxx_common_flags +gxx_modifier_platform +cxx_load_tools +cxx_add_flags +''' diff --git a/tools/wafadmin/Tools/intltool.py b/tools/wafadmin/Tools/intltool.py new file mode 100644 index 000000000..d47f8e114 --- /dev/null +++ b/tools/wafadmin/Tools/intltool.py @@ -0,0 +1,143 @@ +#!/usr/bin/env python +# encoding: utf-8 +# Thomas Nagy, 2006 (ita) + +"intltool support" + +import os, re +import Configure, TaskGen, Task, Utils, Runner, Options, Build, config_c +from TaskGen import feature, before, taskgen +from Logs import error + +""" +Usage: + +bld.new_task_gen(features='intltool_in', source='a.po b.po', podir='po', cache='.intlcache', flags='') + + + +""" +class intltool_in_taskgen(TaskGen.task_gen): + """deprecated""" + def __init__(self, *k, **kw): + TaskGen.task_gen.__init__(self, *k, **kw) + +@before('apply_core') +@feature('intltool_in') +def iapply_intltool_in_f(self): + try: self.meths.remove('apply_core') + except ValueError: pass + + for i in self.to_list(self.source): + node = self.path.find_resource(i) + + podir = getattr(self, 'podir', 'po') + podirnode = self.path.find_dir(podir) + if not podirnode: + error("could not find the podir %r" % podir) + continue + + cache = getattr(self, 'intlcache', '.intlcache') + self.env['INTLCACHE'] = os.path.join(self.path.bldpath(self.env), podir, cache) + self.env['INTLPODIR'] = podirnode.srcpath(self.env) + self.env['INTLFLAGS'] = getattr(self, 'flags', ['-q', '-u', '-c']) + + task = self.create_task('intltool') + task.set_inputs(node) + task.set_outputs(node.change_ext('')) + + task.install_path = self.install_path + +class intltool_po_taskgen(TaskGen.task_gen): + """deprecated""" + def __init__(self, *k, **kw): + TaskGen.task_gen.__init__(self, *k, **kw) + + +@feature('intltool_po') +def apply_intltool_po(self): + try: self.meths.remove('apply_core') + except ValueError: pass + + self.default_install_path = '${LOCALEDIR}' + appname = getattr(self, 'appname', 'set_your_app_name') + podir = getattr(self, 'podir', '') + + def install_translation(task): + out = task.outputs[0] + filename = out.name + (langname, ext) = os.path.splitext(filename) + inst_file = langname + os.sep + 'LC_MESSAGES' + os.sep + appname + '.mo' + self.bld.install_as(os.path.join(self.install_path, inst_file), out, self.env, self.chmod) + + linguas = self.path.find_resource(os.path.join(podir, 'LINGUAS')) + if linguas: + # scan LINGUAS file for locales to process + file = open(linguas.abspath()) + langs = [] + for line in file.readlines(): + # ignore lines containing comments + if not line.startswith('#'): + langs += line.split() + file.close() + re_linguas = re.compile('[-a-zA-Z_@.]+') + for lang in langs: + # Make sure that we only process lines which contain locales + if re_linguas.match(lang): + node = self.path.find_resource(os.path.join(podir, re_linguas.match(lang).group() + '.po')) + task = self.create_task('po') + task.set_inputs(node) + task.set_outputs(node.change_ext('.mo')) + if self.bld.is_install: task.install = install_translation + else: + Utils.pprint('RED', "Error no LINGUAS file found in po directory") + +Task.simple_task_type('po', '${POCOM} -o ${TGT} ${SRC}', color='BLUE', shell=False) +Task.simple_task_type('intltool', + '${INTLTOOL} ${INTLFLAGS} ${INTLCACHE} ${INTLPODIR} ${SRC} ${TGT}', + color='BLUE', after="cc_link cxx_link", shell=False) + +def detect(conf): + pocom = conf.find_program('msgfmt') + if not pocom: + # if msgfmt should not be mandatory, catch the thrown exception in your wscript + conf.fatal('The program msgfmt (gettext) is mandatory!') + conf.env['POCOM'] = pocom + + # NOTE: it is possible to set INTLTOOL in the environment, but it must not have spaces in it + + intltool = conf.find_program('intltool-merge', var='INTLTOOL') + if not intltool: + # if intltool-merge should not be mandatory, catch the thrown exception in your wscript + if Options.platform == 'win32': + perl = conf.find_program('perl', var='PERL') + if not perl: + conf.fatal('The program perl (required by intltool) could not be found') + + intltooldir = Configure.find_file('intltool-merge', os.environ['PATH'].split(os.pathsep)) + if not intltooldir: + conf.fatal('The program intltool-merge (intltool, gettext-devel) is mandatory!') + + conf.env['INTLTOOL'] = Utils.to_list(conf.env['PERL']) + [intltooldir + os.sep + 'intltool-merge'] + conf.check_message('intltool', '', True, ' '.join(conf.env['INTLTOOL'])) + else: + conf.fatal('The program intltool-merge (intltool, gettext-devel) is mandatory!') + + def getstr(varname): + return getattr(Options.options, varname, '') + + prefix = conf.env['PREFIX'] + datadir = getstr('datadir') + if not datadir: datadir = os.path.join(prefix,'share') + + conf.define('LOCALEDIR', os.path.join(datadir, 'locale')) + conf.define('DATADIR', datadir) + + if conf.env['CC'] or conf.env['CXX']: + # Define to 1 if <locale.h> is present + conf.check(header_name='locale.h') + +def set_options(opt): + opt.add_option('--want-rpath', type='int', default=1, dest='want_rpath', help='set rpath to 1 or 0 [Default 1]') + opt.add_option('--datadir', type='string', default='', dest='datadir', help='read-only application data') + diff --git a/tools/wafadmin/Tools/javaw.py b/tools/wafadmin/Tools/javaw.py new file mode 100644 index 000000000..a1f7374b1 --- /dev/null +++ b/tools/wafadmin/Tools/javaw.py @@ -0,0 +1,255 @@ +#!/usr/bin/env python +# encoding: utf-8 +# Thomas Nagy, 2006-2008 (ita) + +""" +Java support + +Javac is one of the few compilers that behaves very badly: +* it outputs files where it wants to (-d is only for the package root) +* it recompiles files silently behind your back +* it outputs an undefined amount of files (inner classes) + +Fortunately, the convention makes it possible to use the build dir without +too many problems for the moment + +Inner classes must be located and cleaned when a problem arise, +for the moment waf does not track the production of inner classes. + +Adding all the files to a task and executing it if any of the input files +change is only annoying for the compilation times + +Compilation can be run using Jython[1] rather than regular Python. Instead of +running one of the following commands: + ./waf configure + python waf configure +You would have to run: + java -jar /path/to/jython.jar waf configure + +[1] http://www.jython.org/ +""" + +import os, re +from Configure import conf +import TaskGen, Task, Utils, Options, Build +from TaskGen import feature, before, taskgen + +class_check_source = ''' +public class Test { + public static void main(String[] argv) { + Class lib; + if (argv.length < 1) { + System.err.println("Missing argument"); + System.exit(77); + } + try { + lib = Class.forName(argv[0]); + } catch (ClassNotFoundException e) { + System.err.println("ClassNotFoundException"); + System.exit(1); + } + lib = null; + System.exit(0); + } +} +''' + +@feature('jar') +@before('apply_core') +def jar_files(self): + basedir = getattr(self, 'basedir', '.') + destfile = getattr(self, 'destfile', 'test.jar') + jaropts = getattr(self, 'jaropts', []) + jarcreate = getattr(self, 'jarcreate', 'cf') + + dir = self.path.find_dir(basedir) + if not dir: raise + + jaropts.append('-C') + jaropts.append(dir.abspath(self.env)) + jaropts.append('.') + + out = self.path.find_or_declare(destfile) + + tsk = self.create_task('jar_create') + tsk.set_outputs(out) + tsk.inputs = [x for x in dir.find_iter(src=0, bld=1) if x.id != out.id] + tsk.env['JAROPTS'] = jaropts + tsk.env['JARCREATE'] = jarcreate + +@feature('javac') +@before('apply_core') +def apply_java(self): + Utils.def_attrs(self, jarname='', jaropts='', classpath='', + sourcepath='.', srcdir='.', source_re='**/*.java', + jar_mf_attributes={}, jar_mf_classpath=[]) + + if getattr(self, 'source_root', None): + # old stuff + self.srcdir = self.source_root + + + nodes_lst = [] + + if not self.classpath: + if not self.env['CLASSPATH']: + self.env['CLASSPATH'] = '..' + os.pathsep + '.' + else: + self.env['CLASSPATH'] = self.classpath + + srcdir_node = self.path.find_dir(self.srcdir) + if not srcdir_node: + raise Utils.WafError('could not find srcdir %r' % self.srcdir) + + src_nodes = [x for x in srcdir_node.ant_glob(self.source_re, flat=False)] + bld_nodes = [x.change_ext('.class') for x in src_nodes] + + self.env['OUTDIR'] = [srcdir_node.abspath(self.env)] + + tsk = self.create_task('javac') + tsk.set_inputs(src_nodes) + tsk.set_outputs(bld_nodes) + + if getattr(self, 'compat', None): + tsk.env.append_value('JAVACFLAGS', ['-source', self.compat]) + + if hasattr(self, 'sourcepath'): + fold = [self.path.find_dir(x) for x in self.to_list(self.sourcepath)] + names = os.pathsep.join([x.srcpath() for x in fold]) + else: + names = srcdir_node.srcpath() + + if names: + tsk.env.append_value('JAVACFLAGS', ['-sourcepath', names]) + + if self.jarname: + tsk = self.create_task('jar_create') + tsk.set_inputs(bld_nodes) + tsk.set_outputs(self.path.find_or_declare(self.jarname)) + + if not self.env['JAROPTS']: + if self.jaropts: + self.env['JAROPTS'] = self.jaropts + else: + dirs = '.' + self.env['JAROPTS'] = ['-C', ''.join(self.env['OUTDIR']), dirs] + +Task.simple_task_type('jar_create', '${JAR} ${JARCREATE} ${TGT} ${JAROPTS}', color='GREEN') +cls = Task.simple_task_type('javac', '${JAVAC} -classpath ${CLASSPATH} -d ${OUTDIR} ${JAVACFLAGS} ${SRC}') +cls.color = 'BLUE' +def post_run_javac(self): + """this is for cleaning the folder + javac creates single files for inner classes + but it is not possible to know which inner classes in advance""" + + par = {} + for x in self.inputs: + par[x.parent.id] = x.parent + + inner = {} + for k in par.values(): + path = k.abspath(self.env) + lst = os.listdir(path) + + for u in lst: + if u.find('$') >= 0: + inner_class_node = k.find_or_declare(u) + inner[inner_class_node.id] = inner_class_node + + to_add = set(inner.keys()) - set([x.id for x in self.outputs]) + for x in to_add: + self.outputs.append(inner[x]) + + return Task.Task.post_run(self) +cls.post_run = post_run_javac + +def detect(conf): + # If JAVA_PATH is set, we prepend it to the path list + java_path = conf.environ['PATH'].split(os.pathsep) + v = conf.env + + if 'JAVA_HOME' in conf.environ: + java_path = [os.path.join(conf.environ['JAVA_HOME'], 'bin')] + java_path + conf.env['JAVA_HOME'] = [conf.environ['JAVA_HOME']] + + for x in 'javac java jar'.split(): + conf.find_program(x, var=x.upper(), path_list=java_path) + conf.env[x.upper()] = conf.cmd_to_list(conf.env[x.upper()]) + v['JAVA_EXT'] = ['.java'] + + if 'CLASSPATH' in conf.environ: + v['CLASSPATH'] = conf.environ['CLASSPATH'] + + if not v['JAR']: conf.fatal('jar is required for making java packages') + if not v['JAVAC']: conf.fatal('javac is required for compiling java classes') + v['JARCREATE'] = 'cf' # can use cvf + +@conf +def check_java_class(self, classname, with_classpath=None): + """Check if the specified java class is installed""" + + import shutil + + javatestdir = '.waf-javatest' + + classpath = javatestdir + if self.env['CLASSPATH']: + classpath += os.pathsep + self.env['CLASSPATH'] + if isinstance(with_classpath, str): + classpath += os.pathsep + with_classpath + + shutil.rmtree(javatestdir, True) + os.mkdir(javatestdir) + + java_file = open(os.path.join(javatestdir, 'Test.java'), 'w') + java_file.write(class_check_source) + java_file.close() + + # Compile the source + Utils.exec_command(self.env['JAVAC'] + [os.path.join(javatestdir, 'Test.java')], shell=False) + + # Try to run the app + cmd = self.env['JAVA'] + ['-cp', classpath, 'Test', classname] + self.log.write("%s\n" % str(cmd)) + found = Utils.exec_command(cmd, shell=False, log=self.log) + + self.check_message('Java class %s' % classname, "", not found) + + shutil.rmtree(javatestdir, True) + + return found + +@conf +def check_jni_headers(conf): + """ + Check for jni headers and libraries + + On success the environment variable xxx_JAVA is added for uselib + """ + + if not conf.env.CC_NAME and not conf.env.CXX_NAME: + conf.fatal('load a compiler first (gcc, g++, ..)') + + if not conf.env.JAVA_HOME: + conf.fatal('set JAVA_HOME in the system environment') + + # jni requires the jvm + javaHome = conf.env['JAVA_HOME'][0] + + b = Build.BuildContext() + b.load_dirs(conf.srcdir, conf.blddir) + dir = b.root.find_dir(conf.env.JAVA_HOME[0] + '/include') + f = dir.ant_glob('**/(jni|jni_md).h', flat=False) + incDirs = [x.parent.abspath() for x in f] + + dir = b.root.find_dir(conf.env.JAVA_HOME[0]) + f = dir.ant_glob('**/*jvm.(so|dll)', flat=False) + libDirs = [x.parent.abspath() for x in f] or [javaHome] + + for i, d in enumerate(libDirs): + if conf.check(header_name='jni.h', define_name='HAVE_JNI_H', lib='jvm', + libpath=d, includes=incDirs, uselib_store='JAVA', uselib='JAVA'): + break + else: + conf.fatal('could not find lib jvm in %r (see config.log)' % libDirs) + diff --git a/tools/wafadmin/Tools/kde4.py b/tools/wafadmin/Tools/kde4.py new file mode 100644 index 000000000..7f7a78046 --- /dev/null +++ b/tools/wafadmin/Tools/kde4.py @@ -0,0 +1,76 @@ +#!/usr/bin/env python +# encoding: utf-8 +# Thomas Nagy, 2006 (ita) + +import os, sys, re +import Options, TaskGen, Task, Utils +from TaskGen import taskgen, feature, after + +class msgfmt_taskgen(TaskGen.task_gen): + def __init__(self, *k, **kw): + TaskGen.task_gen.__init__(self, *k, **kw) + +@feature('msgfmt') +def init_msgfmt(self): + #langs = '' # for example "foo/fr foo/br" + self.default_install_path = '${KDE4_LOCALE_INSTALL_DIR}' + +@feature('msgfmt') +@after('init_msgfmt') +def apply_msgfmt(self): + for lang in self.to_list(self.langs): + node = self.path.find_resource(lang+'.po') + task = self.create_task('msgfmt') + task.set_inputs(node) + task.set_outputs(node.change_ext('.mo')) + + if not self.bld.is_install: continue + langname = lang.split('/') + langname = langname[-1] + task.install_path = self.install_path + os.sep + langname + os.sep + 'LC_MESSAGES' + task.filename = getattr(self, 'appname', 'set_your_appname') + '.mo' + task.chmod = self.chmod + +def detect(conf): + kdeconfig = conf.find_program('kde4-config') + if not kdeconfig: + conf.fatal('we need kde4-config') + prefix = Utils.cmd_output('%s --prefix' % kdeconfig, silent=True).strip() + file = '%s/share/apps/cmake/modules/KDELibsDependencies.cmake' % prefix + try: os.stat(file) + except OSError: + file = '%s/share/kde4/apps/cmake/modules/KDELibsDependencies.cmake' % prefix + try: os.stat(file) + except OSError: conf.fatal('could not open %s' % file) + + try: + txt = Utils.readf(file) + except (OSError, IOError): + conf.fatal('could not read %s' % file) + + txt = txt.replace('\\\n', '\n') + fu = re.compile('#(.*)\n') + txt = fu.sub('', txt) + + setregexp = re.compile('([sS][eE][tT]\s*\()\s*([^\s]+)\s+\"([^"]+)\"\)') + found = setregexp.findall(txt) + + for (_, key, val) in found: + #print key, val + conf.env[key] = val + + # well well, i could just write an interpreter for cmake files + conf.env['LIB_KDECORE']='kdecore' + conf.env['LIB_KDEUI'] ='kdeui' + conf.env['LIB_KIO'] ='kio' + conf.env['LIB_KHTML'] ='khtml' + conf.env['LIB_KPARTS'] ='kparts' + + conf.env['LIBPATH_KDECORE'] = conf.env['KDE4_LIB_INSTALL_DIR'] + conf.env['CPPPATH_KDECORE'] = conf.env['KDE4_INCLUDE_INSTALL_DIR'] + conf.env.append_value('CPPPATH_KDECORE', conf.env['KDE4_INCLUDE_INSTALL_DIR']+"/KDE") + + conf.env['MSGFMT'] = conf.find_program('msgfmt') + +Task.simple_task_type('msgfmt', '${MSGFMT} ${SRC} -o ${TGT}', color='BLUE', shell=False) + diff --git a/tools/wafadmin/Tools/libtool.py b/tools/wafadmin/Tools/libtool.py new file mode 100644 index 000000000..69d027ea5 --- /dev/null +++ b/tools/wafadmin/Tools/libtool.py @@ -0,0 +1,333 @@ +#!/usr/bin/env python +# encoding: utf-8 +# Matthias Jahn, 2008, jahn matthias ath freenet punto de +# Thomas Nagy, 2008 (ita) + +import sys, re, os, optparse + +import TaskGen, Task, Utils, preproc +from Logs import error, debug, warn +from TaskGen import taskgen, after, before, feature + +REVISION="0.1.3" + +""" +if you want to use the code here, you must use something like this: +obj = obj.create(...) +obj.features.append("libtool") +obj.vnum = "1.2.3" # optional, but versioned libraries are common +""" + +# fake libtool files +fakelibtool_vardeps = ['CXX', 'PREFIX'] +def fakelibtool_build(task): + # Writes a .la file, used by libtool + env = task.env + dest = open(task.outputs[0].abspath(env), 'w') + sname = task.inputs[0].name + fu = dest.write + fu("# Generated by ltmain.sh - GNU libtool 1.5.18 - (pwn3d by BKsys II code name WAF)\n") + if env['vnum']: + nums = env['vnum'].split('.') + libname = task.inputs[0].name + name3 = libname+'.'+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(env['libtoolvars']+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" % env['PREFIX']) + dest.close() + return 0 + +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 ValueError: + pass + file.close() + return dc + +@feature("libtool") +@after('apply_link') +def apply_link_libtool(self): + if self.type != 'program': + linktask = self.link_task + latask = self.create_task('fakelibtool') + latask.set_inputs(linktask.outputs) + latask.set_outputs(linktask.outputs[0].change_ext('.la')) + self.latask = latask + + if self.bld.is_install: + self.bld.install_files('${PREFIX}/lib', linktask.outputs[0], self.env) + +@feature("libtool") +@before('apply_core') +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: + dict = read_la_file(p+'/lib'+l+'.la') + linkflags2 = dict.get('dependency_libs', '') + for v in linkflags2.split(): + if v.endswith('.la'): + libtool_files.append(v) + libtool_vars.append(v) + continue + self.env.append_unique('LINKFLAGS', v) + break + + 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) + +Task.task_type_from_func('fakelibtool', vars=fakelibtool_vardeps, func=fakelibtool_build, color='BLUE', after="cc_link cxx_link static_link") + +class libtool_la_file: + def __init__ (self, la_filename): + self.__la_filename = la_filename + #remove path and .la suffix + self.linkname = str(os.path.split(la_filename)[-1])[:-3] + if self.linkname.startswith("lib"): + self.linkname = self.linkname[3:] + # The name that we can dlopen(3). + self.dlname = None + # Names of this library + self.library_names = None + # The name of the static archive. + self.old_library = None + # Libraries that this one depends upon. + self.dependency_libs = None + # Version information for libIlmImf. + self.current = None + self.age = None + self.revision = None + # Is this an already installed library? + self.installed = None + # Should we warn about portability when linking against -modules? + self.shouldnotlink = None + # Files to dlopen/dlpreopen + self.dlopen = None + self.dlpreopen = None + # Directory that this library needs to be installed in: + self.libdir = '/usr/lib' + if not self.__parse(): + raise "file %s not found!!" %(la_filename) + + def __parse(self): + "Retrieve the variables from a file" + if not os.path.isfile(self.__la_filename): return 0 + la_file=open(self.__la_filename, 'r') + for line in la_file: + ln = line.strip() + if not ln: continue + if ln[0]=='#': continue + (key, value) = str(ln).split('=', 1) + key = key.strip() + value = value.strip() + if value == "no": value = False + elif value == "yes": value = True + else: + try: value = int(value) + except ValueError: value = value.strip("'") + setattr(self, key, value) + la_file.close() + return 1 + + def get_libs(self): + """return linkflags for this lib""" + libs = [] + if self.dependency_libs: + libs = str(self.dependency_libs).strip().split() + if libs == None: + libs = [] + # add la lib and libdir + libs.insert(0, "-l%s" % self.linkname.strip()) + libs.insert(0, "-L%s" % self.libdir.strip()) + return libs + + def __str__(self): + return '''\ +dlname = "%(dlname)s" +library_names = "%(library_names)s" +old_library = "%(old_library)s" +dependency_libs = "%(dependency_libs)s" +version = %(current)s.%(age)s.%(revision)s +installed = "%(installed)s" +shouldnotlink = "%(shouldnotlink)s" +dlopen = "%(dlopen)s" +dlpreopen = "%(dlpreopen)s" +libdir = "%(libdir)s"''' % self.__dict__ + +class libtool_config: + def __init__ (self, la_filename): + self.__libtool_la_file = libtool_la_file(la_filename) + tmp = self.__libtool_la_file + self.__version = [int(tmp.current), int(tmp.age), int(tmp.revision)] + self.__sub_la_files = [] + self.__sub_la_files.append(la_filename) + self.__libs = None + + def __cmp__(self, other): + """make it compareable with X.Y.Z versions (Y and Z are optional)""" + if not other: + return 1 + othervers = [int(s) for s in str(other).split(".")] + selfvers = self.__version + return cmp(selfvers, othervers) + + def __str__(self): + return "\n".join([ + str(self.__libtool_la_file), + ' '.join(self.__libtool_la_file.get_libs()), + '* New getlibs:', + ' '.join(self.get_libs()) + ]) + + def __get_la_libs(self, la_filename): + return libtool_la_file(la_filename).get_libs() + + def get_libs(self): + """return the complete uniqe linkflags that do not + contain .la files anymore""" + libs_list = list(self.__libtool_la_file.get_libs()) + libs_map = {} + while len(libs_list) > 0: + entry = libs_list.pop(0) + if entry: + if str(entry).endswith(".la"): + ## prevents duplicate .la checks + if entry not in self.__sub_la_files: + self.__sub_la_files.append(entry) + libs_list.extend(self.__get_la_libs(entry)) + else: + libs_map[entry]=1 + self.__libs = libs_map.keys() + return self.__libs + + def get_libs_only_L(self): + if not self.__libs: self.get_libs() + libs = self.__libs + libs = [s for s in libs if str(s).startswith('-L')] + return libs + + def get_libs_only_l(self): + if not self.__libs: self.get_libs() + libs = self.__libs + libs = [s for s in libs if str(s).startswith('-l')] + return libs + + def get_libs_only_other(self): + if not self.__libs: self.get_libs() + libs = self.__libs + libs = [s for s in libs if not(str(s).startswith('-L')or str(s).startswith('-l'))] + return libs + +def useCmdLine(): + """parse cmdline args and control build""" + usage = '''Usage: %prog [options] PathToFile.la +example: %prog --atleast-version=2.0.0 /usr/lib/libIlmImf.la +nor: %prog --libs /usr/lib/libamarok.la''' + parser = optparse.OptionParser(usage) + a = parser.add_option + a("--version", dest = "versionNumber", + action = "store_true", default = False, + help = "output version of libtool-config" + ) + a("--debug", dest = "debug", + action = "store_true", default = False, + help = "enable debug" + ) + a("--libs", dest = "libs", + action = "store_true", default = False, + help = "output all linker flags" + ) + a("--libs-only-l", dest = "libs_only_l", + action = "store_true", default = False, + help = "output -l flags" + ) + a("--libs-only-L", dest = "libs_only_L", + action = "store_true", default = False, + help = "output -L flags" + ) + a("--libs-only-other", dest = "libs_only_other", + action = "store_true", default = False, + help = "output other libs (e.g. -pthread)" + ) + a("--atleast-version", dest = "atleast_version", + default=None, + help = "return 0 if the module is at least version ATLEAST_VERSION" + ) + a("--exact-version", dest = "exact_version", + default=None, + help = "return 0 if the module is exactly version EXACT_VERSION" + ) + a("--max-version", dest = "max_version", + default=None, + help = "return 0 if the module is at no newer than version MAX_VERSION" + ) + + (options, args) = parser.parse_args() + if len(args) != 1 and not options.versionNumber: + parser.error("incorrect number of arguments") + if options.versionNumber: + print("libtool-config version %s" % REVISION) + return 0 + ltf = libtool_config(args[0]) + if options.debug: + print(ltf) + if options.atleast_version: + if ltf >= options.atleast_version: return 0 + sys.exit(1) + if options.exact_version: + if ltf == options.exact_version: return 0 + sys.exit(1) + if options.max_version: + if ltf <= options.max_version: return 0 + sys.exit(1) + + def p(x): + print(" ".join(x)) + if options.libs: p(ltf.get_libs()) + elif options.libs_only_l: p(ltf.get_libs_only_l()) + elif options.libs_only_L: p(ltf.get_libs_only_L()) + elif options.libs_only_other: p(ltf.get_libs_only_other()) + return 0 + +if __name__ == '__main__': + useCmdLine() + diff --git a/tools/wafadmin/Tools/lua.py b/tools/wafadmin/Tools/lua.py new file mode 100644 index 000000000..c6dd6ae17 --- /dev/null +++ b/tools/wafadmin/Tools/lua.py @@ -0,0 +1,25 @@ +#!/usr/bin/env python +# encoding: utf-8 +# Sebastian Schlingmann, 2008 +# Thomas Nagy, 2008 (ita) + +import TaskGen +from TaskGen import taskgen, feature +from Constants import * + +TaskGen.declare_chain( + name = 'luac', + rule = '${LUAC} -s -o ${TGT} ${SRC}', + ext_in = '.lua', + ext_out = '.luac', + reentrant = 0, + install = 'LUADIR', # env variable +) + +@feature('lua') +def init_lua(self): + self.default_chmod = O755 + +def detect(conf): + conf.find_program('luac', var='LUAC', mandatory = True) + diff --git a/tools/wafadmin/Tools/misc.py b/tools/wafadmin/Tools/misc.py new file mode 100644 index 000000000..f289133e8 --- /dev/null +++ b/tools/wafadmin/Tools/misc.py @@ -0,0 +1,433 @@ +#!/usr/bin/env python +# encoding: utf-8 +# Thomas Nagy, 2006 (ita) + +""" +Custom objects: + - execute a function everytime + - copy a file somewhere else +""" + +import shutil, re, os +import TaskGen, Node, Task, Utils, Build, Constants +from TaskGen import feature, taskgen, after, before +from Logs import debug + +def copy_func(tsk): + "Make a file copy. This might be used to make other kinds of file processing (even calling a compiler is possible)" + env = tsk.env + infile = tsk.inputs[0].abspath(env) + outfile = tsk.outputs[0].abspath(env) + try: + shutil.copy2(infile, outfile) + except (OSError, IOError): + return 1 + else: + if tsk.chmod: os.chmod(outfile, tsk.chmod) + return 0 + +def action_process_file_func(tsk): + "Ask the function attached to the task to process it" + if not tsk.fun: raise Utils.WafError('task must have a function attached to it for copy_func to work!') + return tsk.fun(tsk) + +class cmd_taskgen(TaskGen.task_gen): + def __init__(self, *k, **kw): + TaskGen.task_gen.__init__(self, *k, **kw) + +@feature('cmd') +def apply_cmd(self): + "call a command everytime" + if not self.fun: raise Utils.WafError('cmdobj needs a function!') + tsk = Task.TaskBase() + tsk.fun = self.fun + tsk.env = self.env + self.tasks.append(tsk) + tsk.install_path = self.install_path + +class copy_taskgen(TaskGen.task_gen): + "By default, make a file copy, if fun is provided, fun will make the copy (or call a compiler, etc)" + def __init__(self, *k, **kw): + TaskGen.task_gen.__init__(self, *k, **kw) + +@feature('copy') +@before('apply_core') +def apply_copy(self): + Utils.def_attrs(self, fun=copy_func) + self.default_install_path = 0 + + lst = self.to_list(self.source) + self.meths.remove('apply_core') + + for filename in lst: + node = self.path.find_resource(filename) + if not node: raise Utils.WafError('cannot find input file %s for processing' % filename) + + target = self.target + if not target or len(lst)>1: target = node.name + + # TODO the file path may be incorrect + newnode = self.path.find_or_declare(target) + + tsk = self.create_task('copy') + tsk.set_inputs(node) + tsk.set_outputs(newnode) + tsk.fun = self.fun + tsk.chmod = self.chmod + + if not tsk.env: + tsk.debug() + raise Utils.WafError('task without an environment') + +def subst_func(tsk): + "Substitutes variables in a .in file" + + m4_re = re.compile('@(\w+)@', re.M) + + env = tsk.env + infile = tsk.inputs[0].abspath(env) + outfile = tsk.outputs[0].abspath(env) + + code = Utils.readf(infile) + + # replace all % by %% to prevent errors by % signs in the input file while string formatting + code = code.replace('%', '%%') + + s = m4_re.sub(r'%(\1)s', code) + + di = tsk.dict or {} + if not di: + names = m4_re.findall(code) + for i in names: + di[i] = env.get_flat(i) or env.get_flat(i.upper()) + + file = open(outfile, 'w') + file.write(s % di) + file.close() + if tsk.chmod: os.chmod(outfile, tsk.chmod) + +class subst_taskgen(TaskGen.task_gen): + def __init__(self, *k, **kw): + TaskGen.task_gen.__init__(self, *k, **kw) + +@feature('subst') +@before('apply_core') +def apply_subst(self): + Utils.def_attrs(self, fun=subst_func) + self.default_install_path = 0 + lst = self.to_list(self.source) + self.meths.remove('apply_core') + + self.dict = getattr(self, 'dict', {}) + + for filename in lst: + node = self.path.find_resource(filename) + if not node: raise Utils.WafError('cannot find input file %s for processing' % filename) + + if self.target: + newnode = self.path.find_or_declare(self.target) + else: + newnode = node.change_ext('') + + try: + self.dict = self.dict.get_merged_dict() + except AttributeError: + pass + + if self.dict and not self.env['DICT_HASH']: + self.env = self.env.copy() + keys = list(self.dict.keys()) + keys.sort() + lst = [self.dict[x] for x in keys] + self.env['DICT_HASH'] = str(Utils.h_list(lst)) + + tsk = self.create_task('copy') + tsk.set_inputs(node) + tsk.set_outputs(newnode) + tsk.fun = self.fun + tsk.dict = self.dict + tsk.dep_vars = ['DICT_HASH'] + tsk.install_path = self.install_path + tsk.chmod = self.chmod + + if not tsk.env: + tsk.debug() + raise Utils.WafError('task without an environment') + +#################### +## command-output #### +#################### + +class cmd_arg(object): + """command-output arguments for representing files or folders""" + def __init__(self, name, template='%s'): + self.name = name + self.template = template + self.node = None + +class input_file(cmd_arg): + def find_node(self, base_path): + assert isinstance(base_path, Node.Node) + self.node = base_path.find_resource(self.name) + if self.node is None: + raise Utils.WafError("Input file %s not found in " % (self.name, base_path)) + + def get_path(self, env, absolute): + if absolute: + return self.template % self.node.abspath(env) + else: + return self.template % self.node.srcpath(env) + +class output_file(cmd_arg): + def find_node(self, base_path): + assert isinstance(base_path, Node.Node) + self.node = base_path.find_or_declare(self.name) + if self.node is None: + raise Utils.WafError("Output file %s not found in " % (self.name, base_path)) + + def get_path(self, env, absolute): + if absolute: + return self.template % self.node.abspath(env) + else: + return self.template % self.node.bldpath(env) + +class cmd_dir_arg(cmd_arg): + def find_node(self, base_path): + assert isinstance(base_path, Node.Node) + self.node = base_path.find_dir(self.name) + if self.node is None: + raise Utils.WafError("Directory %s not found in " % (self.name, base_path)) + +class input_dir(cmd_dir_arg): + def get_path(self, dummy_env, dummy_absolute): + return self.template % self.node.abspath() + +class output_dir(cmd_dir_arg): + def get_path(self, env, dummy_absolute): + return self.template % self.node.abspath(env) + + +class command_output(Task.Task): + color = "BLUE" + def __init__(self, env, command, command_node, command_args, stdin, stdout, cwd, os_env, stderr): + Task.Task.__init__(self, env, normal=1) + assert isinstance(command, (str, Node.Node)) + self.command = command + self.command_args = command_args + self.stdin = stdin + self.stdout = stdout + self.cwd = cwd + self.os_env = os_env + self.stderr = stderr + + if command_node is not None: self.dep_nodes = [command_node] + self.dep_vars = [] # additional environment variables to look + + def run(self): + task = self + #assert len(task.inputs) > 0 + + def input_path(node, template): + if task.cwd is None: + return template % node.bldpath(task.env) + else: + return template % node.abspath() + def output_path(node, template): + fun = node.abspath + if task.cwd is None: fun = node.bldpath + return template % fun(task.env) + + if isinstance(task.command, Node.Node): + argv = [input_path(task.command, '%s')] + else: + argv = [task.command] + + for arg in task.command_args: + if isinstance(arg, str): + argv.append(arg) + else: + assert isinstance(arg, cmd_arg) + argv.append(arg.get_path(task.env, (task.cwd is not None))) + + if task.stdin: + stdin = open(input_path(task.stdin, '%s')) + else: + stdin = None + + if task.stdout: + stdout = open(output_path(task.stdout, '%s'), "w") + else: + stdout = None + + if task.stderr: + stderr = open(output_path(task.stderr, '%s'), "w") + else: + stderr = None + + if task.cwd is None: + cwd = ('None (actually %r)' % os.getcwd()) + else: + cwd = repr(task.cwd) + debug("command-output: cwd=%s, stdin=%r, stdout=%r, argv=%r" % + (cwd, stdin, stdout, argv)) + + if task.os_env is None: + os_env = os.environ + else: + os_env = task.os_env + command = Utils.pproc.Popen(argv, stdin=stdin, stdout=stdout, stderr=stderr, cwd=task.cwd, env=os_env) + return command.wait() + +class cmd_output_taskgen(TaskGen.task_gen): + def __init__(self, *k, **kw): + TaskGen.task_gen.__init__(self, *k, **kw) + +@feature('command-output') +def init_cmd_output(self): + Utils.def_attrs(self, + stdin = None, + stdout = None, + stderr = None, + # the command to execute + command = None, + + # whether it is an external command; otherwise it is assumed + # to be an executable binary or script that lives in the + # source or build tree. + command_is_external = False, + + # extra parameters (argv) to pass to the command (excluding + # the command itself) + argv = [], + + # dependencies to other objects -> this is probably not what you want (ita) + # values must be 'task_gen' instances (not names!) + dependencies = [], + + # dependencies on env variable contents + dep_vars = [], + + # input files that are implicit, i.e. they are not + # stdin, nor are they mentioned explicitly in argv + hidden_inputs = [], + + # output files that are implicit, i.e. they are not + # stdout, nor are they mentioned explicitly in argv + hidden_outputs = [], + + # change the subprocess to this cwd (must use obj.input_dir() or output_dir() here) + cwd = None, + + # OS environment variables to pass to the subprocess + # if None, use the default environment variables unchanged + os_env = None) + +@feature('command-output') +@after('init_cmd_output') +def apply_cmd_output(self): + if self.command is None: + raise Utils.WafError("command-output missing command") + if self.command_is_external: + cmd = self.command + cmd_node = None + else: + cmd_node = self.path.find_resource(self.command) + assert cmd_node is not None, ('''Could not find command '%s' in source tree. +Hint: if this is an external command, +use command_is_external=True''') % (self.command,) + cmd = cmd_node + + if self.cwd is None: + cwd = None + else: + assert isinstance(cwd, CmdDirArg) + self.cwd.find_node(self.path) + + args = [] + inputs = [] + outputs = [] + + for arg in self.argv: + if isinstance(arg, cmd_arg): + arg.find_node(self.path) + if isinstance(arg, input_file): + inputs.append(arg.node) + if isinstance(arg, output_file): + outputs.append(arg.node) + + if self.stdout is None: + stdout = None + else: + assert isinstance(self.stdout, str) + stdout = self.path.find_or_declare(self.stdout) + if stdout is None: + raise Utils.WafError("File %s not found" % (self.stdout,)) + outputs.append(stdout) + + if self.stderr is None: + stderr = None + else: + assert isinstance(self.stderr, str) + stderr = self.path.find_or_declare(self.stderr) + if stderr is None: + raise Utils.WafError("File %s not found" % (self.stderr,)) + outputs.append(stderr) + + if self.stdin is None: + stdin = None + else: + assert isinstance(self.stdin, str) + stdin = self.path.find_resource(self.stdin) + if stdin is None: + raise Utils.WafError("File %s not found" % (self.stdin,)) + inputs.append(stdin) + + for hidden_input in self.to_list(self.hidden_inputs): + node = self.path.find_resource(hidden_input) + if node is None: + raise Utils.WafError("File %s not found in dir %s" % (hidden_input, self.path)) + inputs.append(node) + + for hidden_output in self.to_list(self.hidden_outputs): + node = self.path.find_or_declare(hidden_output) + if node is None: + raise Utils.WafError("File %s not found in dir %s" % (hidden_output, self.path)) + outputs.append(node) + + if not (inputs or getattr(self, 'no_inputs', None)): + raise Utils.WafError('command-output objects must have at least one input file or give self.no_inputs') + if not (outputs or getattr(self, 'no_outputs', None)): + raise Utils.WafError('command-output objects must have at least one output file or give self.no_outputs') + + task = command_output(self.env, cmd, cmd_node, self.argv, stdin, stdout, cwd, self.os_env, stderr) + Utils.copy_attrs(self, task, 'before after ext_in ext_out', only_if_set=True) + self.tasks.append(task) + + task.inputs = inputs + task.outputs = outputs + task.dep_vars = self.to_list(self.dep_vars) + + for dep in self.dependencies: + assert dep is not self + dep.post() + for dep_task in dep.tasks: + task.set_run_after(dep_task) + + if not task.inputs: + # the case for svnversion, always run, and update the output nodes + task.runnable_status = type(Task.TaskBase.run)(runnable_status, task, task.__class__) # always run + task.post_run = type(Task.TaskBase.run)(post_run, task, task.__class__) + + # TODO the case with no outputs? + +def post_run(self): + for x in self.outputs: + h = Utils.h_file(x.abspath(self.env)) + self.generator.bld.node_sigs[self.env.variant()][x.id] = h + +def runnable_status(self): + return Constants.RUN_ME + +Task.task_type_from_func('copy', vars=[], func=action_process_file_func) +TaskGen.task_gen.classes['command-output'] = cmd_output_taskgen + diff --git a/tools/wafadmin/Tools/msvc.py b/tools/wafadmin/Tools/msvc.py new file mode 100644 index 000000000..8f05516e6 --- /dev/null +++ b/tools/wafadmin/Tools/msvc.py @@ -0,0 +1,775 @@ +#!/usr/bin/env python +# encoding: utf-8 +# Carlos Rafael Giani, 2006 (dv) +# Tamas Pal, 2007 (folti) +# Nicolas Mercier, 2009 +# Microsoft Visual C++/Intel C++ compiler support - beta, needs more testing + +# usage: +# +# conf.env['MSVC_VERSIONS'] = ['msvc 9.0', 'msvc 8.0', 'wsdk 7.0', 'intel 11', 'PocketPC 9.0', 'Smartphone 8.0'] +# conf.env['MSVC_TARGETS'] = ['x64'] +# conf.check_tool('msvc') +# OR conf.check_tool('msvc', funs='no_autodetect') +# conf.check_lib_msvc('gdi32') +# conf.check_libs_msvc('kernel32 user32', mandatory=true) +# ... +# obj.uselib = 'KERNEL32 USER32 GDI32' +# +# platforms and targets will be tested in the order they appear; +# the first good configuration will be used +# supported platforms : +# ia64, x64, x86, x86_amd64, x86_ia64 + +# compilers supported : +# msvc => Visual Studio, versions 7.1 (2003), 8,0 (2005), 9.0 (2008) +# wsdk => Windows SDK, versions 6.0, 6.1, 7.0 +# icl => Intel compiler, versions 9,10,11 +# Smartphone => Compiler/SDK for Smartphone devices (armv4/v4i) +# PocketPC => Compiler/SDK for PocketPC devices (armv4/v4i) + + +import os, sys, re, string, optparse +import Utils, TaskGen, Runner, Configure, Task, Options +from Logs import debug, info, warn, error +from TaskGen import after, before, feature + +from Configure import conftest, conf +import ccroot, cc, cxx, ar, winres +from libtool import read_la_file + +import _winreg + +pproc = Utils.pproc + +# importlibs provided by MSVC/Platform SDK. Do NOT search them.... +g_msvc_systemlibs = """ +aclui activeds ad1 adptif adsiid advapi32 asycfilt authz bhsupp bits bufferoverflowu cabinet +cap certadm certidl ciuuid clusapi comctl32 comdlg32 comsupp comsuppd comsuppw comsuppwd comsvcs +credui crypt32 cryptnet cryptui d3d8thk daouuid dbgeng dbghelp dciman32 ddao35 ddao35d +ddao35u ddao35ud delayimp dhcpcsvc dhcpsapi dlcapi dnsapi dsprop dsuiext dtchelp +faultrep fcachdll fci fdi framedyd framedyn gdi32 gdiplus glauxglu32 gpedit gpmuuid +gtrts32w gtrtst32hlink htmlhelp httpapi icm32 icmui imagehlp imm32 iphlpapi iprop +kernel32 ksguid ksproxy ksuser libcmt libcmtd libcpmt libcpmtd loadperf lz32 mapi +mapi32 mgmtapi minidump mmc mobsync mpr mprapi mqoa mqrt msacm32 mscms mscoree +msdasc msimg32 msrating mstask msvcmrt msvcurt msvcurtd mswsock msxml2 mtx mtxdm +netapi32 nmapinmsupp npptools ntdsapi ntdsbcli ntmsapi ntquery odbc32 odbcbcp +odbccp32 oldnames ole32 oleacc oleaut32 oledb oledlgolepro32 opends60 opengl32 +osptk parser pdh penter pgobootrun pgort powrprof psapi ptrustm ptrustmd ptrustu +ptrustud qosname rasapi32 rasdlg rassapi resutils riched20 rpcndr rpcns4 rpcrt4 rtm +rtutils runtmchk scarddlg scrnsave scrnsavw secur32 sensapi setupapi sfc shell32 +shfolder shlwapi sisbkup snmpapi sporder srclient sti strsafe svcguid tapi32 thunk32 +traffic unicows url urlmon user32 userenv usp10 uuid uxtheme vcomp vcompd vdmdbg +version vfw32 wbemuuid webpost wiaguid wininet winmm winscard winspool winstrm +wintrust wldap32 wmiutils wow32 ws2_32 wsnmp32 wsock32 wst wtsapi32 xaswitch xolehlp +""".split() + + +all_msvc_platforms = [ ('x64', 'amd64'), ('x86', 'x86'), ('ia64', 'ia64'), ('x86_amd64', 'amd64'), ('x86_ia64', 'ia64') ] +all_wince_platforms = [ ('armv4', 'arm'), ('armv4i', 'arm'), ('mipsii', 'mips'), ('mipsii_fp', 'mips'), ('mipsiv', 'mips'), ('mipsiv_fp', 'mips'), ('sh4', 'sh'), ('x86', 'cex86') ] +all_icl_platforms = [ ('intel64', 'amd64'), ('em64t', 'amd64'), ('ia32', 'x86'), ('Itanium', 'ia64')] + +def setup_msvc(conf, versions): + platforms = Utils.to_list(conf.env['MSVC_TARGETS']) or [i for i,j in all_msvc_platforms+all_icl_platforms+all_wince_platforms] + desired_versions = conf.env['MSVC_VERSIONS'] or [v for v,_ in versions][::-1] + versiondict = dict(versions) + + for version in desired_versions: + try: + targets = dict(versiondict [version]) + for target in platforms: + try: + arch,(p1,p2,p3) = targets[target] + compiler,version = version.split() + return compiler,p1,p2,p3 + except KeyError: continue + except KeyError: continue + conf.fatal('msvc: Impossible to find a valid architecture for building (in setup_msvc)') + +@conf +def get_msvc_version(conf, compiler, version, target, vcvars): + debug('msvc: get_msvc_version: ' + compiler + ' ' + version + ' ' + target + ' ...') + batfile = os.path.join(conf.blddir, 'waf-print-msvc.bat') + f = open(batfile, 'w') + f.write("""@echo off +set INCLUDE= +set LIB= +call "%s" %s +echo PATH=%%PATH%% +echo INCLUDE=%%INCLUDE%% +echo LIB=%%LIB%% +""" % (vcvars,target)) + f.close() + sout = Utils.cmd_output(['cmd', '/E:on', '/V:on', '/C', batfile]) + lines = sout.splitlines() + + for x in ('Setting environment', 'Setting SDK environment', 'Intel(R) C++ Compiler'): + if lines[0].find(x) != -1: + break + else: + debug('msvc: get_msvc_version: %r %r %r -> not found' % (compiler, version, target)) + conf.fatal('msvc: Impossible to find a valid architecture for building (in get_msvc_version)') + + for line in lines[1:]: + if line.startswith('PATH='): + path = line[5:] + MSVC_PATH = path.split(';') + elif line.startswith('INCLUDE='): + MSVC_INCDIR = [i for i in line[8:].split(';') if i] + elif line.startswith('LIB='): + MSVC_LIBDIR = [i for i in line[4:].split(';') if i] + + # Check if the compiler is usable at all. + # The detection may return 64-bit versions even on 32-bit systems, and these would fail to run. + env = {} + env.update(os.environ) + env.update(PATH = path) + compiler_name, linker_name, lib_name = _get_prog_names(conf, compiler) + cxx = conf.find_program(compiler_name, path_list=MSVC_PATH) + # delete CL if exists. because it could contain parameters wich can change cl's behaviour rather catastrophically. + if env.has_key('CL'): + del(env['CL']) + + try: + p = pproc.Popen([cxx, '/help'], env=env, stdout=pproc.PIPE, stderr=pproc.PIPE) + out, err = p.communicate() + if p.returncode != 0: + raise Exception('return code: %r: %r' % (p.returncode, err)) + except Exception, e: + debug('msvc: get_msvc_version: %r %r %r -> failure' % (compiler, version, target)) + debug(str(e)) + conf.fatal('msvc: cannot run the compiler (in get_msvc_version)') + else: + debug('msvc: get_msvc_version: %r %r %r -> OK' % (compiler, version, target)) + + return (MSVC_PATH, MSVC_INCDIR, MSVC_LIBDIR) + +@conf +def gather_wsdk_versions(conf, versions): + version_pattern = re.compile('^v..?.?\...?.?') + try: + all_versions = _winreg.OpenKey(_winreg.HKEY_LOCAL_MACHINE, 'SOFTWARE\\Wow6432node\\Microsoft\\Microsoft SDKs\\Windows') + except WindowsError: + try: + all_versions = _winreg.OpenKey(_winreg.HKEY_LOCAL_MACHINE, 'SOFTWARE\\Microsoft\\Microsoft SDKs\\Windows') + except WindowsError: + return + index = 0 + while 1: + try: + version = _winreg.EnumKey(all_versions, index) + except WindowsError: + break + index = index + 1 + if not version_pattern.match(version): + continue + try: + msvc_version = _winreg.OpenKey(all_versions, version) + path,type = _winreg.QueryValueEx(msvc_version,'InstallationFolder') + except WindowsError: + continue + if os.path.isfile(os.path.join(path, 'bin', 'SetEnv.cmd')): + targets = [] + for target,arch in all_msvc_platforms: + try: + targets.append((target, (arch, conf.get_msvc_version('wsdk', version, '/'+target, os.path.join(path, 'bin', 'SetEnv.cmd'))))) + except Configure.ConfigurationError: + pass + versions.append(('wsdk ' + version[1:], targets)) + +@conf +def gather_msvc_versions(conf, versions): + # checks SmartPhones SDKs + try: + ce_sdk = _winreg.OpenKey(_winreg.HKEY_LOCAL_MACHINE, 'SOFTWARE\\Wow6432node\\Microsoft\\Windows CE Tools\\SDKs') + except WindowsError: + try: + ce_sdk = _winreg.OpenKey(_winreg.HKEY_LOCAL_MACHINE, 'SOFTWARE\\Microsoft\\Windows CE Tools\\SDKs') + except WindowsError: + ce_sdk = '' + if ce_sdk: + supported_wince_platforms = [] + ce_index = 0 + while 1: + try: + sdk_device = _winreg.EnumKey(ce_sdk, ce_index) + except WindowsError: + break + ce_index = ce_index + 1 + sdk = _winreg.OpenKey(ce_sdk, sdk_device) + path,type = _winreg.QueryValueEx(sdk, 'SDKRootDir') + path=str(path) + path,device = os.path.split(path) + if not device: + path,device = os.path.split(path) + for arch,compiler in all_wince_platforms: + platforms = [] + if os.path.isdir(os.path.join(path, device, 'Lib', arch)): + platforms.append((arch, compiler, os.path.join(path, device, 'Include', arch), os.path.join(path, device, 'Lib', arch))) + if platforms: + supported_wince_platforms.append((device, platforms)) + # checks MSVC + version_pattern = re.compile('^..?\...?') + for vcver,vcvar in [('VCExpress','exp'), ('VisualStudio','')]: + try: + all_versions = _winreg.OpenKey(_winreg.HKEY_LOCAL_MACHINE, 'SOFTWARE\\Wow6432node\\Microsoft\\'+vcver) + except WindowsError: + try: + all_versions = _winreg.OpenKey(_winreg.HKEY_LOCAL_MACHINE, 'SOFTWARE\\Microsoft\\'+vcver) + except WindowsError: + continue + index = 0 + while 1: + try: + version = _winreg.EnumKey(all_versions, index) + except WindowsError: + break + index = index + 1 + if not version_pattern.match(version): + continue + try: + msvc_version = _winreg.OpenKey(all_versions, version + "\\Setup\\VS") + path,type = _winreg.QueryValueEx(msvc_version, 'ProductDir') + path=str(path) + targets = [] + if ce_sdk: + for device,platforms in supported_wince_platforms: + cetargets = [] + for platform,compiler,include,lib in platforms: + winCEpath = os.path.join(path, 'VC', 'ce') + if os.path.isdir(winCEpath): + common_bindirs,_1,_2 = conf.get_msvc_version('msvc', version, 'x86', os.path.join(path, 'Common7', 'Tools', 'vsvars32.bat')) + if os.path.isdir(os.path.join(winCEpath, 'lib', platform)): + bindirs = [os.path.join(winCEpath, 'bin', compiler), os.path.join(winCEpath, 'bin', 'x86_'+compiler)] + common_bindirs + incdirs = [include, os.path.join(winCEpath, 'include'), os.path.join(winCEpath, 'atlmfc', 'include')] + libdirs = [lib, os.path.join(winCEpath, 'lib', platform), os.path.join(winCEpath, 'atlmfc', 'lib', platform)] + cetargets.append((platform, (platform, (bindirs,incdirs,libdirs)))) + versions.append((device+' '+version, cetargets)) + if os.path.isfile(os.path.join(path, 'VC', 'vcvarsall.bat')): + for target,realtarget in all_msvc_platforms[::-1]: + try: + targets.append((target, (realtarget, conf.get_msvc_version('msvc', version, target, os.path.join(path, 'VC', 'vcvarsall.bat'))))) + except: + pass + elif os.path.isfile(os.path.join(path, 'Common7', 'Tools', 'vsvars32.bat')): + try: + targets.append(('x86', ('x86', conf.get_msvc_version('msvc', version, 'x86', os.path.join(path, 'Common7', 'Tools', 'vsvars32.bat'))))) + except Configure.ConfigurationError: + pass + versions.append(('msvc '+version, targets)) + + except WindowsError: + continue + +@conf +def gather_icl_versions(conf, versions): + version_pattern = re.compile('^...?.?\....?.?') + try: + all_versions = _winreg.OpenKey(_winreg.HKEY_LOCAL_MACHINE, 'SOFTWARE\\Wow6432node\\Intel\\Compilers\\C++') + except WindowsError: + try: + all_versions = _winreg.OpenKey(_winreg.HKEY_LOCAL_MACHINE, 'SOFTWARE\\Intel\\Compilers\\C++') + except WindowsError: + return + index = 0 + while 1: + try: + version = _winreg.EnumKey(all_versions, index) + except WindowsError: + break + index = index + 1 + if not version_pattern.match(version): + continue + targets = [] + for target,arch in all_icl_platforms: + try: + icl_version = _winreg.OpenKey(all_versions, version+'\\'+target) + path,type = _winreg.QueryValueEx(icl_version,'ProductDir') + if os.path.isfile(os.path.join(path, 'bin', 'iclvars.bat')): + try: + targets.append((target, (arch, conf.get_msvc_version('intel', version, target, os.path.join(path, 'bin', 'iclvars.bat'))))) + except Configure.ConfigurationError: + pass + except WindowsError: + continue + major = version[0:2] + versions.append(('intel ' + major, targets)) + +@conf +def get_msvc_versions(conf): + if not conf.env['MSVC_INSTALLED_VERSIONS']: + conf.env['MSVC_INSTALLED_VERSIONS'] = [] + conf.gather_msvc_versions(conf.env['MSVC_INSTALLED_VERSIONS']) + conf.gather_wsdk_versions(conf.env['MSVC_INSTALLED_VERSIONS']) + conf.gather_icl_versions(conf.env['MSVC_INSTALLED_VERSIONS']) + return conf.env['MSVC_INSTALLED_VERSIONS'] + +@conf +def print_all_msvc_detected(conf): + for version,targets in conf.env['MSVC_INSTALLED_VERSIONS']: + info(version) + for target,l in targets: + info("\t"+target) + +def detect_msvc(conf): + versions = get_msvc_versions(conf) + return setup_msvc(conf, versions) + +@conf +def find_lt_names_msvc(self, libname, is_static=False): + """ + Win32/MSVC specific code to glean out information from libtool la files. + this function is not attached to the task_gen class + """ + lt_names=[ + 'lib%s.la' % libname, + '%s.la' % libname, + ] + + for path in self.env['LIBPATH']: + for la in lt_names: + laf=os.path.join(path,la) + dll=None + if os.path.exists(laf): + ltdict=read_la_file(laf) + lt_libdir=None + if ltdict.get('libdir', ''): + lt_libdir = ltdict['libdir'] + if not is_static and ltdict.get('library_names', ''): + dllnames=ltdict['library_names'].split() + dll=dllnames[0].lower() + dll=re.sub('\.dll$', '', dll) + return (lt_libdir, dll, False) + elif ltdict.get('old_library', ''): + olib=ltdict['old_library'] + if os.path.exists(os.path.join(path,olib)): + return (path, olib, True) + elif lt_libdir != '' and os.path.exists(os.path.join(lt_libdir,olib)): + return (lt_libdir, olib, True) + else: + return (None, olib, True) + else: + raise Utils.WafError('invalid libtool object file: %s' % laf) + return (None, None, None) + +@conf +def libname_msvc(self, libname, is_static=False, mandatory=False): + lib = libname.lower() + lib = re.sub('\.lib$','',lib) + + if lib in g_msvc_systemlibs: + return lib + + lib=re.sub('^lib','',lib) + + if lib == 'm': + return None + + (lt_path, lt_libname, lt_static) = self.find_lt_names_msvc(lib, is_static) + + if lt_path != None and lt_libname != None: + if lt_static == True: + # file existance check has been made by find_lt_names + return os.path.join(lt_path,lt_libname) + + if lt_path != None: + _libpaths=[lt_path] + self.env['LIBPATH'] + else: + _libpaths=self.env['LIBPATH'] + + static_libs=[ + 'lib%ss.lib' % lib, + 'lib%s.lib' % lib, + '%ss.lib' % lib, + '%s.lib' %lib, + ] + + dynamic_libs=[ + 'lib%s.dll.lib' % lib, + 'lib%s.dll.a' % lib, + '%s.dll.lib' % lib, + '%s.dll.a' % lib, + 'lib%s_d.lib' % lib, + '%s_d.lib' % lib, + '%s.lib' %lib, + ] + + libnames=static_libs + if not is_static: + libnames=dynamic_libs + static_libs + + for path in _libpaths: + for libn in libnames: + if os.path.exists(os.path.join(path, libn)): + debug('msvc: lib found: %s' % os.path.join(path,libn)) + return re.sub('\.lib$', '',libn) + + #if no lib can be found, just return the libname as msvc expects it + if mandatory: + self.fatal("The library %r could not be found" % libname) + return re.sub('\.lib$', '', libname) + +@conf +def check_lib_msvc(self, libname, is_static=False, uselib_store=None, mandatory=False): + "This is the api to use" + libn = self.libname_msvc(libname, is_static, mandatory) + + if not uselib_store: + uselib_store = libname.upper() + + # Note: ideally we should be able to place the lib in the right env var, either STATICLIB or LIB, + # but we don't distinguish static libs from shared libs. + # This is ok since msvc doesn't have any special linker flag to select static libs (no env['STATICLIB_MARKER']) + if False and is_static: # disabled + self.env['STATICLIB_' + uselib_store] = [libn] + else: + self.env['LIB_' + uselib_store] = [libn] + +@conf +def check_libs_msvc(self, libnames, is_static=False, mandatory=False): + for libname in Utils.to_list(libnames): + self.check_lib_msvc(libname, is_static, mandatory=mandatory) + +@conftest +def no_autodetect(conf): + conf.eval_rules(detect.replace('autodetect', '')) + + +detect = ''' +autodetect +find_msvc +msvc_common_flags +cc_load_tools +cxx_load_tools +cc_add_flags +cxx_add_flags +''' + +@conftest +def autodetect(conf): + v = conf.env + compiler, path, includes, libdirs = detect_msvc(conf) + v['PATH'] = path + v['CPPPATH'] = includes + v['LIBPATH'] = libdirs + v['MSVC_COMPILER'] = compiler + +def _get_prog_names(conf, compiler): + if compiler=='intel': + compiler_name = 'ICL' + linker_name = 'XILINK' + lib_name = 'XILIB' + else: + # assumes CL.exe + compiler_name = 'CL' + linker_name = 'LINK' + lib_name = 'LIB' + return compiler_name, linker_name, lib_name + +@conftest +def find_msvc(conf): + # due to path format limitations, limit operation only to native Win32. Yeah it sucks. + if sys.platform != 'win32': + conf.fatal('MSVC module only works under native Win32 Python! cygwin is not supported yet') + + v = conf.env + + compiler, path, includes, libdirs = detect_msvc(conf) + v['PATH'] = path + v['CPPPATH'] = includes + v['LIBPATH'] = libdirs + + compiler_name, linker_name, lib_name = _get_prog_names(conf, compiler) + + # compiler + cxx = None + if v['CXX']: cxx = v['CXX'] + elif 'CXX' in conf.environ: cxx = conf.environ['CXX'] + if not cxx: cxx = conf.find_program(compiler_name, var='CXX', path_list=path) + if not cxx: conf.fatal('%s was not found (compiler)' % compiler_name) + cxx = conf.cmd_to_list(cxx) + + # before setting anything, check if the compiler is really msvc + env = dict(conf.environ) + env.update(PATH = ';'.join(path)) + if not Utils.cmd_output([cxx, '/nologo', '/?'], silent=True, env=env): + conf.fatal('the msvc compiler could not be identified') + + # c/c++ compiler + v['CC'] = v['CXX'] = cxx + v['CC_NAME'] = v['CXX_NAME'] = 'msvc' + + # environment flags + try: v.prepend_value('CPPPATH', conf.environ['INCLUDE']) + except KeyError: pass + try: v.prepend_value('LIBPATH', conf.environ['LIB']) + except KeyError: pass + + # linker + if not v['LINK_CXX']: + link = conf.find_program(linker_name, path_list=path) + if link: v['LINK_CXX'] = link + else: conf.fatal('%s was not found (linker)' % linker_name) + v['LINK'] = link + + if not v['LINK_CC']: v['LINK_CC'] = v['LINK_CXX'] + + # staticlib linker + if not v['AR']: + stliblink = conf.find_program(lib_name, path_list=path) + if not stliblink: return + v['AR'] = stliblink + v['ARFLAGS'] = ['/NOLOGO'] + + # manifest tool. Not required for VS 2003 and below. Must have for VS 2005 and later + manifesttool = conf.find_program('MT', path_list=path) + if manifesttool: + v['MT'] = manifesttool + v['MTFLAGS'] = ['/NOLOGO'] + + conf.check_tool('winres') + + if not conf.env['WINRC']: + warn('Resource compiler not found. Compiling resource file is disabled') + +@conftest +def msvc_common_flags(conf): + v = conf.env + + v['CPPFLAGS'] = ['/W3', '/nologo', '/EHsc'] + + v['CCDEFINES_ST'] = '/D%s' + v['CXXDEFINES_ST'] = '/D%s' + + # TODO just use _WIN32, which defined by the compiler itself! + v['CCDEFINES'] = ['WIN32'] # avoid using this, any compiler predefines the _WIN32 marcro anyway + v['CXXDEFINES'] = ['WIN32'] # avoid using this, any compiler predefines the _WIN32 marcro anyway + + v['_CCINCFLAGS'] = [] + v['_CCDEFFLAGS'] = [] + v['_CXXINCFLAGS'] = [] + v['_CXXDEFFLAGS'] = [] + + v['CC_SRC_F'] = '' + v['CC_TGT_F'] = ['/c', '/Fo'] + v['CXX_SRC_F'] = '' + v['CXX_TGT_F'] = ['/c', '/Fo'] + + v['CPPPATH_ST'] = '/I%s' # template for adding include paths + + v['AR_TGT_F'] = v['CCLNK_TGT_F'] = v['CXXLNK_TGT_F'] = '/OUT:' + + # Subsystem specific flags + v['CPPFLAGS_CONSOLE'] = ['/SUBSYSTEM:CONSOLE'] + v['CPPFLAGS_NATIVE'] = ['/SUBSYSTEM:NATIVE'] + v['CPPFLAGS_POSIX'] = ['/SUBSYSTEM:POSIX'] + v['CPPFLAGS_WINDOWS'] = ['/SUBSYSTEM:WINDOWS'] + v['CPPFLAGS_WINDOWSCE'] = ['/SUBSYSTEM:WINDOWSCE'] + + # CRT specific flags + v['CPPFLAGS_CRT_MULTITHREADED'] = ['/MT'] + v['CPPFLAGS_CRT_MULTITHREADED_DLL'] = ['/MD'] + + # TODO these are defined by the compiler itself! + v['CPPDEFINES_CRT_MULTITHREADED'] = ['_MT'] # this is defined by the compiler itself! + v['CPPDEFINES_CRT_MULTITHREADED_DLL'] = ['_MT', '_DLL'] # these are defined by the compiler itself! + + v['CPPFLAGS_CRT_MULTITHREADED_DBG'] = ['/MTd'] + v['CPPFLAGS_CRT_MULTITHREADED_DLL_DBG'] = ['/MDd'] + + # TODO these are defined by the compiler itself! + v['CPPDEFINES_CRT_MULTITHREADED_DBG'] = ['_DEBUG', '_MT'] # these are defined by the compiler itself! + v['CPPDEFINES_CRT_MULTITHREADED_DLL_DBG'] = ['_DEBUG', '_MT', '_DLL'] # these are defined by the compiler itself! + + # compiler debug levels + v['CCFLAGS'] = ['/TC'] + v['CCFLAGS_OPTIMIZED'] = ['/O2', '/DNDEBUG'] + v['CCFLAGS_RELEASE'] = ['/O2', '/DNDEBUG'] + # TODO _DEBUG is defined by the compiler itself! + v['CCFLAGS_DEBUG'] = ['/Od', '/RTC1', '/D_DEBUG', '/ZI'] + v['CCFLAGS_ULTRADEBUG'] = ['/Od', '/RTC1', '/D_DEBUG', '/ZI'] + + v['CXXFLAGS'] = ['/TP'] + v['CXXFLAGS_OPTIMIZED'] = ['/O2', '/DNDEBUG'] + v['CXXFLAGS_RELEASE'] = ['/O2', '/DNDEBUG'] + # TODO _DEBUG is defined by the compiler itself! + v['CXXFLAGS_DEBUG'] = ['/Od', '/RTC1', '/D_DEBUG', '/ZI'] + v['CXXFLAGS_ULTRADEBUG'] = ['/Od', '/RTC1', '/D_DEBUG', '/ZI'] + + # linker + v['LIB'] = [] + + v['LIB_ST'] = '%s.lib' # template for adding libs + v['LIBPATH_ST'] = '/LIBPATH:%s' # template for adding libpaths + v['STATICLIB_ST'] = 'lib%s.lib' # Note: to be able to distinguish between a static lib and a dll import lib, it's a good pratice to name the static lib 'lib%s.lib' and the dll import lib '%s.lib' + v['STATICLIBPATH_ST'] = '/LIBPATH:%s' + + v['LINKFLAGS'] = ['/NOLOGO', '/MANIFEST'] + + # shared library + v['shlib_CCFLAGS'] = [''] + v['shlib_CXXFLAGS'] = [''] + v['shlib_LINKFLAGS']= ['/DLL'] + v['shlib_PATTERN'] = '%s.dll' + v['implib_PATTERN'] = '%s.lib' + v['IMPLIB_ST'] = '/IMPLIB:%s' + + # static library + v['staticlib_LINKFLAGS'] = [''] + v['staticlib_PATTERN'] = 'lib%s.lib' # Note: to be able to distinguish between a static lib and a dll import lib, it's a good pratice to name the static lib 'lib%s.lib' and the dll import lib '%s.lib' + + # program + v['program_PATTERN'] = '%s.exe' + + +####################################################################################################### +##### conf above, build below + +@after('apply_link') +@feature('cc', 'cxx') +def apply_flags_msvc(self): + if self.env.CC_NAME != 'msvc': + return + + subsystem = getattr(self, 'subsystem', '') + if subsystem: + subsystem = '/subsystem:%s' % subsystem + flags = 'cstaticlib' in self.features and 'ARFLAGS' or 'LINKFLAGS' + self.env.append_value(flags, subsystem) + + if 'cstaticlib' not in self.features: + for d in (f.lower() for f in self.env.LINKFLAGS): + if d[1:] == 'debug': + pdbnode = self.link_task.outputs[0].change_ext('.pdb') + pdbfile = pdbnode.bldpath(self.env) + self.link_task.outputs.append(pdbnode) + self.bld.install_files(self.install_path, [pdbnode], env=self.env) + break + +@feature('cprogram', 'cshlib', 'cstaticlib') +@after('apply_lib_vars') +@before('apply_obj_vars') +def apply_obj_vars_msvc(self): + if self.env['CC_NAME'] != 'msvc': + return + + try: + self.meths.remove('apply_obj_vars') + except ValueError: + pass + + libpaths = getattr(self, 'libpaths', []) + if not libpaths: self.libpaths = libpaths + + env = self.env + app = env.append_unique + + cpppath_st = env['CPPPATH_ST'] + lib_st = env['LIB_ST'] + staticlib_st = env['STATICLIB_ST'] + libpath_st = env['LIBPATH_ST'] + staticlibpath_st = env['STATICLIBPATH_ST'] + + for i in env['LIBPATH']: + app('LINKFLAGS', libpath_st % i) + if not libpaths.count(i): + libpaths.append(i) + + for i in env['LIBPATH']: + app('LINKFLAGS', staticlibpath_st % i) + if not libpaths.count(i): + libpaths.append(i) + + # i doubt that anyone will make a fully static binary anyway + if not env['FULLSTATIC']: + if env['STATICLIB'] or env['LIB']: + app('LINKFLAGS', env['SHLIB_MARKER']) # TODO does SHLIB_MARKER work? + + for i in env['STATICLIB']: + app('LINKFLAGS', staticlib_st % i) + + for i in env['LIB']: + app('LINKFLAGS', lib_st % i) + +# split the manifest file processing from the link task, like for the rc processing + +@feature('cprogram', 'cshlib') +@after('apply_link') +def apply_manifest(self): + """Special linker for MSVC with support for embedding manifests into DLL's + and executables compiled by Visual Studio 2005 or probably later. Without + the manifest file, the binaries are unusable. + See: http://msdn2.microsoft.com/en-us/library/ms235542(VS.80).aspx + Problems with this tool: it is always called whether MSVC creates manifests or not.""" + + if self.env.CC_NAME != 'msvc': + return + + tsk = self.create_task('msvc_manifest') + tsk.set_inputs(self.link_task.outputs[0]) + +def exec_mf(self): + env = self.env + outfile = self.inputs[0].bldpath(env) + manifest = outfile + '.manifest' + if os.path.exists(manifest): + debug('msvc: manifesttool') + mtool = env['MT'] + if not mtool: + return 0 + + mode = '' + # embedding mode. Different for EXE's and DLL's. + # see: http://msdn2.microsoft.com/en-us/library/ms235591(VS.80).aspx + if 'cprogram' in self.generator.features: + mode = '1' + elif 'cshlib' in self.generator.features: + mode = '2' + + debug('msvc: embedding manifest') + #flags = ' '.join(env['MTFLAGS'] or []) + + lst = [] + lst.extend(Utils.to_list(env['MT'])) + lst.extend(Utils.to_list(env['MTFLAGS'])) + lst.extend(Utils.to_list("-manifest")) + lst.extend(Utils.to_list(manifest)) + lst.extend(Utils.to_list("-outputresource:%s;%s" % (outfile, mode))) + + #cmd='%s %s -manifest "%s" -outputresource:"%s";#%s' % (mtool, flags, + # manifest, outfile, mode) + lst = [lst] + ret = self.exec_command(*lst) + + return ret + +cls = Task.task_type_from_func('msvc_manifest', vars=['MT', 'MTFLAGS'], color='BLUE', func=exec_mf, ext_in='.bin') +cls.quiet = 1 + +########## stupid evil command modification: concatenate the tokens /Fx, /doc, and /x: with the next token + +def exec_command_msvc(self, *k, **kw): + "instead of quoting all the paths and keep using the shell, we can just join the options msvc is interested in" + if self.env['CC_NAME'] == 'msvc': + if isinstance(k[0], list): + lst = [] + carry = '' + for a in k[0]: + if len(a) == 3 and a.startswith('/F') or a == '/doc' or a[-1] == ':': + carry = a + else: + lst.append(carry + a) + carry = '' + k = [lst] + + env = dict(os.environ) + env.update(PATH = ';'.join(self.env['PATH'])) + kw['env'] = env + + return self.generator.bld.exec_command(*k, **kw) + +for k in 'cc cxx winrc cc_link cxx_link static_link'.split(): + cls = Task.TaskBase.classes.get(k, None) + if cls: + cls.exec_command = exec_command_msvc + diff --git a/tools/wafadmin/Tools/nasm.py b/tools/wafadmin/Tools/nasm.py new file mode 100644 index 000000000..9ee42a825 --- /dev/null +++ b/tools/wafadmin/Tools/nasm.py @@ -0,0 +1,52 @@ +#!/usr/bin/env python +# encoding: utf-8 +# Thomas Nagy, 2008 + +""" +Nasm processing +""" + +import os +import TaskGen, Task, Utils +from TaskGen import taskgen, before, extension + +nasm_str = '${NASM} ${NASM_FLAGS} ${NASM_INCLUDES} ${SRC} -o ${TGT}' + +EXT_NASM = ['.s', '.S', '.asm', '.ASM', '.spp', '.SPP'] + +@before('apply_link') +def apply_nasm_vars(self): + + # flags + if hasattr(self, 'nasm_flags'): + for flag in self.to_list(self.nasm_flags): + self.env.append_value('NASM_FLAGS', flag) + + # includes - well, if we suppose it works with c processing + if hasattr(self, 'includes'): + for inc in self.to_list(self.includes): + node = self.path.find_dir(inc) + if not node: + raise Utils.WafError('cannot find the dir' + inc) + self.env.append_value('NASM_INCLUDES', '-I%s' % node.srcpath(self.env)) + self.env.append_value('NASM_INCLUDES', '-I%s' % node.bldpath(self.env)) + +@extension(EXT_NASM) +def nasm_file(self, node): + try: obj_ext = self.obj_ext + except AttributeError: obj_ext = '_%d.o' % self.idx + + task = self.create_task('nasm') + task.inputs = [node] + task.outputs = [node.change_ext(obj_ext)] + + self.compiled_tasks.append(task) + + self.meths.append('apply_nasm_vars') + +# create our action here +Task.simple_task_type('nasm', nasm_str, color='BLUE', ext_out='.o', shell=False) + +def detect(conf): + nasm = conf.find_program(['nasm', 'yasm'], var='NASM', mandatory=True) + diff --git a/tools/wafadmin/Tools/ocaml.py b/tools/wafadmin/Tools/ocaml.py new file mode 100644 index 000000000..2efd2c882 --- /dev/null +++ b/tools/wafadmin/Tools/ocaml.py @@ -0,0 +1,313 @@ +#!/usr/bin/env python +# encoding: utf-8 +# Thomas Nagy, 2006 (ita) + +"ocaml support" + +import os, re +import TaskGen, Utils, Task, Build +from Logs import error +from TaskGen import taskgen, feature, before, after, extension + +EXT_MLL = ['.mll'] +EXT_MLY = ['.mly'] +EXT_MLI = ['.mli'] +EXT_MLC = ['.c'] +EXT_ML = ['.ml'] + +open_re = re.compile('^\s*open\s+([a-zA-Z]+)(;;){0,1}$', re.M) +foo = re.compile(r"""(\(\*)|(\*\))|("(\\.|[^"\\])*"|'(\\.|[^'\\])*'|.[^()*"'\\]*)""", re.M) +def filter_comments(txt): + meh = [0] + def repl(m): + if m.group(1): meh[0] += 1 + elif m.group(2): meh[0] -= 1 + elif not meh[0]: return m.group(0) + return '' + return foo.sub(repl, txt) + +def scan(self): + node = self.inputs[0] + code = filter_comments(node.read(self.env)) + + global open_re + names = [] + import_iterator = open_re.finditer(code) + if import_iterator: + for import_match in import_iterator: + names.append(import_match.group(1)) + found_lst = [] + raw_lst = [] + for name in names: + nd = None + for x in self.incpaths: + nd = x.find_resource(name.lower()+'.ml') + if not nd: nd = x.find_resource(name+'.ml') + if nd: + found_lst.append(nd) + break + else: + raw_lst.append(name) + + return (found_lst, raw_lst) + +native_lst=['native', 'all', 'c_object'] +bytecode_lst=['bytecode', 'all'] +class ocaml_taskgen(TaskGen.task_gen): + def __init__(self, *k, **kw): + TaskGen.task_gen.__init__(self, *k, **kw) + +@feature('ocaml') +def init_ml(self): + Utils.def_attrs(self, + type = 'all', + incpaths_lst = [], + bld_incpaths_lst = [], + mlltasks = [], + mlytasks = [], + mlitasks = [], + native_tasks = [], + bytecode_tasks = [], + linktasks = [], + bytecode_env = None, + native_env = None, + compiled_tasks = [], + includes = '', + uselib = '', + are_deps_set = 0) + +@feature('ocaml') +@after('init_ml') +def init_envs_ml(self): + + self.islibrary = getattr(self, 'islibrary', False) + + global native_lst, bytecode_lst + self.native_env = None + if self.type in native_lst: + self.native_env = self.env.copy() + if self.islibrary: self.native_env['OCALINKFLAGS'] = '-a' + + self.bytecode_env = None + if self.type in bytecode_lst: + self.bytecode_env = self.env.copy() + if self.islibrary: self.bytecode_env['OCALINKFLAGS'] = '-a' + + if self.type == 'c_object': + self.native_env.append_unique('OCALINKFLAGS_OPT', '-output-obj') + +@feature('ocaml') +@before('apply_vars_ml') +@after('init_envs_ml') +def apply_incpaths_ml(self): + inc_lst = self.includes.split() + lst = self.incpaths_lst + for dir in inc_lst: + node = self.path.find_dir(dir) + if not node: + error("node not found: " + str(dir)) + continue + self.bld.rescan(node) + if not node in lst: lst.append(node) + self.bld_incpaths_lst.append(node) + # now the nodes are added to self.incpaths_lst + +@feature('ocaml') +@before('apply_core') +def apply_vars_ml(self): + for i in self.incpaths_lst: + if self.bytecode_env: + app = self.bytecode_env.append_value + app('OCAMLPATH', '-I') + app('OCAMLPATH', i.srcpath(self.env)) + app('OCAMLPATH', '-I') + app('OCAMLPATH', i.bldpath(self.env)) + + if self.native_env: + app = self.native_env.append_value + app('OCAMLPATH', '-I') + app('OCAMLPATH', i.bldpath(self.env)) + app('OCAMLPATH', '-I') + app('OCAMLPATH', i.srcpath(self.env)) + + varnames = ['INCLUDES', 'OCAMLFLAGS', 'OCALINKFLAGS', 'OCALINKFLAGS_OPT'] + for name in self.uselib.split(): + for vname in varnames: + cnt = self.env[vname+'_'+name] + if cnt: + if self.bytecode_env: self.bytecode_env.append_value(vname, cnt) + if self.native_env: self.native_env.append_value(vname, cnt) + +@feature('ocaml') +@after('apply_core') +def apply_link_ml(self): + + if self.bytecode_env: + ext = self.islibrary and '.cma' or '.run' + + linktask = self.create_task('ocalink') + linktask.bytecode = 1 + linktask.set_outputs(self.path.find_or_declare(self.target + ext)) + linktask.obj = self + linktask.env = self.bytecode_env + self.linktasks.append(linktask) + + if self.native_env: + if self.type == 'c_object': ext = '.o' + elif self.islibrary: ext = '.cmxa' + else: ext = '' + + linktask = self.create_task('ocalinkx') + linktask.set_outputs(self.path.find_or_declare(self.target + ext)) + linktask.obj = self + linktask.env = self.native_env + self.linktasks.append(linktask) + + # we produce a .o file to be used by gcc + self.compiled_tasks.append(linktask) + +@extension(EXT_MLL) +def mll_hook(self, node): + mll_task = self.create_task('ocamllex', self.native_env) + mll_task.set_inputs(node) + mll_task.set_outputs(node.change_ext('.ml')) + self.mlltasks.append(mll_task) + + self.allnodes.append(mll_task.outputs[0]) + +@extension(EXT_MLY) +def mly_hook(self, node): + mly_task = self.create_task('ocamlyacc', self.native_env) + mly_task.set_inputs(node) + mly_task.set_outputs([node.change_ext('.ml'), node.change_ext('.mli')]) + self.mlytasks.append(mly_task) + self.allnodes.append(mly_task.outputs[0]) + + task = self.create_task('ocamlcmi', self.native_env) + task.set_inputs(mly_task.outputs[1]) + task.set_outputs(mly_task.outputs[1].change_ext('.cmi')) + +@extension(EXT_MLI) +def mli_hook(self, node): + task = self.create_task('ocamlcmi', self.native_env) + task.set_inputs(node) + task.set_outputs(node.change_ext('.cmi')) + self.mlitasks.append(task) + +@extension(EXT_MLC) +def mlc_hook(self, node): + task = self.create_task('ocamlcc', self.native_env) + task.set_inputs(node) + task.set_outputs(node.change_ext('.o')) + + self.compiled_tasks.append(task) + +@extension(EXT_ML) +def ml_hook(self, node): + if self.native_env: + task = self.create_task('ocamlx', self.native_env) + task.set_inputs(node) + task.set_outputs(node.change_ext('.cmx')) + task.obj = self + task.incpaths = self.bld_incpaths_lst + self.native_tasks.append(task) + + if self.bytecode_env: + task = self.create_task('ocaml', self.bytecode_env) + task.set_inputs(node) + task.obj = self + task.bytecode = 1 + task.incpaths = self.bld_incpaths_lst + task.set_outputs(node.change_ext('.cmo')) + self.bytecode_tasks.append(task) + +def compile_may_start(self): + if not getattr(self, 'flag_deps', ''): + self.flag_deps = 1 + + # the evil part is that we can only compute the dependencies after the + # source files can be read (this means actually producing the source files) + if getattr(self, 'bytecode', ''): alltasks = self.obj.bytecode_tasks + else: alltasks = self.obj.native_tasks + + self.signature() # ensure that files are scanned - unfortunately + tree = self.generator.bld + env = self.env + for node in self.inputs: + lst = tree.node_deps[self.unique_id()] + for depnode in lst: + for t in alltasks: + if t == self: continue + if depnode in t.inputs: + self.set_run_after(t) + + # TODO necessary to get the signature right - for now + delattr(self, 'cache_sig') + self.signature() + + return Task.Task.runnable_status(self) + +b = Task.simple_task_type +cls = b('ocamlx', '${OCAMLOPT} ${OCAMLPATH} ${OCAMLFLAGS} ${INCLUDES} -c -o ${TGT} ${SRC}', color='GREEN', shell=False) +cls.runnable_status = compile_may_start +cls.scan = scan + +b = Task.simple_task_type +cls = b('ocaml', '${OCAMLC} ${OCAMLPATH} ${OCAMLFLAGS} ${INCLUDES} -c -o ${TGT} ${SRC}', color='GREEN', shell=False) +cls.runnable_status = compile_may_start +cls.scan = scan + + +b('ocamlcmi', '${OCAMLC} ${OCAMLPATH} ${INCLUDES} -o ${TGT} -c ${SRC}', color='BLUE', before="ocaml ocamlcc ocamlx") +b('ocamlcc', 'cd ${TGT[0].bld_dir(env)} && ${OCAMLOPT} ${OCAMLFLAGS} ${OCAMLPATH} ${INCLUDES} -c ${SRC[0].abspath(env)}', color='GREEN') + +b('ocamllex', '${OCAMLLEX} ${SRC} -o ${TGT}', color='BLUE', before="ocamlcmi ocaml ocamlcc") +b('ocamlyacc', '${OCAMLYACC} -b ${TGT[0].bld_base(env)} ${SRC}', color='BLUE', before="ocamlcmi ocaml ocamlcc") + + +def link_may_start(self): + if not getattr(self, 'order', ''): + + # now reorder the inputs given the task dependencies + if getattr(self, 'bytecode', 0): alltasks = self.obj.bytecode_tasks + else: alltasks = self.obj.native_tasks + + # this part is difficult, we do not have a total order on the tasks + # if the dependencies are wrong, this may not stop + seen = [] + pendant = []+alltasks + while pendant: + task = pendant.pop(0) + if task in seen: continue + for x in task.run_after: + if not x in seen: + pendant.append(task) + break + else: + seen.append(task) + self.inputs = [x.outputs[0] for x in seen] + self.order = 1 + return Task.Task.runnable_status(self) + +act = b('ocalink', '${OCAMLC} -o ${TGT} ${INCLUDES} ${OCALINKFLAGS} ${SRC}', color='YELLOW', after="ocaml ocamlcc") +act.runnable_status = link_may_start +act = b('ocalinkx', '${OCAMLOPT} -o ${TGT} ${INCLUDES} ${OCALINKFLAGS_OPT} ${SRC}', color='YELLOW', after="ocamlx ocamlcc") +act.runnable_status = link_may_start + +def detect(conf): + opt = conf.find_program('ocamlopt', var='OCAMLOPT') + occ = conf.find_program('ocamlc', var='OCAMLC') + if (not opt) or (not occ): + conf.fatal('The objective caml compiler was not found:\ninstall it or make it available in your PATH') + + v = conf.env + v['OCAMLC'] = occ + v['OCAMLOPT'] = opt + v['OCAMLLEX'] = conf.find_program('ocamllex', var='OCAMLLEX') + v['OCAMLYACC'] = conf.find_program('ocamlyacc', var='OCAMLYACC') + v['OCAMLFLAGS'] = '' + v['OCAMLLIB'] = Utils.cmd_output(conf.env['OCAMLC']+' -where').strip()+os.sep + v['LIBPATH_OCAML'] = Utils.cmd_output(conf.env['OCAMLC']+' -where').strip()+os.sep + v['CPPPATH_OCAML'] = Utils.cmd_output(conf.env['OCAMLC']+' -where').strip()+os.sep + v['LIB_OCAML'] = 'camlrun' + diff --git a/tools/wafadmin/Tools/osx.py b/tools/wafadmin/Tools/osx.py new file mode 100644 index 000000000..15a1ec966 --- /dev/null +++ b/tools/wafadmin/Tools/osx.py @@ -0,0 +1,185 @@ +#!/usr/bin/env python +# encoding: utf-8 +# Thomas Nagy 2008 + +"""MacOSX related tools + +To compile an executable into a Mac application bundle (a .app), set its 'mac_app' attribute + obj.mac_app = True + +To make a bundled shared library (a .bundle), set the 'mac_bundle' attribute: + obj.mac_bundle = True +""" + +import os, shutil, sys, platform +import TaskGen, Task, Build, Options, Utils +from TaskGen import taskgen, feature, after, before +from Logs import error, debug + +# plist template +app_info = ''' +<?xml version="1.0" encoding="UTF-8"?> +<!DOCTYPE plist SYSTEM "file://localhost/System/Library/DTDs/PropertyList.dtd"> +<plist version="0.9"> +<dict> + <key>CFBundlePackageType</key> + <string>APPL</string> + <key>CFBundleGetInfoString</key> + <string>Created by Waf</string> + <key>CFBundleSignature</key> + <string>????</string> + <key>NOTE</key> + <string>THIS IS A GENERATED FILE, DO NOT MODIFY</string> + <key>CFBundleExecutable</key> + <string>%s</string> +</dict> +</plist> +''' + +# see WAF issue 285 +# and also http://trac.macports.org/ticket/17059 +@feature('cc', 'cxx') +@before('apply_lib_vars') +def set_macosx_deployment_target(self): + if self.env['MACOSX_DEPLOYMENT_TARGET']: + os.environ['MACOSX_DEPLOYMENT_TARGET'] = self.env['MACOSX_DEPLOYMENT_TARGET'] + elif 'MACOSX_DEPLOYMENT_TARGET' not in os.environ: + if sys.platform == 'darwin': + os.environ['MACOSX_DEPLOYMENT_TARGET'] = '.'.join(platform.mac_ver()[0].split('.')[:2]) + +@feature('cc', 'cxx') +@after('apply_lib_vars') +def apply_framework(self): + for x in self.to_list(self.env['FRAMEWORKPATH']): + frameworkpath_st = '-F%s' + self.env.append_unique('CXXFLAGS', frameworkpath_st % x) + self.env.append_unique('CCFLAGS', frameworkpath_st % x) + self.env.append_unique('LINKFLAGS', frameworkpath_st % x) + + for x in self.to_list(self.env['FRAMEWORK']): + self.env.append_value('LINKFLAGS', ['-framework', x]) + +@taskgen +def create_bundle_dirs(self, name, out): + bld = self.bld + dir = out.parent.get_dir(name) + + if not dir: + dir = out.__class__(name, out.parent, 1) + bld.rescan(dir) + contents = out.__class__('Contents', dir, 1) + bld.rescan(contents) + macos = out.__class__('MacOS', contents, 1) + bld.rescan(macos) + return dir + +def bundle_name_for_output(out): + name = out.name + k = name.rfind('.') + if k >= 0: + name = name[:k] + '.app' + else: + name = name + '.app' + return name + +@taskgen +@after('apply_link') +@feature('cprogram') +def create_task_macapp(self): + """Use env['MACAPP'] to force *all* executables to be transformed into Mac applications + or use obj.mac_app = True to build specific targets as Mac apps""" + if self.env['MACAPP'] or getattr(self, 'mac_app', False): + apptask = self.create_task('macapp', self.env) + apptask.set_inputs(self.link_task.outputs) + + out = self.link_task.outputs[0] + + name = bundle_name_for_output(out) + dir = self.create_bundle_dirs(name, out) + + n1 = dir.find_or_declare(['Contents', 'MacOS', out.name]) + + apptask.set_outputs([n1]) + apptask.chmod = 0755 + apptask.install_path = os.path.join(self.install_path, name, 'Contents', 'MacOS') + self.apptask = apptask + +@after('apply_link') +@feature('cprogram') +def create_task_macplist(self): + """Use env['MACAPP'] to force *all* executables to be transformed into Mac applications + or use obj.mac_app = True to build specific targets as Mac apps""" + if self.env['MACAPP'] or getattr(self, 'mac_app', False): + # check if the user specified a plist before using our template + if not getattr(self, 'mac_plist', False): + self.mac_plist = app_info + + plisttask = self.create_task('macplist', self.env) + plisttask.set_inputs(self.link_task.outputs) + + out = self.link_task.outputs[0] + self.mac_plist = self.mac_plist % (out.name) + + name = bundle_name_for_output(out) + dir = self.create_bundle_dirs(name, out) + + n1 = dir.find_or_declare(['Contents', 'Info.plist']) + + plisttask.set_outputs([n1]) + plisttask.mac_plist = self.mac_plist + plisttask.install_path = os.path.join(self.install_path, name, 'Contents') + self.plisttask = plisttask + +@after('apply_link') +@feature('cshlib') +def apply_link_osx(self): + name = self.link_task.outputs[0].name + if getattr(self, 'vnum', None): + name = name.replace('.dylib', '.%s.dylib' % self.vnum) + + path = os.path.join(Utils.subst_vars(self.install_path, self.env), name) + if '-dynamiclib' in self.env['LINKFLAGS']: + self.env.append_value('LINKFLAGS', '-install_name') + self.env.append_value('LINKFLAGS', path) + +@before('apply_link', 'apply_lib_vars') +@feature('cc', 'cxx') +def apply_bundle(self): + """use env['MACBUNDLE'] to force all shlibs into mac bundles + or use obj.mac_bundle = True for specific targets only""" + if not ('cshlib' in self.features or 'shlib' in self.features): return + if self.env['MACBUNDLE'] or getattr(self, 'mac_bundle', False): + self.env['shlib_PATTERN'] = self.env['macbundle_PATTERN'] + uselib = self.uselib = self.to_list(self.uselib) + if not 'MACBUNDLE' in uselib: uselib.append('MACBUNDLE') + +@after('apply_link') +@feature('cshlib') +def apply_bundle_remove_dynamiclib(self): + if self.env['MACBUNDLE'] or getattr(self, 'mac_bundle', False): + if not getattr(self, 'vnum', None): + try: + self.env['LINKFLAGS'].remove('-dynamiclib') + except ValueError: + pass + +# TODO REMOVE IN 1.6 (global variable) +app_dirs = ['Contents', 'Contents/MacOS', 'Contents/Resources'] + +def app_build(task): + env = task.env + shutil.copy2(task.inputs[0].srcpath(env), task.outputs[0].abspath(env)) + + return 0 + +def plist_build(task): + env = task.env + f = open(task.outputs[0].abspath(env), "w") + f.write(task.mac_plist) + f.close() + + return 0 + +Task.task_type_from_func('macapp', vars=[], func=app_build, after="cxx_link cc_link static_link") +Task.task_type_from_func('macplist', vars=[], func=plist_build, after="cxx_link cc_link static_link") + diff --git a/tools/wafadmin/Tools/perl.py b/tools/wafadmin/Tools/perl.py new file mode 100644 index 000000000..c41a588d7 --- /dev/null +++ b/tools/wafadmin/Tools/perl.py @@ -0,0 +1,120 @@ +#!/usr/bin/env python +# encoding: utf-8 +# andersg at 0x63.nu 2007 + +import os +import Task, Options, Utils +from Configure import conf +from TaskGen import extension, taskgen, feature, before + +xsubpp_str = '${PERL} ${XSUBPP} -noprototypes -typemap ${EXTUTILS_TYPEMAP} ${SRC} > ${TGT}' +EXT_XS = ['.xs'] + +@before('apply_incpaths', 'apply_type_vars', 'apply_lib_vars') +@feature('perlext') +def init_perlext(self): + self.uselib = self.to_list(getattr(self, 'uselib', '')) + if not 'PERL' in self.uselib: self.uselib.append('PERL') + if not 'PERLEXT' in self.uselib: self.uselib.append('PERLEXT') + self.env['shlib_PATTERN'] = self.env['perlext_PATTERN'] + +@extension(EXT_XS) +def xsubpp_file(self, node): + gentask = self.create_task('xsubpp') + gentask.set_inputs(node) + outnode = node.change_ext('.c') + gentask.set_outputs(outnode) + + self.allnodes.append(outnode) + +Task.simple_task_type('xsubpp', xsubpp_str, color='BLUE', before="cc cxx", shell=False) + +@conf +def check_perl_version(conf, minver=None): + """ + Checks if perl is installed. + + If installed the variable PERL will be set in environment. + + Perl binary can be overridden by --with-perl-binary config variable + + """ + res = True + + if not getattr(Options.options, 'perlbinary', None): + perl = conf.find_program("perl", var="PERL") + if not perl: + return False + else: + perl = Options.options.perlbinary + conf.env['PERL'] = perl + + version = Utils.cmd_output(perl + " -e'printf \"%vd\", $^V'") + if not version: + res = False + version = "Unknown" + elif not minver is None: + ver = tuple(map(int, version.split("."))) + if ver < minver: + res = False + + if minver is None: + cver = "" + else: + cver = ".".join(map(str,minver)) + conf.check_message("perl", cver, res, version) + return res + +@conf +def check_perl_module(conf, module): + """ + Check if specified perlmodule is installed. + + Minimum version can be specified by specifying it after modulename + like this: + + conf.check_perl_module("Some::Module 2.92") + """ + cmd = [conf.env['PERL'], '-e', 'use %s' % module] + r = Utils.pproc.call(cmd, stdout=Utils.pproc.PIPE, stderr=Utils.pproc.PIPE) == 0 + conf.check_message("perl module %s" % module, "", r) + return r + +@conf +def check_perl_ext_devel(conf): + """ + Check for configuration needed to build perl extensions. + + Sets different xxx_PERLEXT variables in the environment. + + Also sets the ARCHDIR_PERL variable useful as installation path, + which can be overridden by --with-perl-archdir option. + """ + if not conf.env['PERL']: + return False + + perl = conf.env['PERL'] + + def read_out(cmd): + return Utils.to_list(Utils.cmd_output(perl + cmd)) + + conf.env["LINKFLAGS_PERLEXT"] = read_out(" -MConfig -e'print $Config{lddlflags}'") + conf.env["CPPPATH_PERLEXT"] = read_out(" -MConfig -e'print \"$Config{archlib}/CORE\"'") + conf.env["CCFLAGS_PERLEXT"] = read_out(" -MConfig -e'print \"$Config{ccflags} $Config{cccdlflags}\"'") + + conf.env["XSUBPP"] = read_out(" -MConfig -e'print \"$Config{privlib}/ExtUtils/xsubpp$Config{exe_ext}\"'") + conf.env["EXTUTILS_TYPEMAP"] = read_out(" -MConfig -e'print \"$Config{privlib}/ExtUtils/typemap\"'") + + if not getattr(Options.options, 'perlarchdir', None): + conf.env["ARCHDIR_PERL"] = Utils.cmd_output(perl + " -MConfig -e'print $Config{sitearch}'") + else: + conf.env["ARCHDIR_PERL"] = getattr(Options.options, 'perlarchdir') + + conf.env['perlext_PATTERN'] = '%s.' + Utils.cmd_output(perl + " -MConfig -e'print $Config{dlext}'") + + return True + +def set_options(opt): + opt.add_option("--with-perl-binary", type="string", dest="perlbinary", help = 'Specify alternate perl binary', default=None) + opt.add_option("--with-perl-archdir", type="string", dest="perlarchdir", help = 'Specify directory where to install arch specific files', default=None) + diff --git a/tools/wafadmin/Tools/preproc.py b/tools/wafadmin/Tools/preproc.py new file mode 100644 index 000000000..b88b4e103 --- /dev/null +++ b/tools/wafadmin/Tools/preproc.py @@ -0,0 +1,809 @@ +#!/usr/bin/env python +# encoding: utf-8 +# Thomas Nagy, 2006-2009 (ita) + +""" +C/C++ preprocessor for finding dependencies + +Reasons for using the Waf preprocessor by default +1. Some c/c++ extensions (Qt) require a custom preprocessor for obtaining the dependencies (.moc files) +2. Not all compilers provide .d files for obtaining the dependencies (portability) +3. A naive file scanner will not catch the constructs such as "#include foo()" +4. A naive file scanner will catch unnecessary dependencies (change an unused header -> recompile everything) + +Regarding the speed concerns: +a. the preprocessing is performed only when files must be compiled +b. the macros are evaluated only for #if/#elif/#include +c. the time penalty is about 10% +d. system headers are not scanned + +Now if you do not want the Waf preprocessor, the tool "gccdeps" uses the .d files produced +during the compilation to track the dependencies (useful when used with the boost libraries). +It only works with gcc though, and it cannot be used with Qt builds. A dumb +file scanner will be added in the future, so we will have most bahaviours. +""" +# TODO: more varargs, pragma once +# TODO: dumb file scanner tracking all includes + +import re, sys, os, string +import Logs, Build, Utils +from Logs import debug, error +import traceback + +class PreprocError(Utils.WafError): + pass + +POPFILE = '-' + +go_absolute = 0 +"set to 1 to track headers on files in /usr/include - else absolute paths are ignored" + +standard_includes = ['/usr/include'] +if sys.platform == "win32": + standard_includes = [] + +use_trigraphs = 0 +'apply the trigraph rules first' + +strict_quotes = 0 +"Keep <> for system includes (do not search for those includes)" + +g_optrans = { +'not':'!', +'and':'&&', +'bitand':'&', +'and_eq':'&=', +'or':'||', +'bitor':'|', +'or_eq':'|=', +'xor':'^', +'xor_eq':'^=', +'compl':'~', +} +"these ops are for c++, to reset, set an empty dict" + +# ignore #warning and #error +re_lines = re.compile(\ + '^[ \t]*(#|%:)[ \t]*(ifdef|ifndef|if|else|elif|endif|include|import|define|undef|pragma)[ \t]*(.*)\r*$', + re.IGNORECASE | re.MULTILINE) + +re_mac = re.compile("^[a-zA-Z_]\w*") +re_fun = re.compile('^[a-zA-Z_][a-zA-Z0-9_]*[(]') +re_pragma_once = re.compile('^\s*once\s*', re.IGNORECASE) +re_nl = re.compile('\\\\\r*\n', re.MULTILINE) +re_cpp = re.compile(\ + r"""(/\*[^*]*\*+([^/*][^*]*\*+)*/)|//[^\n]*|("(\\.|[^"\\])*"|'(\\.|[^'\\])*'|.[^/"'\\]*)""", + re.MULTILINE) +trig_def = [('??'+a, b) for a, b in zip("=-/!'()<>", r'#~\|^[]{}')] +chr_esc = {'0':0, 'a':7, 'b':8, 't':9, 'n':10, 'f':11, 'v':12, 'r':13, '\\':92, "'":39} + +NUM = 'i' +OP = 'O' +IDENT = 'T' +STR = 's' +CHAR = 'c' + +tok_types = [NUM, STR, IDENT, OP] +exp_types = [ + r"""0[xX](?P<hex>[a-fA-F0-9]+)(?P<qual1>[uUlL]*)|L*?'(?P<char>(\\.|[^\\'])+)'|(?P<n1>\d+)[Ee](?P<exp0>[+-]*?\d+)(?P<float0>[fFlL]*)|(?P<n2>\d*\.\d+)([Ee](?P<exp1>[+-]*?\d+))?(?P<float1>[fFlL]*)|(?P<n4>\d+\.\d*)([Ee](?P<exp2>[+-]*?\d+))?(?P<float2>[fFlL]*)|(?P<oct>0*)(?P<n0>\d+)(?P<qual2>[uUlL]*)""", + r'L?"([^"\\]|\\.)*"', + r'[a-zA-Z_]\w*', + r'%:%:|<<=|>>=|\.\.\.|<<|<%|<:|<=|>>|>=|\+\+|\+=|--|->|-=|\*=|/=|%:|%=|%>|==|&&|&=|\|\||\|=|\^=|:>|!=|##|[\(\)\{\}\[\]<>\?\|\^\*\+&=:!#;,%/\-\?\~\.]', +] +re_clexer = re.compile('|'.join(["(?P<%s>%s)" % (name, part) for name, part in zip(tok_types, exp_types)]), re.M) + +accepted = 'a' +ignored = 'i' +undefined = 'u' +skipped = 's' + +def repl(m): + s = m.group(1) + if s is not None: return ' ' + s = m.group(3) + if s is None: return '' + return s + +def filter_comments(filename): + # return a list of tuples : keyword, line + code = Utils.readf(filename) + if use_trigraphs: + for (a, b) in trig_def: code = code.split(a).join(b) + code = re_nl.sub('', code) + code = re_cpp.sub(repl, code) + return [(m.group(2), m.group(3)) for m in re.finditer(re_lines, code)] + +prec = {} +# op -> number, needed for such expressions: #if 1 && 2 != 0 +ops = ['* / %', '+ -', '<< >>', '< <= >= >', '== !=', '& | ^', '&& ||', ','] +for x in range(len(ops)): + syms = ops[x] + for u in syms.split(): + prec[u] = x + +def reduce_nums(val_1, val_2, val_op): + """apply arithmetic rules and try to return an integer result""" + #print val_1, val_2, val_op + + # now perform the operation, make certain a and b are numeric + try: a = 0 + val_1 + except TypeError: a = int(val_1) + try: b = 0 + val_2 + except TypeError: b = int(val_2) + + d = val_op + if d == '%': c = a%b + elif d=='+': c = a+b + elif d=='-': c = a-b + elif d=='*': c = a*b + elif d=='/': c = a/b + elif d=='^': c = a^b + elif d=='|': c = a|b + elif d=='||': c = int(a or b) + elif d=='&': c = a&b + elif d=='&&': c = int(a and b) + elif d=='==': c = int(a == b) + elif d=='!=': c = int(a != b) + elif d=='<=': c = int(a <= b) + elif d=='<': c = int(a < b) + elif d=='>': c = int(a > b) + elif d=='>=': c = int(a >= b) + elif d=='^': c = int(a^b) + elif d=='<<': c = a<<b + elif d=='>>': c = a>>b + else: c = 0 + return c + +def get_num(lst): + if not lst: raise PreprocError("empty list for get_num") + (p, v) = lst[0] + if p == OP: + if v == '(': + count_par = 1 + i = 1 + while i < len(lst): + (p, v) = lst[i] + + if p == OP: + if v == ')': + count_par -= 1 + if count_par == 0: + break + elif v == '(': + count_par += 1 + i += 1 + else: + raise PreprocError("rparen expected %r" % lst) + + (num, _) = get_term(lst[1:i]) + return (num, lst[i+1:]) + + elif v == '+': + return get_num(lst[1:]) + elif v == '-': + num, lst = get_num(lst[1:]) + return (reduce_nums('-1', num, '*'), lst) + elif v == '!': + num, lst = get_num(lst[1:]) + return (int(not int(num)), lst) + elif v == '~': + return (~ int(num), lst) + else: + raise PreprocError("invalid op token %r for get_num" % lst) + elif p == NUM: + return v, lst[1:] + elif p == IDENT: + # all macros should have been replaced, remaining identifiers eval to 0 + return 0, lst[1:] + else: + raise PreprocError("invalid token %r for get_num" % lst) + +def get_term(lst): + if not lst: raise PreprocError("empty list for get_term") + num, lst = get_num(lst) + if not lst: + return (num, []) + (p, v) = lst[0] + if p == OP: + if v == '&&' and not num: + return (num, []) + elif v == '||' and num: + return (num, []) + elif v == ',': + # skip + return get_term(lst[1:]) + elif v == '?': + count_par = 0 + i = 1 + while i < len(lst): + (p, v) = lst[i] + + if p == OP: + if v == ')': + count_par -= 1 + elif v == '(': + count_par += 1 + elif v == ':': + if count_par == 0: + break + i += 1 + else: + raise PreprocError("rparen expected %r" % lst) + + if int(num): + return get_term(lst[1:i]) + else: + return get_term(lst[i+1:]) + + else: + num2, lst = get_num(lst[1:]) + + if not lst: + # no more tokens to process + num2 = reduce_nums(num, num2, v) + return get_term([(NUM, num2)] + lst) + + # operator precedence + p2, v2 = lst[0] + if p2 != OP: + raise PreprocError("op expected %r" % lst) + + if prec[v2] >= prec[v]: + num2 = reduce_nums(num, num2, v) + return get_term([(NUM, num2)] + lst) + else: + num3, lst = get_num(lst[1:]) + num3 = reduce_nums(num2, num3, v2) + return get_term([(NUM, num), (p, v), (NUM, num3)] + lst) + + + raise PreprocError("cannot reduce %r" % lst) + +def reduce_eval(lst): + """take a list of tokens and output true or false (#if/#elif conditions)""" + num, lst = get_term(lst) + return (NUM, num) + +def stringize(lst): + """use for converting a list of tokens to a string""" + lst = [str(v2) for (p2, v2) in lst] + return "".join(lst) + +def paste_tokens(t1, t2): + """ + here is what we can paste: + a ## b -> ab + > ## = -> >= + a ## 2 -> a2 + """ + p1 = None + if t1[0] == OP and t2[0] == OP: + p1 = OP + elif t1[0] == IDENT and (t2[0] == IDENT or t2[0] == NUM): + p1 = IDENT + elif t1[0] == NUM and t2[0] == NUM: + p1 = NUM + if not p1: + raise PreprocError('tokens do not make a valid paste %r and %r' % (t1, t2)) + return (p1, t1[1] + t2[1]) + +def reduce_tokens(lst, defs, ban=[]): + """replace the tokens in lst, using the macros provided in defs, and a list of macros that cannot be re-applied""" + i = 0 + + while i < len(lst): + (p, v) = lst[i] + + if p == IDENT and v == "defined": + del lst[i] + if i < len(lst): + (p2, v2) = lst[i] + if p2 == IDENT: + if v2 in defs: + lst[i] = (NUM, 1) + else: + lst[i] = (NUM, 0) + elif p2 == OP and v2 == '(': + del lst[i] + (p2, v2) = lst[i] + del lst[i] # remove the ident, and change the ) for the value + if v2 in defs: + lst[i] = (NUM, 1) + else: + lst[i] = (NUM, 0) + else: + raise PreprocError("invalid define expression %r" % lst) + + elif p == IDENT and v in defs: + + if isinstance(defs[v], str): + a, b = extract_macro(defs[v]) + defs[v] = b + macro_def = defs[v] + to_add = macro_def[1] + + if isinstance(macro_def[0], list): + # macro without arguments + del lst[i] + for x in xrange(len(to_add)): + lst.insert(i, to_add[x]) + i += 1 + else: + # collect the arguments for the funcall + + args = [] + del lst[i] + + if i >= len(lst): + raise PreprocError("expected '(' after %r (got nothing)" % v) + + (p2, v2) = lst[i] + if p2 != OP or v2 != '(': + raise PreprocError("expected '(' after %r" % v) + + del lst[i] + + one_param = [] + count_paren = 0 + while i < len(lst): + p2, v2 = lst[i] + + del lst[i] + if p2 == OP and count_paren == 0: + if v2 == '(': + one_param.append((p2, v2)) + count_paren += 1 + elif v2 == ')': + if one_param: args.append(one_param) + break + elif v2 == ',': + if not one_param: raise PreprocError("empty param in funcall %s" % p) + args.append(one_param) + one_param = [] + else: + one_param.append((p2, v2)) + else: + one_param.append((p2, v2)) + if v2 == '(': count_paren += 1 + elif v2 == ')': count_paren -= 1 + else: + raise PreprocError('malformed macro') + + # substitute the arguments within the define expression + accu = [] + arg_table = macro_def[0] + j = 0 + while j < len(to_add): + (p2, v2) = to_add[j] + + if p2 == OP and v2 == '#': + # stringize is for arguments only + if j+1 < len(to_add) and to_add[j+1][0] == IDENT and to_add[j+1][1] in arg_table: + toks = args[arg_table[to_add[j+1][1]]] + accu.append((STR, stringize(toks))) + j += 1 + else: + accu.append((p2, v2)) + elif p2 == OP and v2 == '##': + # token pasting, how can man invent such a complicated system? + if accu and j+1 < len(to_add): + # we have at least two tokens + + t1 = accu[-1] + + if to_add[j+1][0] == IDENT and to_add[j+1][1] in arg_table: + toks = args[arg_table[to_add[j+1][1]]] + + if toks: + accu[-1] = paste_tokens(t1, toks[0]) #(IDENT, accu[-1][1] + toks[0][1]) + accu.extend(toks[1:]) + else: + # error, case "a##" + accu.append((p2, v2)) + accu.extend(toks) + elif to_add[j+1][0] == IDENT and to_add[j+1][1] == '__VA_ARGS__': + # TODO not sure + # first collect the tokens + va_toks = [] + st = len(macro_def[0]) + pt = len(args) + for x in args[pt-st+1:]: + va_toks.extend(x) + va_toks.append((OP, ',')) + if va_toks: va_toks.pop() # extra comma + if len(accu)>1: + (p3, v3) = accu[-1] + (p4, v4) = accu[-2] + if v3 == '##': + # remove the token paste + accu.pop() + if v4 == ',' and pt < st: + # remove the comma + accu.pop() + accu += va_toks + else: + accu[-1] = paste_tokens(t1, to_add[j+1]) + + j += 1 + else: + # invalid paste, case "##a" or "b##" + accu.append((p2, v2)) + + elif p2 == IDENT and v2 in arg_table: + toks = args[arg_table[v2]] + reduce_tokens(toks, defs, ban+[v]) + accu.extend(toks) + else: + accu.append((p2, v2)) + + j += 1 + + + reduce_tokens(accu, defs, ban+[v]) + + for x in xrange(len(accu)-1, -1, -1): + lst.insert(i, accu[x]) + + i += 1 + + +def eval_macro(lst, adefs): + """reduce the tokens from the list lst, and try to return a 0/1 result""" + reduce_tokens(lst, adefs, []) + if not lst: raise PreprocError("missing tokens to evaluate") + (p, v) = reduce_eval(lst) + return int(v) != 0 + +def extract_macro(txt): + """process a macro definition from "#define f(x, y) x * y" into a function or a simple macro without arguments""" + t = tokenize(txt) + if re_fun.search(txt): + p, name = t[0] + + p, v = t[1] + if p != OP: raise PreprocError("expected open parenthesis") + + i = 1 + pindex = 0 + params = {} + prev = '(' + + while 1: + i += 1 + p, v = t[i] + + if prev == '(': + if p == IDENT: + params[v] = pindex + pindex += 1 + prev = p + elif p == OP and v == ')': + break + else: + raise PreprocError("unexpected token (3)") + elif prev == IDENT: + if p == OP and v == ',': + prev = v + elif p == OP and v == ')': + break + else: + raise PreprocError("comma or ... expected") + elif prev == ',': + if p == IDENT: + params[v] = pindex + pindex += 1 + prev = p + elif p == OP and v == '...': + raise PreprocError("not implemented (1)") + else: + raise PreprocError("comma or ... expected (2)") + elif prev == '...': + raise PreprocError("not implemented (2)") + else: + raise PreprocError("unexpected else") + + #~ print (name, [params, t[i+1:]]) + return (name, [params, t[i+1:]]) + else: + (p, v) = t[0] + return (v, [[], t[1:]]) + +re_include = re.compile('^\s*(<(?P<a>.*)>|"(?P<b>.*)")') +def extract_include(txt, defs): + """process a line in the form "#include foo" to return a string representing the file""" + m = re_include.search(txt) + if m: + if m.group('a'): return '<', m.group('a') + if m.group('b'): return '"', m.group('b') + + # perform preprocessing and look at the result, it must match an include + toks = tokenize(txt) + reduce_tokens(toks, defs, ['waf_include']) + + if not toks: + raise PreprocError("could not parse include %s" % txt) + + if len(toks) == 1: + if toks[0][0] == STR: + return '"', toks[0][1] + else: + if toks[0][1] == '<' and toks[-1][1] == '>': + return stringize(toks).lstrip('<').rstrip('>') + + raise PreprocError("could not parse include %s." % txt) + +def parse_char(txt): + if not txt: raise PreprocError("attempted to parse a null char") + if txt[0] != '\\': + return ord(txt) + c = txt[1] + if c == 'x': + if len(txt) == 4 and txt[3] in string.hexdigits: return int(txt[2:], 16) + return int(txt[2:], 16) + elif c.isdigit(): + if c == '0' and len(txt)==2: return 0 + for i in 3, 2, 1: + if len(txt) > i and txt[1:1+i].isdigit(): + return (1+i, int(txt[1:1+i], 8)) + else: + try: return chr_esc[c] + except KeyError: raise PreprocError("could not parse char literal '%s'" % txt) + +def tokenize(s): + """convert a string into a list of tokens (shlex.split does not apply to c/c++/d)""" + ret = [] + for match in re_clexer.finditer(s): + m = match.group + for name in tok_types: + v = m(name) + if v: + if name == IDENT: + try: v = g_optrans[v]; name = OP + except KeyError: + # c++ specific + if v.lower() == "true": + v = 1 + name = NUM + elif v.lower() == "false": + v = 0 + name = NUM + elif name == NUM: + if m('oct'): v = int(v, 8) + elif m('hex'): v = int(m('hex'), 16) + elif m('n0'): v = m('n0') + else: + v = m('char') + if v: v = parse_char(v) + else: v = m('n2') or m('n4') + elif name == OP: + if v == '%:': v = '#' + elif v == '%:%:': v = '##' + elif name == STR: + # remove the quotes around the string + v = v[1:-1] + ret.append((name, v)) + break + return ret + +class c_parser(object): + def __init__(self, nodepaths=None, defines=None): + #self.lines = txt.split('\n') + self.lines = [] + + if defines is None: + self.defs = {} + else: + self.defs = dict(defines) # make a copy + self.state = [] + + self.env = None # needed for the variant when searching for files + + self.count_files = 0 + self.currentnode_stack = [] + + self.nodepaths = nodepaths or [] + + self.nodes = [] + self.names = [] + + # file added + self.curfile = '' + self.ban_includes = [] + + def tryfind(self, filename): + self.curfile = filename + + # for msvc it should be a for loop on the whole stack + found = self.currentnode_stack[-1].find_resource(filename) + + for n in self.nodepaths: + if found: + break + found = n.find_resource(filename) + + if not found: + if not filename in self.names: + self.names.append(filename) + else: + self.nodes.append(found) + if filename[-4:] != '.moc': + self.addlines(found) + return found + + def addlines(self, node): + + self.currentnode_stack.append(node.parent) + filepath = node.abspath(self.env) + + self.count_files += 1 + if self.count_files > 30000: raise PreprocError("recursion limit exceeded") + pc = self.parse_cache + debug('preproc: reading file %r' % filepath) + try: + lns = pc[filepath] + except KeyError: + pass + else: + self.lines = lns + self.lines + return + + try: + lines = filter_comments(filepath) + lines.append((POPFILE, '')) + pc[filepath] = lines # cache the lines filtered + self.lines = lines + self.lines + except IOError: + raise PreprocError("could not read the file %s" % filepath) + except Exception: + if Logs.verbose > 0: + error("parsing %s failed" % filepath) + traceback.print_exc() + + def start(self, node, env): + debug('preproc: scanning %s (in %s)' % (node.name, node.parent.name)) + + self.env = env + variant = node.variant(env) + bld = node.__class__.bld + try: + self.parse_cache = bld.parse_cache + except AttributeError: + bld.parse_cache = {} + self.parse_cache = bld.parse_cache + + self.addlines(node) + if env['DEFLINES']: + self.lines = [('define', x) for x in env['DEFLINES']] + self.lines + + while self.lines: + (kind, line) = self.lines.pop(0) + if kind == POPFILE: + self.currentnode_stack.pop() + continue + try: + self.process_line(kind, line) + except Exception, e: + if Logs.verbose: + debug('preproc: line parsing failed (%s): %s %s' % (e, line, Utils.ex_stack())) + + def process_line(self, token, line): + ve = Logs.verbose + if ve: debug('preproc: line is %s - %s state is %s' % (token, line, self.state)) + state = self.state + + # make certain we define the state if we are about to enter in an if block + if token in ['ifdef', 'ifndef', 'if']: + state.append(undefined) + elif token == 'endif': + state.pop() + + # skip lines when in a dead 'if' branch, wait for the endif + if not token in ['else', 'elif', 'endif']: + if skipped in self.state or ignored in self.state: + return + + if token == 'if': + ret = eval_macro(tokenize(line), self.defs) + if ret: state[-1] = accepted + else: state[-1] = ignored + elif token == 'ifdef': + m = re_mac.search(line) + if m and m.group(0) in self.defs: state[-1] = accepted + else: state[-1] = ignored + elif token == 'ifndef': + m = re_mac.search(line) + if m and m.group(0) in self.defs: state[-1] = ignored + else: state[-1] = accepted + elif token == 'include' or token == 'import': + (kind, inc) = extract_include(line, self.defs) + if inc in self.ban_includes: return + if token == 'import': self.ban_includes.append(inc) + if ve: debug('preproc: include found %s (%s) ' % (inc, kind)) + if kind == '"' or not strict_quotes: + self.tryfind(inc) + elif token == 'elif': + if state[-1] == accepted: + state[-1] = skipped + elif state[-1] == ignored: + if eval_macro(tokenize(line), self.defs): + state[-1] = accepted + elif token == 'else': + if state[-1] == accepted: state[-1] = skipped + elif state[-1] == ignored: state[-1] = accepted + elif token == 'define': + m = re_mac.search(line) + if m: + name = m.group(0) + if ve: debug('preproc: define %s %s' % (name, line)) + self.defs[name] = line + else: + raise PreprocError("invalid define line %s" % line) + elif token == 'undef': + m = re_mac.search(line) + if m and m.group(0) in self.defs: + self.defs.__delitem__(m.group(0)) + #print "undef %s" % name + elif token == 'pragma': + if re_pragma_once.search(line.lower()): + self.ban_includes.append(self.curfile) + +def get_deps(node, env, nodepaths=[]): + """ + Get the dependencies using a c/c++ preprocessor, this is required for finding dependencies of the kind + #include some_macro() + """ + + gruik = c_parser(nodepaths) + gruik.start(node, env) + return (gruik.nodes, gruik.names) + +#################### dumb dependency scanner + +re_inc = re.compile(\ + '^[ \t]*(#|%:)[ \t]*(include)[ \t]*(.*)\r*$', + re.IGNORECASE | re.MULTILINE) + +def lines_includes(filename): + code = Utils.readf(filename) + if use_trigraphs: + for (a, b) in trig_def: code = code.split(a).join(b) + code = re_nl.sub('', code) + code = re_cpp.sub(repl, code) + return [(m.group(2), m.group(3)) for m in re.finditer(re_inc, code)] + +def get_deps_simple(node, env, nodepaths=[], defines={}): + """ + Get the dependencies by just looking recursively at the #include statements + """ + + nodes = [] + names = [] + + def find_deps(node): + lst = lines_includes(node.abspath(env)) + + for (_, line) in lst: + (t, filename) = extract_include(line, defines) + if filename in names: + continue + + if filename.endswith('.moc'): + names.append(filename) + + found = None + for n in nodepaths: + if found: + break + found = n.find_resource(filename) + + if not found: + if not filename in names: + names.append(filename) + elif not found in nodes: + nodes.append(found) + find_deps(node) + + find_deps(node) + return (nodes, names) + + diff --git a/tools/wafadmin/Tools/python.py b/tools/wafadmin/Tools/python.py new file mode 100644 index 000000000..641fa116b --- /dev/null +++ b/tools/wafadmin/Tools/python.py @@ -0,0 +1,401 @@ +#!/usr/bin/env python +# encoding: utf-8 +# Thomas Nagy, 2007 (ita) +# Gustavo Carneiro (gjc), 2007 + +"Python support" + +import os, sys +import TaskGen, Utils, Utils, Runner, Options, Build +from Logs import debug, warn, info +from TaskGen import extension, taskgen, before, after, feature +from Configure import conf + +EXT_PY = ['.py'] +FRAG_2 = ''' +#ifdef __cplusplus +extern "C" { +#endif + void Py_Initialize(void); + void Py_Finalize(void); +#ifdef __cplusplus +} +#endif +int main() +{ + Py_Initialize(); + Py_Finalize(); + return 0; +} +''' + +@before('apply_incpaths', 'apply_lib_vars', 'apply_type_vars') +@feature('pyext') +@before('apply_bundle') +def init_pyext(self): + self.default_install_path = '${PYTHONDIR}' + self.uselib = self.to_list(getattr(self, 'uselib', '')) + if not 'PYEXT' in self.uselib: + self.uselib.append('PYEXT') + self.env['MACBUNDLE'] = True + +@before('apply_link', 'apply_lib_vars', 'apply_type_vars') +@after('apply_bundle') +@feature('pyext') +def pyext_shlib_ext(self): + # override shlib_PATTERN set by the osx module + self.env['shlib_PATTERN'] = self.env['pyext_PATTERN'] + +@before('apply_incpaths', 'apply_lib_vars', 'apply_type_vars') +@feature('pyembed') +def init_pyembed(self): + self.uselib = self.to_list(getattr(self, 'uselib', '')) + if not 'PYEMBED' in self.uselib: + self.uselib.append('PYEMBED') + +@extension(EXT_PY) +def process_py(self, node): + if not (self.bld.is_install or self.install_path): + return + def inst_py(ctx): + install_pyfile(self, node) + self.bld.add_post_fun(inst_py) + +def install_pyfile(self, node): + path = self.bld.get_install_path(self.install_path + os.sep + node.name, self.env) + + self.bld.install_files(self.install_path, [node], self.env, self.chmod, postpone=False) + if self.bld.is_install < 0: + info("* removing byte compiled python files") + for x in 'co': + try: + os.remove(path + x) + except OSError: + pass + + if self.bld.is_install > 0: + if self.env['PYC'] or self.env['PYO']: + info("* byte compiling %r" % path) + + if self.env['PYC']: + program = (""" +import sys, py_compile +for pyfile in sys.argv[1:]: + py_compile.compile(pyfile, pyfile + 'c') +""") + argv = [self.env['PYTHON'], '-c', program, path] + ret = Utils.pproc.Popen(argv).wait() + if ret: + raise Utils.WafError('bytecode compilation failed %r' % path) + + if self.env['PYO']: + program = (""" +import sys, py_compile +for pyfile in sys.argv[1:]: + py_compile.compile(pyfile, pyfile + 'o') +""") + argv = [self.env['PYTHON'], self.env['PYFLAGS_OPT'], '-c', program, path] + ret = Utils.pproc.Popen(argv).wait() + if ret: + raise Utils.WafError('bytecode compilation failed %r' % path) + +# COMPAT +class py_taskgen(TaskGen.task_gen): + def __init__(self, *k, **kw): + TaskGen.task_gen.__init__(self, *k, **kw) + +@before('apply_core') +@after('vars_target_cprogram', 'vars_target_cstaticlib') +@feature('py') +def init_py(self): + self.default_install_path = '${PYTHONDIR}' + +def _get_python_variables(python_exe, variables, imports=['import sys']): + """Run a python interpreter and print some variables""" + program = list(imports) + program.append('') + for v in variables: + program.append("print(repr(%s))" % v) + os_env = dict(os.environ) + try: + del os_env['MACOSX_DEPLOYMENT_TARGET'] # see comments in the OSX tool + except KeyError: + pass + proc = Utils.pproc.Popen([python_exe, "-c", '\n'.join(program)], stdout=Utils.pproc.PIPE, env=os_env) + output = proc.communicate()[0].split("\n") + if proc.returncode: + if Options.options.verbose: + warn("Python program to extract python configuration variables failed:\n%s" + % '\n'.join(["line %03i: %s" % (lineno+1, line) for lineno, line in enumerate(program)])) + raise RuntimeError + return_values = [] + for s in output: + s = s.strip() + if not s: + continue + if s == 'None': + return_values.append(None) + elif s[0] == "'" and s[-1] == "'": + return_values.append(s[1:-1]) + elif s[0].isdigit(): + return_values.append(int(s)) + else: break + return return_values + +@conf +def check_python_headers(conf): + """Check for headers and libraries necessary to extend or embed python. + + On success the environment variables xxx_PYEXT and xxx_PYEMBED are added for uselib + + PYEXT: for compiling python extensions + PYEMBED: for embedding a python interpreter""" + + if not conf.env['CC_NAME'] and not conf.env['CXX_NAME']: + conf.fatal('load a compiler first (gcc, g++, ..)') + + if not conf.env['PYTHON_VERSION']: + conf.check_python_version() + + env = conf.env + python = env['PYTHON'] + assert python, ("python is %r !" % (python,)) + + ## On Mac OSX we need to use mac bundles for python plugins + if Options.platform == 'darwin': + conf.check_tool('osx') + + try: + # Get some python configuration variables using distutils + v = 'prefix SO SYSLIBS LDFLAGS SHLIBS LIBDIR LIBPL INCLUDEPY Py_ENABLE_SHARED MACOSX_DEPLOYMENT_TARGET'.split() + (python_prefix, python_SO, python_SYSLIBS, python_LDFLAGS, python_SHLIBS, + python_LIBDIR, python_LIBPL, INCLUDEPY, Py_ENABLE_SHARED, + python_MACOSX_DEPLOYMENT_TARGET) = \ + _get_python_variables(python, ["get_config_var('%s')" % x for x in v], + ['from distutils.sysconfig import get_config_var']) + except RuntimeError: + conf.fatal("Python development headers not found (-v for details).") + + conf.log.write("""Configuration returned from %r: +python_prefix = %r +python_SO = %r +python_SYSLIBS = %r +python_LDFLAGS = %r +python_SHLIBS = %r +python_LIBDIR = %r +python_LIBPL = %r +INCLUDEPY = %r +Py_ENABLE_SHARED = %r +MACOSX_DEPLOYMENT_TARGET = %r +""" % (python, python_prefix, python_SO, python_SYSLIBS, python_LDFLAGS, python_SHLIBS, + python_LIBDIR, python_LIBPL, INCLUDEPY, Py_ENABLE_SHARED, python_MACOSX_DEPLOYMENT_TARGET)) + + if python_MACOSX_DEPLOYMENT_TARGET: + conf.env['MACOSX_DEPLOYMENT_TARGET'] = python_MACOSX_DEPLOYMENT_TARGET + conf.environ['MACOSX_DEPLOYMENT_TARGET'] = python_MACOSX_DEPLOYMENT_TARGET + + env['pyext_PATTERN'] = '%s'+python_SO + + # Check for python libraries for embedding + if python_SYSLIBS is not None: + for lib in python_SYSLIBS.split(): + if lib.startswith('-l'): + lib = lib[2:] # strip '-l' + env.append_value('LIB_PYEMBED', lib) + if python_SHLIBS is not None: + for lib in python_SHLIBS.split(): + if lib.startswith('-l'): + lib = lib[2:] # strip '-l' + env.append_value('LIB_PYEMBED', lib) + + if Options.platform != 'darwin' and python_LDFLAGS: + env.append_value('LINKFLAGS_PYEMBED', python_LDFLAGS.split()) + + result = False + name = 'python' + env['PYTHON_VERSION'] + + if python_LIBDIR is not None: + path = [python_LIBDIR] + conf.log.write("\n\n# Trying LIBDIR: %r\n" % path) + result = conf.check(lib=name, uselib='PYEMBED', libpath=path) + + if not result and python_LIBPL is not None: + conf.log.write("\n\n# try again with -L$python_LIBPL (some systems don't install the python library in $prefix/lib)\n") + path = [python_LIBPL] + result = conf.check(lib=name, uselib='PYEMBED', libpath=path) + + if not result: + conf.log.write("\n\n# try again with -L$prefix/libs, and pythonXY name rather than pythonX.Y (win32)\n") + path = [os.path.join(python_prefix, "libs")] + name = 'python' + env['PYTHON_VERSION'].replace('.', '') + result = conf.check(lib=name, uselib='PYEMBED', libpath=path) + + if result: + env['LIBPATH_PYEMBED'] = path + env.append_value('LIB_PYEMBED', name) + else: + conf.log.write("\n\n### LIB NOT FOUND\n") + + # under certain conditions, python extensions must link to + # python libraries, not just python embedding programs. + if (sys.platform == 'win32' or sys.platform.startswith('os2') + or sys.platform == 'darwin' or Py_ENABLE_SHARED): + env['LIBPATH_PYEXT'] = env['LIBPATH_PYEMBED'] + env['LIB_PYEXT'] = env['LIB_PYEMBED'] + + # We check that pythonX.Y-config exists, and if it exists we + # use it to get only the includes, else fall back to distutils. + python_config = conf.find_program( + 'python%s-config' % ('.'.join(env['PYTHON_VERSION'].split('.')[:2])), + var='PYTHON_CONFIG') + if not python_config: + python_config = conf.find_program( + 'python-config-%s' % ('.'.join(env['PYTHON_VERSION'].split('.')[:2])), + var='PYTHON_CONFIG') + + includes = [] + if python_config: + for incstr in Utils.cmd_output("%s %s --includes" % (python, python_config)).strip().split(): + # strip the -I or /I + if (incstr.startswith('-I') + or incstr.startswith('/I')): + incstr = incstr[2:] + # append include path, unless already given + if incstr not in includes: + includes.append(incstr) + conf.log.write("Include path for Python extensions " + "(found via python-config --includes): %r\n" % (includes,)) + env['CPPPATH_PYEXT'] = includes + env['CPPPATH_PYEMBED'] = includes + else: + conf.log.write("Include path for Python extensions " + "(found via distutils module): %r\n" % (INCLUDEPY,)) + env['CPPPATH_PYEXT'] = [INCLUDEPY] + env['CPPPATH_PYEMBED'] = [INCLUDEPY] + + # Code using the Python API needs to be compiled with -fno-strict-aliasing + if env['CC_NAME'] == 'gcc': + env.append_value('CCFLAGS_PYEMBED', '-fno-strict-aliasing') + env.append_value('CCFLAGS_PYEXT', '-fno-strict-aliasing') + if env['CXX_NAME'] == 'gcc': + env.append_value('CXXFLAGS_PYEMBED', '-fno-strict-aliasing') + env.append_value('CXXFLAGS_PYEXT', '-fno-strict-aliasing') + + # See if it compiles + test_env = env.copy() + a = test_env.append_value + a('CPPPATH', env['CPPPATH_PYEMBED']) + a('LIBPATH', env['LIBPATH_PYEMBED']) + a('LIB', env['LIB_PYEMBED']) + a('LINKFLAGS', env['LINKFLAGS_PYEMBED']) + a('CXXFLAGS', env['CXXFLAGS_PYEMBED']) + a('CCFLAGS', env['CCFLAGS_PYEMBED']) + + conf.check(header_name='Python.h', define_name='HAVE_PYTHON_H', + env=test_env, fragment=FRAG_2, + errmsg='Could not find the python development headers', mandatory=1) + +@conf +def check_python_version(conf, minver=None): + """ + Check if the python interpreter is found matching a given minimum version. + minver should be a tuple, eg. to check for python >= 2.4.2 pass (2,4,2) as minver. + + If successful, PYTHON_VERSION is defined as 'MAJOR.MINOR' + (eg. '2.4') of the actual python version found, and PYTHONDIR is + defined, pointing to the site-packages directory appropriate for + this python version, where modules/packages/extensions should be + installed. + """ + assert minver is None or isinstance(minver, tuple) + python = conf.env['PYTHON'] + assert python, ("python is %r !" % (python,)) + + # Get python version string + cmd = [python, "-c", "import sys\nfor x in sys.version_info: print(str(x))"] + debug('python: Running python command %r' % cmd) + proc = Utils.pproc.Popen(cmd, stdout=Utils.pproc.PIPE) + lines = proc.communicate()[0].split() + assert len(lines) == 5, "found %i lines, expected 5: %r" % (len(lines), lines) + pyver_tuple = (int(lines[0]), int(lines[1]), int(lines[2]), lines[3], int(lines[4])) + + # compare python version with the minimum required + result = (minver is None) or (pyver_tuple >= minver) + + if result: + # define useful environment variables + pyver = '.'.join([str(x) for x in pyver_tuple[:2]]) + conf.env['PYTHON_VERSION'] = pyver + + if 'PYTHONDIR' in conf.environ: + pydir = conf.environ['PYTHONDIR'] + else: + if sys.platform == 'win32': + (python_LIBDEST, pydir) = \ + _get_python_variables(python, + ["get_config_var('LIBDEST')", + "get_python_lib(standard_lib=0, prefix=%r)" % conf.env['PREFIX']], + ['from distutils.sysconfig import get_config_var, get_python_lib']) + else: + python_LIBDEST = None + (pydir,) = \ + _get_python_variables(python, + ["get_python_lib(standard_lib=0, prefix=%r)" % conf.env['PREFIX']], + ['from distutils.sysconfig import get_config_var, get_python_lib']) + if python_LIBDEST is None: + if conf.env['LIBDIR']: + python_LIBDEST = os.path.join(conf.env['LIBDIR'], "python" + pyver) + else: + python_LIBDEST = os.path.join(conf.env['PREFIX'], "lib", "python" + pyver) + + if hasattr(conf, 'define'): # conf.define is added by the C tool, so may not exist + conf.define('PYTHONDIR', pydir) + conf.env['PYTHONDIR'] = pydir + + # Feedback + pyver_full = '.'.join(map(str, pyver_tuple[:3])) + if minver is None: + conf.check_message_custom('Python version', '', pyver_full) + else: + minver_str = '.'.join(map(str, minver)) + conf.check_message('Python version', ">= %s" % (minver_str,), result, option=pyver_full) + + if not result: + conf.fatal("Python too old.") + +@conf +def check_python_module(conf, module_name): + """ + Check if the selected python interpreter can import the given python module. + """ + result = not Utils.pproc.Popen([conf.env['PYTHON'], "-c", "import %s" % module_name], + stderr=Utils.pproc.PIPE, stdout=Utils.pproc.PIPE).wait() + conf.check_message('Python module', module_name, result) + if not result: + conf.fatal("Python module not found.") + +def detect(conf): + python = conf.find_program('python', var='PYTHON') + if not python: return + + v = conf.env + + v['PYCMD'] = '"import sys, py_compile;py_compile.compile(sys.argv[1], sys.argv[2])"' + v['PYFLAGS'] = '' + v['PYFLAGS_OPT'] = '-O' + + v['PYC'] = getattr(Options.options, 'pyc', 1) + v['PYO'] = getattr(Options.options, 'pyo', 1) + +def set_options(opt): + opt.add_option('--nopyc', + action='store_false', + default=1, + help = 'Do not install bytecode compiled .pyc files (configuration) [Default:install]', + dest = 'pyc') + opt.add_option('--nopyo', + action='store_false', + default=1, + help='Do not install optimised compiled .pyo files (configuration) [Default:install]', + dest='pyo') + diff --git a/tools/wafadmin/Tools/qt4.py b/tools/wafadmin/Tools/qt4.py new file mode 100644 index 000000000..dce270335 --- /dev/null +++ b/tools/wafadmin/Tools/qt4.py @@ -0,0 +1,531 @@ +#!/usr/bin/env python +# encoding: utf-8 +# Thomas Nagy, 2006 (ita) + +""" +Qt4 support + +If QT4_ROOT is given (absolute path), the configuration will look in it first + +This module also demonstrates how to add tasks dynamically (when the build has started) +""" + +try: + from xml.sax import make_parser + from xml.sax.handler import ContentHandler +except ImportError: + has_xml = False + ContentHandler = object +else: + has_xml = True + +import os, sys +import ccroot, cxx +import TaskGen, Task, Utils, Runner, Options, Node +from TaskGen import taskgen, feature, after, extension +from Logs import error +from Constants import * + +MOC_H = ['.h', '.hpp', '.hxx', '.hh'] +EXT_RCC = ['.qrc'] +EXT_UI = ['.ui'] +EXT_QT4 = ['.cpp', '.cc', '.cxx', '.C'] + +class qxx_task(Task.Task): + "A cpp task that may create a moc task dynamically" + + before = ['cxx_link', 'static_link'] + + def __init__(self, *k, **kw): + Task.Task.__init__(self, *k, **kw) + self.moc_done = 0 + + def scan(self): + (nodes, names) = ccroot.scan(self) + # for some reasons (variants) the moc node may end in the list of node deps + for x in nodes: + if x.name.endswith('.moc'): + nodes.remove(x) + names.append(x.relpath_gen(self.inputs[0].parent)) + return (nodes, names) + + def runnable_status(self): + if self.moc_done: + # if there is a moc task, delay the computation of the file signature + for t in self.run_after: + if not t.hasrun: + return ASK_LATER + # the moc file enters in the dependency calculation + # so we need to recompute the signature when the moc file is present + self.signature() + return Task.Task.runnable_status(self) + else: + # yes, really, there are people who generate cxx files + for t in self.run_after: + if not t.hasrun: + return ASK_LATER + self.add_moc_tasks() + return ASK_LATER + + def add_moc_tasks(self): + + node = self.inputs[0] + tree = node.__class__.bld + + try: + # compute the signature once to know if there is a moc file to create + self.signature() + except KeyError: + # the moc file may be referenced somewhere else + pass + else: + # remove the signature, it must be recomputed with the moc task + delattr(self, 'cache_sig') + + moctasks=[] + mocfiles=[] + variant = node.variant(self.env) + try: + tmp_lst = tree.raw_deps[self.unique_id()] + tree.raw_deps[self.unique_id()] = [] + except KeyError: + tmp_lst = [] + for d in tmp_lst: + if not d.endswith('.moc'): continue + # paranoid check + if d in mocfiles: + error("paranoia owns") + continue + # process that base.moc only once + mocfiles.append(d) + + # find the extension - this search is done only once + ext = '' + try: ext = Options.options.qt_header_ext + except AttributeError: pass + + if not ext: + base2 = d[:-4] + paths = [node.parent.srcpath(self.env), node.parent.bldpath(self.env)] + poss = [(x, y) for x in MOC_H for y in paths] + for (i, path) in poss: + try: + # TODO we could use find_resource + os.stat(os.path.join(path, base2+i)) + except OSError: + pass + else: + ext = i + break + if not ext: raise Utils.WafError("no header found for %s which is a moc file" % str(d)) + + # next time we will not search for the extension (look at the 'for' loop below) + h_node = node.parent.find_resource(base2+i) + m_node = h_node.change_ext('.moc') + tree.node_deps[(self.inputs[0].parent.id, self.env.variant(), m_node.name)] = h_node + + # create the task + task = Task.TaskBase.classes['moc'](self.env, normal=0) + task.set_inputs(h_node) + task.set_outputs(m_node) + + generator = tree.generator + generator.outstanding.insert(0, task) + generator.total += 1 + + moctasks.append(task) + + # remove raw deps except the moc files to save space (optimization) + tmp_lst = tree.raw_deps[self.unique_id()] = mocfiles + + # look at the file inputs, it is set right above + lst = tree.node_deps.get(self.unique_id(), ()) + for d in lst: + name = d.name + if name.endswith('.moc'): + task = Task.TaskBase.classes['moc'](self.env, normal=0) + task.set_inputs(tree.node_deps[(self.inputs[0].parent.id, self.env.variant(), name)]) # 1st element in a tuple + task.set_outputs(d) + + generator = tree.generator + generator.outstanding.insert(0, task) + generator.total += 1 + + moctasks.append(task) + + # simple scheduler dependency: run the moc task before others + self.run_after = moctasks + self.moc_done = 1 + + run = Task.TaskBase.classes['cxx'].__dict__['run'] + +def translation_update(task): + outs = [a.abspath(task.env) for a in task.outputs] + outs = " ".join(outs) + lupdate = task.env['QT_LUPDATE'] + + for x in task.inputs: + file = x.abspath(task.env) + cmd = "%s %s -ts %s" % (lupdate, file, outs) + Utils.pprint('BLUE', cmd) + task.generator.bld.exec_command(cmd) + +class XMLHandler(ContentHandler): + def __init__(self): + self.buf = [] + self.files = [] + def startElement(self, name, attrs): + if name == 'file': + self.buf = [] + def endElement(self, name): + if name == 'file': + self.files.append(''.join(self.buf)) + def characters(self, cars): + self.buf.append(cars) + +def scan(self): + "add the dependency on the files referenced in the qrc" + node = self.inputs[0] + parser = make_parser() + curHandler = XMLHandler() + parser.setContentHandler(curHandler) + fi = open(self.inputs[0].abspath(self.env)) + parser.parse(fi) + fi.close() + + nodes = [] + names = [] + root = self.inputs[0].parent + for x in curHandler.files: + x = x.encode('utf8') + nd = root.find_resource(x) + if nd: nodes.append(nd) + else: names.append(x) + + return (nodes, names) + +@extension(EXT_RCC) +def create_rcc_task(self, node): + "hook for rcc files" + + rcnode = node.change_ext('_rc.cpp') + + rcctask = self.create_task('rcc') + rcctask.inputs = [node] + rcctask.outputs = [rcnode] + + cpptask = self.create_task('cxx') + cpptask.inputs = [rcnode] + cpptask.outputs = [rcnode.change_ext('.o')] + self.compiled_tasks.append(cpptask) + + return cpptask + +@extension(EXT_UI) +def create_uic_task(self, node): + "hook for uic tasks" + uictask = self.create_task('ui4') + uictask.inputs = [node] + uictask.outputs = [self.path.find_or_declare(self.env['ui_PATTERN'] % node.name[:-3])] + +class qt4_taskgen(cxx.cxx_taskgen): + def __init__(self, *k, **kw): + cxx.cxx_taskgen.__init__(self, *k, **kw) + self.features.append('qt4') + +@extension('.ts') +def add_lang(self, node): + """add all the .ts file into self.lang""" + self.lang = self.to_list(getattr(self, 'lang', [])) + [node] + +@feature('qt4') +@after('apply_link') +def apply_qt4(self): + if getattr(self, 'lang', None): + update = getattr(self, 'update', None) + lst=[] + trans=[] + for l in self.to_list(self.lang): + + if not isinstance(l, Node.Node): + l = self.path.find_resource(l+'.ts') + + t = self.create_task('ts2qm') + t.set_inputs(l) + t.set_outputs(l.change_ext('.qm')) + lst.append(t.outputs[0]) + + if update: + trans.append(t.inputs[0]) + + if update and Options.options.trans_qt4: + # we need the cpp files given, except the rcc task we create after + # FIXME may be broken + u = Task.TaskCmd(translation_update, self.env, 2) + u.inputs = [a.inputs[0] for a in self.compiled_tasks] + u.outputs = trans + + if getattr(self, 'langname', None): + t = Task.TaskBase.classes['qm2rcc'](self.env) + t.set_inputs(lst) + t.set_outputs(self.path.find_or_declare(self.langname+'.qrc')) + t.path = self.path + k = create_rcc_task(self, t.outputs[0]) + self.link_task.inputs.append(k.outputs[0]) + + lst = [] + for flag in self.to_list(self.env['CXXFLAGS']): + if len(flag) < 2: continue + if flag[0:2] == '-D' or flag[0:2] == '-I': + lst.append(flag) + self.env['MOC_FLAGS'] = lst + +@extension(EXT_QT4) +def cxx_hook(self, node): + # create the compilation task: cpp or cc + task = self.create_task('qxx') + self.compiled_tasks.append(task) + try: obj_ext = self.obj_ext + except AttributeError: obj_ext = '_%d.o' % self.idx + + task.inputs = [node] + task.outputs = [node.change_ext(obj_ext)] + +def process_qm2rcc(task): + outfile = task.outputs[0].abspath(task.env) + f = open(outfile, 'w') + f.write('<!DOCTYPE RCC><RCC version="1.0">\n<qresource>\n') + for k in task.inputs: + f.write(' <file>') + #f.write(k.name) + f.write(k.path_to_parent(task.path)) + f.write('</file>\n') + f.write('</qresource>\n</RCC>') + f.close() + +b = Task.simple_task_type +b('moc', '${QT_MOC} ${MOC_FLAGS} ${SRC} ${MOC_ST} ${TGT}', color='BLUE', vars=['QT_MOC', 'MOC_FLAGS'], shell=False) +cls = b('rcc', '${QT_RCC} -name ${SRC[0].name} ${SRC[0].abspath(env)} ${RCC_ST} -o ${TGT}', color='BLUE', before='cxx moc qxx_task', after="qm2rcc", shell=False) +cls.scan = scan +b('ui4', '${QT_UIC} ${SRC} -o ${TGT}', color='BLUE', before='cxx moc qxx_task', shell=False) +b('ts2qm', '${QT_LRELEASE} ${QT_LRELEASE_FLAGS} ${SRC} -qm ${TGT}', color='BLUE', before='qm2rcc', shell=False) + +Task.task_type_from_func('qm2rcc', vars=[], func=process_qm2rcc, color='BLUE', before='rcc', after='ts2qm') + +def detect_qt4(conf): + env = conf.env + opt = Options.options + + qtdir = getattr(opt, 'qtdir', '') + qtbin = getattr(opt, 'qtbin', '') + qtlibs = getattr(opt, 'qtlibs', '') + useframework = getattr(opt, 'use_qt4_osxframework', True) + + paths = [] + + # the path to qmake has been given explicitely + if qtbin: + paths = [qtbin] + + # the qt directory has been given - we deduce the qt binary path + if not qtdir: + qtdir = conf.environ.get('QT4_ROOT', '') + qtbin = os.path.join(qtdir, 'bin') + paths = [qtbin] + + # no qtdir, look in the path and in /usr/local/Trolltech + if not qtdir: + paths = os.environ.get('PATH', '').split(os.pathsep) + paths.append('/usr/share/qt4/bin/') + try: + lst = os.listdir('/usr/local/Trolltech/') + except OSError: + pass + else: + if lst: + lst.sort() + lst.reverse() + + # keep the highest version + qtdir = '/usr/local/Trolltech/%s/' % lst[0] + qtbin = os.path.join(qtdir, 'bin') + paths.append(qtbin) + + # at the end, try to find qmake in the paths given + # keep the one with the highest version + cand = None + prev_ver = ['4', '0', '0'] + for qmk in ['qmake-qt4', 'qmake4', 'qmake']: + qmake = conf.find_program(qmk, path_list=paths) + if qmake: + try: + version = Utils.cmd_output([qmake, '-query', 'QT_VERSION']).strip() + except ValueError: + pass + else: + if version: + new_ver = version.split('.') + if new_ver > prev_ver: + cand = qmake + prev_ver = new_ver + if cand: + qmake = cand + else: + conf.fatal('could not find qmake for qt4') + + conf.env.QMAKE = qmake + qtincludes = Utils.cmd_output([qmake, '-query', 'QT_INSTALL_HEADERS']).strip() + qtdir = Utils.cmd_output([qmake, '-query', 'QT_INSTALL_PREFIX']).strip() + os.sep + qtbin = Utils.cmd_output([qmake, '-query', 'QT_INSTALL_BINS']).strip() + os.sep + + if not qtlibs: + try: + qtlibs = Utils.cmd_output([qmake, '-query', 'QT_LIBRARIES']).strip() + os.sep + except ValueError: + qtlibs = os.path.join(qtdir, 'lib') + + def find_bin(lst, var): + for f in lst: + ret = conf.find_program(f, path_list=paths) + if ret: + env[var]=ret + break + + vars = "QtCore QtGui QtUiTools QtNetwork QtOpenGL QtSql QtSvg QtTest QtXml QtWebKit Qt3Support".split() + + framework_ok = False + if sys.platform == "darwin" and useframework: + for i in vars: + e = conf.create_framework_configurator() + e.path = [qtlibs, '/Library/Frameworks'] + e.name = i + e.remove_dot_h = True + e.run() + + if not i == 'QtCore': + # strip -F flag so it don't get reduant + for r in env['CCFLAGS_' + i.upper()]: + if r.startswith('-F'): + env['CCFLAGS_' + i.upper()].remove(r) + break + +# incflag = '-I%s' % os.path.join(qtincludes, i) +# if not incflag in env["CCFLAGS_" + i.upper ()]: +# env['CCFLAGS_' + i.upper ()] += [incflag] +# if not incflag in env["CXXFLAGS_" + i.upper ()]: +# env['CXXFLAGS_' + i.upper ()] += [incflag] + + # now we add some static depends. + if conf.is_defined('HAVE_QTOPENGL'): + env.append_unique('FRAMEWORK_QTOPENGL', 'OpenGL') + + if conf.is_defined('HAVE_QTGUI'): + env.append_unique('FRAMEWORK_QTGUI', ['AppKit', 'ApplicationServices']) + + framework_ok = True + + # check for the qt includes first + if not conf.is_defined("HAVE_QTGUI"): + if not qtincludes: qtincludes = os.path.join(qtdir, 'include') + env.QTINCLUDEPATH = qtincludes + + lst = [qtincludes, '/usr/share/qt4/include/', '/opt/qt4/include'] + conf.check(header_name='QtGui/QFont', define_name='HAVE_QTGUI', mandatory=1, includes=lst) + + find_bin(['uic-qt3', 'uic3'], 'QT_UIC3') + find_bin(['uic-qt4', 'uic'], 'QT_UIC') + if not env['QT_UIC']: + conf.fatal('cannot find the uic compiler for qt4') + + try: + version = Utils.cmd_output(env['QT_UIC'] + " -version 2>&1").strip() + except ValueError: + conf.fatal('your uic compiler is for qt3, add uic for qt4 to your path') + + version = version.replace('Qt User Interface Compiler ','') + version = version.replace('User Interface Compiler for Qt', '') + if version.find(" 3.") != -1: + conf.check_message('uic version', '(too old)', 0, option='(%s)'%version) + sys.exit(1) + conf.check_message('uic version', '', 1, option='(%s)'%version) + + find_bin(['moc-qt4', 'moc'], 'QT_MOC') + find_bin(['rcc'], 'QT_RCC') + find_bin(['lrelease-qt4', 'lrelease'], 'QT_LRELEASE') + find_bin(['lupdate-qt4', 'lupdate'], 'QT_LUPDATE') + + env['UIC3_ST']= '%s -o %s' + env['UIC_ST'] = '%s -o %s' + env['MOC_ST'] = '-o' + env['ui_PATTERN'] = 'ui_%s.h' + env['QT_LRELEASE_FLAGS'] = ['-silent'] + + if not framework_ok: # framework_ok is false either when the platform isn't OSX, Qt4 shall not be used as framework, or Qt4 could not be found as framework + vars_debug = [a+'_debug' for a in vars] + + pkgconfig = env['pkg-config'] or 'PKG_CONFIG_PATH=%s:%s/pkgconfig:/usr/lib/qt4/lib/pkgconfig:/opt/qt4/lib/pkgconfig:/usr/lib/qt4/lib:/opt/qt4/lib pkg-config --silence-errors' % (qtlibs, qtlibs) + for i in vars_debug+vars: + try: + conf.check_cfg(package=i, args='--cflags --libs', path=pkgconfig) + except ValueError: + pass + + # the libpaths are set nicely, unfortunately they make really long command-lines + # remove the qtcore ones from qtgui, etc + def process_lib(vars_, coreval): + for d in vars_: + var = d.upper() + if var == 'QTCORE': continue + + value = env['LIBPATH_'+var] + if value: + core = env[coreval] + accu = [] + for lib in value: + if lib in core: continue + accu.append(lib) + env['LIBPATH_'+var] = accu + + process_lib(vars, 'LIBPATH_QTCORE') + process_lib(vars_debug, 'LIBPATH_QTCORE_DEBUG') + + # rpath if wanted + if Options.options.want_rpath: + def process_rpath(vars_, coreval): + for d in vars_: + var = d.upper() + value = env['LIBPATH_'+var] + if value: + core = env[coreval] + accu = [] + for lib in value: + if var != 'QTCORE': + if lib in core: + continue + accu.append('-Wl,--rpath='+lib) + env['RPATH_'+var] = accu + process_rpath(vars, 'LIBPATH_QTCORE') + process_rpath(vars_debug, 'LIBPATH_QTCORE_DEBUG') + + env['QTLOCALE'] = str(env['PREFIX'])+'/share/locale' + +def detect(conf): + detect_qt4(conf) + +def set_options(opt): + opt.add_option('--want-rpath', type='int', default=1, dest='want_rpath', help='set rpath to 1 or 0 [Default 1]') + + opt.add_option('--header-ext', + type='string', + default='', + help='header extension for moc files', + dest='qt_header_ext') + + for i in 'qtdir qtbin qtlibs'.split(): + opt.add_option('--'+i, type='string', default='', dest=i) + + if sys.platform == "darwin": + opt.add_option('--no-qt4-framework', action="store_false", help='do not use the framework version of Qt4 in OS X', dest='use_qt4_osxframework',default=True) + + opt.add_option('--translate', action="store_true", help="collect translation strings", dest="trans_qt4", default=False) + diff --git a/tools/wafadmin/Tools/suncc.py b/tools/wafadmin/Tools/suncc.py new file mode 100644 index 000000000..8f79315a5 --- /dev/null +++ b/tools/wafadmin/Tools/suncc.py @@ -0,0 +1,75 @@ +#!/usr/bin/env python +# encoding: utf-8 +# Thomas Nagy, 2006 (ita) +# Ralf Habacker, 2006 (rh) + +import os, optparse +import Utils, Options, Configure +import ccroot, ar +from Configure import conftest + +@conftest +def find_scc(conf): + v = conf.env + cc = None + if v['CC']: cc = v['CC'] + elif 'CC' in conf.environ: cc = conf.environ['CC'] + #if not cc: cc = conf.find_program('gcc', var='CC') + if not cc: cc = conf.find_program('cc', var='CC') + if not cc: conf.fatal('suncc was not found') + + try: + if not Utils.cmd_output('%s -flags' % cc): + conf.fatal('suncc %r was not found' % cc) + except ValueError: + conf.fatal('suncc -flags could not be executed') + + v['CC'] = cc + v['CC_NAME'] = 'sun' + +@conftest +def scc_common_flags(conf): + v = conf.env + + # CPPFLAGS CCDEFINES _CCINCFLAGS _CCDEFFLAGS + + v['CC_SRC_F'] = '' + v['CC_TGT_F'] = ['-c', '-o', ''] + v['CPPPATH_ST'] = '-I%s' # template for adding include paths + + # linker + if not v['LINK_CC']: v['LINK_CC'] = v['CC'] + v['CCLNK_SRC_F'] = '' + v['CCLNK_TGT_F'] = ['-o', ''] # solaris hack, separate the -o from the target + + v['LIB_ST'] = '-l%s' # template for adding libs + v['LIBPATH_ST'] = '-L%s' # template for adding libpaths + v['STATICLIB_ST'] = '-l%s' + v['STATICLIBPATH_ST'] = '-L%s' + v['CCDEFINES_ST'] = '-D%s' + + v['SONAME_ST'] = '-Wl,-h -Wl,%s' + v['SHLIB_MARKER'] = '-Bdynamic' + v['STATICLIB_MARKER'] = '-Bstatic' + + # program + v['program_PATTERN'] = '%s' + + # shared library + v['shlib_CCFLAGS'] = ['-Kpic', '-DPIC'] + v['shlib_LINKFLAGS'] = ['-G'] + v['shlib_PATTERN'] = 'lib%s.so' + + # static lib + v['staticlib_LINKFLAGS'] = ['-Bstatic'] + v['staticlib_PATTERN'] = 'lib%s.a' + +detect = ''' +find_scc +find_cpp +find_ar +scc_common_flags +cc_load_tools +cc_add_flags +''' + diff --git a/tools/wafadmin/Tools/suncxx.py b/tools/wafadmin/Tools/suncxx.py new file mode 100644 index 000000000..f168502e0 --- /dev/null +++ b/tools/wafadmin/Tools/suncxx.py @@ -0,0 +1,69 @@ +#!/usr/bin/env python +# encoding: utf-8 +# Thomas Nagy, 2006 (ita) +# Ralf Habacker, 2006 (rh) + +import os, optparse +import Utils, Options, Configure +import ccroot, ar +from Configure import conftest + +@conftest +def find_sxx(conf): + v = conf.env + cc = None + if v['CXX']: cc = v['CXX'] + elif 'CXX' in conf.environ: cc = conf.environ['CXX'] + #if not cc: cc = conf.find_program('g++', var='CXX') + if not cc: cc = conf.find_program('c++', var='CXX') + if not cc: cc = conf.find_program('CC', var='CXX') #studio + if not cc: conf.fatal('sunc++ was not found') + v['CXX'] = cc + v['CXX_NAME'] = 'sun' + +@conftest +def sxx_common_flags(conf): + v = conf.env + + # CPPFLAGS CXXDEFINES _CXXINCFLAGS _CXXDEFFLAGS + + v['CXX_SRC_F'] = '' + v['CXX_TGT_F'] = ['-c', '-o', ''] + v['CPPPATH_ST'] = '-I%s' # template for adding include paths + + # linker + if not v['LINK_CXX']: v['LINK_CXX'] = v['CXX'] + v['CXXLNK_SRC_F'] = '' + v['CXXLNK_TGT_F'] = ['-o', ''] # solaris hack, separate the -o from the target + + v['LIB_ST'] = '-l%s' # template for adding libs + v['LIBPATH_ST'] = '-L%s' # template for adding libpaths + v['STATICLIB_ST'] = '-l%s' + v['STATICLIBPATH_ST'] = '-L%s' + v['CXXDEFINES_ST'] = '-D%s' + + v['SONAME_ST'] = '-Wl,-h -Wl,%s' + v['SHLIB_MARKER'] = '-Bdynamic' + v['STATICLIB_MARKER'] = '-Bstatic' + + # program + v['program_PATTERN'] = '%s' + + # shared library + v['shlib_CXXFLAGS'] = ['-Kpic', '-DPIC'] + v['shlib_LINKFLAGS'] = ['-G'] + v['shlib_PATTERN'] = 'lib%s.so' + + # static lib + v['staticlib_LINKFLAGS'] = ['-Bstatic'] + v['staticlib_PATTERN'] = 'lib%s.a' + +detect = ''' +find_sxx +find_cpp +find_ar +sxx_common_flags +cxx_load_tools +cxx_add_flags +''' + diff --git a/tools/wafadmin/Tools/tex.py b/tools/wafadmin/Tools/tex.py new file mode 100644 index 000000000..dd8ec2afa --- /dev/null +++ b/tools/wafadmin/Tools/tex.py @@ -0,0 +1,235 @@ +#!/usr/bin/env python +# encoding: utf-8 +# Thomas Nagy, 2006 (ita) + +"TeX/LaTeX/PDFLaTeX support" + +import os, re +import Utils, TaskGen, Task, Runner, Build +from TaskGen import feature, before +from Logs import error, warn, debug + +re_tex = re.compile(r'\\(?P<type>include|input|import|bringin){(?P<file>[^{}]*)}', re.M) +def scan(self): + node = self.inputs[0] + env = self.env + + nodes = [] + names = [] + if not node: return (nodes, names) + + code = Utils.readf(node.abspath(env)) + + curdirnode = self.curdirnode + abs = curdirnode.abspath() + for match in re_tex.finditer(code): + path = match.group('file') + if path: + for k in ['', '.tex', '.ltx']: + # add another loop for the tex include paths? + debug('tex: trying %s%s' % (path, k)) + try: + os.stat(abs+os.sep+path+k) + except OSError: + continue + found = path+k + node = curdirnode.find_resource(found) + if node: + nodes.append(node) + else: + debug('tex: could not find %s' % path) + names.append(path) + + debug("tex: found the following : %s and names %s" % (nodes, names)) + return (nodes, names) + +g_bibtex_re = re.compile('bibdata', re.M) +def tex_build(task, command='LATEX'): + env = task.env + bld = task.generator.bld + + com = '%s %s' % (env[command], env.get_flat(command+'FLAGS')) + if not env['PROMPT_LATEX']: com = "%s %s" % (com, '-interaction=batchmode') + + node = task.inputs[0] + reldir = node.bld_dir(env) + srcfile = node.srcpath(env) + + lst = [] + for c in Utils.split_path(reldir): + if c: lst.append('..') + sr = os.path.join(*(lst + [srcfile])) + sr2 = os.path.join(*(lst + [node.parent.srcpath(env)])) + + aux_node = node.change_ext('.aux') + idx_node = node.change_ext('.idx') + + hash = '' + old_hash = '' + + nm = aux_node.name + docuname = nm[ : len(nm) - 4 ] # 4 is the size of ".aux" + + latex_compile_cmd = 'cd %s && TEXINPUTS=%s:$TEXINPUTS %s %s' % (reldir, sr2, com, sr) + warn('first pass on %s' % command) + ret = bld.exec_command(latex_compile_cmd) + if ret: return ret + + # look in the .aux file if there is a bibfile to process + try: + ct = Utils.readf(aux_node.abspath(env)) + except (OSError, IOError): + error('error bibtex scan') + else: + fo = g_bibtex_re.findall(ct) + + # yes, there is a .aux file to process + if fo: + bibtex_compile_cmd = 'cd %s && BIBINPUTS=%s:$BIBINPUTS %s %s' % (reldir, sr2, env['BIBTEX'], docuname) + + warn('calling bibtex') + ret = bld.exec_command(bibtex_compile_cmd) + if ret: + error('error when calling bibtex %s' % bibtex_compile_cmd) + return ret + + # look on the filesystem if there is a .idx file to process + try: + idx_path = idx_node.abspath(env) + os.stat(idx_path) + except OSError: + error('error file.idx scan') + else: + makeindex_compile_cmd = 'cd %s && %s %s' % (reldir, env['MAKEINDEX'], idx_path) + warn('calling makeindex') + ret = bld.exec_command(makeindex_compile_cmd) + if ret: + error('error when calling makeindex %s' % makeindex_compile_cmd) + return ret + + i = 0 + while i < 10: + # prevent against infinite loops - one never knows + i += 1 + + # watch the contents of file.aux + old_hash = hash + try: + hash = Utils.h_file(aux_node.abspath(env)) + except KeyError: + error('could not read aux.h -> %s' % aux_node.abspath(env)) + pass + + # debug + #print "hash is, ", hash, " ", old_hash + + # stop if file.aux does not change anymore + if hash and hash == old_hash: break + + # run the command + warn('calling %s' % command) + ret = bld.exec_command(latex_compile_cmd) + if ret: + error('error when calling %s %s' % (command, latex_compile_cmd)) + return ret + + # 0 means no error + return 0 + +latex_vardeps = ['LATEX', 'LATEXFLAGS'] +def latex_build(task): + return tex_build(task, 'LATEX') + +pdflatex_vardeps = ['PDFLATEX', 'PDFLATEXFLAGS'] +def pdflatex_build(task): + return tex_build(task, 'PDFLATEX') + +class tex_taskgen(TaskGen.task_gen): + def __init__(self, *k, **kw): + TaskGen.task_gen.__init__(self, *k, **kw) + +@feature('tex') +@before('apply_core') +def apply_tex(self): + if not getattr(self, 'type', None) in ['latex', 'pdflatex']: + self.type = 'pdflatex' + + tree = self.bld + outs = Utils.to_list(getattr(self, 'outs', [])) + + # prompt for incomplete files (else the batchmode is used) + self.env['PROMPT_LATEX'] = getattr(self, 'prompt', 1) + + deps_lst = [] + + if getattr(self, 'deps', None): + deps = self.to_list(self.deps) + for filename in deps: + n = self.path.find_resource(filename) + if not n in deps_lst: deps_lst.append(n) + + self.source = self.to_list(self.source) + for filename in self.source: + base, ext = os.path.splitext(filename) + + node = self.path.find_resource(filename) + if not node: raise Utils.WafError('cannot find %s' % filename) + + if self.type == 'latex': + task = self.create_task('latex') + task.set_inputs(node) + task.set_outputs(node.change_ext('.dvi')) + elif self.type == 'pdflatex': + task = self.create_task('pdflatex') + task.set_inputs(node) + task.set_outputs(node.change_ext('.pdf')) + + task.env = self.env + task.curdirnode = self.path + + # add the manual dependencies + if deps_lst: + variant = node.variant(self.env) + try: + lst = tree.node_deps[task.unique_id()] + for n in deps_lst: + if not n in lst: + lst.append(n) + except KeyError: + tree.node_deps[task.unique_id()] = deps_lst + + if self.type == 'latex': + if 'ps' in outs: + pstask = self.create_task('dvips') + pstask.set_inputs(task.outputs) + pstask.set_outputs(node.change_ext('.ps')) + if 'pdf' in outs: + pdftask = self.create_task('dvipdf') + pdftask.set_inputs(task.outputs) + pdftask.set_outputs(node.change_ext('.pdf')) + elif self.type == 'pdflatex': + if 'ps' in outs: + pstask = self.create_task('pdf2ps') + pstask.set_inputs(task.outputs) + pstask.set_outputs(node.change_ext('.ps')) + self.source = [] + +def detect(conf): + v = conf.env + for p in 'tex latex pdflatex bibtex dvips dvipdf ps2pdf makeindex pdf2ps'.split(): + conf.find_program(p, var=p.upper()) + v[p.upper()+'FLAGS'] = '' + v['DVIPSFLAGS'] = '-Ppdf' + +b = Task.simple_task_type +b('tex', '${TEX} ${TEXFLAGS} ${SRC}', color='BLUE', shell=False) +b('bibtex', '${BIBTEX} ${BIBTEXFLAGS} ${SRC}', color='BLUE', shell=False) +b('dvips', '${DVIPS} ${DVIPSFLAGS} ${SRC} -o ${TGT}', color='BLUE', after="latex pdflatex tex bibtex", shell=False) +b('dvipdf', '${DVIPDF} ${DVIPDFFLAGS} ${SRC} ${TGT}', color='BLUE', after="latex pdflatex tex bibtex", shell=False) +b('pdf2ps', '${PDF2PS} ${PDF2PSFLAGS} ${SRC} ${TGT}', color='BLUE', after="dvipdf pdflatex", shell=False) +b = Task.task_type_from_func +cls = b('latex', latex_build, vars=latex_vardeps) +cls.scan = scan +cls = b('pdflatex', pdflatex_build, vars=pdflatex_vardeps) +cls.scan = scan + diff --git a/tools/wafadmin/Tools/vala.py b/tools/wafadmin/Tools/vala.py new file mode 100644 index 000000000..31f91a42d --- /dev/null +++ b/tools/wafadmin/Tools/vala.py @@ -0,0 +1,273 @@ +#!/usr/bin/env python +# encoding: utf-8 +# Ali Sabil, 2007 + +import os.path, shutil +import Task, Runner, Utils, Logs, Build, Node +from TaskGen import extension, after, before + +EXT_VALA = ['.vala', '.gs'] + +class valac_task(Task.Task): + + vars = ("VALAC", "VALAC_VERSION", "VALAFLAGS") + before = ("cc", "cxx") + + def run(self): + env = self.env + inputs = [a.srcpath(env) for a in self.inputs] + valac = env['VALAC'] + vala_flags = env.get_flat('VALAFLAGS') + top_src = self.generator.bld.srcnode.abspath() + top_bld = self.generator.bld.srcnode.abspath(env) + + if env['VALAC_VERSION'] > (0, 1, 6): + cmd = [valac, '-C', '--quiet', vala_flags] + else: + cmd = [valac, '-C', vala_flags] + + if self.threading: + cmd.append('--thread') + + if self.target_glib: + cmd.append('--target-glib=%s' % self.target_glib) + + features = self.generator.features + + if 'cshlib' in features or 'cstaticlib' in features: + output_dir = self.outputs[0].bld_dir(env) + cmd.append('--library ' + self.target) + if env['VALAC_VERSION'] >= (0, 7, 0): + cmd.append('--header ' + os.path.join(output_dir, self.target + '.h')) + self.outputs.append(self.generator.path.find_or_declare(self.target + '.h')) + cmd.append('--basedir ' + top_src) + cmd.append('-d ' + top_bld) + if env['VALAC_VERSION'] > (0, 7, 2) and hasattr(self, 'gir'): + cmd.append('--gir=%s.gir' % self.gir) + + else: + output_dir = self.outputs[0].bld_dir(env) + cmd.append('-d %s' % output_dir) + + for vapi_dir in self.vapi_dirs: + cmd.append('--vapidir=%s' % vapi_dir) + + for package in self.packages: + cmd.append('--pkg %s' % package) + + for package in self.packages_private: + cmd.append('--pkg %s' % package) + + cmd.append(" ".join(inputs)) + result = self.generator.bld.exec_command(" ".join(cmd)) + + if not 'cprogram' in features: + # generate the .deps file + if self.packages: + filename = os.path.join(self.generator.path.abspath(env), "%s.deps" % self.target) + deps = open(filename, 'w') + for package in self.packages: + deps.write(package + '\n') + deps.close() + + # handle vala 0.1.6 who doesn't honor --directory for the generated .vapi + self._fix_output("../%s.vapi" % self.target) + # handle vala >= 0.1.7 who has a weid definition for --directory + self._fix_output("%s.vapi" % self.target) + # handle vala >= 0.2.0 who doesn't honor --directory for the generated .gidl + self._fix_output("%s.gidl" % self.target) + # handle vala >= 0.3.6 who doesn't honor --directory for the generated .gir + self._fix_output("%s.gir" % self.target) + if hasattr(self, 'gir'): + self._fix_output("%s.gir" % self.gir) + + first = None + for node in self.outputs: + if not first: + first = node + else: + if first.parent.id != node.parent.id: + # issue #483 + if env['VALAC_VERSION'] < (0, 7, 0): + shutil.move(first.parent.abspath(self.env) + os.sep + node.name, node.abspath(self.env)) + return result + + def install(self): + bld = self.generator.bld + features = self.generator.features + + if self.attr("install_path") and ("cshlib" in features or "cstaticlib" in features): + headers_list = [o for o in self.outputs if o.suffix() == ".h"] + vapi_list = [o for o in self.outputs if (o.suffix() in (".vapi", ".deps"))] + gir_list = [o for o in self.outputs if o.suffix() == ".gir"] + + for header in headers_list: + top_src = self.generator.bld.srcnode + package = self.env['PACKAGE'] + try: + api_version = Utils.g_module.API_VERSION + except AttributeError: + version = Utils.g_module.VERSION.split(".") + if version[0] == "0": + api_version = "0." + version[1] + else: + api_version = version[0] + ".0" + install_path = '${INCLUDEDIR}/%s-%s/%s' % (package, api_version, header.relpath_gen(top_src)) + bld.install_as(install_path, header, self.env) + for vapi in vapi_list: + bld.install_files('${DATAROOTDIR}/vala/vapi', vapi, self.env) + for gir in gir_list: + bld.install_files('${DATAROOTDIR}/gir-1.0', gir, self.env) + + def _fix_output(self, output): + top_bld = self.generator.bld.srcnode.abspath(self.env) + try: + src = os.path.join(top_bld, output) + dst = self.generator.path.abspath (self.env) + shutil.move(src, dst) + except: + pass + +@extension(EXT_VALA) +def vala_file(self, node): + valatask = getattr(self, "valatask", None) + # there is only one vala task and it compiles all vala files .. :-/ + if not valatask: + valatask = self.create_task('valac') + self.valatask = valatask + self.includes = Utils.to_list(getattr(self, 'includes', [])) + valatask.packages = [] + valatask.packages_private = Utils.to_list(getattr(self, 'packages_private', [])) + valatask.vapi_dirs = [] + valatask.target = self.target + valatask.threading = False + valatask.install_path = self.install_path + valatask.target_glib = None + + packages = Utils.to_list(getattr(self, 'packages', [])) + vapi_dirs = Utils.to_list(getattr(self, 'vapi_dirs', [])) + includes = [] + + if hasattr(self, 'uselib_local'): + local_packages = Utils.to_list(self.uselib_local) + seen = [] + while len(local_packages) > 0: + package = local_packages.pop() + if package in seen: + continue + seen.append(package) + + # check if the package exists + package_obj = self.name_to_obj(package) + if not package_obj: + raise Utils.WafError("object '%s' was not found in uselib_local (required by '%s')" % (package, self.name)) + + package_name = package_obj.target + package_node = package_obj.path + package_dir = package_node.relpath_gen(self.path) + + for task in package_obj.tasks: + for output in task.outputs: + if output.name == package_name + ".vapi": + valatask.set_run_after(task) + if package_name not in packages: + packages.append(package_name) + if package_dir not in vapi_dirs: + vapi_dirs.append(package_dir) + if package_dir not in includes: + includes.append(package_dir) + + if hasattr(package_obj, 'uselib_local'): + lst = self.to_list(package_obj.uselib_local) + lst.reverse() + local_packages = [pkg for pkg in lst if pkg not in seen] + local_packages + + valatask.packages = packages + for vapi_dir in vapi_dirs: + try: + valatask.vapi_dirs.append(self.path.find_dir(vapi_dir).abspath()) + valatask.vapi_dirs.append(self.path.find_dir(vapi_dir).abspath(self.env)) + except AttributeError: + Logs.warn("Unable to locate Vala API directory: '%s'" % vapi_dir) + + self.includes.append(node.bld.srcnode.abspath()) + self.includes.append(node.bld.srcnode.abspath(self.env)) + for include in includes: + try: + self.includes.append(self.path.find_dir(include).abspath()) + self.includes.append(self.path.find_dir(include).abspath(self.env)) + except AttributeError: + Logs.warn("Unable to locate include directory: '%s'" % include) + + if hasattr(self, 'threading'): + valatask.threading = self.threading + self.uselib = self.to_list(self.uselib) + if not 'GTHREAD' in self.uselib: + self.uselib.append('GTHREAD') + + if hasattr(self, 'target_glib'): + valatask.target_glib = self.target_glib + + if hasattr(self, 'gir'): + valatask.gir = self.gir + + env = valatask.env + + output_nodes = [] + + c_node = node.change_ext('.c') + output_nodes.append(c_node) + self.allnodes.append(c_node) + + if env['VALAC_VERSION'] < (0, 7, 0): + output_nodes.append(node.change_ext('.h')) + else: + if not 'cprogram' in self.features: + output_nodes.append(self.path.find_or_declare('%s.h' % self.target)) + + if not 'cprogram' in self.features: + output_nodes.append(self.path.find_or_declare('%s.vapi' % self.target)) + if env['VALAC_VERSION'] > (0, 7, 2): + if hasattr(self, 'gir'): + output_nodes.append(self.path.find_or_declare('%s.gir' % self.gir)) + elif env['VALAC_VERSION'] > (0, 3, 5): + output_nodes.append(self.path.find_or_declare('%s.gir' % self.target)) + elif env['VALAC_VERSION'] > (0, 1, 7): + output_nodes.append(self.path.find_or_declare('%s.gidl' % self.target)) + if valatask.packages: + output_nodes.append(self.path.find_or_declare('%s.deps' % self.target)) + + valatask.inputs.append(node) + valatask.outputs.extend(output_nodes) + +def detect(conf): + min_version = (0, 1, 6) + min_version_str = "%d.%d.%d" % min_version + + valac = conf.find_program('valac', var='VALAC', mandatory=True) + + if not conf.env["HAVE_GTHREAD"]: + conf.check_cfg(package='gthread-2.0', uselib_store='GTHREAD', args='--cflags --libs') + + try: + output = Utils.cmd_output(valac + " --version", silent=True) + version = output.split(' ', 1)[-1].strip().split(".") + version = [int(x) for x in version] + valac_version = tuple(version) + except Exception: + valac_version = (0, 0, 0) + + conf.check_message('program version', + 'valac >= ' + min_version_str, + valac_version >= min_version, + "%d.%d.%d" % valac_version) + + conf.check_tool('gnu_dirs') + + if valac_version < min_version: + conf.fatal("valac version too old to be used with this tool") + return + + conf.env['VALAC_VERSION'] = valac_version + conf.env['VALAFLAGS'] = '' + diff --git a/tools/wafadmin/Tools/winres.py b/tools/wafadmin/Tools/winres.py new file mode 100644 index 000000000..e0148cb16 --- /dev/null +++ b/tools/wafadmin/Tools/winres.py @@ -0,0 +1,49 @@ +#!/usr/bin/env python +# encoding: utf-8 +# Brant Young, 2007 + +"This hook is called when the class cpp/cc task generator encounters a '.rc' file: X{.rc -> [.res|.rc.o]}" + +import os, sys, re +import TaskGen, Task +from Utils import quote_whitespace +from TaskGen import extension + +EXT_WINRC = ['.rc'] + +winrc_str = '${WINRC} ${_CPPDEFFLAGS} ${_CCDEFFLAGS} ${WINRCFLAGS} ${_CPPINCFLAGS} ${_CCINCFLAGS} ${WINRC_TGT_F} ${TGT} ${WINRC_SRC_F} ${SRC}' + +@extension(EXT_WINRC) +def rc_file(self, node): + obj_ext = '.rc.o' + if self.env['WINRC_TGT_F'] == '/fo': obj_ext = '.res' + + rctask = self.create_task('winrc') + rctask.set_inputs(node) + rctask.set_outputs(node.change_ext(obj_ext)) + + # make linker can find compiled resource files + self.compiled_tasks.append(rctask) + +# create our action, for use with rc file +Task.simple_task_type('winrc', winrc_str, color='BLUE', before='cc cxx', shell=False) + +def detect(conf): + v = conf.env + + winrc = v['WINRC'] + v['WINRC_TGT_F'] = '-o' + v['WINRC_SRC_F'] = '-i' + # find rc.exe + if not winrc: + if v['CC_NAME'] in ['gcc', 'cc', 'g++', 'c++']: + winrc = conf.find_program('windres', var='WINRC', path_list = v['PATH']) + elif v['CC_NAME'] == 'msvc': + winrc = conf.find_program('RC', var='WINRC', path_list = v['PATH']) + v['WINRC_TGT_F'] = '/fo' + v['WINRC_SRC_F'] = '' + if not winrc: + conf.fatal('winrc was not found!') + + v['WINRCFLAGS'] = '' + diff --git a/tools/wafadmin/Utils.py b/tools/wafadmin/Utils.py new file mode 100644 index 000000000..2223b834f --- /dev/null +++ b/tools/wafadmin/Utils.py @@ -0,0 +1,652 @@ +#!/usr/bin/env python +# encoding: utf-8 +# Thomas Nagy, 2005 (ita) + +""" +Utilities, the stable ones are the following: + +* h_file: compute a unique value for a file (hash), it uses + the module fnv if it is installed (see waf/utils/fnv & http://code.google.com/p/waf/wiki/FAQ) + else, md5 (see the python docs) + + For large projects (projects with more than 15000 files) or slow hard disks and filesystems (HFS) + it is possible to use a hashing based on the path and the size (may give broken cache results) + The method h_file MUST raise an OSError if the file is a folder + + import stat + def h_file(filename): + st = os.stat(filename) + if stat.S_ISDIR(st[stat.ST_MODE]): raise IOError('not a file') + m = Utils.md5() + m.update(str(st.st_mtime)) + m.update(str(st.st_size)) + m.update(filename) + return m.digest() + + To replace the function in your project, use something like this: + import Utils + Utils.h_file = h_file + +* h_list +* h_fun +* get_term_cols +* ordered_dict + +""" + +import os, sys, imp, string, errno, traceback, inspect, re, shutil, datetime, gc + +# In python 3.0 we can get rid of all this +try: from UserDict import UserDict +except ImportError: from collections import UserDict +if sys.hexversion >= 0x2060000 or os.name == 'java': + import subprocess as pproc +else: + import pproc +import Logs +from Constants import * + +is_win32 = sys.platform == 'win32' + +try: + # defaultdict in python 2.5 + from collections import defaultdict as DefaultDict +except ImportError: + class DefaultDict(dict): + def __init__(self, default_factory): + super(DefaultDict, self).__init__() + self.default_factory = default_factory + def __getitem__(self, key): + try: + return super(DefaultDict, self).__getitem__(key) + except KeyError: + value = self.default_factory() + self[key] = value + return value + +class WafError(Exception): + def __init__(self, *args): + self.args = args + self.stack = traceback.extract_stack() + Exception.__init__(self, *args) + def __str__(self): + return str(len(self.args) == 1 and self.args[0] or self.args) + +class WscriptError(WafError): + def __init__(self, message, wscript_file=None): + if wscript_file: + self.wscript_file = wscript_file + self.wscript_line = None + else: + (self.wscript_file, self.wscript_line) = self.locate_error() + + msg_file_line = '' + if self.wscript_file: + msg_file_line = "%s:" % self.wscript_file + if self.wscript_line: + msg_file_line += "%s:" % self.wscript_line + err_message = "%s error: %s" % (msg_file_line, message) + WafError.__init__(self, err_message) + + def locate_error(self): + stack = traceback.extract_stack() + stack.reverse() + for frame in stack: + file_name = os.path.basename(frame[0]) + is_wscript = (file_name == WSCRIPT_FILE or file_name == WSCRIPT_BUILD_FILE) + if is_wscript: + return (frame[0], frame[1]) + return (None, None) + +indicator = is_win32 and '\x1b[A\x1b[K%s%s%s\r' or '\x1b[K%s%s%s\r' + +try: + from fnv import new as md5 + import Constants + Constants.SIG_NIL = 'signofnv' + + def h_file(filename): + m = md5() + try: + m.hfile(filename) + x = m.digest() + if x is None: raise OSError("not a file") + return x + except SystemError: + raise OSError("not a file" + filename) + +except ImportError: + try: + from hashlib import md5 + except ImportError: + from md5 import md5 + + def h_file(filename): + f = open(filename, 'rb') + m = md5() + readBytes = 100000 + while (filename): + filename = f.read(100000) + m.update(filename) + f.close() + return m.digest() + +class ordered_dict(UserDict): + def __init__(self, dict = None): + self.allkeys = [] + UserDict.__init__(self, dict) + + def __delitem__(self, key): + self.allkeys.remove(key) + UserDict.__delitem__(self, key) + + def __setitem__(self, key, item): + if key not in self.allkeys: self.allkeys.append(key) + UserDict.__setitem__(self, key, item) + +def exec_command(s, **kw): + if 'log' in kw: + kw['stdout'] = kw['stderr'] = kw['log'] + del(kw['log']) + kw['shell'] = isinstance(s, str) + + try: + proc = pproc.Popen(s, **kw) + return proc.wait() + except OSError: + return -1 + +if is_win32: + def exec_command(s, **kw): + if 'log' in kw: + kw['stdout'] = kw['stderr'] = kw['log'] + del(kw['log']) + kw['shell'] = isinstance(s, str) + + if len(s) > 2000: + startupinfo = pproc.STARTUPINFO() + startupinfo.dwFlags |= pproc.STARTF_USESHOWWINDOW + kw['startupinfo'] = startupinfo + + try: + if 'stdout' not in kw: + kw['stdout'] = pproc.PIPE + kw['stderr'] = pproc.STDOUT + proc = pproc.Popen(s,**kw) + (stdout, _) = proc.communicate() + Logs.info(stdout) + else: + proc = pproc.Popen(s,**kw) + return proc.wait() + except OSError: + return -1 + +listdir = os.listdir +if is_win32: + def listdir_win32(s): + if re.match('^[A-Za-z]:$', s): + # os.path.isdir fails if s contains only the drive name... (x:) + s += os.sep + if not os.path.isdir(s): + e = OSError() + e.errno = errno.ENOENT + raise e + return os.listdir(s) + listdir = listdir_win32 + +def waf_version(mini = 0x010000, maxi = 0x100000): + "Halts if the waf version is wrong" + ver = HEXVERSION + try: min_val = mini + 0 + except TypeError: min_val = int(mini.replace('.', '0'), 16) + + if min_val > ver: + Logs.error("waf version should be at least %s (%s found)" % (mini, ver)) + sys.exit(0) + + try: max_val = maxi + 0 + except TypeError: max_val = int(maxi.replace('.', '0'), 16) + + if max_val < ver: + Logs.error("waf version should be at most %s (%s found)" % (maxi, ver)) + sys.exit(0) + +def python_24_guard(): + if sys.hexversion<0x20400f0: + raise ImportError("Waf requires Python >= 2.3 but the raw source requires Python 2.4") + +def ex_stack(): + exc_type, exc_value, tb = sys.exc_info() + exc_lines = traceback.format_exception(exc_type, exc_value, tb) + return ''.join(exc_lines) + +def to_list(sth): + if isinstance(sth, str): + return sth.split() + else: + return sth + +g_loaded_modules = {} +"index modules by absolute path" + +g_module=None +"the main module is special" + +def load_module(file_path, name=WSCRIPT_FILE): + "this function requires an absolute path" + try: + return g_loaded_modules[file_path] + except KeyError: + pass + + module = imp.new_module(name) + + try: + code = readf(file_path, m='rU') + except (IOError, OSError): + raise WscriptError('Could not read the file %r' % file_path) + + module.waf_hash_val = code + + module_dir = os.path.dirname(file_path) + sys.path.insert(0, module_dir) + try: + exec(code, module.__dict__) + except Exception: + raise WscriptError(traceback.format_exc(), file_path) + sys.path.remove(module_dir) + + g_loaded_modules[file_path] = module + + return module + +def set_main_module(file_path): + "Load custom options, if defined" + global g_module + g_module = load_module(file_path, 'wscript_main') + g_module.root_path = file_path + + # note: to register the module globally, use the following: + # sys.modules['wscript_main'] = g_module + +def to_hashtable(s): + "used for importing env files" + tbl = {} + lst = s.split('\n') + for line in lst: + if not line: continue + mems = line.split('=') + tbl[mems[0]] = mems[1] + return tbl + +def get_term_cols(): + "console width" + return 80 +try: + import struct, fcntl, termios +except ImportError: + pass +else: + if Logs.got_tty: + def myfun(): + dummy_lines, cols = struct.unpack("HHHH", \ + fcntl.ioctl(sys.stderr.fileno(),termios.TIOCGWINSZ , \ + struct.pack("HHHH", 0, 0, 0, 0)))[:2] + return cols + # we actually try the function once to see if it is suitable + try: + myfun() + except IOError: + pass + else: + get_term_cols = myfun + +rot_idx = 0 +rot_chr = ['\\', '|', '/', '-'] +"the rotation character in the progress bar" + + +def split_path(path): + return path.split('/') + +def split_path_cygwin(path): + if path.startswith('//'): + ret = path.split('/')[2:] + ret[0] = '/' + ret[0] + return ret + return path.split('/') + +re_sp = re.compile('[/\\\\]') +def split_path_win32(path): + if path.startswith('\\\\'): + ret = re.split(re_sp, path)[2:] + ret[0] = '\\' + ret[0] + return ret + return re.split(re_sp, path) + +if sys.platform == 'cygwin': + split_path = split_path_cygwin +elif is_win32: + split_path = split_path_win32 + +def copy_attrs(orig, dest, names, only_if_set=False): + for a in to_list(names): + u = getattr(orig, a, ()) + if u or not only_if_set: + setattr(dest, a, u) + +def def_attrs(cls, **kw): + ''' + set attributes for class. + @param cls [any class]: the class to update the given attributes in. + @param kw [dictionary]: dictionary of attributes names and values. + + if the given class hasn't one (or more) of these attributes, add the attribute with its value to the class. + ''' + for k, v in kw.iteritems(): + if not hasattr(cls, k): + setattr(cls, k, v) + +quote_define_name_table = None +def quote_define_name(path): + "Converts a string to a constant name, foo/zbr-xpto.h -> FOO_ZBR_XPTO_H" + global quote_define_name_table + if not quote_define_name_table: + invalid_chars = set([chr(x) for x in xrange(256)]) - set(string.digits + string.uppercase) + quote_define_name_table = string.maketrans(''.join(invalid_chars), '_'*len(invalid_chars)) + return string.translate(string.upper(path), quote_define_name_table) + +def quote_whitespace(path): + return (path.strip().find(' ') > 0 and '"%s"' % path or path).replace('""', '"') + +def trimquotes(s): + if not s: return '' + s = s.rstrip() + if s[0] == "'" and s[-1] == "'": return s[1:-1] + return s + +def h_list(lst): + m = md5() + m.update(str(lst)) + return m.digest() + +def h_fun(fun): + try: + return fun.code + except AttributeError: + try: + h = inspect.getsource(fun) + except IOError: + h = "nocode" + try: + fun.code = h + except AttributeError: + pass + return h + +def pprint(col, str, label='', sep=os.linesep): + "print messages in color" + sys.stderr.write("%s%s%s %s%s" % (Logs.colors(col), str, Logs.colors.NORMAL, label, sep)) + +def check_dir(dir): + """If a folder doesn't exists, create it.""" + try: + os.stat(dir) + except OSError: + try: + os.makedirs(dir) + except OSError, e: + raise WafError("Cannot create folder '%s' (original error: %s)" % (dir, e)) + +def cmd_output(cmd, **kw): + + silent = False + if 'silent' in kw: + silent = kw['silent'] + del(kw['silent']) + + if 'e' in kw: + tmp = kw['e'] + del(kw['e']) + kw['env'] = tmp + + kw['shell'] = isinstance(cmd, str) + kw['stdout'] = pproc.PIPE + if silent: + kw['stderr'] = pproc.PIPE + + try: + p = pproc.Popen(cmd, **kw) + output = p.communicate()[0] + except OSError, e: + raise ValueError(str(e)) + + if p.returncode: + if not silent: + msg = "command execution failed: %s -> %r" % (cmd, str(output)) + raise ValueError(msg) + output = '' + return output + +reg_subst = re.compile(r"(\\\\)|(\$\$)|\$\{([^}]+)\}") +def subst_vars(expr, params): + "substitute ${PREFIX}/bin in /usr/local/bin" + def repl_var(m): + if m.group(1): + return '\\' + if m.group(2): + return '$' + try: + # environments may contain lists + return params.get_flat(m.group(3)) + except AttributeError: + return params[m.group(3)] + return reg_subst.sub(repl_var, expr) + +def unversioned_sys_platform_to_binary_format(unversioned_sys_platform): + "infers the binary format from the unversioned_sys_platform name." + + if unversioned_sys_platform in ('linux', 'freebsd', 'netbsd', 'openbsd', 'sunos'): + return 'elf' + elif unversioned_sys_platform == 'darwin': + return 'mac-o' + elif unversioned_sys_platform in ('win32', 'cygwin', 'uwin', 'msys'): + return 'pe' + # TODO we assume all other operating systems are elf, which is not true. + # we may set this to 'unknown' and have ccroot and other tools handle the case "gracefully" (whatever that means). + return 'elf' + +def unversioned_sys_platform(): + """returns an unversioned name from sys.platform. + sys.plaform is not very well defined and depends directly on the python source tree. + The version appended to the names is unreliable as it's taken from the build environment at the time python was built, + i.e., it's possible to get freebsd7 on a freebsd8 system. + So we remove the version from the name, except for special cases where the os has a stupid name like os2 or win32. + Some possible values of sys.platform are, amongst others: + aix3 aix4 atheos beos5 darwin freebsd2 freebsd3 freebsd4 freebsd5 freebsd6 freebsd7 + generic irix5 irix6 linux2 mac netbsd1 next3 os2emx riscos sunos5 unixware7 + Investigating the python source tree may reveal more values. + """ + s = sys.platform + if s == 'java': + # The real OS is hidden under the JVM. + from java.lang import System + s = System.getProperty('os.name') + # see http://lopica.sourceforge.net/os.html for a list of possible values + if s == 'Mac OS X': + return 'darwin' + elif s.startswith('Windows '): + return 'win32' + elif s == 'OS/2': + return 'os2' + elif s == 'HP-UX': + return 'hpux' + elif s in ('SunOS', 'Solaris'): + return 'sunos' + else: s = s.lower() + if s == 'win32' or s.endswith('os2') and s != 'sunos2': return s + return re.split('\d+$', s)[0] + +#@deprecated('use unversioned_sys_platform instead') +def detect_platform(): + """this function has been in the Utils module for some time. + It's hard to guess what people have used it for. + It seems its goal is to return an unversionned sys.platform, but it's not handling all platforms. + For example, the version is not removed on freebsd and netbsd, amongst others. + """ + s = sys.platform + + # known POSIX + for x in 'cygwin linux irix sunos hpux aix darwin'.split(): + # sys.platform may be linux2 + if s.find(x) >= 0: + return x + + # unknown POSIX + if os.name in 'posix java os2'.split(): + return os.name + + return s + +def load_tool(tool, tooldir=None): + if tooldir: + assert isinstance(tooldir, list) + sys.path = tooldir + sys.path + try: + try: + return __import__(tool) + except ImportError, e: + raise WscriptError('Could not load the tool %r in %r' % (tool, sys.path)) + finally: + if tooldir: + for d in tooldir: + sys.path.remove(d) + +def readf(fname, m='r'): + "get the contents of a file, it is not used anywhere for the moment" + f = None + try: + f = open(fname, m) + txt = f.read() + finally: + if f: f.close() + return txt + +def nada(*k, **kw): + """A function that does nothing""" + pass + +def diff_path(top, subdir): + """difference between two absolute paths""" + top = os.path.normpath(top).replace('\\', '/').split('/') + subdir = os.path.normpath(subdir).replace('\\', '/').split('/') + if len(top) == len(subdir): return '' + diff = subdir[len(top) - len(subdir):] + return os.path.join(*diff) + +class Context(object): + """A base class for commands to be executed from Waf scripts""" + + def set_curdir(self, dir): + self.curdir_ = dir + + def get_curdir(self): + try: + return self.curdir_ + except AttributeError: + self.curdir_ = os.getcwd() + return self.get_curdir() + + curdir = property(get_curdir, set_curdir) + + def recurse(self, dirs, name=''): + """The function for calling scripts from folders, it tries to call wscript + function_name + and if that file does not exist, it will call the method 'function_name' from a file named wscript + the dirs can be a list of folders or a string containing space-separated folder paths + """ + if not name: + name = inspect.stack()[1][3] + + if isinstance(dirs, str): + dirs = to_list(dirs) + + for x in dirs: + if os.path.isabs(x): + nexdir = x + else: + nexdir = os.path.join(self.curdir, x) + + base = os.path.join(nexdir, WSCRIPT_FILE) + + try: + txt = readf(base + '_' + name, m='rU') + except (OSError, IOError): + try: + module = load_module(base) + except OSError: + raise WscriptError('No such script %s' % base) + + try: + f = module.__dict__[name] + except KeyError: + raise WscriptError('No function %s defined in %s' % (name, base)) + + if getattr(self.__class__, 'pre_recurse', None): + self.pre_recurse(f, base, nexdir) + old = self.curdir + self.curdir = nexdir + try: + f(self) + finally: + self.curdir = old + if getattr(self.__class__, 'post_recurse', None): + self.post_recurse(module, base, nexdir) + else: + dc = {'ctx': self} + if getattr(self.__class__, 'pre_recurse', None): + dc = self.pre_recurse(txt, base + '_' + name, nexdir) + old = self.curdir + self.curdir = nexdir + try: + try: + exec(txt, dc) + except Exception: + raise WscriptError(traceback.format_exc(), base) + finally: + self.curdir = old + if getattr(self.__class__, 'post_recurse', None): + self.post_recurse(txt, base + '_' + name, nexdir) + +if is_win32: + old = shutil.copy2 + def copy2(src, dst): + old(src, dst) + shutil.copystat(src, src) + setattr(shutil, 'copy2', copy2) + +def get_elapsed_time(start): + "Format a time delta (datetime.timedelta) using the format DdHhMmS.MSs" + delta = datetime.datetime.now() - start + # cast to int necessary for python 3.0 + days = int(delta.days) + hours = int(delta.seconds / 3600) + minutes = int((delta.seconds - hours * 3600) / 60) + seconds = delta.seconds - hours * 3600 - minutes * 60 \ + + float(delta.microseconds) / 1000 / 1000 + result = '' + if days: + result += '%dd' % days + if days or hours: + result += '%dh' % hours + if days or hours or minutes: + result += '%dm' % minutes + return '%s%.3fs' % (result, seconds) + +if os.name == 'java': + # For Jython (they should really fix the inconsistency) + try: + gc.disable() + gc.enable() + except NotImplementedError: + gc.disable = gc.enable + diff --git a/tools/wafadmin/__init__.py b/tools/wafadmin/__init__.py new file mode 100644 index 000000000..01273cfd0 --- /dev/null +++ b/tools/wafadmin/__init__.py @@ -0,0 +1,3 @@ +#!/usr/bin/env python +# encoding: utf-8 +# Thomas Nagy, 2005 (ita) diff --git a/tools/wafadmin/pproc.py b/tools/wafadmin/pproc.py new file mode 100644 index 000000000..cb15178b4 --- /dev/null +++ b/tools/wafadmin/pproc.py @@ -0,0 +1,620 @@ +# borrowed from python 2.5.2c1 +# Copyright (c) 2003-2005 by Peter Astrand <astrand@lysator.liu.se> +# Licensed to PSF under a Contributor Agreement. + +import sys +mswindows = (sys.platform == "win32") + +import os +import types +import traceback +import gc + +class CalledProcessError(Exception): + def __init__(self, returncode, cmd): + self.returncode = returncode + self.cmd = cmd + def __str__(self): + return "Command '%s' returned non-zero exit status %d" % (self.cmd, self.returncode) + +if mswindows: + import threading + import msvcrt + if 0: + import pywintypes + from win32api import GetStdHandle, STD_INPUT_HANDLE, \ + STD_OUTPUT_HANDLE, STD_ERROR_HANDLE + from win32api import GetCurrentProcess, DuplicateHandle, \ + GetModuleFileName, GetVersion + from win32con import DUPLICATE_SAME_ACCESS, SW_HIDE + from win32pipe import CreatePipe + from win32process import CreateProcess, STARTUPINFO, \ + GetExitCodeProcess, STARTF_USESTDHANDLES, \ + STARTF_USESHOWWINDOW, CREATE_NEW_CONSOLE + from win32event import WaitForSingleObject, INFINITE, WAIT_OBJECT_0 + else: + from _subprocess import * + class STARTUPINFO: + dwFlags = 0 + hStdInput = None + hStdOutput = None + hStdError = None + wShowWindow = 0 + class pywintypes: + error = IOError +else: + import select + import errno + import fcntl + import pickle + +__all__ = ["Popen", "PIPE", "STDOUT", "call", "check_call", "CalledProcessError"] + +try: + MAXFD = os.sysconf("SC_OPEN_MAX") +except: + MAXFD = 256 + +try: + False +except NameError: + False = 0 + True = 1 + +_active = [] + +def _cleanup(): + for inst in _active[:]: + if inst.poll(_deadstate=sys.maxint) >= 0: + try: + _active.remove(inst) + except ValueError: + pass + +PIPE = -1 +STDOUT = -2 + + +def call(*popenargs, **kwargs): + return Popen(*popenargs, **kwargs).wait() + +def check_call(*popenargs, **kwargs): + retcode = call(*popenargs, **kwargs) + cmd = kwargs.get("args") + if cmd is None: + cmd = popenargs[0] + if retcode: + raise CalledProcessError(retcode, cmd) + return retcode + + +def list2cmdline(seq): + result = [] + needquote = False + for arg in seq: + bs_buf = [] + + if result: + result.append(' ') + + needquote = (" " in arg) or ("\t" in arg) or arg == "" + if needquote: + result.append('"') + + for c in arg: + if c == '\\': + bs_buf.append(c) + elif c == '"': + result.append('\\' * len(bs_buf)*2) + bs_buf = [] + result.append('\\"') + else: + if bs_buf: + result.extend(bs_buf) + bs_buf = [] + result.append(c) + + if bs_buf: + result.extend(bs_buf) + + if needquote: + result.extend(bs_buf) + result.append('"') + + return ''.join(result) + +class Popen(object): + def __init__(self, args, bufsize=0, executable=None, + stdin=None, stdout=None, stderr=None, + preexec_fn=None, close_fds=False, shell=False, + cwd=None, env=None, universal_newlines=False, + startupinfo=None, creationflags=0): + _cleanup() + + self._child_created = False + if not isinstance(bufsize, (int, long)): + raise TypeError("bufsize must be an integer") + + if mswindows: + if preexec_fn is not None: + raise ValueError("preexec_fn is not supported on Windows platforms") + if close_fds: + raise ValueError("close_fds is not supported on Windows platforms") + else: + if startupinfo is not None: + raise ValueError("startupinfo is only supported on Windows platforms") + if creationflags != 0: + raise ValueError("creationflags is only supported on Windows platforms") + + self.stdin = None + self.stdout = None + self.stderr = None + self.pid = None + self.returncode = None + self.universal_newlines = universal_newlines + + (p2cread, p2cwrite, + c2pread, c2pwrite, + errread, errwrite) = self._get_handles(stdin, stdout, stderr) + + self._execute_child(args, executable, preexec_fn, close_fds, + cwd, env, universal_newlines, + startupinfo, creationflags, shell, + p2cread, p2cwrite, + c2pread, c2pwrite, + errread, errwrite) + + if mswindows: + if stdin is None and p2cwrite is not None: + os.close(p2cwrite) + p2cwrite = None + if stdout is None and c2pread is not None: + os.close(c2pread) + c2pread = None + if stderr is None and errread is not None: + os.close(errread) + errread = None + + if p2cwrite: + self.stdin = os.fdopen(p2cwrite, 'wb', bufsize) + if c2pread: + if universal_newlines: + self.stdout = os.fdopen(c2pread, 'rU', bufsize) + else: + self.stdout = os.fdopen(c2pread, 'rb', bufsize) + if errread: + if universal_newlines: + self.stderr = os.fdopen(errread, 'rU', bufsize) + else: + self.stderr = os.fdopen(errread, 'rb', bufsize) + + + def _translate_newlines(self, data): + data = data.replace("\r\n", "\n") + data = data.replace("\r", "\n") + return data + + + def __del__(self, sys=sys): + if not self._child_created: + return + self.poll(_deadstate=sys.maxint) + if self.returncode is None and _active is not None: + _active.append(self) + + + def communicate(self, input=None): + if [self.stdin, self.stdout, self.stderr].count(None) >= 2: + stdout = None + stderr = None + if self.stdin: + if input: + self.stdin.write(input) + self.stdin.close() + elif self.stdout: + stdout = self.stdout.read() + elif self.stderr: + stderr = self.stderr.read() + self.wait() + return (stdout, stderr) + + return self._communicate(input) + + + if mswindows: + def _get_handles(self, stdin, stdout, stderr): + if stdin is None and stdout is None and stderr is None: + return (None, None, None, None, None, None) + + p2cread, p2cwrite = None, None + c2pread, c2pwrite = None, None + errread, errwrite = None, None + + if stdin is None: + p2cread = GetStdHandle(STD_INPUT_HANDLE) + if p2cread is not None: + pass + elif stdin is None or stdin == PIPE: + p2cread, p2cwrite = CreatePipe(None, 0) + p2cwrite = p2cwrite.Detach() + p2cwrite = msvcrt.open_osfhandle(p2cwrite, 0) + elif isinstance(stdin, int): + p2cread = msvcrt.get_osfhandle(stdin) + else: + p2cread = msvcrt.get_osfhandle(stdin.fileno()) + p2cread = self._make_inheritable(p2cread) + + if stdout is None: + c2pwrite = GetStdHandle(STD_OUTPUT_HANDLE) + if c2pwrite is not None: + pass + elif stdout is None or stdout == PIPE: + c2pread, c2pwrite = CreatePipe(None, 0) + c2pread = c2pread.Detach() + c2pread = msvcrt.open_osfhandle(c2pread, 0) + elif isinstance(stdout, int): + c2pwrite = msvcrt.get_osfhandle(stdout) + else: + c2pwrite = msvcrt.get_osfhandle(stdout.fileno()) + c2pwrite = self._make_inheritable(c2pwrite) + + if stderr is None: + errwrite = GetStdHandle(STD_ERROR_HANDLE) + if errwrite is not None: + pass + elif stderr is None or stderr == PIPE: + errread, errwrite = CreatePipe(None, 0) + errread = errread.Detach() + errread = msvcrt.open_osfhandle(errread, 0) + elif stderr == STDOUT: + errwrite = c2pwrite + elif isinstance(stderr, int): + errwrite = msvcrt.get_osfhandle(stderr) + else: + errwrite = msvcrt.get_osfhandle(stderr.fileno()) + errwrite = self._make_inheritable(errwrite) + + return (p2cread, p2cwrite, + c2pread, c2pwrite, + errread, errwrite) + def _make_inheritable(self, handle): + return DuplicateHandle(GetCurrentProcess(), handle, GetCurrentProcess(), 0, 1, DUPLICATE_SAME_ACCESS) + + def _find_w9xpopen(self): + w9xpopen = os.path.join(os.path.dirname(GetModuleFileName(0)), "w9xpopen.exe") + if not os.path.exists(w9xpopen): + w9xpopen = os.path.join(os.path.dirname(sys.exec_prefix), "w9xpopen.exe") + if not os.path.exists(w9xpopen): + raise RuntimeError("Cannot locate w9xpopen.exe, which is needed for Popen to work with your shell or platform.") + return w9xpopen + + def _execute_child(self, args, executable, preexec_fn, close_fds, + cwd, env, universal_newlines, + startupinfo, creationflags, shell, + p2cread, p2cwrite, + c2pread, c2pwrite, + errread, errwrite): + + if not isinstance(args, types.StringTypes): + args = list2cmdline(args) + + if startupinfo is None: + startupinfo = STARTUPINFO() + if None not in (p2cread, c2pwrite, errwrite): + startupinfo.dwFlags |= STARTF_USESTDHANDLES + startupinfo.hStdInput = p2cread + startupinfo.hStdOutput = c2pwrite + startupinfo.hStdError = errwrite + + if shell: + startupinfo.dwFlags |= STARTF_USESHOWWINDOW + startupinfo.wShowWindow = SW_HIDE + comspec = os.environ.get("COMSPEC", "cmd.exe") + args = comspec + " /c " + args + if (GetVersion() >= 0x80000000L or + os.path.basename(comspec).lower() == "command.com"): + w9xpopen = self._find_w9xpopen() + args = '"%s" %s' % (w9xpopen, args) + creationflags |= CREATE_NEW_CONSOLE + + try: + hp, ht, pid, tid = CreateProcess(executable, args, None, None, 1, creationflags, env, cwd, startupinfo) + except pywintypes.error, e: + raise WindowsError(*e.args) + + self._child_created = True + self._handle = hp + self.pid = pid + ht.Close() + + if p2cread is not None: + p2cread.Close() + if c2pwrite is not None: + c2pwrite.Close() + if errwrite is not None: + errwrite.Close() + + + def poll(self, _deadstate=None): + if self.returncode is None: + if WaitForSingleObject(self._handle, 0) == WAIT_OBJECT_0: + self.returncode = GetExitCodeProcess(self._handle) + return self.returncode + + + def wait(self): + if self.returncode is None: + obj = WaitForSingleObject(self._handle, INFINITE) + self.returncode = GetExitCodeProcess(self._handle) + return self.returncode + + def _readerthread(self, fh, buffer): + buffer.append(fh.read()) + + def _communicate(self, input): + stdout = None + stderr = None + + if self.stdout: + stdout = [] + stdout_thread = threading.Thread(target=self._readerthread, args=(self.stdout, stdout)) + stdout_thread.setDaemon(True) + stdout_thread.start() + if self.stderr: + stderr = [] + stderr_thread = threading.Thread(target=self._readerthread, args=(self.stderr, stderr)) + stderr_thread.setDaemon(True) + stderr_thread.start() + + if self.stdin: + if input is not None: + self.stdin.write(input) + self.stdin.close() + + if self.stdout: + stdout_thread.join() + if self.stderr: + stderr_thread.join() + + if stdout is not None: + stdout = stdout[0] + if stderr is not None: + stderr = stderr[0] + + if self.universal_newlines and hasattr(file, 'newlines'): + if stdout: + stdout = self._translate_newlines(stdout) + if stderr: + stderr = self._translate_newlines(stderr) + + self.wait() + return (stdout, stderr) + + else: + def _get_handles(self, stdin, stdout, stderr): + p2cread, p2cwrite = None, None + c2pread, c2pwrite = None, None + errread, errwrite = None, None + + if stdin is None: + pass + elif stdin == PIPE: + p2cread, p2cwrite = os.pipe() + elif isinstance(stdin, int): + p2cread = stdin + else: + p2cread = stdin.fileno() + + if stdout is None: + pass + elif stdout == PIPE: + c2pread, c2pwrite = os.pipe() + elif isinstance(stdout, int): + c2pwrite = stdout + else: + c2pwrite = stdout.fileno() + + if stderr is None: + pass + elif stderr == PIPE: + errread, errwrite = os.pipe() + elif stderr == STDOUT: + errwrite = c2pwrite + elif isinstance(stderr, int): + errwrite = stderr + else: + errwrite = stderr.fileno() + + return (p2cread, p2cwrite, c2pread, c2pwrite, errread, errwrite) + + def _set_cloexec_flag(self, fd): + try: + cloexec_flag = fcntl.FD_CLOEXEC + except AttributeError: + cloexec_flag = 1 + + old = fcntl.fcntl(fd, fcntl.F_GETFD) + fcntl.fcntl(fd, fcntl.F_SETFD, old | cloexec_flag) + + def _close_fds(self, but): + for i in xrange(3, MAXFD): + if i == but: + continue + try: + os.close(i) + except: + pass + + def _execute_child(self, args, executable, preexec_fn, close_fds, + cwd, env, universal_newlines, startupinfo, creationflags, shell, + p2cread, p2cwrite, c2pread, c2pwrite, errread, errwrite): + + if isinstance(args, types.StringTypes): + args = [args] + else: + args = list(args) + + if shell: + args = ["/bin/sh", "-c"] + args + + if executable is None: + executable = args[0] + + errpipe_read, errpipe_write = os.pipe() + self._set_cloexec_flag(errpipe_write) + + gc_was_enabled = gc.isenabled() + gc.disable() + try: + self.pid = os.fork() + except: + if gc_was_enabled: + gc.enable() + raise + self._child_created = True + if self.pid == 0: + try: + if p2cwrite: + os.close(p2cwrite) + if c2pread: + os.close(c2pread) + if errread: + os.close(errread) + os.close(errpipe_read) + + if p2cread: + os.dup2(p2cread, 0) + if c2pwrite: + os.dup2(c2pwrite, 1) + if errwrite: + os.dup2(errwrite, 2) + + if p2cread and p2cread not in (0,): + os.close(p2cread) + if c2pwrite and c2pwrite not in (p2cread, 1): + os.close(c2pwrite) + if errwrite and errwrite not in (p2cread, c2pwrite, 2): + os.close(errwrite) + + if close_fds: + self._close_fds(but=errpipe_write) + + if cwd is not None: + os.chdir(cwd) + + if preexec_fn: + apply(preexec_fn) + + if env is None: + os.execvp(executable, args) + else: + os.execvpe(executable, args, env) + + except: + exc_type, exc_value, tb = sys.exc_info() + exc_lines = traceback.format_exception(exc_type, exc_value, tb) + exc_value.child_traceback = ''.join(exc_lines) + os.write(errpipe_write, pickle.dumps(exc_value)) + + os._exit(255) + + if gc_was_enabled: + gc.enable() + os.close(errpipe_write) + if p2cread and p2cwrite: + os.close(p2cread) + if c2pwrite and c2pread: + os.close(c2pwrite) + if errwrite and errread: + os.close(errwrite) + + data = os.read(errpipe_read, 1048576) + os.close(errpipe_read) + if data != "": + os.waitpid(self.pid, 0) + child_exception = pickle.loads(data) + raise child_exception + + def _handle_exitstatus(self, sts): + if os.WIFSIGNALED(sts): + self.returncode = -os.WTERMSIG(sts) + elif os.WIFEXITED(sts): + self.returncode = os.WEXITSTATUS(sts) + else: + raise RuntimeError("Unknown child exit status!") + + def poll(self, _deadstate=None): + if self.returncode is None: + try: + pid, sts = os.waitpid(self.pid, os.WNOHANG) + if pid == self.pid: + self._handle_exitstatus(sts) + except os.error: + if _deadstate is not None: + self.returncode = _deadstate + return self.returncode + + def wait(self): + if self.returncode is None: + pid, sts = os.waitpid(self.pid, 0) + self._handle_exitstatus(sts) + return self.returncode + + def _communicate(self, input): + read_set = [] + write_set = [] + stdout = None + stderr = None + + if self.stdin: + self.stdin.flush() + if input: + write_set.append(self.stdin) + else: + self.stdin.close() + if self.stdout: + read_set.append(self.stdout) + stdout = [] + if self.stderr: + read_set.append(self.stderr) + stderr = [] + + input_offset = 0 + while read_set or write_set: + rlist, wlist, xlist = select.select(read_set, write_set, []) + + if self.stdin in wlist: + bytes_written = os.write(self.stdin.fileno(), buffer(input, input_offset, 512)) + input_offset += bytes_written + if input_offset >= len(input): + self.stdin.close() + write_set.remove(self.stdin) + + if self.stdout in rlist: + data = os.read(self.stdout.fileno(), 1024) + if data == "": + self.stdout.close() + read_set.remove(self.stdout) + stdout.append(data) + + if self.stderr in rlist: + data = os.read(self.stderr.fileno(), 1024) + if data == "": + self.stderr.close() + read_set.remove(self.stderr) + stderr.append(data) + + if stdout is not None: + stdout = ''.join(stdout) + if stderr is not None: + stderr = ''.join(stderr) + + if self.universal_newlines and hasattr(file, 'newlines'): + if stdout: + stdout = self._translate_newlines(stdout) + if stderr: + stderr = self._translate_newlines(stderr) + + self.wait() + return (stdout, stderr) + diff --git a/tools/wafadmin/py3kfixes.py b/tools/wafadmin/py3kfixes.py new file mode 100644 index 000000000..252dd994b --- /dev/null +++ b/tools/wafadmin/py3kfixes.py @@ -0,0 +1,95 @@ +#!/usr/bin/env python +# encoding: utf-8 +# Thomas Nagy, 2009 (ita) + +""" +Fixes for py3k go here +""" + +import os + +all_modifs = {} + +def modif(dir, name, fun): + if name == '*': + lst = os.listdir(dir) + ['Tools' + os.sep + x for x in os.listdir(os.path.join(dir, 'Tools'))] + for x in lst: + if x.endswith('.py'): + modif(dir, x, fun) + return + + filename = os.path.join(dir, name) + f = open(filename, 'r') + txt = f.read() + f.close() + + txt = fun(txt) + + f = open(filename, 'w') + f.write(txt) + f.close() + +def subst(filename): + def do_subst(fun): + global all_modifs + try: + all_modifs[filename] += fun + except KeyError: + all_modifs[filename] = [fun] + return fun + return do_subst + +@subst('Constants.py') +def r1(code): + code = code.replace("'iluvcuteoverload'", "b'iluvcuteoverload'") + code = code.replace("ABI=7", "ABI=37") + return code + +@subst('Tools/ccroot.py') +def r2(code): + code = code.replace("p.stdin.write('\\n')", "p.stdin.write(b'\\n')") + code = code.replace("out=str(out)", "out=out.decode('utf-8')") + return code + +@subst('Utils.py') +def r3(code): + code = code.replace("m.update(str(lst))", "m.update(str(lst).encode())") + return code + +@subst('Task.py') +def r4(code): + code = code.replace("up(self.__class__.__name__)", "up(self.__class__.__name__.encode())") + code = code.replace("up(self.env.variant())", "up(self.env.variant().encode())") + code = code.replace("up(x.parent.abspath())", "up(x.parent.abspath().encode())") + code = code.replace("up(x.name)", "up(x.name.encode())") + code = code.replace('class TaskBase(object):\n\t__metaclass__=store_task_type', 'class TaskBase(object, metaclass=store_task_type):') + code = code.replace('keys=self.cstr_groups.keys()', 'keys=list(self.cstr_groups.keys())') + return code + +@subst('Build.py') +def r5(code): + code = code.replace("cPickle.dump(data,file,-1)", "cPickle.dump(data,file)") + code = code.replace('for node in src_dir_node.childs.values():', 'for node in list(src_dir_node.childs.values()):') + return code + +@subst('*') +def r6(code): + code = code.replace('xrange', 'range') + code = code.replace('iteritems', 'items') + code = code.replace('maxint', 'maxsize') + code = code.replace('iterkeys', 'keys') + code = code.replace('Error,e:', 'Error as e:') + code = code.replace('Exception,e:', 'Exception as e:') + return code + +@subst('TaskGen.py') +def r7(code): + code = code.replace('class task_gen(object):\n\t__metaclass__=register_obj', 'class task_gen(object, metaclass=register_obj):') + return code + +def fixdir(dir): + global all_modifs + for k in all_modifs: + for v in all_modifs[k]: + modif(os.path.join(dir, 'wafadmin'), k, v) + #print('substitutions finished') |