123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613 |
- #!/usr/bin/env python
- # encoding: utf-8
- # Thomas Nagy, 2005-2018 (ita)
- "Module called for configuring, compiling and installing targets"
- from __future__ import with_statement
- import os, shlex, shutil, traceback, errno, sys, stat
- from waflib import Utils, Configure, Logs, Options, ConfigSet, Context, Errors, Build, Node
- build_dir_override = None
- no_climb_commands = ['configure']
- default_cmd = "build"
- def waf_entry_point(current_directory, version, wafdir):
- """
- This is the main entry point, all Waf execution starts here.
- :param current_directory: absolute path representing the current directory
- :type current_directory: string
- :param version: version number
- :type version: string
- :param wafdir: absolute path representing the directory of the waf library
- :type wafdir: string
- """
- Logs.init_log()
- if Context.WAFVERSION != version:
- Logs.error('Waf script %r and library %r do not match (directory %r)', version, Context.WAFVERSION, wafdir)
- sys.exit(1)
- # Store current directory before any chdir
- Context.waf_dir = wafdir
- Context.run_dir = Context.launch_dir = current_directory
- start_dir = current_directory
- no_climb = os.environ.get('NOCLIMB')
- if len(sys.argv) > 1:
- # os.path.join handles absolute paths
- # if sys.argv[1] is not an absolute path, then it is relative to the current working directory
- potential_wscript = os.path.join(current_directory, sys.argv[1])
- if os.path.basename(potential_wscript) == Context.WSCRIPT_FILE and os.path.isfile(potential_wscript):
- # need to explicitly normalize the path, as it may contain extra '/.'
- path = os.path.normpath(os.path.dirname(potential_wscript))
- start_dir = os.path.abspath(path)
- no_climb = True
- sys.argv.pop(1)
- ctx = Context.create_context('options')
- (options, commands, env) = ctx.parse_cmd_args(allow_unknown=True)
- if options.top:
- start_dir = Context.run_dir = Context.top_dir = options.top
- no_climb = True
- if options.out:
- Context.out_dir = options.out
- # if 'configure' is in the commands, do not search any further
- if not no_climb:
- for k in no_climb_commands:
- for y in commands:
- if y.startswith(k):
- no_climb = True
- break
- # try to find a lock file (if the project was configured)
- # at the same time, store the first wscript file seen
- cur = start_dir
- while cur:
- try:
- lst = os.listdir(cur)
- except OSError:
- lst = []
- Logs.error('Directory %r is unreadable!', cur)
- if Options.lockfile in lst:
- env = ConfigSet.ConfigSet()
- try:
- env.load(os.path.join(cur, Options.lockfile))
- ino = os.stat(cur)[stat.ST_INO]
- except EnvironmentError:
- pass
- else:
- # check if the folder was not moved
- for x in (env.run_dir, env.top_dir, env.out_dir):
- if not x:
- continue
- if Utils.is_win32:
- if cur == x:
- load = True
- break
- else:
- # if the filesystem features symlinks, compare the inode numbers
- try:
- ino2 = os.stat(x)[stat.ST_INO]
- except OSError:
- pass
- else:
- if ino == ino2:
- load = True
- break
- else:
- Logs.warn('invalid lock file in %s', cur)
- load = False
- if load:
- Context.run_dir = env.run_dir
- Context.top_dir = env.top_dir
- Context.out_dir = env.out_dir
- break
- if not Context.run_dir:
- if Context.WSCRIPT_FILE in lst:
- Context.run_dir = cur
- next = os.path.dirname(cur)
- if next == cur:
- break
- cur = next
- if no_climb:
- break
- if not Context.run_dir:
- if options.whelp:
- Logs.warn('These are the generic options (no wscript/project found)')
- ctx.parser.print_help()
- sys.exit(0)
- Logs.error('Waf: Run from a folder containing a %r file (or try -h for the generic options)', Context.WSCRIPT_FILE)
- sys.exit(1)
- try:
- os.chdir(Context.run_dir)
- except OSError:
- Logs.error('Waf: The folder %r is unreadable', Context.run_dir)
- sys.exit(1)
- try:
- set_main_module(os.path.normpath(os.path.join(Context.run_dir, Context.WSCRIPT_FILE)))
- except Errors.WafError as e:
- Logs.pprint('RED', e.verbose_msg)
- Logs.error(str(e))
- sys.exit(1)
- except Exception as e:
- Logs.error('Waf: The wscript in %r is unreadable', Context.run_dir)
- traceback.print_exc(file=sys.stdout)
- sys.exit(2)
- if options.profile:
- import cProfile, pstats
- cProfile.runctx('from waflib import Scripting; Scripting.run_commands()', {}, {}, 'profi.txt')
- p = pstats.Stats('profi.txt')
- p.sort_stats('time').print_stats(75) # or 'cumulative'
- else:
- try:
- try:
- run_commands()
- except:
- if options.pdb:
- import pdb
- type, value, tb = sys.exc_info()
- traceback.print_exc()
- pdb.post_mortem(tb)
- else:
- raise
- except Errors.WafError as e:
- if Logs.verbose > 1:
- Logs.pprint('RED', e.verbose_msg)
- Logs.error(e.msg)
- sys.exit(1)
- except SystemExit:
- raise
- except Exception as e:
- traceback.print_exc(file=sys.stdout)
- sys.exit(2)
- except KeyboardInterrupt:
- Logs.pprint('RED', 'Interrupted')
- sys.exit(68)
- def set_main_module(file_path):
- """
- Read the main wscript file into :py:const:`waflib.Context.Context.g_module` and
- bind default functions such as ``init``, ``dist``, ``distclean`` if not defined.
- Called by :py:func:`waflib.Scripting.waf_entry_point` during the initialization.
- :param file_path: absolute path representing the top-level wscript file
- :type file_path: string
- """
- Context.g_module = Context.load_module(file_path)
- Context.g_module.root_path = file_path
- # note: to register the module globally, use the following:
- # sys.modules['wscript_main'] = g_module
- def set_def(obj):
- name = obj.__name__
- if not name in Context.g_module.__dict__:
- setattr(Context.g_module, name, obj)
- for k in (dist, distclean, distcheck):
- set_def(k)
- # add dummy init and shutdown functions if they're not defined
- if not 'init' in Context.g_module.__dict__:
- Context.g_module.init = Utils.nada
- if not 'shutdown' in Context.g_module.__dict__:
- Context.g_module.shutdown = Utils.nada
- if not 'options' in Context.g_module.__dict__:
- Context.g_module.options = Utils.nada
- def parse_options():
- """
- Parses the command-line options and initialize the logging system.
- Called by :py:func:`waflib.Scripting.waf_entry_point` during the initialization.
- """
- ctx = Context.create_context('options')
- ctx.execute()
- if not Options.commands:
- Options.commands.append(default_cmd)
- if Options.options.whelp:
- ctx.parser.print_help()
- sys.exit(0)
- def run_command(cmd_name):
- """
- Executes a single Waf command. Called by :py:func:`waflib.Scripting.run_commands`.
- :param cmd_name: command to execute, like ``build``
- :type cmd_name: string
- """
- ctx = Context.create_context(cmd_name)
- ctx.log_timer = Utils.Timer()
- ctx.options = Options.options # provided for convenience
- ctx.cmd = cmd_name
- try:
- ctx.execute()
- finally:
- # Issue 1374
- ctx.finalize()
- return ctx
- def run_commands():
- """
- Execute the Waf commands that were given on the command-line, and the other options
- Called by :py:func:`waflib.Scripting.waf_entry_point` during the initialization, and executed
- after :py:func:`waflib.Scripting.parse_options`.
- """
- parse_options()
- run_command('init')
- while Options.commands:
- cmd_name = Options.commands.pop(0)
- ctx = run_command(cmd_name)
- Logs.info('%r finished successfully (%s)', cmd_name, ctx.log_timer)
- run_command('shutdown')
- ###########################################################################################
- def distclean_dir(dirname):
- """
- Distclean function called in the particular case when::
- top == out
- :param dirname: absolute path of the folder to clean
- :type dirname: string
- """
- for (root, dirs, files) in os.walk(dirname):
- for f in files:
- if f.endswith(('.o', '.moc', '.exe')):
- fname = os.path.join(root, f)
- try:
- os.remove(fname)
- except OSError:
- Logs.warn('Could not remove %r', fname)
- for x in (Context.DBFILE, 'config.log'):
- try:
- os.remove(x)
- except OSError:
- pass
- try:
- shutil.rmtree('c4che')
- except OSError:
- pass
- def distclean(ctx):
- '''removes build folders and data'''
- def remove_and_log(k, fun):
- try:
- fun(k)
- except EnvironmentError as e:
- if e.errno != errno.ENOENT:
- Logs.warn('Could not remove %r', k)
- # remove waf cache folders on the top-level
- if not Options.commands:
- for k in os.listdir('.'):
- for x in '.waf-2 waf-2 .waf3-2 waf3-2'.split():
- if k.startswith(x):
- remove_and_log(k, shutil.rmtree)
- # remove a build folder, if any
- cur = '.'
- if ctx.options.no_lock_in_top:
- cur = ctx.options.out
- try:
- lst = os.listdir(cur)
- except OSError:
- Logs.warn('Could not read %r', cur)
- return
- if Options.lockfile in lst:
- f = os.path.join(cur, Options.lockfile)
- try:
- env = ConfigSet.ConfigSet(f)
- except EnvironmentError:
- Logs.warn('Could not read %r', f)
- return
- if not env.out_dir or not env.top_dir:
- Logs.warn('Invalid lock file %r', f)
- return
- if env.out_dir == env.top_dir:
- distclean_dir(env.out_dir)
- else:
- remove_and_log(env.out_dir, shutil.rmtree)
- for k in (env.out_dir, env.top_dir, env.run_dir):
- p = os.path.join(k, Options.lockfile)
- remove_and_log(p, os.remove)
- class Dist(Context.Context):
- '''creates an archive containing the project source code'''
- cmd = 'dist'
- fun = 'dist'
- algo = 'tar.bz2'
- ext_algo = {}
- def execute(self):
- """
- See :py:func:`waflib.Context.Context.execute`
- """
- self.recurse([os.path.dirname(Context.g_module.root_path)])
- self.archive()
- def archive(self):
- """
- Creates the source archive.
- """
- import tarfile
- arch_name = self.get_arch_name()
- try:
- self.base_path
- except AttributeError:
- self.base_path = self.path
- node = self.base_path.make_node(arch_name)
- try:
- node.delete()
- except OSError:
- pass
- files = self.get_files()
- if self.algo.startswith('tar.'):
- tar = tarfile.open(node.abspath(), 'w:' + self.algo.replace('tar.', ''))
- for x in files:
- self.add_tar_file(x, tar)
- tar.close()
- elif self.algo == 'zip':
- import zipfile
- zip = zipfile.ZipFile(node.abspath(), 'w', compression=zipfile.ZIP_DEFLATED)
- for x in files:
- archive_name = self.get_base_name() + '/' + x.path_from(self.base_path)
- zip.write(x.abspath(), archive_name, zipfile.ZIP_DEFLATED)
- zip.close()
- else:
- self.fatal('Valid algo types are tar.bz2, tar.gz, tar.xz or zip')
- try:
- from hashlib import sha256
- except ImportError:
- digest = ''
- else:
- digest = ' (sha256=%r)' % sha256(node.read(flags='rb')).hexdigest()
- Logs.info('New archive created: %s%s', self.arch_name, digest)
- def get_tar_path(self, node):
- """
- Return the path to use for a node in the tar archive, the purpose of this
- is to let subclases resolve symbolic links or to change file names
- :return: absolute path
- :rtype: string
- """
- return node.abspath()
- def add_tar_file(self, x, tar):
- """
- Adds a file to the tar archive. Symlinks are not verified.
- :param x: file path
- :param tar: tar file object
- """
- p = self.get_tar_path(x)
- tinfo = tar.gettarinfo(name=p, arcname=self.get_tar_prefix() + '/' + x.path_from(self.base_path))
- tinfo.uid = 0
- tinfo.gid = 0
- tinfo.uname = 'root'
- tinfo.gname = 'root'
- if os.path.isfile(p):
- with open(p, 'rb') as f:
- tar.addfile(tinfo, fileobj=f)
- else:
- tar.addfile(tinfo)
- def get_tar_prefix(self):
- """
- Returns the base path for files added into the archive tar file
- :rtype: string
- """
- try:
- return self.tar_prefix
- except AttributeError:
- return self.get_base_name()
- def get_arch_name(self):
- """
- Returns the archive file name.
- Set the attribute *arch_name* to change the default value::
- def dist(ctx):
- ctx.arch_name = 'ctx.tar.bz2'
- :rtype: string
- """
- try:
- self.arch_name
- except AttributeError:
- self.arch_name = self.get_base_name() + '.' + self.ext_algo.get(self.algo, self.algo)
- return self.arch_name
- def get_base_name(self):
- """
- Returns the default name of the main directory in the archive, which is set to *appname-version*.
- Set the attribute *base_name* to change the default value::
- def dist(ctx):
- ctx.base_name = 'files'
- :rtype: string
- """
- try:
- self.base_name
- except AttributeError:
- appname = getattr(Context.g_module, Context.APPNAME, 'noname')
- version = getattr(Context.g_module, Context.VERSION, '1.0')
- self.base_name = appname + '-' + version
- return self.base_name
- def get_excl(self):
- """
- Returns the patterns to exclude for finding the files in the top-level directory.
- Set the attribute *excl* to change the default value::
- def dist(ctx):
- ctx.excl = 'build **/*.o **/*.class'
- :rtype: string
- """
- try:
- return self.excl
- except AttributeError:
- self.excl = Node.exclude_regs + ' **/waf-2.* **/.waf-2.* **/waf3-2.* **/.waf3-2.* **/*~ **/*.rej **/*.orig **/*.pyc **/*.pyo **/*.bak **/*.swp **/.lock-w*'
- if Context.out_dir:
- nd = self.root.find_node(Context.out_dir)
- if nd:
- self.excl += ' ' + nd.path_from(self.base_path)
- return self.excl
- def get_files(self):
- """
- Files to package are searched automatically by :py:func:`waflib.Node.Node.ant_glob`.
- Set *files* to prevent this behaviour::
- def dist(ctx):
- ctx.files = ctx.path.find_node('wscript')
- Files are also searched from the directory 'base_path', to change it, set::
- def dist(ctx):
- ctx.base_path = path
- :rtype: list of :py:class:`waflib.Node.Node`
- """
- try:
- files = self.files
- except AttributeError:
- files = self.base_path.ant_glob('**/*', excl=self.get_excl())
- return files
- def dist(ctx):
- '''makes a tarball for redistributing the sources'''
- pass
- class DistCheck(Dist):
- """creates an archive with dist, then tries to build it"""
- fun = 'distcheck'
- cmd = 'distcheck'
- def execute(self):
- """
- See :py:func:`waflib.Context.Context.execute`
- """
- self.recurse([os.path.dirname(Context.g_module.root_path)])
- self.archive()
- self.check()
- def make_distcheck_cmd(self, tmpdir):
- cfg = []
- if Options.options.distcheck_args:
- cfg = shlex.split(Options.options.distcheck_args)
- else:
- cfg = [x for x in sys.argv if x.startswith('-')]
- cmd = [sys.executable, sys.argv[0], 'configure', 'build', 'install', 'uninstall', '--destdir=' + tmpdir] + cfg
- return cmd
- def check(self):
- """
- Creates the archive, uncompresses it and tries to build the project
- """
- import tempfile, tarfile
- with tarfile.open(self.get_arch_name()) as t:
- for x in t:
- t.extract(x)
- instdir = tempfile.mkdtemp('.inst', self.get_base_name())
- cmd = self.make_distcheck_cmd(instdir)
- ret = Utils.subprocess.Popen(cmd, cwd=self.get_base_name()).wait()
- if ret:
- raise Errors.WafError('distcheck failed with code %r' % ret)
- if os.path.exists(instdir):
- raise Errors.WafError('distcheck succeeded, but files were left in %s' % instdir)
- shutil.rmtree(self.get_base_name())
- def distcheck(ctx):
- '''checks if the project compiles (tarball from 'dist')'''
- pass
- def autoconfigure(execute_method):
- """
- Decorator that enables context commands to run *configure* as needed.
- """
- def execute(self):
- """
- Wraps :py:func:`waflib.Context.Context.execute` on the context class
- """
- if not Configure.autoconfig:
- return execute_method(self)
- env = ConfigSet.ConfigSet()
- do_config = False
- try:
- env.load(os.path.join(Context.top_dir, Options.lockfile))
- except EnvironmentError:
- Logs.warn('Configuring the project')
- do_config = True
- else:
- if env.run_dir != Context.run_dir:
- do_config = True
- else:
- h = 0
- for f in env.files:
- try:
- h = Utils.h_list((h, Utils.readf(f, 'rb')))
- except EnvironmentError:
- do_config = True
- break
- else:
- do_config = h != env.hash
- if do_config:
- cmd = env.config_cmd or 'configure'
- if Configure.autoconfig == 'clobber':
- tmp = Options.options.__dict__
- if env.options:
- Options.options.__dict__ = env.options
- try:
- run_command(cmd)
- finally:
- Options.options.__dict__ = tmp
- else:
- run_command(cmd)
- run_command(self.cmd)
- else:
- return execute_method(self)
- return execute
- Build.BuildContext.execute = autoconfigure(Build.BuildContext.execute)
|