|
|
|
# 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):
|
deprecate running "meson builddir" without setup subcommand
This is ambiguous, if the build directory has the same name as a
subcommand then we end up running the subcommand. It also means we have
a hard time adding *new* subcommands, because if it is a popular name of
a build directory then suddenly scripts that try to set up a build
directory end up running a subcommand instead.
The fact that we support this at all is a legacy design. Back in the
day, the "meson" program was for setting up a build directory and all
other tools were their own entry points, e.g. `mesontest` or
`mesonconf`. Then in commit fa278f351fe3d6924b4d1961f77b5b4a36e133f8 we
migrated to the subcommand mechanism. So, for backwards compatibility,
we made those tools print a warning and then invoke `meson <tool>`. We
also made the `meson` tool default to setup.
However, we only warned for the other tools whose entry points were
eventually deleted. We never warned for setup itself, we just continued
to silently default to setup if no tool was provided.
`meson setup` has worked since 0.42, which is 5 years old this week.
It's available essentially everywhere. No one needs to use the old
backwards-compatible invocation method, but it continues to drag down
our ability to innovate. Let's finally do what we should have done a
long time ago, and sunset it.
2 years ago
|
|
|
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:
|
deprecate running "meson builddir" without setup subcommand
This is ambiguous, if the build directory has the same name as a
subcommand then we end up running the subcommand. It also means we have
a hard time adding *new* subcommands, because if it is a popular name of
a build directory then suddenly scripts that try to set up a build
directory end up running a subcommand instead.
The fact that we support this at all is a legacy design. Back in the
day, the "meson" program was for setting up a build directory and all
other tools were their own entry points, e.g. `mesontest` or
`mesonconf`. Then in commit fa278f351fe3d6924b4d1961f77b5b4a36e133f8 we
migrated to the subcommand mechanism. So, for backwards compatibility,
we made those tools print a warning and then invoke `meson <tool>`. We
also made the `meson` tool default to setup.
However, we only warned for the other tools whose entry points were
eventually deleted. We never warned for setup itself, we just continued
to silently default to setup if no tool was provided.
`meson setup` has worked since 0.42, which is 5 years old this week.
It's available essentially everywhere. No one needs to use the old
backwards-compatible invocation method, but it continues to drag down
our ability to innovate. Let's finally do what we should have done a
long time ago, and sunset it.
2 years ago
|
|
|
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:
|
deprecate running "meson builddir" without setup subcommand
This is ambiguous, if the build directory has the same name as a
subcommand then we end up running the subcommand. It also means we have
a hard time adding *new* subcommands, because if it is a popular name of
a build directory then suddenly scripts that try to set up a build
directory end up running a subcommand instead.
The fact that we support this at all is a legacy design. Back in the
day, the "meson" program was for setting up a build directory and all
other tools were their own entry points, e.g. `mesontest` or
`mesonconf`. Then in commit fa278f351fe3d6924b4d1961f77b5b4a36e133f8 we
migrated to the subcommand mechanism. So, for backwards compatibility,
we made those tools print a warning and then invoke `meson <tool>`. We
also made the `meson` tool default to setup.
However, we only warned for the other tools whose entry points were
eventually deleted. We never warned for setup itself, we just continued
to silently default to setup if no tool was provided.
`meson setup` has worked since 0.42, which is 5 years old this week.
It's available essentially everywhere. No one needs to use the old
backwards-compatible invocation method, but it continues to drag down
our ability to innovate. Let's finally do what we should have done a
long time ago, and sunset it.
2 years ago
|
|
|
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 os.environ.get('MESON_SHOW_DEPRECATIONS'):
|
|
|
|
# workaround for https://bugs.python.org/issue34624
|
|
|
|
import warnings
|
|
|
|
for typ in [DeprecationWarning, SyntaxWarning, FutureWarning, PendingDeprecationWarning]:
|
|
|
|
warnings.filterwarnings('error', category=typ, module='mesonbuild')
|
|
|
|
warnings.filterwarnings('ignore', message=".*importlib-resources.*")
|
|
|
|
|
use a more sane check instead of run_custom_lint
Unfortunately, checking for strings without context is exceedingly prone
to false positives, while missing anything that indirectly opens a file.
Python 3.10 has a feature to warn about this though -- and it uses a
runtime check which runs at the same time that the code fails to open
files in the broken Windows locale. Set this up automatically when
running the testsuite.
Sadly, Python's builtin feature to change the warning level, e.g. by
setting EncodingWarning to error at startup, is utterly broken if you
want to limit it to only certain modules. This is tracked in order to be
more efficiently ignored at https://bugs.python.org/issue34624 and
https://github.com/python/cpython/pull/9358
It is also very trigger happy and passing stuff around via environment
variable either messes with the testsuite, or with thirdparty programs
which are implemented in python *such as lots of gnome*, or perhaps
both.
Instead, add runtime code to meson itself, to add a hidden "feature".
In the application source code, running the 'warnings' module, you can
actually get the expected behavior that $PYTHONWARNINGS doesn't have. So
check for a magic testsuite variable every time meson starts up, and if
it does, then go ahead and initialize a warnings filter that makes
EncodingWarning fatal, but *only* when triggered via Meson and not
arbitrary subprocess scripts.
3 years ago
|
|
|
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)
|
use a more sane check instead of run_custom_lint
Unfortunately, checking for strings without context is exceedingly prone
to false positives, while missing anything that indirectly opens a file.
Python 3.10 has a feature to warn about this though -- and it uses a
runtime check which runs at the same time that the code fails to open
files in the broken Windows locale. Set this up automatically when
running the testsuite.
Sadly, Python's builtin feature to change the warning level, e.g. by
setting EncodingWarning to error at startup, is utterly broken if you
want to limit it to only certain modules. This is tracked in order to be
more efficiently ignored at https://bugs.python.org/issue34624 and
https://github.com/python/cpython/pull/9358
It is also very trigger happy and passing stuff around via environment
variable either messes with the testsuite, or with thirdparty programs
which are implemented in python *such as lots of gnome*, or perhaps
both.
Instead, add runtime code to meson itself, to add a hidden "feature".
In the application source code, running the 'warnings' module, you can
actually get the expected behavior that $PYTHONWARNINGS doesn't have. So
check for a magic testsuite variable every time meson starts up, and if
it does, then go ahead and initialize a warnings filter that makes
EncodingWarning fatal, but *only* when triggered via Meson and not
arbitrary subprocess scripts.
3 years ago
|
|
|
|
|
|
|
# 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:
|
do not resolve symlinks when calculating the meson command
We embed the route to executing meson in various cases, most especially
regen rules in build.ninja. And we take care to ensure that it's a
canonicalized path. Although the code has moved around over time, and
adapted in ways both bad and good, the root of the matter really comes
down to commit 69ca8f5b544f700210d9f18613311bcce3c2e37a which notes the
importance of being able to run meson from any location, potentially
not on PATH or anything else.
For this reason, we switched from embedding sys.argv[0] to
os.path.realpath, a very heavy stick indeed. It turns out that that's
not actually a good thing though... simply resolving the absolute path
is enough to ensure we can accurately call meson the same way we
originally did, and it avoids cases where the original way to call meson
is via a stable symlink, and we resolved a hidden location.
Homebrew does this, because the version of a package is embedded into
the install directory. Even the bugfix release. e.g.
```
/opt/homebrew/bin/meson
```
is symlinked to
```
/opt/homebrew/Cellar/meson/1.0.0/bin/meson
```
Since we went beyond absolutizing the path and onwards to canonicalizing
symlinks, we ended up writing the latter to build.ninja, and got a
"command not found" when meson was upgraded to 1.0.1. This was supposed
to work flawlessly, because build directories are compatible across
bugfix releases.
We also get a "command not found" when upgrading to new feature
releases, e.g. 0.64.x to 1.0.0, which is a terrible error message. Meson
explicitly "doesn't support" doing this, we throw a MesonVersionMismatchException
or in some cases warn you and then effectively act like --wipe was given.
But the user is supposed to be informed exactly what the problem is, rather
than getting "command not found".
Since there was never a rationale to get the realpath anyways, downgrade
this to abspath.
Fixes #11520
2 years ago
|
|
|
launcher = os.path.abspath(sys.argv[0])
|
|
|
|
return run(sys.argv[1:], launcher)
|
|
|
|
|
|
|
|
if __name__ == '__main__':
|
|
|
|
sys.exit(main())
|