#!/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 an autotool-like 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: # autotool-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() try: env.load(os.path.join(cwd, Options.lockfile)) except: error('could not load %r' % 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 = getattr(Utils.g_module, 'top', 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 not bld: bld = getattr(Utils.g_module, 'out', None) if not bld: bld = 'build' incomplete_bld = 1 if bld == '.': raise Utils.WafError('Setting blddir="." may cause distclean problems') 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') 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''' global commands 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 not commands and 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 if g_gz in ['gz', 'bz2']: arch_name = tmp_folder + '.tar.' + g_gz else: arch_name = tmp_folder + '.' + 'zip' # 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) if g_gz in ['gz', 'bz2']: tar = tarfile.open(arch_name, 'w:' + g_gz) tar.add(tmp_folder) tar.close() else: Utils.zip_folder(tmp_folder, arch_name, tmp_folder) 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')