2
0
mirror of https://gitlab.com/ita1024/waf.git synced 2024-11-11 04:38:59 +01:00
waf/waflib/Scripting.py

620 lines
16 KiB
Python
Raw Normal View History

2011-09-10 11:13:51 +02:00
#!/usr/bin/env python
# encoding: utf-8
2016-06-25 14:49:27 +02:00
# Thomas Nagy, 2005-2016 (ita)
2011-09-10 11:13:51 +02:00
"Module called for configuring, compiling and installing targets"
import os, shlex, shutil, traceback, errno, sys, stat
2011-09-10 11:13:51 +02:00
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)
2011-09-10 11:13:51 +02:00
sys.exit(1)
if '--version' in sys.argv:
2011-11-28 19:19:25 +01:00
Context.run_dir = current_directory
2011-09-10 11:13:51 +02:00
ctx = Context.create_context('options')
ctx.curdir = current_directory
ctx.parse_args()
sys.exit(0)
if len(sys.argv) > 1:
# os.path.join handles absolute paths in sys.argv[1] accordingly (it discards the previous ones)
# 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])
# maybe check if the file is executable
# perhaps extract 'wscript' as a constant
if os.path.basename(potential_wscript) == 'wscript' and os.path.isfile(potential_wscript):
# need to explicitly normalize the path, as it may contain extra '/.'
# TODO abspath?
current_directory = os.path.normpath(os.path.dirname(potential_wscript))
sys.argv.pop(1)
2011-09-10 11:13:51 +02:00
Context.waf_dir = wafdir
Context.launch_dir = current_directory
# if 'configure' is in the commands, do not search any further
2016-04-19 22:00:21 +02:00
no_climb = os.environ.get('NOCLIMB')
2011-09-10 11:13:51 +02:00
if not no_climb:
for k in no_climb_commands:
for y in sys.argv:
if y.startswith(k):
no_climb = True
break
2011-09-10 11:13:51 +02:00
# if --top is provided assume the build started in the top directory
for i, x in enumerate(sys.argv):
# WARNING: this modifies sys.argv
if x.startswith('--top='):
Context.run_dir = Context.top_dir = Utils.sane_path(x[6:])
sys.argv[i] = '--top=' + Context.run_dir
if x.startswith('--out='):
Context.out_dir = Utils.sane_path(x[6:])
sys.argv[i] = '--out=' + Context.out_dir
2011-09-10 11:13:51 +02:00
# try to find a lock file (if the project was configured)
# at the same time, store the first wscript file seen
cur = current_directory
while cur and not Context.top_dir:
2016-03-01 22:45:39 +01:00
try:
lst = os.listdir(cur)
except OSError:
lst = []
Logs.error('Directory %r is unreadable!', cur)
2011-09-10 11:13:51 +02:00
if Options.lockfile in lst:
env = ConfigSet.ConfigSet()
try:
env.load(os.path.join(cur, Options.lockfile))
ino = os.stat(cur)[stat.ST_INO]
2016-01-17 14:32:23 +01:00
except EnvironmentError:
2011-09-10 11:13:51 +02:00
pass
else:
# check if the folder was not moved
for x in (env.run_dir, env.top_dir, env.out_dir):
2011-09-10 11:13:51 +02:00
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]
2012-02-11 14:31:00 +01:00
except OSError:
2011-09-10 11:13:51 +02:00
pass
else:
if ino == ino2:
load = True
break
else:
Logs.warn('invalid lock file in %s', cur)
2011-09-10 11:13:51 +02:00
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 '-h' in sys.argv or '--help' in sys.argv:
Logs.warn('No wscript file found: the help message may be incomplete')
2011-11-28 19:19:25 +01:00
Context.run_dir = current_directory
2011-09-10 11:13:51 +02:00
ctx = Context.create_context('options')
ctx.curdir = current_directory
ctx.parse_args()
sys.exit(0)
Logs.error('Waf: Run from a directory containing a file named %r', Context.WSCRIPT_FILE)
2011-09-10 11:13:51 +02:00
sys.exit(1)
try:
os.chdir(Context.run_dir)
except OSError:
Logs.error('Waf: The folder %r is unreadable', Context.run_dir)
2011-09-10 11:13:51 +02:00
sys.exit(1)
try:
set_main_module(os.path.normpath(os.path.join(Context.run_dir, Context.WSCRIPT_FILE)))
2011-09-10 11:13:51 +02:00
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)
2011-09-10 11:13:51 +02:00
traceback.print_exc(file=sys.stdout)
sys.exit(2)
2016-03-25 14:20:57 +01:00
if '--profile' in sys.argv:
import cProfile, pstats
2016-06-05 00:23:57 +02:00
cProfile.runctx('from waflib import Scripting; Scripting.run_commands()', {}, {}, 'profi.txt')
2016-03-25 14:20:57 +01:00
p = pstats.Stats('profi.txt')
p.sort_stats('time').print_stats(75) # or 'cumulative'
else:
try:
run_commands()
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)
2011-09-10 11:13:51 +02:00
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)
2015-12-22 18:14:10 +01:00
for k in (dist, distclean, distcheck):
2011-09-10 11:13:51 +02:00
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():
"""
2016-06-25 14:49:27 +02:00
Parses the command-line options and initialize the logging system.
2011-09-10 11:13:51 +02:00
Called by :py:func:`waflib.Scripting.waf_entry_point` during the initialization.
"""
Context.create_context('options').execute()
for var in Options.envvars:
(name, value) = var.split('=', 1)
os.environ[name.strip()] = value
2011-09-10 11:13:51 +02:00
if not Options.commands:
Options.commands = [default_cmd]
2011-12-07 03:27:43 +01:00
Options.commands = [x for x in Options.commands if x != 'options'] # issue 1076
2011-09-10 11:13:51 +02:00
# process some internal Waf options
Logs.verbose = Options.options.verbose
2014-01-14 02:31:08 +01:00
#Logs.init_log()
2011-09-10 11:13:51 +02:00
if Options.options.zones:
Logs.zones = Options.options.zones.split(',')
if not Logs.verbose:
Logs.verbose = 1
elif Logs.verbose > 0:
Logs.zones = ['runner']
if Logs.verbose > 2:
Logs.zones = ['*']
def run_command(cmd_name):
"""
2016-06-25 14:49:27 +02:00
Executes a single Waf command. Called by :py:func:`waflib.Scripting.run_commands`.
2011-09-10 11:13:51 +02:00
:param cmd_name: command to execute, like ``build``
:type cmd_name: string
"""
ctx = Context.create_context(cmd_name)
2012-03-03 14:01:12 +01:00
ctx.log_timer = Utils.Timer()
2011-09-10 11:13:51 +02:00
ctx.options = Options.options # provided for convenience
ctx.cmd = cmd_name
try:
ctx.execute()
finally:
# Issue 1374
ctx.finalize()
2011-09-10 11:13:51 +02:00
return ctx
def run_commands():
"""
2016-06-25 14:49:27 +02:00
Execute the Waf commands that were given on the command-line, and the other options
2011-09-10 11:13:51 +02:00
Called by :py:func:`waflib.Scripting.waf_entry_point` during the initialization, and executed
after :py:func:`waflib.Scripting.parse_options`.
"""
parse_options()
2011-09-10 11:13:51 +02:00
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)
2011-09-10 11:13:51 +02:00
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)
2011-09-10 11:13:51 +02:00
try:
os.remove(fname)
2012-02-11 14:31:00 +01:00
except OSError:
Logs.warn('Could not remove %r', fname)
2011-09-10 11:13:51 +02:00
for x in (Context.DBFILE, 'config.log'):
2011-09-10 11:13:51 +02:00
try:
os.remove(x)
2012-02-11 14:31:00 +01:00
except OSError:
2011-09-10 11:13:51 +02:00
pass
try:
shutil.rmtree('c4che')
2012-02-11 14:31:00 +01:00
except OSError:
2011-09-10 11:13:51 +02:00
pass
def distclean(ctx):
'''removes the build directory'''
lst = os.listdir('.')
for f in lst:
if f == Options.lockfile:
try:
proj = ConfigSet.ConfigSet(f)
2012-02-11 14:31:00 +01:00
except IOError:
Logs.warn('Could not read %r', f)
2011-09-10 11:13:51 +02:00
continue
if proj['out_dir'] != proj['top_dir']:
try:
shutil.rmtree(proj['out_dir'])
2016-06-04 09:33:13 +02:00
except EnvironmentError as e:
2011-09-10 11:13:51 +02:00
if e.errno != errno.ENOENT:
Logs.warn('Could not remove %r', proj['out_dir'])
2011-09-10 11:13:51 +02:00
else:
distclean_dir(proj['out_dir'])
for k in (proj['out_dir'], proj['top_dir'], proj['run_dir']):
2014-02-14 21:25:38 +01:00
p = os.path.join(k, Options.lockfile)
2011-09-10 11:13:51 +02:00
try:
2014-02-14 21:25:38 +01:00
os.remove(p)
2011-09-10 11:13:51 +02:00
except OSError as e:
if e.errno != errno.ENOENT:
Logs.warn('Could not remove %r', p)
2011-09-10 11:13:51 +02:00
2013-10-28 21:25:58 +01:00
# remove local waf cache folders
if not Options.commands:
for x in '.waf-1. waf-1. .waf3-1. waf3-1.'.split():
if f.startswith(x):
shutil.rmtree(f, ignore_errors=True)
2011-09-10 11:13:51 +02:00
class Dist(Context.Context):
2012-07-24 07:06:24 +02:00
'''creates an archive containing the project source code'''
2011-09-10 11:13:51 +02:00
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):
"""
2016-06-25 14:49:27 +02:00
Creates the source archive.
2011-09-10 11:13:51 +02:00
"""
import tarfile
arch_name = self.get_arch_name()
try:
self.base_path
2012-02-11 14:31:00 +01:00
except AttributeError:
2011-09-10 11:13:51 +02:00
self.base_path = self.path
node = self.base_path.make_node(arch_name)
try:
node.delete()
2014-04-20 02:29:27 +02:00
except OSError:
2011-09-10 11:13:51 +02:00
pass
files = self.get_files()
if self.algo.startswith('tar.'):
2016-08-24 18:07:24 +02:00
tar = tarfile.open(node.abspath(), 'w:' + self.algo.replace('tar.', ''))
2011-09-10 11:13:51 +02:00
for x in files:
self.add_tar_file(x, tar)
2011-09-10 11:13:51 +02:00
tar.close()
elif self.algo == 'zip':
import zipfile
2016-08-24 18:07:24 +02:00
zip = zipfile.ZipFile(node.abspath(), 'w', compression=zipfile.ZIP_DEFLATED)
2011-09-10 11:13:51 +02:00
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:
2015-02-24 21:20:15 +01:00
self.fatal('Valid algo types are tar.bz2, tar.gz, tar.xz or zip')
2011-09-10 11:13:51 +02:00
try:
from hashlib import sha256
2011-09-10 11:13:51 +02:00
except ImportError:
digest = ''
2016-05-14 12:15:15 +02:00
else:
digest = ' (sha256=%r)' % sha256(node.read(flags='rb')).hexdigest()
2011-09-10 11:13:51 +02:00
Logs.info('New archive created: %s%s', self.arch_name, digest)
2011-09-10 11:13:51 +02:00
2011-11-19 00:03:28 +01:00
def get_tar_path(self, node):
"""
2016-06-25 14:49:27 +02:00
Return the path to use for a node in the tar archive, the purpose of this
2011-11-19 00:03:28 +01:00
is to let subclases resolve symbolic links or to change file names
2016-06-25 14:49:27 +02:00
:return: absolute path
:rtype: string
2011-11-19 00:03:28 +01:00
"""
return node.abspath()
def add_tar_file(self, x, tar):
"""
2016-06-25 14:49:27 +02:00
Adds a file to the tar archive. Symlinks are not verified.
:param x: file path
:param tar: tar file object
"""
2011-11-19 00:03:28 +01:00
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):
fu = open(p, 'rb')
try:
tar.addfile(tinfo, fileobj=fu)
finally:
2011-11-09 02:54:27 +01:00
fu.close()
else:
tar.addfile(tinfo)
2011-09-10 11:13:51 +02:00
def get_tar_prefix(self):
2016-06-25 14:49:27 +02:00
"""
Returns the base path for files added into the archive tar file
:rtype: string
"""
2011-09-10 11:13:51 +02:00
try:
return self.tar_prefix
2012-02-11 14:31:00 +01:00
except AttributeError:
2011-09-10 11:13:51 +02:00
return self.get_base_name()
def get_arch_name(self):
"""
2016-06-25 14:49:27 +02:00
Returns the archive file name.
Set the attribute *arch_name* to change the default value::
2011-09-10 11:13:51 +02:00
def dist(ctx):
ctx.arch_name = 'ctx.tar.bz2'
:rtype: string
"""
try:
self.arch_name
2012-02-11 14:31:00 +01:00
except AttributeError:
2011-09-10 11:13:51 +02:00
self.arch_name = self.get_base_name() + '.' + self.ext_algo.get(self.algo, self.algo)
return self.arch_name
def get_base_name(self):
"""
2016-06-25 14:49:27 +02:00
Returns the default name of the main directory in the archive, which is set to *appname-version*.
2011-09-10 11:13:51 +02:00
Set the attribute *base_name* to change the default value::
def dist(ctx):
ctx.base_name = 'files'
:rtype: string
"""
try:
self.base_name
2012-02-11 14:31:00 +01:00
except AttributeError:
2011-09-10 11:13:51 +02:00
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):
"""
2016-06-25 14:49:27 +02:00
Returns the patterns to exclude for finding the files in the top-level directory.
Set the attribute *excl* to change the default value::
2011-09-10 11:13:51 +02:00
def dist(ctx):
ctx.excl = 'build **/*.o **/*.class'
:rtype: string
"""
try:
return self.excl
2012-02-11 14:31:00 +01:00
except AttributeError:
2013-10-28 21:32:25 +01:00
self.excl = Node.exclude_regs + ' **/waf-1.8.* **/.waf-1.8* **/waf3-1.8.* **/.waf3-1.8* **/*~ **/*.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)
2011-09-10 11:13:51 +02:00
return self.excl
def get_files(self):
"""
2016-06-25 14:49:27 +02:00
Files to package are searched automatically by :py:func:`waflib.Node.Node.ant_glob`.
Set *files* to prevent this behaviour::
2011-09-10 11:13:51 +02:00
def dist(ctx):
ctx.files = ctx.path.find_node('wscript')
2016-06-25 14:49:27 +02:00
Files are also searched from the directory 'base_path', to change it, set::
2011-09-10 11:13:51 +02:00
def dist(ctx):
ctx.base_path = path
:rtype: list of :py:class:`waflib.Node.Node`
"""
try:
files = self.files
2012-02-11 14:31:00 +01:00
except AttributeError:
2011-09-10 11:13:51 +02:00
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):
"""
2016-06-25 14:49:27 +02:00
Creates an archive of the project, then attempts to build the project in a temporary directory::
2011-09-10 11:13:51 +02:00
$ waf distcheck
"""
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 check(self):
"""
2016-06-25 14:49:27 +02:00
Creates the archive, uncompresses it and tries to build the project
2011-09-10 11:13:51 +02:00
"""
import tempfile, tarfile
try:
t = tarfile.open(self.get_arch_name())
for x in t:
t.extract(x)
finally:
2016-05-18 22:02:47 +02:00
t.close()
2011-09-10 11:13:51 +02:00
cfg = []
if Options.options.distcheck_args:
cfg = shlex.split(Options.options.distcheck_args)
else:
cfg = [x for x in sys.argv if x.startswith('-')]
2011-09-10 11:13:51 +02:00
instdir = tempfile.mkdtemp('.inst', self.get_base_name())
ret = Utils.subprocess.Popen([sys.executable, sys.argv[0], 'configure', 'install', 'uninstall', '--destdir=' + instdir] + cfg, cwd=self.get_base_name()).wait()
2011-09-10 11:13:51 +02:00
if ret:
raise Errors.WafError('distcheck failed with code %r' % ret)
2011-09-10 11:13:51 +02:00
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):
"""
2016-06-25 14:49:27 +02:00
Decorator that enables context commands to run *configure* as needed.
2011-09-10 11:13:51 +02:00
"""
def execute(self):
2016-06-25 14:49:27 +02:00
"""
Wraps :py:func:`waflib.Context.Context.execute` on the context class
"""
2011-09-10 11:13:51 +02:00
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))
2016-05-14 12:15:15 +02:00
except EnvironmentError:
2011-09-10 11:13:51 +02:00
Logs.warn('Configuring the project')
do_config = True
else:
if env.run_dir != Context.run_dir:
do_config = True
else:
h = 0
2016-06-25 23:54:12 +02:00
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
2011-09-10 11:13:51 +02:00
if do_config:
2016-06-25 23:54:12 +02:00
cmd = env.config_cmd or 'configure'
if Configure.autoconfig == 'clobber':
tmp = Options.options.__dict__
Options.options.__dict__ = env.options
try:
2016-06-19 09:01:02 +02:00
run_command(cmd)
finally:
Options.options.__dict__ = tmp
else:
2016-06-19 09:01:02 +02:00
run_command(cmd)
run_command(self.cmd)
2016-06-19 13:39:03 +02:00
else:
return execute_method(self)
2011-09-10 11:13:51 +02:00
return execute
Build.BuildContext.execute = autoconfigure(Build.BuildContext.execute)