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

@ -15,11 +15,97 @@
import sys
import os.path
import importlib
import traceback
import argparse
from . import mesonlib
from . import mlog
from . import mconf, minit, minstall, mintro, msetup, mtest, rewriter
from .mesonlib import MesonException
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):
# 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('Please update your environment')
return 1
# https://github.com/mesonbuild/meson/issues/3653
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')
@ -75,57 +162,7 @@ def run(original_args, mainfile):
else:
return run_script_command(args[1], args[2:])
if len(args) > 0:
# 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
return CommandLineParser().run(args)
def main():
# Always resolve the command path so Ninja can find it for regen, tests, etc.

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

@ -14,7 +14,6 @@
import sys, pickle, os, shutil, subprocess, gzip, errno
import shlex
import argparse
from glob import glob
from .scripts import depfixer
from .scripts import destdir_join
@ -33,15 +32,13 @@ build definitions so that it will not break when the change happens.'''
selinux_updates = []
def buildparser():
parser = argparse.ArgumentParser(prog='meson install')
def add_arguments(parser):
parser.add_argument('-C', default='.', dest='wd',
help='directory to cd into before running')
parser.add_argument('--no-rebuild', default=False, action='store_true',
help='Do not rebuild before installing.')
parser.add_argument('--only-changed', default=False, action='store_true',
help='Only overwrite files that are older than the copied file.')
return parser
class DirMaker:
def __init__(self, lf):
@ -501,9 +498,7 @@ class Installer:
else:
raise
def run(args):
parser = buildparser()
opts = parser.parse_args(args)
def run(opts):
datafilename = 'meson-private/install.dat'
private_dir = os.path.dirname(datafilename)
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.')
installer.do_install(datafilename)
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 mesonlib
from .backend import ninjabackend
import argparse
import sys, os
import pathlib
def buildparser():
parser = argparse.ArgumentParser(prog='meson introspect')
def add_arguments(parser):
parser.add_argument('--targets', action='store_true', dest='list_targets', default=False,
help='List top level targets.')
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,
help='Information about projects.')
parser.add_argument('builddir', nargs='?', default='.', help='The build directory')
return parser
def determine_installed_path(target, installdata):
install_target = None
@ -206,9 +203,8 @@ def list_projinfo(builddata):
result['subprojects'] = subprojects
print(json.dumps(result))
def run(args):
def run(options):
datadir = 'meson-private'
options = buildparser().parse_args(args)
if options.builddir is not None:
datadir = os.path.join(options.builddir, datadir)
if not os.path.isdir(datadir):

@ -19,7 +19,6 @@ import os.path
import platform
import cProfile as profile
import argparse
import traceback
from . import environment, interpreter, mesonlib
from . import build
@ -27,8 +26,7 @@ from . import mlog, coredata
from .mesonlib import MesonException
from .wrap import WrapMode
def buildparser():
parser = argparse.ArgumentParser(prog='meson')
def add_arguments(parser):
coredata.register_builtin_arguments(parser)
parser.add_argument('--cross-file', default=None,
help='File describing cross compilation environment.')
@ -48,7 +46,6 @@ def buildparser():
'is not working.')
parser.add_argument('builddir', nargs='?', default=None)
parser.add_argument('sourcedir', nargs='?', default=None)
return parser
def wrapmodetype(string):
try:
@ -193,35 +190,8 @@ class MesonApp:
os.unlink(cdf)
raise
def run(args):
args = mesonlib.expand_arguments(args)
options = buildparser().parse_args(args)
def run(options):
coredata.parse_cmd_line_options(options)
try:
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()
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()
app = MesonApp(options)
app.generate()
return 0

@ -60,8 +60,7 @@ def determine_worker_count():
num_workers = 1
return num_workers
def buildparser():
parser = argparse.ArgumentParser(prog='meson test')
def add_arguments(parser):
parser.add_argument('--repeat', default=1, dest='repeat', type=int,
help='Number of times to run the tests.')
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')
parser.add_argument('args', nargs='*',
help='Optional list of tests to run')
return parser
def returncode_to_status(retcode):
@ -737,9 +735,7 @@ def rebuild_all(wd):
return True
def run(args):
options = buildparser().parse_args(args)
def run(options):
if options.benchmark:
options.num_processes = 1
@ -784,3 +780,9 @@ def run(args):
else:
print(e)
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 import mlog
import sys, traceback
import argparse
def buildparser():
parser = argparse.ArgumentParser(prog='meson rewrite')
def add_arguments(parser):
parser.add_argument('--sourcedir', default='.',
help='Path to source directory.')
parser.add_argument('--target', default=None,
@ -39,10 +36,8 @@ def buildparser():
parser.add_argument('--filename', default=None,
help='Name of source file to add or remove to target.')
parser.add_argument('commands', nargs='+')
return parser
def run(args):
options = buildparser().parse_args(args)
def run(options):
if options.target is None or options.filename is None:
sys.exit("Must specify both target and filename.")
print('This tool is highly experimental, use with care.')

@ -16,7 +16,6 @@ import json
import sys, os
import configparser
import shutil
import argparse
from glob import glob
@ -208,9 +207,6 @@ def status(options):
else:
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):
parser = argparse.ArgumentParser(prog='wraptool')
add_arguments(parser)
options = parser.parse_args(args)
def run(options):
options.wrap_func(options)
return 0

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

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

Loading…
Cancel
Save