diff options
29 files changed, 214 insertions, 94 deletions
diff --git a/docs/genrelnotes.py b/docs/genrelnotes.py index 6873019..bad6597 100755 --- a/docs/genrelnotes.py +++ b/docs/genrelnotes.py @@ -20,6 +20,7 @@ import argparse import subprocess import re import shutil +import datetime from pathlib import Path RELNOTE_TEMPLATE = '''--- @@ -73,6 +74,10 @@ def generate(relnotes, to_version, source_dir, output_dir): output.parent.mkdir(exist_ok=True, parents=True) with output.open('w', encoding='utf-8') as ofile: ofile.write(RELNOTE_TEMPLATE.format(title, to_version, title_suffix)) + if not output_dir: + date = datetime.date.today() + date_str = date.strftime("%d %B %Y") + ofile.write(f'Meson {to_version} was released on {date_str}\n') for snippetfile in sorted(Path(source_dir, 'markdown/snippets').glob('*.md')): snippet = snippetfile.read_text(encoding='utf-8') ofile.write(snippet) diff --git a/docs/markdown/Commands.md b/docs/markdown/Commands.md index c328461..4a00c4f 100644 --- a/docs/markdown/Commands.md +++ b/docs/markdown/Commands.md @@ -345,6 +345,8 @@ These variables are set in environment in addition to those set using [[meson.ad schemas is compiled. This is automatically set when using `gnome.compile_schemas()`. Note that this requires GLib >= 2.64 when `gnome.compile_schemas()` is used in more than one directory. +- `QEMU_LD_PREFIX` *Since 1.0.0* is set to the `sys_root` value from cross file + when cross compiling and that property is defined. Since *Since 0.62.0* if bash-completion scripts are being installed and the shell is bash, they will be automatically sourced. diff --git a/docs/markdown/Windows-module.md b/docs/markdown/Windows-module.md index 06b3eb2..3d24d76 100644 --- a/docs/markdown/Windows-module.md +++ b/docs/markdown/Windows-module.md @@ -8,6 +8,7 @@ Windows. ### compile_resources ``` + windows = import('windows') windows.compile_resources(...(string | File | CustomTarget | CustomTargetIndex), args: []string, depend_files: [](string | File), diff --git a/docs/markdown/snippets/compiler_prefix_property_array.md b/docs/markdown/snippets/compiler_prefix_property_array.md new file mode 100644 index 0000000..75b7156 --- /dev/null +++ b/docs/markdown/snippets/compiler_prefix_property_array.md @@ -0,0 +1,18 @@ +## Compiler check functions `prefix` kwargs accepts arrays + +The `prefix` kwarg that most compiler check functions support +now accepts an array in addition to a string. The elements of the +array will be concatenated separated by a newline. + +This makes it more readable to write checks that need multiple headers +to be included: + +```meson +cc.check_header('GL/wglew.h', prefix : ['#include <windows.h>', '#include <GL/glew.h>']) +``` + +instead of + +```meson +cc.check_header('GL/wglew.h', prefix : '#include <windows.h>\n#include <GL/glew.h>']) +``` diff --git a/docs/markdown/snippets/devenv.md b/docs/markdown/snippets/devenv.md new file mode 100644 index 0000000..d8a38ba --- /dev/null +++ b/docs/markdown/snippets/devenv.md @@ -0,0 +1,15 @@ +## Developer environment improvements + +When cross compiling, the developer environment now sets all environment +variables for the HOST machine. It now also sets `QEMU_LD_PREFIX` to the +`sys_root` value from cross file if property is defined. That means that cross +compiled executables can often be run transparently on the build machine, for +example when cross compiling for aarch64 linux from x86_64 linux. + +A new argument `--workdir` has been added, by default it is set to build +directory. For example, `meson devenv -C builddir --workdir .` can be used to +remain in the current dir (often source dir) instead. + +`--dump` now prints shell commands like `FOO="/prepend/path:$FOO:/append/path"`, +using the litteral `$FOO` instead of current value of `FOO` from environment. +This makes easier to evaluate those expressions in a different environment. diff --git a/docs/yaml/objects/compiler.yaml b/docs/yaml/objects/compiler.yaml index e10e8fe..01283cd 100644 --- a/docs/yaml/objects/compiler.yaml +++ b/docs/yaml/objects/compiler.yaml @@ -78,13 +78,15 @@ methods: description: You have found a bug if you can see this! kwargs: prefix: - type: str + type: str | list[str] description: | Used to add `#include`s and other things that are required - for the symbol to be declared. System definitions should be - passed via compiler args (eg: `_GNU_SOURCE` is often required for - some symbols to be exposed on Linux, and it should be passed via - `args` keyword argument). + for the symbol to be declared. Since 1.0.0 an array is accepted + too. When an array is passed, the items are concatenated together + separated by a newline. + System definitions should be passed via compiler args + (eg: `_GNU_SOURCE` is often required for some symbols to be exposed + on Linux, and it should be passed via `args` keyword argument). - name: _no_builtin_args returns: void diff --git a/man/meson.1 b/man/meson.1 index 4205bc9..7e7f486 100644 --- a/man/meson.1 +++ b/man/meson.1 @@ -1,4 +1,4 @@ -.TH MESON "1" "November 2022" "meson 0.64.0" "User Commands" +.TH MESON "1" "December 2022" "meson 1.0.0" "User Commands" .SH NAME meson - a high productivity build system .SH DESCRIPTION diff --git a/mesonbuild/backend/backends.py b/mesonbuild/backend/backends.py index 747d80e..27004f8 100644 --- a/mesonbuild/backend/backends.py +++ b/mesonbuild/backend/backends.py @@ -1108,10 +1108,7 @@ class Backend: break is_cross = self.environment.is_cross_build(test_for_machine) - if is_cross and self.environment.need_exe_wrapper(): - exe_wrapper = self.environment.get_exe_wrapper() - else: - exe_wrapper = None + exe_wrapper = self.environment.get_exe_wrapper() machine = self.environment.machines[exe.for_machine] if machine.is_windows() or machine.is_cygwin(): extra_bdeps: T.List[T.Union[build.BuildTarget, build.CustomTarget]] = [] @@ -1850,14 +1847,12 @@ class Backend: env = build.EnvironmentVariables() extra_paths = set() library_paths = set() + build_machine = self.environment.machines[MachineChoice.BUILD] host_machine = self.environment.machines[MachineChoice.HOST] - need_exe_wrapper = self.environment.need_exe_wrapper() - need_wine = need_exe_wrapper and host_machine.is_windows() + need_wine = not build_machine.is_windows() and host_machine.is_windows() for t in self.build.get_targets().values(): - cross_built = not self.environment.machines.matches_build_machine(t.for_machine) - can_run = not cross_built or not need_exe_wrapper or need_wine in_default_dir = t.should_install() and not t.get_install_dir()[2] - if not can_run or not in_default_dir: + if t.for_machine != MachineChoice.HOST or not in_default_dir: continue tdir = os.path.join(self.environment.get_build_dir(), self.get_target_dir(t)) if isinstance(t, build.Executable): @@ -1874,18 +1869,23 @@ class Backend: # LD_LIBRARY_PATH. This allows running system applications using # that library. library_paths.add(tdir) + if need_wine: + # Executable paths should be in both PATH and WINEPATH. + # - Having them in PATH makes bash completion find it, + # and make running "foo.exe" find it when wine-binfmt is installed. + # - Having them in WINEPATH makes "wine foo.exe" find it. + library_paths.update(extra_paths) if library_paths: - if host_machine.is_windows() or host_machine.is_cygwin(): + if need_wine: + env.prepend('WINEPATH', list(library_paths), separator=';') + elif host_machine.is_windows() or host_machine.is_cygwin(): extra_paths.update(library_paths) elif host_machine.is_darwin(): env.prepend('DYLD_LIBRARY_PATH', list(library_paths)) else: env.prepend('LD_LIBRARY_PATH', list(library_paths)) if extra_paths: - if need_wine: - env.prepend('WINEPATH', list(extra_paths), separator=';') - else: - env.prepend('PATH', list(extra_paths)) + env.prepend('PATH', list(extra_paths)) return env def compiler_to_generator(self, target: build.BuildTarget, diff --git a/mesonbuild/backend/ninjabackend.py b/mesonbuild/backend/ninjabackend.py index 339867d..c583024 100644 --- a/mesonbuild/backend/ninjabackend.py +++ b/mesonbuild/backend/ninjabackend.py @@ -314,6 +314,7 @@ class NinjaBuildElement: self.orderdeps = OrderedSet() self.elems = [] self.all_outputs = all_outputs + self.output_errors = '' def add_dep(self, dep): if isinstance(dep, list): @@ -362,7 +363,8 @@ class NinjaBuildElement: self.rule.refcount += 1 def write(self, outfile): - self.check_outputs() + if self.output_errors: + raise MesonException(self.output_errors) ins = ' '.join([ninja_quote(i, True) for i in self.infilenames]) outs = ' '.join([ninja_quote(i, True) for i in self.outfilenames]) implicit_outs = ' '.join([ninja_quote(i, True) for i in self.implicit_outfilenames]) @@ -421,7 +423,7 @@ class NinjaBuildElement: def check_outputs(self): for n in self.outfilenames: if n in self.all_outputs: - raise MesonException(f'Multiple producers for Ninja target "{n}". Please rename your targets.') + self.output_errors = f'Multiple producers for Ninja target "{n}". Please rename your targets.' self.all_outputs[n] = True @dataclass @@ -1283,6 +1285,7 @@ class NinjaBackend(backends.Backend): self.ruledict[rule.name] = rule def add_build(self, build): + build.check_outputs() self.build_elements.append(build) if build.rulename != 'phony': @@ -3331,7 +3334,7 @@ https://gcc.gnu.org/bugzilla/show_bug.cgi?id=47485''')) def generate_scanbuild(self): if not environment.detect_scanbuild(): return - if ('', 'scan-build') in self.build.run_target_names: + if 'scan-build' in self.all_outputs: return cmd = self.environment.get_build_command() + \ ['--internal', 'scanbuild', self.environment.source_dir, self.environment.build_dir] + \ @@ -3352,8 +3355,6 @@ https://gcc.gnu.org/bugzilla/show_bug.cgi?id=47485''')) return if target_name in self.all_outputs: return - if ('', target_name) in self.build.run_target_names: - return cmd = self.environment.get_build_command() + \ ['--internal', 'clang' + name, self.environment.source_dir, self.environment.build_dir] + \ extra_args @@ -3378,8 +3379,6 @@ https://gcc.gnu.org/bugzilla/show_bug.cgi?id=47485''')) import shutil if not shutil.which(tool): return - if ('', target_name) in self.build.run_target_names: - return if target_name in self.all_outputs: return cmd = self.environment.get_build_command() + \ diff --git a/mesonbuild/build.py b/mesonbuild/build.py index 0ef5122..2fd5626 100644 --- a/mesonbuild/build.py +++ b/mesonbuild/build.py @@ -236,7 +236,6 @@ class Build: self.environment = environment self.projects = {} self.targets: 'T.OrderedDict[str, T.Union[CustomTarget, BuildTarget]]' = OrderedDict() - self.run_target_names: T.Set[T.Tuple[str, str]] = set() self.global_args: PerMachine[T.Dict[str, T.List[str]]] = PerMachine({}, {}) self.global_link_args: PerMachine[T.Dict[str, T.List[str]]] = PerMachine({}, {}) self.projects_args: PerMachine[T.Dict[str, T.Dict[str, T.List[str]]]] = PerMachine({}, {}) diff --git a/mesonbuild/coredata.py b/mesonbuild/coredata.py index d94e9ac..b90a567 100644 --- a/mesonbuild/coredata.py +++ b/mesonbuild/coredata.py @@ -53,7 +53,7 @@ if T.TYPE_CHECKING: # # Pip requires that RCs are named like this: '0.1.0.rc1' # But the corresponding Git tag needs to be '0.1.0rc1' -version = '0.99.99' +version = '1.0.0.rc1' backendlist = ['ninja', 'vs', 'vs2010', 'vs2012', 'vs2013', 'vs2015', 'vs2017', 'vs2019', 'vs2022', 'xcode'] diff --git a/mesonbuild/environment.py b/mesonbuild/environment.py index a9df75e..9691cf1 100644 --- a/mesonbuild/environment.py +++ b/mesonbuild/environment.py @@ -25,9 +25,7 @@ from .mesonlib import ( search_version, MesonBugException ) from . import mlog -from .programs import ( - ExternalProgram, EmptyExternalProgram -) +from .programs import ExternalProgram from .envconfig import ( BinaryTable, MachineInfo, Properties, known_cpu_families, CMakeVariables, @@ -852,7 +850,7 @@ class Environment: return value return not machine_info_can_run(self.machines[for_machine]) - def get_exe_wrapper(self) -> ExternalProgram: + def get_exe_wrapper(self) -> T.Optional[ExternalProgram]: if not self.need_exe_wrapper(): - return EmptyExternalProgram() + return None return self.exe_wrapper diff --git a/mesonbuild/interpreter/compiler.py b/mesonbuild/interpreter/compiler.py index 7397321..8b6efd2 100644 --- a/mesonbuild/interpreter/compiler.py +++ b/mesonbuild/interpreter/compiler.py @@ -142,7 +142,13 @@ _INCLUDE_DIRS_KW: KwargInfo[T.List[build.IncludeDirs]] = KwargInfo( default=[], listify=True, ) -_PREFIX_KW = KwargInfo('prefix', str, default='') +_PREFIX_KW: KwargInfo[str] = KwargInfo( + 'prefix', + (str, ContainerTypeInfo(list, str)), + default='', + since_values={list: '1.0.0'}, + convertor=lambda x: '\n'.join(x) if isinstance(x, list) else x) + _NO_BUILTIN_ARGS_KW = KwargInfo('no_builtin_args', bool, default=False) _NAME_KW = KwargInfo('name', str, default='') diff --git a/mesonbuild/interpreter/interpreter.py b/mesonbuild/interpreter/interpreter.py index 552f51c..59cb6e0 100644 --- a/mesonbuild/interpreter/interpreter.py +++ b/mesonbuild/interpreter/interpreter.py @@ -1990,9 +1990,6 @@ class Interpreter(InterpreterBase, HoldableObject): tg = build.RunTarget(name, all_args, kwargs['depends'], self.subdir, self.subproject, self.environment, kwargs['env']) self.add_target(name, tg) - full_name = (self.subproject, name) - assert full_name not in self.build.run_target_names - self.build.run_target_names.add(full_name) return tg @FeatureNew('alias_target', '0.52.0') diff --git a/mesonbuild/mdevenv.py b/mesonbuild/mdevenv.py index b75d18b..b25d73b 100644 --- a/mesonbuild/mdevenv.py +++ b/mesonbuild/mdevenv.py @@ -6,7 +6,8 @@ import itertools from pathlib import Path from . import build, minstall, dependencies -from .mesonlib import MesonException, RealPathAction, is_windows, setup_vsenv, OptionKey, quote_arg, get_wine_shortpath +from .mesonlib import (MesonException, is_windows, setup_vsenv, OptionKey, + get_wine_shortpath, MachineChoice) from . import mlog import typing as T @@ -16,8 +17,10 @@ if T.TYPE_CHECKING: POWERSHELL_EXES = {'pwsh.exe', 'powershell.exe'} def add_arguments(parser: argparse.ArgumentParser) -> None: - parser.add_argument('-C', dest='wd', action=RealPathAction, - help='Directory to cd into before running') + parser.add_argument('-C', dest='builddir', type=Path, default='.', + help='Path to build directory') + parser.add_argument('--workdir', '-w', type=Path, default=None, + help='Directory to cd into before running (default: builddir, Since 1.0.0)') parser.add_argument('--dump', action='store_true', help='Only print required environment (Since 0.62.0)') parser.add_argument('devcmd', nargs=argparse.REMAINDER, metavar='command', @@ -45,15 +48,19 @@ def reduce_winepath(env: T.Dict[str, str]) -> None: env['WINEPATH'] = get_wine_shortpath([winecmd], winepath.split(';')) mlog.log('Meson detected wine and has set WINEPATH accordingly') -def get_env(b: build.Build) -> T.Tuple[T.Dict[str, str], T.Set[str]]: +def get_env(b: build.Build, dump: bool) -> T.Tuple[T.Dict[str, str], T.Set[str]]: extra_env = build.EnvironmentVariables() extra_env.set('MESON_DEVENV', ['1']) extra_env.set('MESON_PROJECT_NAME', [b.project_name]) - env = os.environ.copy() + sysroot = b.environment.properties[MachineChoice.HOST].get_sys_root() + if sysroot: + extra_env.set('QEMU_LD_PREFIX', [sysroot]) + + env = {} if dump else os.environ.copy() varnames = set() for i in itertools.chain(b.devenv, {extra_env}): - env = i.get_env(env) + env = i.get_env(env, dump) varnames |= i.get_names() reduce_winepath(env) @@ -90,7 +97,7 @@ def add_gdb_auto_load(autoload_path: Path, gdb_helper: str, fname: Path) -> None except (FileExistsError, shutil.SameFileError): pass -def write_gdb_script(privatedir: Path, install_data: 'InstallData') -> None: +def write_gdb_script(privatedir: Path, install_data: 'InstallData', workdir: Path) -> None: if not shutil.which('gdb'): return bdir = privatedir.parent @@ -120,26 +127,44 @@ def write_gdb_script(privatedir: Path, install_data: 'InstallData') -> None: gdbinit_path.write_text(gdbinit_line, encoding='utf-8') first_time = True if first_time: - mlog.log('Meson detected GDB helpers and added config in', mlog.bold(str(gdbinit_path))) + gdbinit_path = gdbinit_path.resolve() + workdir_path = workdir.resolve() + rel_path = gdbinit_path.relative_to(workdir_path) + mlog.log('Meson detected GDB helpers and added config in', mlog.bold(str(rel_path))) + mlog.log('To load it automatically you might need to:') + mlog.log(' - Add', mlog.bold(f'add-auto-load-safe-path {gdbinit_path.parent}'), + 'in', mlog.bold('~/.gdbinit')) + if gdbinit_path.parent != workdir_path: + mlog.log(' - Change current workdir to', mlog.bold(str(rel_path.parent)), + 'or use', mlog.bold(f'--init-command {rel_path}')) def run(options: argparse.Namespace) -> int: - privatedir = Path(options.wd) / 'meson-private' + privatedir = Path(options.builddir) / 'meson-private' buildfile = privatedir / 'build.dat' if not buildfile.is_file(): - raise MesonException(f'Directory {options.wd!r} does not seem to be a Meson build directory.') - b = build.load(options.wd) + raise MesonException(f'Directory {options.builddir!r} does not seem to be a Meson build directory.') + b = build.load(options.builddir) + workdir = options.workdir or options.builddir - devenv, varnames = get_env(b) + devenv, varnames = get_env(b, options.dump) if options.dump: if options.devcmd: raise MesonException('--dump option does not allow running other command.') for name in varnames: - print(f'{name}={quote_arg(devenv[name])}') + print(f'{name}="{devenv[name]}"') print(f'export {name}') return 0 + if b.environment.need_exe_wrapper(): + m = 'An executable wrapper could be required' + exe_wrapper = b.environment.get_exe_wrapper() + if exe_wrapper: + cmd = ' '.join(exe_wrapper.get_command()) + m += f': {cmd}' + mlog.log(m) + install_data = minstall.load_install_data(str(privatedir / 'install.dat')) - write_gdb_script(privatedir, install_data) + write_gdb_script(privatedir, install_data, workdir) setup_vsenv(b.need_vsenv) @@ -182,7 +207,7 @@ def run(options: argparse.Namespace) -> int: try: return subprocess.call(args, close_fds=False, env=devenv, - cwd=options.wd) + cwd=workdir) except subprocess.CalledProcessError as e: return e.returncode except FileNotFoundError: diff --git a/mesonbuild/modules/gnome.py b/mesonbuild/modules/gnome.py index 1d5e746..d0d7dbf 100644 --- a/mesonbuild/modules/gnome.py +++ b/mesonbuild/modules/gnome.py @@ -39,7 +39,7 @@ from ..interpreterbase.decorators import typed_pos_args from ..mesonlib import ( MachineChoice, MesonException, OrderedSet, Popen_safe, join_args, ) -from ..programs import OverrideProgram, EmptyExternalProgram +from ..programs import OverrideProgram from ..scripts.gettext import read_linguas if T.TYPE_CHECKING: @@ -1464,9 +1464,8 @@ class GnomeModule(ExtensionModule): t_args.append(f'--{program_name}={path}') if namespace: t_args.append('--namespace=' + namespace) - # if not need_exe_wrapper, we get an EmptyExternalProgram. If none provided, we get NoneType exe_wrapper = state.environment.get_exe_wrapper() - if not isinstance(exe_wrapper, (NoneType, EmptyExternalProgram)): + if exe_wrapper: t_args.append('--run=' + ' '.join(exe_wrapper.get_command())) t_args.append(f'--htmlargs={"@@".join(kwargs["html_args"])}') t_args.append(f'--scanargs={"@@".join(kwargs["scan_args"])}') diff --git a/mesonbuild/modules/python.py b/mesonbuild/modules/python.py index 6bbfdb9..f74d10e 100644 --- a/mesonbuild/modules/python.py +++ b/mesonbuild/modules/python.py @@ -91,7 +91,6 @@ mod_kwargs -= {'name_prefix', 'name_suffix'} class _PythonDependencyBase(_Base): def __init__(self, python_holder: 'PythonInstallation', embed: bool): - self.name = 'python' # override the name from the "real" dependency lookup self.embed = embed self.version: str = python_holder.version self.platform = python_holder.platform diff --git a/mesonbuild/mtest.py b/mesonbuild/mtest.py index f13e755..00812c1 100644 --- a/mesonbuild/mtest.py +++ b/mesonbuild/mtest.py @@ -275,6 +275,7 @@ TYPE_TAPResult = T.Union['TAPParser.Test', 'TAPParser.Error', 'TAPParser.Version', 'TAPParser.Plan', + 'TAPParser.UnknownLine', 'TAPParser.Bailout'] class TAPParser: @@ -299,6 +300,10 @@ class TAPParser: class Error(T.NamedTuple): message: str + class UnknownLine(T.NamedTuple): + message: str + lineno: int + class Version(T.NamedTuple): version: int @@ -380,7 +385,7 @@ class TAPParser: self.state = self._MAIN assert self.state == self._MAIN - if line.startswith('#'): + if not line or line.startswith('#'): return m = self._RE_TEST.match(line) @@ -434,6 +439,9 @@ class TAPParser: else: yield self.Version(version=self.version) return + + # unknown syntax + yield self.UnknownLine(line, self.lineno) else: # end of file if self.state == self._YAML: @@ -673,6 +681,11 @@ class ConsoleLogger(TestLogger): flush=True) if result.verbose or result.res.is_bad(): self.print_log(harness, result) + if result.warnings: + print(flush=True) + for w in result.warnings: + print(w, flush=True) + print(flush=True) if result.verbose or result.res.is_bad(): print(flush=True) @@ -899,6 +912,7 @@ class TestRun: self.junit = None # type: T.Optional[et.ElementTree] self.is_parallel = is_parallel self.verbose = verbose + self.warnings = [] # type: T.List[str] def start(self, cmd: T.List[str]) -> None: self.res = TestResult.RUNNING @@ -1041,9 +1055,13 @@ class TestRunTAP(TestRun): async def parse(self, harness: 'TestHarness', lines: T.AsyncIterator[str]) -> None: res = None + warnings = [] # type: T.List[TAPParser.UnknownLine] + version: int async for i in TAPParser().parse_async(lines): - if isinstance(i, TAPParser.Bailout): + if isinstance(i, TAPParser.Version): + version = i.version + elif isinstance(i, TAPParser.Bailout): res = TestResult.ERROR harness.log_subtest(self, i.message, res) elif isinstance(i, TAPParser.Test): @@ -1051,10 +1069,23 @@ class TestRunTAP(TestRun): if i.result.is_bad(): res = TestResult.FAIL harness.log_subtest(self, i.name or f'subtest {i.number}', i.result) + elif isinstance(i, TAPParser.UnknownLine): + warnings.append(i) elif isinstance(i, TAPParser.Error): self.additional_error += 'TAP parsing error: ' + i.message res = TestResult.ERROR + if warnings: + unknown = str(mlog.yellow('UNKNOWN')) + width = len(str(max(i.lineno for i in warnings))) + for w in warnings: + self.warnings.append(f'stdout: {w.lineno:{width}}: {unknown}: {w.message}') + if version > 13: + self.warnings.append('Unknown TAP output lines have been ignored. Please open a feature request to\n' + 'implement them, or prefix them with a # if they are not TAP syntax.') + else: + self.warnings.append(str(mlog.red('ERROR')) + ': Unknown TAP output lines for a supported TAP version.\n' + 'This is probably a bug in the test; if they are not TAP syntax, prefix them with a #') if all(t.result is TestResult.SKIP for t in self.results): # This includes the case where self.results is empty res = TestResult.SKIP diff --git a/mesonbuild/programs.py b/mesonbuild/programs.py index 458faba..1d616aa 100644 --- a/mesonbuild/programs.py +++ b/mesonbuild/programs.py @@ -345,25 +345,6 @@ class NonExistingExternalProgram(ExternalProgram): # lgtm [py/missing-call-to-i return False -class EmptyExternalProgram(ExternalProgram): # lgtm [py/missing-call-to-init] - ''' - A program object that returns an empty list of commands. Used for cases - such as a cross file exe_wrapper to represent that it's not required. - ''' - - def __init__(self) -> None: - self.name = None - self.command = [] - self.path = None - - def __repr__(self) -> str: - r = '<{} {!r} -> {!r}>' - return r.format(self.__class__.__name__, self.name, self.command) - - def found(self) -> bool: - return True - - class OverrideProgram(ExternalProgram): """A script overriding a program.""" diff --git a/mesonbuild/scripts/clangformat.py b/mesonbuild/scripts/clangformat.py index 9dc5466..f2f6a77 100644 --- a/mesonbuild/scripts/clangformat.py +++ b/mesonbuild/scripts/clangformat.py @@ -18,17 +18,25 @@ from pathlib import Path from .run_tool import run_tool from ..environment import detect_clangformat +from ..mesonlib import version_compare +from ..programs import ExternalProgram import typing as T def run_clang_format(fname: Path, exelist: T.List[str], check: bool) -> subprocess.CompletedProcess: + clangformat_10 = False if check: - original = fname.read_bytes() + cformat_ver = ExternalProgram('clang-format', exelist).get_version() + if version_compare(cformat_ver, '>=10'): + clangformat_10 = True + exelist = exelist + ['--dry-run', '--Werror'] + else: + original = fname.read_bytes() before = fname.stat().st_mtime ret = subprocess.run(exelist + ['-style=file', '-i', str(fname)]) after = fname.stat().st_mtime if before != after: print('File reformatted: ', fname) - if check: + if check and not clangformat_10: # Restore the original if only checking. fname.write_bytes(original) ret.returncode = 1 diff --git a/mesonbuild/utils/core.py b/mesonbuild/utils/core.py index ed413ca..5450cdc 100644 --- a/mesonbuild/utils/core.py +++ b/mesonbuild/utils/core.py @@ -119,23 +119,24 @@ class EnvironmentVariables(HoldableObject): self.envvars.append((self._prepend, name, values, separator)) @staticmethod - def _set(env: T.Dict[str, str], name: str, values: T.List[str], separator: str) -> str: + def _set(env: T.Dict[str, str], name: str, values: T.List[str], separator: str, default_value: T.Optional[str]) -> str: return separator.join(values) @staticmethod - def _append(env: T.Dict[str, str], name: str, values: T.List[str], separator: str) -> str: - curr = env.get(name) + def _append(env: T.Dict[str, str], name: str, values: T.List[str], separator: str, default_value: T.Optional[str]) -> str: + curr = env.get(name, default_value) return separator.join(values if curr is None else [curr] + values) @staticmethod - def _prepend(env: T.Dict[str, str], name: str, values: T.List[str], separator: str) -> str: - curr = env.get(name) + def _prepend(env: T.Dict[str, str], name: str, values: T.List[str], separator: str, default_value: T.Optional[str]) -> str: + curr = env.get(name, default_value) return separator.join(values if curr is None else values + [curr]) - def get_env(self, full_env: T.MutableMapping[str, str]) -> T.Dict[str, str]: + def get_env(self, full_env: T.MutableMapping[str, str], dump: bool = False) -> T.Dict[str, str]: env = full_env.copy() for method, name, values, separator in self.envvars: - env[name] = method(env, name, values, separator) + default_value = f'${name}' if dump else None + env[name] = method(env, name, values, separator, default_value) return env diff --git a/mesonbuild/utils/universal.py b/mesonbuild/utils/universal.py index 33c81c6..68ea0b7 100644 --- a/mesonbuild/utils/universal.py +++ b/mesonbuild/utils/universal.py @@ -1446,7 +1446,7 @@ def Popen_safe_legacy(args: T.List[str], write: T.Optional[str] = None, input_ = write.encode('utf-8') o, e = p.communicate(input_) if o is not None: - if sys.stdout.encoding: + if sys.stdout.encoding is not None: o = o.decode(encoding=sys.stdout.encoding, errors='replace').replace('\r\n', '\n') else: o = o.decode(errors='replace').replace('\r\n', '\n') diff --git a/mesonbuild/wrap/wrap.py b/mesonbuild/wrap/wrap.py index e8c955a..d949f43 100644 --- a/mesonbuild/wrap/wrap.py +++ b/mesonbuild/wrap/wrap.py @@ -582,8 +582,11 @@ class Resolver: verbose_git(['fetch', self.wrap.get('url'), revno], self.dirname, check=True) verbose_git(checkout_cmd, self.dirname, check=True) else: - verbose_git(['-c', 'advice.detachedHead=false', 'clone', *depth_option, '--branch', revno, self.wrap.get('url'), - self.directory], self.subdir_root, check=True) + args = ['-c', 'advice.detachedHead=false', 'clone', *depth_option] + if revno.lower() != 'head': + args += ['--branch', revno] + args += [self.wrap.get('url'), self.directory] + verbose_git(args, self.subdir_root, check=True) if self.wrap.values.get('clone-recursive', '').lower() == 'true': verbose_git(['submodule', 'update', '--init', '--checkout', '--recursive', *depth_option], self.dirname, check=True) diff --git a/test cases/common/132 get define/meson.build b/test cases/common/132 get define/meson.build index df3d02a..02e5a0c 100644 --- a/test cases/common/132 get define/meson.build +++ b/test cases/common/132 get define/meson.build @@ -66,6 +66,13 @@ foreach lang : ['c', 'cpp'] have = cc.get_define('MESON_FAIL_VALUE') assert(have == '', 'MESON_FAIL_VALUE value is "@0@" instead of ""'.format(have)) + # Check if prefix array works properly and has the expected order + have = cc.get_define('MESON_FAIL_VALUE', prefix: ['#define MESON_FAIL_VALUE 1', '#undef MESON_FAIL_VALUE']) + assert(have == '', 'MESON_FAIL_VALUE value is "@0@" instead of ""'.format(have)) + + have = cc.get_define('MESON_SUCCESS_VALUE', prefix: ['#undef MESON_SUCCESS_VALUE', '#define MESON_SUCCESS_VALUE 1']) + assert(have == '1', 'MESON_SUCCESS_VALUE value is "@0@" instead of ""'.format(have)) + # This is used in the test_preprocessor_checks_CPPFLAGS() unit test. have = cc.get_define('MESON_TEST_DEFINE_VALUE') expect = get_option('MESON_TEST_DEFINE_VALUE') diff --git a/test cases/common/51 run target/.clang-format b/test cases/common/51 run target/.clang-format new file mode 100644 index 0000000..9b3aa8b --- /dev/null +++ b/test cases/common/51 run target/.clang-format @@ -0,0 +1 @@ +BasedOnStyle: LLVM diff --git a/test cases/common/51 run target/.clang-tidy b/test cases/common/51 run target/.clang-tidy new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/test cases/common/51 run target/.clang-tidy diff --git a/test cases/common/51 run target/meson.build b/test cases/common/51 run target/meson.build index 85d30f0..dbb6732 100644 --- a/test cases/common/51 run target/meson.build +++ b/test cases/common/51 run target/meson.build @@ -78,8 +78,13 @@ custom_target('configure_script_ct', run_target('ctags', command : converter) -run_target('clang-format', - command : converter) +clangf = run_target('clang-format', + command : [converter, files('.clang-format'), meson.current_build_dir() / 'clang-format']) +custom_target('clang-tidy', + input: '.clang-tidy', + output: 'clang-tidy', + command : [converter, '@INPUT@', '@OUTPUT@']) +alias_target('clang-format-check', clangf) # Check we can pass env to the program. Also check some string substitutions # that were added in 0.57.0 but not documented. This is documented behaviour diff --git a/unittests/subprojectscommandtests.py b/unittests/subprojectscommandtests.py index edd6ac1..bca124d 100644 --- a/unittests/subprojectscommandtests.py +++ b/unittests/subprojectscommandtests.py @@ -103,15 +103,20 @@ class SubprojectsCommandTests(BasePlatformTests): self._git_remote(['commit', '--no-gpg-sign', '--allow-empty', '-m', f'tag {tag} commit'], name) self._git_remote(['tag', '--no-sign', tag], name) - def _wrap_create_git(self, name, revision='master'): + def _wrap_create_git(self, name, revision='master', depth=None): path = self.root_dir / name with open(str((self.subprojects_dir / name).with_suffix('.wrap')), 'w', encoding='utf-8') as f: + if depth is None: + depth_line = '' + else: + depth_line = 'depth = {}'.format(depth) f.write(textwrap.dedent( ''' [wrap-git] url={} revision={} - '''.format(os.path.abspath(str(path)), revision))) + {} + '''.format(os.path.abspath(str(path)), revision, depth_line))) def _wrap_create_file(self, name, tarball='dummy.tar.gz'): path = self.root_dir / tarball @@ -205,6 +210,15 @@ class SubprojectsCommandTests(BasePlatformTests): self._subprojects_cmd(['update', '--reset']) self.assertEqual(self._git_local_commit(subp_name), self._git_remote_commit(subp_name)) + # Create a fake remote git repository and a wrap file targeting + # HEAD and depth = 1. Checks that "meson subprojects download" works. + subp_name = 'sub3' + self._git_create_remote_repo(subp_name) + self._wrap_create_git(subp_name, revision='head', depth='1') + self._subprojects_cmd(['download']) + self.assertPathExists(str(self.subprojects_dir / subp_name)) + self._git_config(self.subprojects_dir / subp_name) + @skipIfNoExecutable('true') def test_foreach(self): self._create_project(self.subprojects_dir / 'sub_file') diff --git a/unittests/taptests.py b/unittests/taptests.py index 477d797..6c2ccb0 100644 --- a/unittests/taptests.py +++ b/unittests/taptests.py @@ -37,6 +37,9 @@ class TAPParserTests(unittest.TestCase): def assert_error(self, events): self.assertEqual(type(next(events)), TAPParser.Error) + def assert_unexpected(self, events, **kwargs): + self.assertEqual(next(events), TAPParser.UnknownLine(**kwargs)) + def assert_bailout(self, events, **kwargs): self.assertEqual(next(events), TAPParser.Bailout(**kwargs)) @@ -255,6 +258,7 @@ class TAPParserTests(unittest.TestCase): def test_unexpected(self): events = self.parse_tap('1..1\ninvalid\nok 1') self.assert_plan(events, num_tests=1, late=False) + self.assert_unexpected(events, message='invalid', lineno=2) self.assert_test(events, number=1, name='', result=TestResult.OK) self.assert_last(events) |