Use a single ArgumentParser for all subcommands

This has the adventage that "meson --help" shows a list of all commands,
making them discoverable. This also reduce the manual parsing of
arguments to the strict minimum needed for backward compatibility.
pull/4204/head
Xavier Claessens 7 years ago
parent 5af2717e76
commit 37067a53c4
  1. 10
      mesonbuild/mconf.py
  2. 139
      mesonbuild/mesonmain.py
  3. 8
      mesonbuild/minit.py
  4. 12
      mesonbuild/minstall.py
  5. 8
      mesonbuild/mintro.py
  6. 34
      mesonbuild/msetup.py
  7. 14
      mesonbuild/mtest.py
  8. 9
      mesonbuild/rewriter.py
  9. 6
      mesonbuild/wrap/wraptool.py
  10. 4
      run_project_tests.py
  11. 2
      run_tests.py

@ -13,17 +13,13 @@
# limitations under the License. # limitations under the License.
import os import os
import argparse
from . import (coredata, mesonlib, build) from . import (coredata, mesonlib, build)
def buildparser(): def add_arguments(parser):
parser = argparse.ArgumentParser(prog='meson configure')
coredata.register_builtin_arguments(parser) coredata.register_builtin_arguments(parser)
parser.add_argument('builddir', nargs='?', default='.') parser.add_argument('builddir', nargs='?', default='.')
parser.add_argument('--clearcache', action='store_true', default=False, parser.add_argument('--clearcache', action='store_true', default=False,
help='Clear cached state (e.g. found dependencies)') help='Clear cached state (e.g. found dependencies)')
return parser
class ConfException(mesonlib.MesonException): class ConfException(mesonlib.MesonException):
@ -149,9 +145,7 @@ class Conf:
self.print_options('Testing options', test_options) self.print_options('Testing options', test_options)
def run(args): def run(options):
args = mesonlib.expand_arguments(args)
options = buildparser().parse_args(args)
coredata.parse_cmd_line_options(options) coredata.parse_cmd_line_options(options)
builddir = os.path.abspath(os.path.realpath(options.builddir)) builddir = os.path.abspath(os.path.realpath(options.builddir))
try: try:

@ -15,11 +15,97 @@
import sys import sys
import os.path import os.path
import importlib import importlib
import traceback
import argparse
from . import mesonlib from . import mesonlib
from . import mlog from . import mlog
from . import mconf, minit, minstall, mintro, msetup, mtest, rewriter
from .mesonlib import MesonException from .mesonlib import MesonException
from .environment import detect_msys2_arch from .environment import detect_msys2_arch
from .wrap import wraptool
class CommandLineParser:
def __init__(self):
self.commands = {}
self.parser = argparse.ArgumentParser(prog='meson')
self.subparsers = self.parser.add_subparsers(title='Commands',
description='If no command is specified it defaults to setup command.')
self.add_command('setup', msetup.add_arguments, msetup.run,
help='Configure the project')
self.add_command('configure', mconf.add_arguments, mconf.run,
help='Change project options',)
self.add_command('install', minstall.add_arguments, minstall.run,
help='Install the project')
self.add_command('introspect', mintro.add_arguments, mintro.run,
help='Introspect project')
self.add_command('init', minit.add_arguments, minit.run,
help='Create a new project')
self.add_command('test', mtest.add_arguments, mtest.run,
help='Run tests')
self.add_command('rewrite', rewriter.add_arguments, rewriter.run,
help='Edit project files')
self.add_command('wrap', wraptool.add_arguments, wraptool.run,
help='Wrap tools')
self.add_command('runpython', self.add_runpython_arguments, self.run_runpython_command,
help='Run a python script')
self.add_command('help', self.add_help_arguments, self.run_help_command,
help='Print help of a subcommand')
def add_command(self, name, add_arguments_func, run_func, help):
p = self.subparsers.add_parser(name, help=help)
add_arguments_func(p)
p.set_defaults(run_func=run_func)
self.commands[name] = p
def add_runpython_arguments(self, parser):
parser.add_argument('script_file')
parser.add_argument('script_args', nargs=argparse.REMAINDER)
def run_runpython_command(self, options):
import runpy
sys.argv[1:] = options.script_args
runpy.run_path(options.script_file, run_name='__main__')
return 0
def add_help_arguments(self, parser):
parser.add_argument('command', nargs='?')
def run_help_command(self, options):
if options.command:
self.commands[options.command].print_help()
else:
self.parser.print_help()
return 0
def run(self, args):
# If first arg is not a known command, assume user wants to run the setup
# command.
known_commands = list(self.commands.keys()) + ['-h', '--help']
if len(args) == 0 or args[0] not in known_commands:
args = ['setup'] + args
args = mesonlib.expand_arguments(args)
options = self.parser.parse_args(args)
try:
return options.run_func(options)
except MesonException as e:
mlog.exception(e)
logfile = mlog.shutdown()
if logfile is not None:
mlog.log("\nA full log can be found at", mlog.bold(logfile))
if os.environ.get('MESON_FORCE_BACKTRACE'):
raise
return 1
except Exception as e:
if os.environ.get('MESON_FORCE_BACKTRACE'):
raise
traceback.print_exc()
return 2
finally:
mlog.shutdown()
def run_script_command(script_name, script_args): def run_script_command(script_name, script_args):
# Map script name to module name for those that doesn't match # Map script name to module name for those that doesn't match
@ -50,6 +136,7 @@ def run(original_args, mainfile):
print('You have python %s.' % sys.version) print('You have python %s.' % sys.version)
print('Please update your environment') print('Please update your environment')
return 1 return 1
# https://github.com/mesonbuild/meson/issues/3653 # https://github.com/mesonbuild/meson/issues/3653
if sys.platform.lower() == 'msys': if sys.platform.lower() == 'msys':
mlog.error('This python3 seems to be msys/python on MSYS2 Windows, which is known to have path semantics incompatible with Meson') mlog.error('This python3 seems to be msys/python on MSYS2 Windows, which is known to have path semantics incompatible with Meson')
@ -75,57 +162,7 @@ def run(original_args, mainfile):
else: else:
return run_script_command(args[1], args[2:]) return run_script_command(args[1], args[2:])
if len(args) > 0: return CommandLineParser().run(args)
# First check if we want to run a subcommand.
cmd_name = args[0]
remaining_args = args[1:]
# "help" is a special case: Since printing of the help may be
# delegated to a subcommand, we edit cmd_name before executing
# the rest of the logic here.
if cmd_name == 'help':
remaining_args += ['--help']
args = remaining_args
cmd_name = args[0]
if cmd_name == 'test':
from . import mtest
return mtest.run(remaining_args)
elif cmd_name == 'install':
from . import minstall
return minstall.run(remaining_args)
elif cmd_name == 'introspect':
from . import mintro
return mintro.run(remaining_args)
elif cmd_name == 'rewrite':
from . import rewriter
return rewriter.run(remaining_args)
elif cmd_name == 'configure':
try:
from . import mconf
return mconf.run(remaining_args)
except MesonException as e:
mlog.exception(e)
sys.exit(1)
elif cmd_name == 'wrap':
from .wrap import wraptool
return wraptool.run(remaining_args)
elif cmd_name == 'init':
from . import minit
return minit.run(remaining_args)
elif cmd_name == 'runpython':
import runpy
script_file = remaining_args[0]
sys.argv[1:] = remaining_args[1:]
runpy.run_path(script_file, run_name='__main__')
sys.exit(0)
else:
# If cmd_name is not a known command, assume user wants to run the
# setup command.
from . import msetup
if cmd_name != 'setup':
remaining_args = args
return msetup.run(remaining_args)
return 0
def main(): def main():
# Always resolve the command path so Ninja can find it for regen, tests, etc. # Always resolve the command path so Ninja can find it for regen, tests, etc.

@ -14,7 +14,7 @@
"""Code that creates simple startup projects.""" """Code that creates simple startup projects."""
import os, sys, argparse, re, shutil, subprocess import os, sys, re, shutil, subprocess
from glob import glob from glob import glob
from mesonbuild import mesonlib from mesonbuild import mesonlib
from mesonbuild.environment import detect_ninja from mesonbuild.environment import detect_ninja
@ -425,8 +425,7 @@ def create_meson_build(options):
open('meson.build', 'w').write(content) open('meson.build', 'w').write(content)
print('Generated meson.build file:\n\n' + content) print('Generated meson.build file:\n\n' + content)
def run(args): def add_arguments(parser):
parser = argparse.ArgumentParser(prog='meson')
parser.add_argument("srcfiles", metavar="sourcefile", nargs="*", parser.add_argument("srcfiles", metavar="sourcefile", nargs="*",
help="source files. default: all recognized files in current directory") help="source files. default: all recognized files in current directory")
parser.add_argument("-n", "--name", help="project name. default: name of current directory") parser.add_argument("-n", "--name", help="project name. default: name of current directory")
@ -441,7 +440,8 @@ def run(args):
parser.add_argument('--type', default='executable', parser.add_argument('--type', default='executable',
choices=['executable', 'library']) choices=['executable', 'library'])
parser.add_argument('--version', default='0.1') parser.add_argument('--version', default='0.1')
options = parser.parse_args(args)
def run(options):
if len(glob('*')) == 0: if len(glob('*')) == 0:
autodetect_options(options, sample=True) autodetect_options(options, sample=True)
if not options.language: if not options.language:

@ -14,7 +14,6 @@
import sys, pickle, os, shutil, subprocess, gzip, errno import sys, pickle, os, shutil, subprocess, gzip, errno
import shlex import shlex
import argparse
from glob import glob from glob import glob
from .scripts import depfixer from .scripts import depfixer
from .scripts import destdir_join from .scripts import destdir_join
@ -33,15 +32,13 @@ build definitions so that it will not break when the change happens.'''
selinux_updates = [] selinux_updates = []
def buildparser(): def add_arguments(parser):
parser = argparse.ArgumentParser(prog='meson install')
parser.add_argument('-C', default='.', dest='wd', parser.add_argument('-C', default='.', dest='wd',
help='directory to cd into before running') help='directory to cd into before running')
parser.add_argument('--no-rebuild', default=False, action='store_true', parser.add_argument('--no-rebuild', default=False, action='store_true',
help='Do not rebuild before installing.') help='Do not rebuild before installing.')
parser.add_argument('--only-changed', default=False, action='store_true', parser.add_argument('--only-changed', default=False, action='store_true',
help='Only overwrite files that are older than the copied file.') help='Only overwrite files that are older than the copied file.')
return parser
class DirMaker: class DirMaker:
def __init__(self, lf): def __init__(self, lf):
@ -501,9 +498,7 @@ class Installer:
else: else:
raise raise
def run(args): def run(opts):
parser = buildparser()
opts = parser.parse_args(args)
datafilename = 'meson-private/install.dat' datafilename = 'meson-private/install.dat'
private_dir = os.path.dirname(datafilename) private_dir = os.path.dirname(datafilename)
log_dir = os.path.join(private_dir, '../meson-logs') log_dir = os.path.join(private_dir, '../meson-logs')
@ -520,6 +515,3 @@ def run(args):
append_to_log(lf, '# Does not contain files installed by custom scripts.') append_to_log(lf, '# Does not contain files installed by custom scripts.')
installer.do_install(datafilename) installer.do_install(datafilename)
return 0 return 0
if __name__ == '__main__':
sys.exit(run(sys.argv[1:]))

@ -23,12 +23,10 @@ import json
from . import build, mtest, coredata as cdata from . import build, mtest, coredata as cdata
from . import mesonlib from . import mesonlib
from .backend import ninjabackend from .backend import ninjabackend
import argparse
import sys, os import sys, os
import pathlib import pathlib
def buildparser(): def add_arguments(parser):
parser = argparse.ArgumentParser(prog='meson introspect')
parser.add_argument('--targets', action='store_true', dest='list_targets', default=False, parser.add_argument('--targets', action='store_true', dest='list_targets', default=False,
help='List top level targets.') help='List top level targets.')
parser.add_argument('--installed', action='store_true', dest='list_installed', default=False, parser.add_argument('--installed', action='store_true', dest='list_installed', default=False,
@ -48,7 +46,6 @@ def buildparser():
parser.add_argument('--projectinfo', action='store_true', dest='projectinfo', default=False, parser.add_argument('--projectinfo', action='store_true', dest='projectinfo', default=False,
help='Information about projects.') help='Information about projects.')
parser.add_argument('builddir', nargs='?', default='.', help='The build directory') parser.add_argument('builddir', nargs='?', default='.', help='The build directory')
return parser
def determine_installed_path(target, installdata): def determine_installed_path(target, installdata):
install_target = None install_target = None
@ -206,9 +203,8 @@ def list_projinfo(builddata):
result['subprojects'] = subprojects result['subprojects'] = subprojects
print(json.dumps(result)) print(json.dumps(result))
def run(args): def run(options):
datadir = 'meson-private' datadir = 'meson-private'
options = buildparser().parse_args(args)
if options.builddir is not None: if options.builddir is not None:
datadir = os.path.join(options.builddir, datadir) datadir = os.path.join(options.builddir, datadir)
if not os.path.isdir(datadir): if not os.path.isdir(datadir):

@ -19,7 +19,6 @@ import os.path
import platform import platform
import cProfile as profile import cProfile as profile
import argparse import argparse
import traceback
from . import environment, interpreter, mesonlib from . import environment, interpreter, mesonlib
from . import build from . import build
@ -27,8 +26,7 @@ from . import mlog, coredata
from .mesonlib import MesonException from .mesonlib import MesonException
from .wrap import WrapMode from .wrap import WrapMode
def buildparser(): def add_arguments(parser):
parser = argparse.ArgumentParser(prog='meson')
coredata.register_builtin_arguments(parser) coredata.register_builtin_arguments(parser)
parser.add_argument('--cross-file', default=None, parser.add_argument('--cross-file', default=None,
help='File describing cross compilation environment.') help='File describing cross compilation environment.')
@ -48,7 +46,6 @@ def buildparser():
'is not working.') 'is not working.')
parser.add_argument('builddir', nargs='?', default=None) parser.add_argument('builddir', nargs='?', default=None)
parser.add_argument('sourcedir', nargs='?', default=None) parser.add_argument('sourcedir', nargs='?', default=None)
return parser
def wrapmodetype(string): def wrapmodetype(string):
try: try:
@ -193,35 +190,8 @@ class MesonApp:
os.unlink(cdf) os.unlink(cdf)
raise raise
def run(args): def run(options):
args = mesonlib.expand_arguments(args)
options = buildparser().parse_args(args)
coredata.parse_cmd_line_options(options) coredata.parse_cmd_line_options(options)
try:
app = MesonApp(options) app = MesonApp(options)
except Exception as e:
# Log directory does not exist, so just print
# to stdout.
print('Error during basic setup:\n')
print(e)
return 1
try:
app.generate() app.generate()
except Exception as e:
if isinstance(e, MesonException):
mlog.exception(e)
# Path to log file
mlog.shutdown()
logfile = os.path.join(app.build_dir, environment.Environment.log_dir, mlog.log_fname)
mlog.log("\nA full log can be found at", mlog.bold(logfile))
if os.environ.get('MESON_FORCE_BACKTRACE'):
raise
return 1
else:
if os.environ.get('MESON_FORCE_BACKTRACE'):
raise
traceback.print_exc()
return 2
finally:
mlog.shutdown()
return 0 return 0

@ -60,8 +60,7 @@ def determine_worker_count():
num_workers = 1 num_workers = 1
return num_workers return num_workers
def buildparser(): def add_arguments(parser):
parser = argparse.ArgumentParser(prog='meson test')
parser.add_argument('--repeat', default=1, dest='repeat', type=int, parser.add_argument('--repeat', default=1, dest='repeat', type=int,
help='Number of times to run the tests.') help='Number of times to run the tests.')
parser.add_argument('--no-rebuild', default=False, action='store_true', parser.add_argument('--no-rebuild', default=False, action='store_true',
@ -102,7 +101,6 @@ def buildparser():
help='Arguments to pass to the specified test(s) or all tests') help='Arguments to pass to the specified test(s) or all tests')
parser.add_argument('args', nargs='*', parser.add_argument('args', nargs='*',
help='Optional list of tests to run') help='Optional list of tests to run')
return parser
def returncode_to_status(retcode): def returncode_to_status(retcode):
@ -737,9 +735,7 @@ def rebuild_all(wd):
return True return True
def run(args): def run(options):
options = buildparser().parse_args(args)
if options.benchmark: if options.benchmark:
options.num_processes = 1 options.num_processes = 1
@ -784,3 +780,9 @@ def run(args):
else: else:
print(e) print(e)
return 1 return 1
def run_with_args(args):
parser = argparse.ArgumentParser(prog='meson test')
add_arguments(parser)
options = parser.parse_args(args)
return run(options)

@ -27,11 +27,8 @@ import mesonbuild.astinterpreter
from mesonbuild.mesonlib import MesonException from mesonbuild.mesonlib import MesonException
from mesonbuild import mlog from mesonbuild import mlog
import sys, traceback import sys, traceback
import argparse
def buildparser():
parser = argparse.ArgumentParser(prog='meson rewrite')
def add_arguments(parser):
parser.add_argument('--sourcedir', default='.', parser.add_argument('--sourcedir', default='.',
help='Path to source directory.') help='Path to source directory.')
parser.add_argument('--target', default=None, parser.add_argument('--target', default=None,
@ -39,10 +36,8 @@ def buildparser():
parser.add_argument('--filename', default=None, parser.add_argument('--filename', default=None,
help='Name of source file to add or remove to target.') help='Name of source file to add or remove to target.')
parser.add_argument('commands', nargs='+') parser.add_argument('commands', nargs='+')
return parser
def run(args): def run(options):
options = buildparser().parse_args(args)
if options.target is None or options.filename is None: if options.target is None or options.filename is None:
sys.exit("Must specify both target and filename.") sys.exit("Must specify both target and filename.")
print('This tool is highly experimental, use with care.') print('This tool is highly experimental, use with care.')

@ -16,7 +16,6 @@ import json
import sys, os import sys, os
import configparser import configparser
import shutil import shutil
import argparse
from glob import glob from glob import glob
@ -208,9 +207,6 @@ def status(options):
else: else:
print('', name, 'not up to date. Have %s %d, but %s %d is available.' % (current_branch, current_revision, latest_branch, latest_revision)) print('', name, 'not up to date. Have %s %d, but %s %d is available.' % (current_branch, current_revision, latest_branch, latest_revision))
def run(args): def run(options):
parser = argparse.ArgumentParser(prog='wraptool')
add_arguments(parser)
options = parser.parse_args(args)
options.wrap_func(options) options.wrap_func(options)
return 0 return 0

@ -247,12 +247,12 @@ def run_test_inprocess(testdir):
os.chdir(testdir) os.chdir(testdir)
test_log_fname = Path('meson-logs', 'testlog.txt') test_log_fname = Path('meson-logs', 'testlog.txt')
try: try:
returncode_test = mtest.run(['--no-rebuild']) returncode_test = mtest.run_with_args(['--no-rebuild'])
if test_log_fname.exists(): if test_log_fname.exists():
test_log = test_log_fname.open(errors='ignore').read() test_log = test_log_fname.open(errors='ignore').read()
else: else:
test_log = '' test_log = ''
returncode_benchmark = mtest.run(['--no-rebuild', '--benchmark', '--logbase', 'benchmarklog']) returncode_benchmark = mtest.run_with_args(['--no-rebuild', '--benchmark', '--logbase', 'benchmarklog'])
finally: finally:
sys.stdout = old_stdout sys.stdout = old_stdout
sys.stderr = old_stderr sys.stderr = old_stderr

@ -181,7 +181,7 @@ def run_mtest_inprocess(commandlist):
old_stderr = sys.stderr old_stderr = sys.stderr
sys.stderr = mystderr = StringIO() sys.stderr = mystderr = StringIO()
try: try:
returncode = mtest.run(commandlist) returncode = mtest.run_with_args(commandlist)
finally: finally:
sys.stdout = old_stdout sys.stdout = old_stdout
sys.stderr = old_stderr sys.stderr = old_stderr

Loading…
Cancel
Save