summaryrefslogtreecommitdiffstats
path: root/mesonbuild/mesonmain.py
diff options
context:
space:
mode:
Diffstat (limited to 'mesonbuild/mesonmain.py')
-rw-r--r--mesonbuild/mesonmain.py290
1 files changed, 290 insertions, 0 deletions
diff --git a/mesonbuild/mesonmain.py b/mesonbuild/mesonmain.py
new file mode 100644
index 0000000..4b4f88c
--- /dev/null
+++ b/mesonbuild/mesonmain.py
@@ -0,0 +1,290 @@
+# Copyright 2012-2021 The Meson development team
+
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+
+# http://www.apache.org/licenses/LICENSE-2.0
+
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+from __future__ import annotations
+
+# Work around some pathlib bugs...
+
+from . import _pathlib
+import sys
+sys.modules['pathlib'] = _pathlib
+
+# This file is an entry point for all commands, including scripts. Include the
+# strict minimum python modules for performance reasons.
+import os.path
+import platform
+import importlib
+import argparse
+
+from .utils.core import MesonException, MesonBugException
+from . import mlog
+
+def errorhandler(e, command):
+ import traceback
+ if isinstance(e, MesonException):
+ 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 e
+ return 1
+ else:
+ # We assume many types of traceback are Meson logic bugs, but most
+ # particularly anything coming from the interpreter during `setup`.
+ # Some things definitely aren't:
+ # - PermissionError is always a problem in the user environment
+ # - runpython doesn't run Meson's own code, even though it is
+ # dispatched by our run()
+ if os.environ.get('MESON_FORCE_BACKTRACE'):
+ raise e
+ traceback.print_exc()
+
+ if command == 'runpython':
+ return 2
+ elif isinstance(e, OSError):
+ mlog.exception("Unhandled python OSError. This is probably not a Meson bug, "
+ "but an issue with your build environment.")
+ return e.errno
+ else: # Exception
+ msg = 'Unhandled python exception'
+ if all(getattr(e, a, None) is not None for a in ['file', 'lineno', 'colno']):
+ e = MesonBugException(msg, e.file, e.lineno, e.colno) # type: ignore
+ else:
+ e = MesonBugException(msg)
+ mlog.exception(e)
+ return 2
+
+# Note: when adding arguments, please also add them to the completion
+# scripts in $MESONSRC/data/shell-completions/
+class CommandLineParser:
+ def __init__(self):
+ # only import these once we do full argparse processing
+ from . import mconf, mdist, minit, minstall, mintro, msetup, mtest, rewriter, msubprojects, munstable_coredata, mcompile, mdevenv
+ from .scripts import env2mfile
+ from .wrap import wraptool
+ import shutil
+
+ self.term_width = shutil.get_terminal_size().columns
+ self.formatter = lambda prog: argparse.HelpFormatter(prog, max_help_position=int(self.term_width / 2), width=self.term_width)
+
+ self.commands = {}
+ self.hidden_commands = []
+ self.parser = argparse.ArgumentParser(prog='meson', formatter_class=self.formatter)
+ self.subparsers = self.parser.add_subparsers(title='Commands', dest='command',
+ description='If no command is specified it defaults to setup command.')
+ self.add_command('setup', msetup.add_arguments, msetup.run,
+ help_msg='Configure the project')
+ self.add_command('configure', mconf.add_arguments, mconf.run,
+ help_msg='Change project options',)
+ self.add_command('dist', mdist.add_arguments, mdist.run,
+ help_msg='Generate release archive',)
+ self.add_command('install', minstall.add_arguments, minstall.run,
+ help_msg='Install the project')
+ self.add_command('introspect', mintro.add_arguments, mintro.run,
+ help_msg='Introspect project')
+ self.add_command('init', minit.add_arguments, minit.run,
+ help_msg='Create a new project')
+ self.add_command('test', mtest.add_arguments, mtest.run,
+ help_msg='Run tests')
+ self.add_command('wrap', wraptool.add_arguments, wraptool.run,
+ help_msg='Wrap tools')
+ self.add_command('subprojects', msubprojects.add_arguments, msubprojects.run,
+ help_msg='Manage subprojects')
+ self.add_command('rewrite', lambda parser: rewriter.add_arguments(parser, self.formatter), rewriter.run,
+ help_msg='Modify the project definition')
+ self.add_command('compile', mcompile.add_arguments, mcompile.run,
+ help_msg='Build the project')
+ self.add_command('devenv', mdevenv.add_arguments, mdevenv.run,
+ help_msg='Run commands in developer environment')
+ self.add_command('env2mfile', env2mfile.add_arguments, env2mfile.run,
+ help_msg='Convert current environment to a cross or native file')
+ # Add new commands above this line to list them in help command
+ self.add_command('help', self.add_help_arguments, self.run_help_command,
+ help_msg='Print help of a subcommand')
+
+ # Hidden commands
+ self.add_command('runpython', self.add_runpython_arguments, self.run_runpython_command,
+ help_msg=argparse.SUPPRESS)
+ self.add_command('unstable-coredata', munstable_coredata.add_arguments, munstable_coredata.run,
+ help_msg=argparse.SUPPRESS)
+
+ def add_command(self, name, add_arguments_func, run_func, help_msg, aliases=None):
+ aliases = aliases or []
+ # FIXME: Cannot have hidden subparser:
+ # https://bugs.python.org/issue22848
+ if help_msg == argparse.SUPPRESS:
+ p = argparse.ArgumentParser(prog='meson ' + name, formatter_class=self.formatter)
+ self.hidden_commands.append(name)
+ else:
+ p = self.subparsers.add_parser(name, help=help_msg, aliases=aliases, formatter_class=self.formatter)
+ add_arguments_func(p)
+ p.set_defaults(run_func=run_func)
+ for i in [name] + aliases:
+ self.commands[i] = p
+
+ def add_runpython_arguments(self, parser: argparse.ArgumentParser):
+ parser.add_argument('-c', action='store_true', dest='eval_arg', default=False)
+ parser.add_argument('--version', action='version', version=platform.python_version())
+ parser.add_argument('script_file')
+ parser.add_argument('script_args', nargs=argparse.REMAINDER)
+
+ def run_runpython_command(self, options):
+ sys.argv[1:] = options.script_args
+ if options.eval_arg:
+ exec(options.script_file)
+ else:
+ import runpy
+ sys.path.insert(0, os.path.dirname(options.script_file))
+ runpy.run_path(options.script_file, run_name='__main__')
+ return 0
+
+ def add_help_arguments(self, parser):
+ parser.add_argument('command', nargs='?', choices=list(self.commands.keys()))
+
+ 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):
+ implicit_setup_command_notice = False
+ # 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 not args or args[0] not in known_commands:
+ implicit_setup_command_notice = True
+ args = ['setup'] + args
+
+ # Hidden commands have their own parser instead of using the global one
+ if args[0] in self.hidden_commands:
+ command = args[0]
+ parser = self.commands[command]
+ args = args[1:]
+ else:
+ parser = self.parser
+ command = None
+
+ from . import mesonlib
+ args = mesonlib.expand_arguments(args)
+ options = parser.parse_args(args)
+
+ if command is None:
+ command = options.command
+
+ # Bump the version here in order to add a pre-exit warning that we are phasing out
+ # support for old python. If this is already the oldest supported version, then
+ # this can never be true and does nothing.
+ pending_python_deprecation_notice = \
+ command in {'setup', 'compile', 'test', 'install'} and sys.version_info < (3, 7)
+
+ try:
+ return options.run_func(options)
+ except Exception as e:
+ return errorhandler(e, command)
+ finally:
+ if implicit_setup_command_notice:
+ mlog.warning('Running the setup command as `meson [options]` instead of '
+ '`meson setup [options]` is ambiguous and deprecated.', fatal=False)
+ if pending_python_deprecation_notice:
+ mlog.notice('You are using Python 3.6 which is EOL. Starting with v0.62.0, '
+ 'Meson will require Python 3.7 or newer', fatal=False)
+ mlog.shutdown()
+
+def run_script_command(script_name, script_args):
+ # Map script name to module name for those that doesn't match
+ script_map = {'exe': 'meson_exe',
+ 'install': 'meson_install',
+ 'delsuffix': 'delwithsuffix',
+ 'gtkdoc': 'gtkdochelper',
+ 'hotdoc': 'hotdochelper',
+ 'regencheck': 'regen_checker'}
+ module_name = script_map.get(script_name, script_name)
+
+ try:
+ module = importlib.import_module('mesonbuild.scripts.' + module_name)
+ except ModuleNotFoundError as e:
+ mlog.exception(e)
+ return 1
+
+ try:
+ return module.run(script_args)
+ except MesonException as e:
+ mlog.error(f'Error in {script_name} helper script:')
+ mlog.exception(e)
+ return 1
+
+def ensure_stdout_accepts_unicode():
+ if sys.stdout.encoding and not sys.stdout.encoding.upper().startswith('UTF-'):
+ sys.stdout.reconfigure(errors='surrogateescape')
+
+def set_meson_command(mainfile):
+ # Set the meson command that will be used to run scripts and so on
+ from . import mesonlib
+ mesonlib.set_meson_command(mainfile)
+
+def run(original_args, mainfile):
+ if sys.version_info >= (3, 10) and os.environ.get('MESON_RUNNING_IN_PROJECT_TESTS'):
+ # workaround for https://bugs.python.org/issue34624
+ import warnings
+ warnings.filterwarnings('error', category=EncodingWarning, module='mesonbuild')
+ # python 3.11 adds a warning that in 3.15, UTF-8 mode will be default.
+ # This is fantastic news, we'd love that. Less fantastic: this warning is silly,
+ # we *want* these checks to be affected. Plus, the recommended alternative API
+ # would (in addition to warning people when UTF-8 mode removed the problem) also
+ # require using a minimum python version of 3.11 (in which the warning was added)
+ # or add verbose if/else soup.
+ warnings.filterwarnings('ignore', message="UTF-8 Mode affects .*getpreferredencoding", category=EncodingWarning)
+
+ # Meson gets confused if stdout can't output Unicode, if the
+ # locale isn't Unicode, just force stdout to accept it. This tries
+ # to emulate enough of PEP 540 to work elsewhere.
+ ensure_stdout_accepts_unicode()
+
+ # https://github.com/mesonbuild/meson/issues/3653
+ if sys.platform == 'cygwin' and os.environ.get('MSYSTEM', '') not in ['MSYS', '']:
+ mlog.error('This python3 seems to be msys/python on MSYS2 Windows, but you are in a MinGW environment')
+ mlog.error('Please install and use mingw-w64-x86_64-python3 and/or mingw-w64-x86_64-meson with Pacman')
+ return 2
+
+ args = original_args[:]
+
+ # Special handling of internal commands called from backends, they don't
+ # need to go through argparse.
+ if len(args) >= 2 and args[0] == '--internal':
+ if args[1] == 'regenerate':
+ set_meson_command(mainfile)
+ from . import msetup
+ try:
+ return msetup.run(['--reconfigure'] + args[2:])
+ except Exception as e:
+ return errorhandler(e, 'setup')
+ else:
+ return run_script_command(args[1], args[2:])
+
+ set_meson_command(mainfile)
+ return CommandLineParser().run(args)
+
+def main():
+ # Always resolve the command path so Ninja can find it for regen, tests, etc.
+ if 'meson.exe' in sys.executable:
+ assert os.path.isabs(sys.executable)
+ launcher = sys.executable
+ else:
+ launcher = os.path.realpath(sys.argv[0])
+ return run(sys.argv[1:], launcher)
+
+if __name__ == '__main__':
+ sys.exit(main())