From be05b6ee8b3c45e6fd1ede97f31627d33b9bc73a Mon Sep 17 00:00:00 2001 From: Thomas Nagy Date: Sun, 10 Sep 2017 19:16:02 +0200 Subject: [PATCH] Accept relative prefix/bindir/libdir paths from launch_dir Additionally: - Scripting.parse_options is back for compatibility reasons - The help message should only be displayed when this is intended - OptionsContext is responsible for the full initialization, so the framework should be usable without requiring Scripting.py - Make it clear that Options.options is an optparse.Values object - Get rid of the state in Options.options --- waflib/Configure.py | 8 ++-- waflib/Options.py | 108 ++++++++++++++++++++------------------------ waflib/Scripting.py | 67 +++++++++++++++------------ 3 files changed, 90 insertions(+), 93 deletions(-) diff --git a/waflib/Configure.py b/waflib/Configure.py index 5232e7ce..f805421b 100644 --- a/waflib/Configure.py +++ b/waflib/Configure.py @@ -197,17 +197,17 @@ class ConfigurationContext(Context.Context): """ if not env.PREFIX: if Options.options.prefix or Utils.is_win32: - env.PREFIX = Utils.sane_path(Options.options.prefix) + env.PREFIX = Options.options.prefix else: - env.PREFIX = '' + env.PREFIX = '/' if not env.BINDIR: if Options.options.bindir: - env.BINDIR = Utils.sane_path(Options.options.bindir) + env.BINDIR = Options.options.bindir else: env.BINDIR = Utils.subst_vars('${PREFIX}/bin', env) if not env.LIBDIR: if Options.options.libdir: - env.LIBDIR = Utils.sane_path(Options.options.libdir) + env.LIBDIR = Options.options.libdir else: env.LIBDIR = Utils.subst_vars('${PREFIX}/lib%s' % Utils.lib64(), env) diff --git a/waflib/Options.py b/waflib/Options.py index 6bb399fa..9bc16cee 100644 --- a/waflib/Options.py +++ b/waflib/Options.py @@ -13,7 +13,7 @@ that reads the ``options`` wscript function. import os, tempfile, optparse, sys, re from waflib import Logs, Utils, Context, Errors -options = {} +options = optparse.Values() """ A global dictionary representing user-provided command-line options:: @@ -43,14 +43,10 @@ class opt_parser(optparse.OptionParser): Command-line options parser. """ def __init__(self, ctx, allow_unknown=False): - # Create a option parser without help function because we need to be able ignore '-h|--help' - # at configuration phase, once the main script found, we will add the help option to make sure - # we if help is needed it will containt also user defined options - optparse.OptionParser.__init__(self, conflict_handler="resolve", add_help_option=False, + optparse.OptionParser.__init__(self, conflict_handler='resolve', add_help_option=False, version='waf %s (%s)' % (Context.WAFVERSION, Context.WAFREVISION)) self.formatter.width = Logs.get_term_cols() self.ctx = ctx - # By default do not allow unknown options self.allow_unknown = allow_unknown def _process_args(self, largs, rargs, values): @@ -136,6 +132,7 @@ class OptionsContext(Context.Context): p('-v', '--verbose', dest='verbose', default=0, action='count', help='verbosity level -v -vv or -vvv [default: 0]') p('--zones', dest='zones', default='', action='store', help='debugging zones (task_gen, deps, tasks, etc)') p('--profile', dest='profile', default='', action='store_true', help=optparse.SUPPRESS_HELP) + p('-h', '--help', dest='whelp', default=0, action='store_true', help="show this help message and exit") gr = self.add_option_group('Configuration options') self.option_groups['configure options'] = gr @@ -261,35 +258,50 @@ class OptionsContext(Context.Context): return group return None - def parse_basic_args(self, _args=None): - """ - Parse basic arguments defined by this module. - This can be called at the very beginning of the Waf initialitation - procedure to initialize pre-configuration commmand line driven options. - """ - try: - if self.parsed_basic_args: - return - except AttributeError: - self.parsed_basic_args = True + def sanitize_path(self, path, cwd=None): + if not cwd: + cwd = Context.launch_dir + p = os.path.expanduser(path) + p = os.path.join(cwd, p) + p = os.path.normpath(p) + p = os.path.abspath(p) + return p - # Allow unknow argument, as at this point the command line may contain - # custom-specified options the context still doesn't know. - self.parser.allow_unknown = True - - global options + def parse_cmd_args(self, _args=None, cwd=None, allow_unknown=False): + """ + Just parse the arguments + """ + self.parser.allow_unknown = allow_unknown (options, leftover_args) = self.parser.parse_args(args=_args) + envvars = [] + commands = [] + for arg in leftover_args: + if '=' in arg: + envvars.append(arg) + elif arg != 'options': + commands.append(arg) - if options.destdir: - options.destdir = Utils.sane_path(options.destdir) + for name in 'top out destdir prefix bindir libdir'.split(): + # those paths are usually expanded from Context.launch_dir + if getattr(options, name, None): + path = self.sanitize_path(getattr(options, name), cwd) + setattr(options, name, path) + return options, commands, envvars - if options.top: - options.top = Utils.sane_path(options.top) + def init_module_vars(self, arg_options, arg_commands, arg_envvars): + options.__dict__.clear() + del commands[:] + del envvars[:] - if options.out: - options.out = Utils.sane_path(options.out) + options.__dict__.update(arg_options.__dict__) + commands.extend(arg_commands) + envvars.extend(arg_envvars) - Logs.verbose = options.verbose + for var in envvars: + (name, value) = var.split('=', 1) + os.environ[name.strip()] = value + + def init_logs(self, options, commands, envvars): if options.verbose >= 1: self.load('errcheck') @@ -298,48 +310,25 @@ class OptionsContext(Context.Context): if options.zones: Logs.zones = options.zones.split(',') - if not Logs.verbose: - Logs.verbose = 1 + if not Logs.verbose: + Logs.verbose = 1 elif Logs.verbose > 0: Logs.zones = ['runner'] - if Logs.verbose > 2: Logs.zones = ['*'] def parse_args(self, _args=None): """ Parses arguments from a list which is not necessarily the command-line. + Initializes the module variables options, commands and envvars + If help is requested, prints it and exit the application :param _args: arguments :type _args: list of strings """ - self.parse_basic_args() - - # Make sure all specified arguments make sense - self.parser.allow_unknown = False - self.parser._add_help_option() - - global options, commands, envvars - (custom_options, leftover_args) = self.parser.parse_args(args=_args) - - # Update global options with custom ones - for currOptionName, currOptionValue in vars(custom_options).iteritems(): - try: - getattr(options, currOptionName) - except AttributeError: - setattr(options, currOptionName, currOptionValue) - - for arg in leftover_args: - if '=' in arg: - envvars.append(arg) - (name, value) = var.split('=', 1) - os.environ[name.strip()] = value - else: - commands.append(arg) - - if not commands: - commands = ['build'] - commands = [x for x in commands if x != 'options'] # issue 1076 + options, commands, envvars = self.parse_cmd_args() + self.init_logs(options, commands, envvars) + self.init_module_vars(options, commands, envvars) def execute(self): """ @@ -348,3 +337,4 @@ class OptionsContext(Context.Context): super(OptionsContext, self).execute() self.parse_args() Utils.alloc_process_pool(options.jobs) + diff --git a/waflib/Scripting.py b/waflib/Scripting.py index 6e4e8592..b7ad4afd 100644 --- a/waflib/Scripting.py +++ b/waflib/Scripting.py @@ -32,42 +32,39 @@ def waf_entry_point(current_directory, version, wafdir): # Store current directory before any chdir Context.waf_dir = wafdir - Context.launch_dir = current_directory + 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 in sys.argv[1] accordingly (it discards the previous ones) + # 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]) - # 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): + 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 '/.' - # TODO abspath? - current_directory = os.path.normpath(os.path.dirname(potential_wscript)) + path = os.path.normpath(os.path.dirname(potential_wscript)) + start_dir = os.path.abspath(path) + no_climb = True sys.argv.pop(1) - # Parse basic args to find out pre-configuration phase options - optsCtx = Context.create_context('options') - optsCtx.parse_basic_args() + ctx = Context.create_context('options') + (options, commands, env) = ctx.parse_cmd_args(allow_unknown=True) + if options.top: + Context.run_dir = Context.top_dir = options.top + if options.out: + Context.out_dir = options.out # if 'configure' is in the commands, do not search any further - no_climb = os.environ.get('NOCLIMB') if not no_climb: for k in no_climb_commands: - for y in sys.argv: + for y in commands: if y.startswith(k): no_climb = True break - # if --top is provided assume the build started in the top directory - if Options.options.top: - Context.run_dir = Context.top_dir = Options.options.top - if Options.options.out: - Context.out_dir = Options.options.out - # try to find a lock file (if the project was configured) # at the same time, store the first wscript file seen - cur = current_directory + cur = start_dir while cur and not Context.top_dir: try: lst = os.listdir(cur) @@ -123,9 +120,11 @@ def waf_entry_point(current_directory, version, wafdir): break if not Context.run_dir: - Logs.warn('No wscript file found: the help message may be incomplete') - optsCtx.parser.print_help() - Logs.error('Waf: Run from a directory containing a file named %r', Context.WSCRIPT_FILE) + 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: @@ -145,14 +144,12 @@ def waf_entry_point(current_directory, version, wafdir): traceback.print_exc(file=sys.stdout) sys.exit(2) - if Options.options.profile: + 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: - # Execute 'option' context to load custom defined commands - optsCtx.execute() try: run_commands() except Errors.WafError as e: @@ -198,6 +195,19 @@ def set_main_module(file_path): 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`. @@ -222,6 +232,7 @@ def run_commands(): 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) @@ -494,11 +505,7 @@ def dist(ctx): pass class DistCheck(Dist): - """ - Creates an archive of the project, then attempts to build the project in a temporary directory:: - - $ waf distcheck - """ + """creates an archive with dist, then tries to build it""" fun = 'distcheck' cmd = 'distcheck'