diff options
27 files changed, 511 insertions, 229 deletions
diff --git a/.travis.yml b/.travis.yml index 7658fa0..8f393f1 100644 --- a/.travis.yml +++ b/.travis.yml @@ -16,7 +16,7 @@ compiler: env: - MESON_ARGS="" - - MESON_ARGS="--unity=on" + - RUN_TESTS_ARGS="--no-unittests" MESON_ARGS="--unity=on" language: - cpp @@ -63,4 +63,4 @@ script: /bin/sh -c "cd /root && mkdir -p tools; wget -c http://nirbheek.in/files/binaries/ninja/linux-amd64/ninja -O /root/tools/ninja; chmod +x /root/tools/ninja; CC=$CC CXX=$CXX OBJC=$CC OBJCXX=$CXX PATH=/root/tools:$PATH MESON_FIXED_NINJA=1 ./run_tests.py $RUN_TESTS_ARGS -- $MESON_ARGS && chmod -R a+rwX .coverage" fi # Ensure that llvm is added after $PATH, otherwise the clang from that llvm install will be used instead of the native apple clang. - - if [[ "$TRAVIS_OS_NAME" == "osx" ]]; then SDKROOT=$(xcodebuild -version -sdk macosx Path) CPPFLAGS=-I/usr/local/include LDFLAGS=-L/usr/local/lib OBJC=$CC OBJCXX=$CXX PATH=$HOME/tools:/usr/local/opt/qt/bin:$PATH:$(brew --prefix llvm)/bin MESON_FIXED_NINJA=1 ./run_tests.py --backend=ninja -- $MESON_ARGS ; fi + - if [[ "$TRAVIS_OS_NAME" == "osx" ]]; then SDKROOT=$(xcodebuild -version -sdk macosx Path) CPPFLAGS=-I/usr/local/include LDFLAGS=-L/usr/local/lib OBJC=$CC OBJCXX=$CXX PATH=$HOME/tools:/usr/local/opt/qt/bin:$PATH:$(brew --prefix llvm)/bin MESON_FIXED_NINJA=1 ./run_tests.py $RUN_TESTS_ARGS --backend=ninja -- $MESON_ARGS ; fi diff --git a/ci/azure-steps.yml b/ci/azure-steps.yml index 77f61fe..36e6fb4 100644 --- a/ci/azure-steps.yml +++ b/ci/azure-steps.yml @@ -154,6 +154,10 @@ steps: where.exe python python --version + # Needed for running unit tests in parallel. + python -m pip install --upgrade pytest-xdist + + echo "" echo "Locating cl, rc:" where.exe cl diff --git a/ciimage/Dockerfile b/ciimage/Dockerfile index d5f4816..dc66854 100644 --- a/ciimage/Dockerfile +++ b/ciimage/Dockerfile @@ -7,6 +7,7 @@ ENV DC=gdc RUN sed -i '/^#\sdeb-src /s/^#//' "/etc/apt/sources.list" \ && apt-get -y update && apt-get -y upgrade \ && apt-get -y build-dep meson \ +&& apt-get -y install python3-pytest-xdist \ && apt-get -y install python3-pip libxml2-dev libxslt1-dev cmake libyaml-dev \ && python3 -m pip install hotdoc codecov \ && apt-get -y install wget unzip \ diff --git a/docs/markdown/Contributing.md b/docs/markdown/Contributing.md index f8f1824..d724b75 100644 --- a/docs/markdown/Contributing.md +++ b/docs/markdown/Contributing.md @@ -304,3 +304,9 @@ line switches. - Prefer specific solutions to generic frameworks. Solve the end user's problems rather than providing them tools to do it themselves. + +- Never use features of the Unix shell (or Windows shell for that + matter). Doing things like forwaring output with `>` or invoking + multiple commands with `&&` are not permitted. Whenever these sorts + of requirements show up, write an internal Python script with the + desired functionality and use that instead. diff --git a/docs/markdown/FAQ.md b/docs/markdown/FAQ.md index e5b7a9c..06379ae 100644 --- a/docs/markdown/FAQ.md +++ b/docs/markdown/FAQ.md @@ -402,3 +402,90 @@ the form `foo.lib` when building with MSVC, you can set the kwarg to `''` and the [`name_suffix:`](https://mesonbuild.com/Reference-manual.html#library) kwarg to `'lib'`. To get the default behaviour for each, you can either not specify the kwarg, or pass `[]` (an empty array) to it. + +## Do I need to add my headers to the sources list like in Autotools? + +Autotools requires you to add private and public headers to the sources list so +that it knows what files to include in the tarball generated by `make dist`. +Meson's `dist` command simply gathers everything committed to your git/hg +repository and adds it to the tarball, so adding headers to the sources list is +pointless. + +Meson uses Ninja which uses compiler dependency information to automatically +figure out dependencies between C sources and headers, so it will rebuild +things correctly when a header changes. + +The only exception to this are generated headers, for which you must [declare +dependencies correctly](#how-do-i-tell-meson-that-my-sources-use-generated-headers). + +If, for whatever reason, you do add non-generated headers to the sources list +of a target, Meson will simply ignore them. + +## How do I tell Meson that my sources use generated headers? + +Let's say you use a [`custom_target()`](https://mesonbuild.com/Reference-manual.html#custom_target) +to generate the headers, and then `#include` them in your C code. Here's how +you ensure that Meson generates the headers before trying to compile any +sources in the build target: + +```meson +libfoo_gen_headers = custom_target('gen-headers', ..., output: 'foo-gen.h') +libfoo_sources = files('foo-utils.c', 'foo-lib.c') +# Add generated headers to the list of sources for the build target +libfoo = library('foo', sources: libfoo_sources + libfoo_gen_headers) +``` + +Now let's say you have a new target that links to `libfoo`: + +```meson +libbar_sources = files('bar-lib.c') +libbar = library('bar', sources: libbar_sources, link_with: libfoo) +``` + +This adds a **link-time** dependency between the two targets, but note that the +sources of the targets have **no compile-time** dependencies and can be built +in any order; which improves parallelism and speeds up builds. + +If the sources in `libbar` *also* use `foo-gen.h`, that's a *compile-time* +dependency, and you'll have to add `libfoo_gen_headers` to `sources:` for +`libbar` too: + +```meson +libbar_sources = files('bar-lib.c') +libbar = library('bar', sources: libbar_sources + libfoo_gen_headers, link_with: libfoo) +``` + +Alternatively, if you have multiple libraries with sources that link to +a library and also use its generated headers, this code is equivalent to above: + +```meson +# Add generated headers to the list of sources for the build target +libfoo = library('foo', sources: libfoo_sources + libfoo_gen_headers) + +# Declare a dependency that will add the generated headers to sources +libfoo_dep = declare_dependency(link_with: libfoo, sources: libfoo_gen_headers) + +... + +libbar = library('bar', sources: libbar_sources, dependencies: libfoo_dep) +``` + +**Note:** You should only add *headers* to `sources:` while declaring +a dependency. If your custom target outputs both sources and headers, you can +use the subscript notation to get only the header(s): + +```meson +libfoo_gen_sources = custom_target('gen-headers', ..., output: ['foo-gen.h', 'foo-gen.c']) +libfoo_gen_headers = libfoo_gen_sources[0] + +# Add static and generated sources to the target +libfoo = library('foo', sources: libfoo_sources + libfoo_gen_sources) + +# Declare a dependency that will add the generated *headers* to sources +libfoo_dep = declare_dependency(link_with: libfoo, sources: libfoo_gen_headers) +... +libbar = library('bar', sources: libbar_sources, dependencies: libfoo_dep) +``` + +A good example of a generator that outputs both sources and headers is +[`gnome.mkenums()`](https://mesonbuild.com/Gnome-module.html#gnomemkenums). diff --git a/docs/markdown/Installing.md b/docs/markdown/Installing.md index 66d090d..5abfdd4 100644 --- a/docs/markdown/Installing.md +++ b/docs/markdown/Installing.md @@ -115,3 +115,24 @@ command in the build tree: ```console $ meson install --no-rebuild --only-changed ``` + +## Finer control over install locations + +Sometimes it is necessary to only install a subsection of output files +or install them in different directories. This can be done by +specifying `install_dir` as an array rather than a single string. The +array must have as many items as there are outputs and each entry +specifies how the corresponding output file should be installed. For +example: + +```meson +custom_target(... + output: ['file1', 'file2', 'file3'], + install_dir: ['path1', false, 'path3'], + ... +) +``` + +In this case `file1` would be installed to `/prefix/path1/file1`, +`file2` would not be installed at all and `file3` would be installed +to `/prefix/path3/file3'. diff --git a/docs/markdown/Reference-manual.md b/docs/markdown/Reference-manual.md index 9c98547..3793ce3 100644 --- a/docs/markdown/Reference-manual.md +++ b/docs/markdown/Reference-manual.md @@ -52,7 +52,7 @@ Like `add_global_arguments` but the arguments are passed to the linker. ### add_languages() ``` meson - add_languages(*langs*) + bool add_languages(*langs*) ``` Add support for new programming languages. Equivalent to having them @@ -64,6 +64,9 @@ project('foobar', 'c') if compiling_for_osx add_languages('objc') endif +if add_languages('cpp', required : false) + executable('cpp-app', 'main.cpp') +endif ``` Takes one keyword argument, `required`. It defaults to `true`, which diff --git a/mesonbuild/backend/backends.py b/mesonbuild/backend/backends.py index b9bf166..982b0ee 100644 --- a/mesonbuild/backend/backends.py +++ b/mesonbuild/backend/backends.py @@ -812,11 +812,6 @@ class Backend: true. You might want to set `native: true` instead to build it for the build machine.'''.format(exe.name)) raise MesonException(s) - else: - mlog.warning(''' - Target {} is used as a generator, but is built for the host - machine. This means most cross builds will fail. You might want to - set `native: true` instead to build it for the build machine.'''.format(exe.name)) exe_arr = [os.path.join(self.environment.get_build_dir(), self.get_target_filename(exe))] else: exe_arr = exe.get_command() diff --git a/mesonbuild/backend/xcodebackend.py b/mesonbuild/backend/xcodebackend.py index 7c66250..5bd8106 100644 --- a/mesonbuild/backend/xcodebackend.py +++ b/mesonbuild/backend/xcodebackend.py @@ -738,7 +738,7 @@ class XCodeBackend(backends.Backend): ldstr = ' '.join(ldargs) valid = self.buildconfmap[target_name][buildtype] langargs = {} - for lang in self.environment.coredata.compilers: + for lang in self.environment.coredata.compilers[target.for_machine]: if lang not in langnamemap: continue # Add compile args added using add_project_arguments() diff --git a/mesonbuild/compilers/clike.py b/mesonbuild/compilers/clike.py index 4335b81..046ffba 100644 --- a/mesonbuild/compilers/clike.py +++ b/mesonbuild/compilers/clike.py @@ -160,61 +160,10 @@ class CLikeCompiler: def get_std_shared_lib_link_args(self): return ['-shared'] - @functools.lru_cache() - def _get_search_dirs(self, env): - extra_args = ['--print-search-dirs'] - stdo = None - with self._build_wrapper('', env, extra_args=extra_args, - dependencies=None, mode='compile', - want_output=True) as p: - stdo = p.stdo - return stdo - - def _split_fetch_real_dirs(self, pathstr): - # We need to use the path separator used by the compiler for printing - # lists of paths ("gcc --print-search-dirs"). By default - # we assume it uses the platform native separator. - pathsep = os.pathsep - - # clang uses ':' instead of ';' on Windows https://reviews.llvm.org/D61121 - # so we need to repair things like 'C:\foo:C:\bar' - if pathsep == ';': - pathstr = re.sub(r':([^/\\])', r';\1', pathstr) - - # pathlib treats empty paths as '.', so filter those out - paths = [p for p in pathstr.split(pathsep) if p] - - result = [] - for p in paths: - # GCC returns paths like this: - # /usr/lib/gcc/x86_64-linux-gnu/8/../../../../x86_64-linux-gnu/lib - # It would make sense to normalize them to get rid of the .. parts - # Sadly when you are on a merged /usr fs it also kills these: - # /lib/x86_64-linux-gnu - # since /lib is a symlink to /usr/lib. This would mean - # paths under /lib would be considered not a "system path", - # which is wrong and breaks things. Store everything, just to be sure. - pobj = Path(p) - unresolved = pobj.as_posix() - if pobj.exists(): - if unresolved not in result: - result.append(unresolved) - try: - resolved = Path(p).resolve().as_posix() - if resolved not in result: - result.append(resolved) - except FileNotFoundError: - pass - return tuple(result) - def get_compiler_dirs(self, env, name): ''' Get dirs from the compiler, either `libraries:` or `programs:` ''' - stdo = self._get_search_dirs(env) - for line in stdo.split('\n'): - if line.startswith(name + ':'): - return self._split_fetch_real_dirs(line.split('=', 1)[1]) return () @functools.lru_cache() @@ -384,18 +333,22 @@ class CLikeCompiler: # Select a CRT if needed since we're linking if mode == 'link': args += self.get_linker_debug_crt_args() - if mode in {'compile', 'preprocess'}: - # Add CFLAGS/CXXFLAGS/OBJCFLAGS/OBJCXXFLAGS and CPPFLAGS from the env - sys_args = env.coredata.get_external_args(self.for_machine, self.language) - # Apparently it is a thing to inject linker flags both - # via CFLAGS _and_ LDFLAGS, even though the former are - # also used during linking. These flags can break - # argument checks. Thanks, Autotools. - cleaned_sys_args = self.remove_linkerlike_args(sys_args) - args += cleaned_sys_args - elif mode == 'link': + + # Add CFLAGS/CXXFLAGS/OBJCFLAGS/OBJCXXFLAGS and CPPFLAGS from the env + sys_args = env.coredata.get_external_args(self.for_machine, self.language) + # Apparently it is a thing to inject linker flags both + # via CFLAGS _and_ LDFLAGS, even though the former are + # also used during linking. These flags can break + # argument checks. Thanks, Autotools. + cleaned_sys_args = self.remove_linkerlike_args(sys_args) + args += cleaned_sys_args + + if mode == 'link': # Add LDFLAGS from the env - args += env.coredata.get_external_link_args(self.for_machine, self.language) + sys_ld_args = env.coredata.get_external_link_args(self.for_machine, self.language) + # CFLAGS and CXXFLAGS go to both linking and compiling, but we want them + # to only appear on the command line once. Remove dupes. + args += [x for x in sys_ld_args if x not in sys_args] args += self.get_compiler_args_for_mode(mode) return args @@ -431,11 +384,11 @@ class CLikeCompiler: with self._build_wrapper(code, env, extra_args, dependencies, mode, disable_cache=disable_cache) as p: return p.returncode == 0, p.cached - def _build_wrapper(self, code, env, extra_args, dependencies=None, mode='compile', want_output=False, disable_cache=False): + def _build_wrapper(self, code, env, extra_args, dependencies=None, mode='compile', want_output=False, disable_cache=False, temp_dir=None): args = self._get_compiler_check_args(env, extra_args, dependencies, mode) if disable_cache or want_output: - return self.compile(code, extra_args=args, mode=mode, want_output=want_output) - return self.cached_compile(code, env.coredata, extra_args=args, mode=mode) + return self.compile(code, extra_args=args, mode=mode, want_output=want_output, temp_dir=env.scratch_dir) + return self.cached_compile(code, env.coredata, extra_args=args, mode=mode, temp_dir=env.scratch_dir) def links(self, code, env, *, extra_args=None, dependencies=None, disable_cache=False): return self.compiles(code, env, extra_args=extra_args, @@ -550,6 +503,7 @@ class CLikeCompiler: {prefix} int main(int argc, char **argv) {{ {type} something; + return 0; }}''' if not self.compiles(t.format(**fargs), env, extra_args=extra_args, dependencies=dependencies)[0]: @@ -639,7 +593,7 @@ class CLikeCompiler: mode='preprocess').to_native() func = lambda: self.cached_compile(code.format(**fargs), env.coredata, extra_args=args, mode='preprocess') if disable_cache: - func = lambda: self.compile(code.format(**fargs), extra_args=args, mode='preprocess') + func = lambda: self.compile(code.format(**fargs), extra_args=args, mode='preprocess', temp_dir=env.scratch_dir) with func() as p: cached = p.cached if p.returncode != 0: @@ -860,7 +814,7 @@ class CLikeCompiler: ''' args = self.get_compiler_check_args() n = 'symbols_have_underscore_prefix' - with self.compile(code, extra_args=args, mode='compile', want_output=True) as p: + with self._build_wrapper(code, env, extra_args=args, mode='compile', want_output=True, temp_dir=env.scratch_dir) as p: if p.returncode != 0: m = 'BUG: Unable to compile {!r} check: {}' raise RuntimeError(m.format(n, p.stdo)) diff --git a/mesonbuild/compilers/compilers.py b/mesonbuild/compilers/compilers.py index 86c1e33..8800923 100644 --- a/mesonbuild/compilers/compilers.py +++ b/mesonbuild/compilers/compilers.py @@ -13,8 +13,10 @@ # limitations under the License. import abc, contextlib, enum, os.path, re, tempfile, shlex +import functools import subprocess from typing import List, Optional, Tuple +from pathlib import Path from ..linkers import StaticLinker from .. import coredata @@ -1152,11 +1154,11 @@ class Compiler: return args @contextlib.contextmanager - def compile(self, code, extra_args=None, *, mode='link', want_output=False): + def compile(self, code, extra_args=None, *, mode='link', want_output=False, temp_dir=None): if extra_args is None: extra_args = [] try: - with tempfile.TemporaryDirectory() as tmpdirname: + with tempfile.TemporaryDirectory(dir=temp_dir) as tmpdirname: if isinstance(code, str): srcname = os.path.join(tmpdirname, 'testfile.' + self.default_suffix) @@ -1201,7 +1203,7 @@ class Compiler: pass @contextlib.contextmanager - def cached_compile(self, code, cdata: coredata.CoreData, *, extra_args=None, mode: str = 'link'): + def cached_compile(self, code, cdata: coredata.CoreData, *, extra_args=None, mode: str = 'link', temp_dir=None): assert(isinstance(cdata, coredata.CoreData)) # Calculate the key @@ -1210,7 +1212,7 @@ class Compiler: # Check if not cached if key not in cdata.compiler_check_cache: - with self.compile(code, extra_args=extra_args, mode=mode, want_output=False) as p: + with self.compile(code, extra_args=extra_args, mode=mode, want_output=False, temp_dir=temp_dir) as p: # Remove all attributes except the following # This way the object can be serialized tokeep = ['args', 'commands', 'input_name', 'output_name', @@ -2005,6 +2007,64 @@ class GnuLikeCompiler(abc.ABC): return parameter_list + @functools.lru_cache() + def _get_search_dirs(self, env): + extra_args = ['--print-search-dirs'] + stdo = None + with self._build_wrapper('', env, extra_args=extra_args, + dependencies=None, mode='compile', + want_output=True) as p: + stdo = p.stdo + return stdo + + def _split_fetch_real_dirs(self, pathstr): + # We need to use the path separator used by the compiler for printing + # lists of paths ("gcc --print-search-dirs"). By default + # we assume it uses the platform native separator. + pathsep = os.pathsep + + # clang uses ':' instead of ';' on Windows https://reviews.llvm.org/D61121 + # so we need to repair things like 'C:\foo:C:\bar' + if pathsep == ';': + pathstr = re.sub(r':([^/\\])', r';\1', pathstr) + + # pathlib treats empty paths as '.', so filter those out + paths = [p for p in pathstr.split(pathsep) if p] + + result = [] + for p in paths: + # GCC returns paths like this: + # /usr/lib/gcc/x86_64-linux-gnu/8/../../../../x86_64-linux-gnu/lib + # It would make sense to normalize them to get rid of the .. parts + # Sadly when you are on a merged /usr fs it also kills these: + # /lib/x86_64-linux-gnu + # since /lib is a symlink to /usr/lib. This would mean + # paths under /lib would be considered not a "system path", + # which is wrong and breaks things. Store everything, just to be sure. + pobj = Path(p) + unresolved = pobj.as_posix() + if pobj.exists(): + if unresolved not in result: + result.append(unresolved) + try: + resolved = Path(p).resolve().as_posix() + if resolved not in result: + result.append(resolved) + except FileNotFoundError: + pass + return tuple(result) + + def get_compiler_dirs(self, env, name): + ''' + Get dirs from the compiler, either `libraries:` or `programs:` + ''' + stdo = self._get_search_dirs(env) + for line in stdo.split('\n'): + if line.startswith(name + ':'): + return self._split_fetch_real_dirs(line.split('=', 1)[1]) + return () + + class GnuCompiler(GnuLikeCompiler): """ GnuCompiler represents an actual GCC in its many incarnations. diff --git a/mesonbuild/compilers/cpp.py b/mesonbuild/compilers/cpp.py index 718dbdf..16269be 100644 --- a/mesonbuild/compilers/cpp.py +++ b/mesonbuild/compilers/cpp.py @@ -370,9 +370,11 @@ class VisualStudioLikeCPPCompilerMixin: 'vc++11': (True, 11), 'vc++14': (True, 14), 'vc++17': (True, 17), + 'vc++latest': (True, "latest"), 'c++11': (False, 11), 'c++14': (False, 14), 'c++17': (False, 17), + 'c++latest': (False, "latest"), } def get_option_link_args(self, options): diff --git a/mesonbuild/coredata.py b/mesonbuild/coredata.py index 08e8827..1a397c7 100644 --- a/mesonbuild/coredata.py +++ b/mesonbuild/coredata.py @@ -433,6 +433,7 @@ class CoreData: self.builtins['libdir'].value = 'lib' def sanitize_prefix(self, prefix): + prefix = os.path.expanduser(prefix) if not os.path.isabs(prefix): raise MesonException('prefix value {!r} must be an absolute path' ''.format(prefix)) diff --git a/mesonbuild/dependencies/dev.py b/mesonbuild/dependencies/dev.py index f7b107c..223e6dc 100644 --- a/mesonbuild/dependencies/dev.py +++ b/mesonbuild/dependencies/dev.py @@ -460,12 +460,12 @@ class LLVMDependency(ExternalDependency): methods = cls._process_method_kw(kwargs) candidates = [] - if DependencyMethods.CMAKE in methods: - candidates.append(functools.partial(LLVMDependencyCMake, env, kwargs)) - if DependencyMethods.CONFIG_TOOL in methods: candidates.append(functools.partial(LLVMDependencyConfigTool, env, kwargs)) + if DependencyMethods.CMAKE in methods: + candidates.append(functools.partial(LLVMDependencyCMake, env, kwargs)) + return candidates @staticmethod diff --git a/mesonbuild/envconfig.py b/mesonbuild/envconfig.py index 03c6346..011ab5d 100644 --- a/mesonbuild/envconfig.py +++ b/mesonbuild/envconfig.py @@ -340,6 +340,10 @@ This is probably wrong, it should always point to the native compiler.''' % evar command = os.environ.get(evar) if command is not None: command = shlex.split(command) + + # Do not return empty or blank string entries + if command is not None and (len(command) == 0 or len(command[0].strip()) == 0): + return None return command class Directories: diff --git a/mesonbuild/environment.py b/mesonbuild/environment.py index 0cf511f..a47208d 100644 --- a/mesonbuild/environment.py +++ b/mesonbuild/environment.py @@ -376,7 +376,6 @@ class Environment: def __init__(self, source_dir, build_dir, options): self.source_dir = source_dir self.build_dir = build_dir - # Do not try to create build directories when build_dir is none. # This reduced mode is used by the --buildoptions introspector if build_dir is not None: @@ -910,7 +909,7 @@ class Environment: return PathScaleFortranCompiler(compiler, version, for_machine, is_cross, exe_wrap, full_version=full_version) if 'PGI Compilers' in out: - if self.machine[for_machine].is_darwin(): + if self.machines[for_machine].is_darwin(): compiler_type = CompilerType.PGI_OSX elif self.machines[for_machine].is_windows(): compiler_type = CompilerType.PGI_WIN diff --git a/mesonbuild/interpreter.py b/mesonbuild/interpreter.py index f5bb4e5..a20dcd5 100644 --- a/mesonbuild/interpreter.py +++ b/mesonbuild/interpreter.py @@ -3853,6 +3853,23 @@ different subdirectory. for_machine = self.machine_from_native_kwarg(kwargs) self.add_project_arguments(node, self.build.projects_link_args[for_machine], args, kwargs) + def warn_about_builtin_args(self, args): + warnargs = ('/W1', '/W2', '/W3', '/W4', '/Wall', '-Wall', '-Wextra', '-Wpedantic') + optargs = ('-O0', '-O2', '-O3', '-Os', '/O1', '/O2', '/Os') + for arg in args: + if arg in warnargs: + mlog.warning("Consider using the builtin warning_level option instead of adding warning flags by hand.") + elif arg in optargs: + mlog.warning('Consider using the builtin optimization level rather than adding flags by hand.') + elif arg == '-g': + mlog.warning('Consider using the builtin debug option rather than adding flags by hand.') + elif arg == '-pipe': + mlog.warning("You don't need to add -pipe, Meson will use it automatically when it is available.") + elif arg.startswith('-fsanitize'): + mlog.warning('Consider using the builtin option for sanitizers rather than adding flags by hand.') + elif arg.startswith('-std=') or arg.startswith('/std:'): + mlog.warning('Consider using the builtin option for language standard version rather than adding flags by hand.') + def add_global_arguments(self, node, argsdict, args, kwargs): if self.is_subproject(): msg = 'Function \'{}\' cannot be used in subprojects because ' \ @@ -3881,6 +3898,8 @@ different subdirectory. if 'language' not in kwargs: raise InvalidCode('Missing language definition in {}'.format(node.func_name)) + self.warn_about_builtin_args(args) + for lang in mesonlib.stringlistify(kwargs['language']): lang = lang.lower() argsdict[lang] = argsdict.get(lang, []) + args diff --git a/mesonbuild/mintro.py b/mesonbuild/mintro.py index b700b00..e81a8e1 100644 --- a/mesonbuild/mintro.py +++ b/mesonbuild/mintro.py @@ -123,6 +123,8 @@ def list_installed(installdata): res[path] = os.path.join(installdata.prefix, installdir, os.path.basename(path)) for path, installpath, _ in installdata.man: res[path] = os.path.join(installdata.prefix, installpath) + for path, installpath, _, _ in installdata.install_subdirs: + res[path] = os.path.join(installdata.prefix, installpath) return res def list_targets_from_source(intr: IntrospectionInterpreter): diff --git a/mesonbuild/modules/windows.py b/mesonbuild/modules/windows.py index efc3218..7cf46f7 100644 --- a/mesonbuild/modules/windows.py +++ b/mesonbuild/modules/windows.py @@ -23,7 +23,7 @@ from . import get_include_args from . import ModuleReturnValue from . import ExtensionModule from ..interpreter import CustomTargetHolder -from ..interpreterbase import permittedKwargs, FeatureNewKwargs +from ..interpreterbase import permittedKwargs, FeatureNewKwargs, flatten from ..dependencies import ExternalProgram class ResourceCompilerType(enum.Enum): @@ -78,7 +78,7 @@ class WindowsModule(ExtensionModule): @FeatureNewKwargs('windows.compile_resources', '0.47.0', ['depend_files', 'depends']) @permittedKwargs({'args', 'include_directories', 'depend_files', 'depends'}) def compile_resources(self, state, args, kwargs): - extra_args = mesonlib.stringlistify(kwargs.get('args', [])) + extra_args = mesonlib.stringlistify(flatten(kwargs.get('args', []))) wrc_depend_files = extract_as_list(kwargs, 'depend_files', pop = True) wrc_depends = extract_as_list(kwargs, 'depends', pop = True) for d in wrc_depends: diff --git a/mesonbuild/scripts/dist.py b/mesonbuild/scripts/dist.py index 47fde23..1a62c70 100644 --- a/mesonbuild/scripts/dist.py +++ b/mesonbuild/scripts/dist.py @@ -144,11 +144,15 @@ def create_dist_hg(dist_name, src_root, bld_root, dist_sub, dist_scripts): return (xzname, ) -def check_dist(packagename, meson_command): +def check_dist(packagename, meson_command, privdir): print('Testing distribution package %s' % packagename) - unpackdir = tempfile.mkdtemp() - builddir = tempfile.mkdtemp() - installdir = tempfile.mkdtemp() + unpackdir = os.path.join(privdir, 'dist-unpack') + builddir = os.path.join(privdir, 'dist-build') + installdir = os.path.join(privdir, 'dist-install') + for p in (unpackdir, builddir, installdir): + if os.path.exists(p): + shutil.rmtree(p) + os.mkdir(p) ninja_bin = detect_ninja() try: tf = tarfile.open(packagename) @@ -200,7 +204,7 @@ def run(args): return 1 error_count = 0 for name in names: - rc = check_dist(name, meson_command) # Check only one. + rc = check_dist(name, meson_command, priv_dir) # Check only one. if rc == 0: create_hash(name) error_count += rc diff --git a/mesonbuild/scripts/meson_exe.py b/mesonbuild/scripts/meson_exe.py index 85dfe99..3fe327f 100644 --- a/mesonbuild/scripts/meson_exe.py +++ b/mesonbuild/scripts/meson_exe.py @@ -78,6 +78,11 @@ def run_exe(exe): stdout=subprocess.PIPE, stderr=subprocess.PIPE) stdout, stderr = p.communicate() + + if p.returncode == 0xc0000135: + # STATUS_DLL_NOT_FOUND on Windows indicating a common problem that is otherwise hard to diagnose + raise FileNotFoundError('Missing DLLs on calling {!r}'.format(exe.name)) + if exe.capture and p.returncode == 0: with open(exe.capture, 'wb') as output: output.write(stdout) diff --git a/run_project_tests.py b/run_project_tests.py index 3bd3253..154da00 100755 --- a/run_project_tests.py +++ b/run_project_tests.py @@ -803,6 +803,8 @@ def check_format(): for (root, _, files) in os.walk('.'): if '.dub' in root: # external deps are here continue + if '.pytest_cache' in root: + continue if 'meson-logs' in root or 'meson-private' in root: continue for fname in files: @@ -870,6 +872,8 @@ if __name__ == '__main__': choices=backendlist) parser.add_argument('--failfast', action='store_true', help='Stop running if test case fails') + parser.add_argument('--no-unittests', action='store_true', + help='Not used, only here to simplify run_tests.py') parser.add_argument('--only', help='name of test(s) to run', nargs='+') options = parser.parse_args() setup_commands(options.backend) diff --git a/run_tests.py b/run_tests.py index f427736..051b91e 100755 --- a/run_tests.py +++ b/run_tests.py @@ -262,6 +262,7 @@ def main(): choices=backendlist) parser.add_argument('--cross', default=False, dest='cross', action='store_true') parser.add_argument('--failfast', action='store_true') + parser.add_argument('--no-unittests', action='store_true', default=False) (options, _) = parser.parse_known_args() # Enable coverage early... enable_coverage = options.cov @@ -273,6 +274,7 @@ def main(): returncode = 0 cross = options.cross backend, _ = guess_backend(options.backend, shutil.which('msbuild')) + no_unittests = options.no_unittests # Running on a developer machine? Be nice! if not mesonlib.is_windows() and not mesonlib.is_haiku() and 'CI' not in os.environ: os.nice(20) @@ -314,12 +316,16 @@ def main(): returncode += subprocess.call(cmd, env=env) if options.failfast and returncode != 0: return returncode - cmd = mesonlib.python_command + ['run_unittests.py', '-v'] - if options.failfast: - cmd += ['--failfast'] - returncode += subprocess.call(cmd, env=env) - if options.failfast and returncode != 0: - return returncode + if no_unittests: + print('Skipping all unit tests.') + returncode = 0 + else: + cmd = mesonlib.python_command + ['run_unittests.py', '-v'] + if options.failfast: + cmd += ['--failfast'] + returncode += subprocess.call(cmd, env=env) + if options.failfast and returncode != 0: + return returncode cmd = mesonlib.python_command + ['run_project_tests.py'] + sys.argv[1:] returncode += subprocess.call(cmd, env=env) else: diff --git a/run_unittests.py b/run_unittests.py index 602c6b7..33174b1 100755 --- a/run_unittests.py +++ b/run_unittests.py @@ -1221,7 +1221,7 @@ class BasePlatformTests(unittest.TestCase): os.environ.update(self.orig_env) super().tearDown() - def _run(self, command, workdir=None): + def _run(self, command, *, workdir=None, override_envvars=None): ''' Run a command while printing the stdout and stderr to stdout, and also return a copy of it @@ -1229,8 +1229,14 @@ class BasePlatformTests(unittest.TestCase): # If this call hangs CI will just abort. It is very hard to distinguish # between CI issue and test bug in that case. Set timeout and fail loud # instead. + if override_envvars is None: + env = None + else: + env = os.environ.copy() + env.update(override_envvars) + p = subprocess.run(command, stdout=subprocess.PIPE, - stderr=subprocess.STDOUT, env=os.environ.copy(), + stderr=subprocess.STDOUT, env=env, universal_newlines=True, cwd=workdir, timeout=60 * 5) print(p.stdout) if p.returncode != 0: @@ -1239,7 +1245,11 @@ class BasePlatformTests(unittest.TestCase): raise subprocess.CalledProcessError(p.returncode, command, output=p.stdout) return p.stdout - def init(self, srcdir, extra_args=None, default_args=True, inprocess=False): + def init(self, srcdir, *, + extra_args=None, + default_args=True, + inprocess=False, + override_envvars=None): self.assertPathExists(srcdir) if extra_args is None: extra_args = [] @@ -1254,7 +1264,13 @@ class BasePlatformTests(unittest.TestCase): self.privatedir = os.path.join(self.builddir, 'meson-private') if inprocess: try: + if override_envvars is not None: + old_envvars = os.environ.copy() + os.environ.update(override_envvars) (returncode, out, err) = run_configure_inprocess(self.meson_args + args + extra_args) + if override_envvars is not None: + os.environ.clear() + os.environ.update(old_envvars) if 'MESON_SKIP_TEST' in out: raise unittest.SkipTest('Project requested skipping.') if returncode != 0: @@ -1274,7 +1290,7 @@ class BasePlatformTests(unittest.TestCase): mesonbuild.mlog.log_file = None else: try: - out = self._run(self.setup_command + args + extra_args) + out = self._run(self.setup_command + args + extra_args, override_envvars=override_envvars) except unittest.SkipTest: raise unittest.SkipTest('Project requested skipping: ' + srcdir) except Exception: @@ -1282,40 +1298,52 @@ class BasePlatformTests(unittest.TestCase): raise return out - def build(self, target=None, extra_args=None): + def build(self, target=None, *, extra_args=None, override_envvars=None): if extra_args is None: extra_args = [] # Add arguments for building the target (if specified), # and using the build dir (if required, with VS) args = get_builddir_target_args(self.backend, self.builddir, target) - return self._run(self.build_command + args + extra_args, workdir=self.builddir) + return self._run(self.build_command + args + extra_args, workdir=self.builddir, override_envvars=override_envvars) - def clean(self): + def clean(self, *, override_envvars=None): dir_args = get_builddir_target_args(self.backend, self.builddir, None) - self._run(self.clean_command + dir_args, workdir=self.builddir) + self._run(self.clean_command + dir_args, workdir=self.builddir, override_envvars=override_envvars) - def run_tests(self, inprocess=False): + def run_tests(self, *, inprocess=False, override_envvars=None): if not inprocess: - self._run(self.test_command, workdir=self.builddir) + self._run(self.test_command, workdir=self.builddir, override_envvars=override_envvars) else: - run_mtest_inprocess(['-C', self.builddir]) + if override_envvars is not None: + old_envvars = os.environ.copy() + os.environ.update(override_envvars) + try: + run_mtest_inprocess(['-C', self.builddir]) + finally: + if override_envvars is not None: + os.environ.clear() + os.environ.update(old_envvars) - def install(self, *, use_destdir=True): + def install(self, *, use_destdir=True, override_envvars=None): if self.backend is not Backend.ninja: raise unittest.SkipTest('{!r} backend can\'t install files'.format(self.backend.name)) if use_destdir: - os.environ['DESTDIR'] = self.installdir - self._run(self.install_command, workdir=self.builddir) + destdir = {'DESTDIR': self.installdir} + if override_envvars is None: + override_envvars = destdir + else: + override_envvars.update(destdir) + self._run(self.install_command, workdir=self.builddir, override_envvars=override_envvars) - def uninstall(self): - self._run(self.uninstall_command, workdir=self.builddir) + def uninstall(self, *, override_envvars=None): + self._run(self.uninstall_command, workdir=self.builddir, override_envvars=override_envvars) - def run_target(self, target): + def run_target(self, target, *, override_envvars=None): ''' Run a Ninja target while printing the stdout and stderr to stdout, and also return a copy of it ''' - return self.build(target=target) + return self.build(target=target, override_envvars=override_envvars) def setconf(self, arg, will_build=True): if not isinstance(arg, list): @@ -1498,7 +1526,7 @@ class AllPlatformTests(BasePlatformTests): # This can just be a relative path, but we want to test # that passing this as an absolute path also works '--libdir=' + prefix + '/' + libdir] - self.init(testdir, extra_args, default_args=False) + self.init(testdir, extra_args=extra_args, default_args=False) opts = self.introspect('--buildoptions') for opt in opts: if opt['name'] == 'prefix': @@ -1514,11 +1542,11 @@ class AllPlatformTests(BasePlatformTests): testdir = os.path.join(self.common_test_dir, '1 trivial') # libdir being inside prefix is ok args = ['--prefix', '/opt', '--libdir', '/opt/lib32'] - self.init(testdir, args) + self.init(testdir, extra_args=args) self.wipe() # libdir not being inside prefix is not ok args = ['--prefix', '/usr', '--libdir', '/opt/lib32'] - self.assertRaises(subprocess.CalledProcessError, self.init, testdir, args) + self.assertRaises(subprocess.CalledProcessError, self.init, testdir, extra_args=args) self.wipe() # libdir must be inside prefix even when set via mesonconf self.init(testdir) @@ -1558,7 +1586,7 @@ class AllPlatformTests(BasePlatformTests): } for prefix in expected: args = ['--prefix', prefix] - self.init(testdir, args, default_args=False) + self.init(testdir, extra_args=args, default_args=False) opts = self.introspect('--buildoptions') for opt in opts: name = opt['name'] @@ -1597,7 +1625,7 @@ class AllPlatformTests(BasePlatformTests): 'sharedstatedir': '/var/state'}, } for args in expected: - self.init(testdir, args.split(), default_args=False) + self.init(testdir, extra_args=args.split(), default_args=False) opts = self.introspect('--buildoptions') for opt in opts: name = opt['name'] @@ -1695,6 +1723,34 @@ class AllPlatformTests(BasePlatformTests): self.assertPathListEqual(intro[0]['install_filename'], ['/usr/lib/libstat.a']) self.assertPathListEqual(intro[1]['install_filename'], ['/usr/bin/prog' + exe_suffix]) + def test_install_subdir_introspection(self): + ''' + Test that the Meson introspection API also contains subdir install information + https://github.com/mesonbuild/meson/issues/5556 + ''' + testdir = os.path.join(self.common_test_dir, '63 install subdir') + self.init(testdir) + intro = self.introspect('--installed') + expected = { + 'sub2': 'share/sub2', + 'subdir/sub1': 'share/sub1', + 'subdir/sub_elided': 'share', + 'sub1': 'share/sub1', + 'sub/sub1': 'share/sub1', + 'sub_elided': 'share', + 'nested_elided/sub': 'share', + } + + self.assertEqual(len(intro), len(expected)) + + # Convert expected to PurePath + expected_converted = {PurePath(os.path.join(testdir, key)): PurePath(os.path.join(self.prefix, val)) for key, val in expected.items()} + intro_converted = {PurePath(key): PurePath(val) for key, val in intro.items()} + + for src, dst in expected_converted.items(): + self.assertIn(src, intro_converted) + self.assertEqual(dst, intro_converted[src]) + def test_install_introspection_multiple_outputs(self): ''' Tests that the Meson introspection API exposes multiple install filenames correctly without crashing @@ -1727,7 +1783,7 @@ class AllPlatformTests(BasePlatformTests): def test_forcefallback(self): testdir = os.path.join(self.unit_test_dir, '31 forcefallback') - self.init(testdir, ['--wrap-mode=forcefallback']) + self.init(testdir, extra_args=['--wrap-mode=forcefallback']) self.build() self.run_tests() @@ -2159,9 +2215,10 @@ class AllPlatformTests(BasePlatformTests): # !, ^, *, and < confuse lcc preprocessor value = 'spaces and fun@$&()-=_+{}[]:;>?,./~`' for env_var in ['CPPFLAGS', 'CFLAGS']: - os.environ[env_var] = '-D{}="{}"'.format(define, value) - os.environ['LDFLAGS'] = '-DMESON_FAIL_VALUE=cflags-read'.format(define) - self.init(testdir, ['-D{}={}'.format(define, value)]) + env = {} + env[env_var] = '-D{}="{}"'.format(define, value) + env['LDFLAGS'] = '-DMESON_FAIL_VALUE=cflags-read'.format(define) + self.init(testdir, extra_args=['-D{}={}'.format(define, value)], override_envvars=env) def test_custom_target_exe_data_deterministic(self): testdir = os.path.join(self.common_test_dir, '114 custom target capture') @@ -2514,9 +2571,8 @@ int main(int argc, char **argv) { self.build_static_lib(cc, stlinker, source, objectfile, stlibfile, extra_args=['-DFOO_STATIC']) self.build_shared_lib(cc, source, objectfile, shlibfile, impfile) # Run test - os.environ['PKG_CONFIG_LIBDIR'] = self.builddir try: - self.init(testdir) + self.init(testdir, override_envvars={'PKG_CONFIG_LIBDIR': self.builddir}) self.build() self.run_tests() finally: @@ -2755,12 +2811,12 @@ int main(int argc, char **argv) { name = os.path.basename(f.name) with mock.patch.dict(os.environ, {'XDG_DATA_HOME': d}): - self.init(testdir, ['--cross-file=' + name], inprocess=True) + self.init(testdir, extra_args=['--cross-file=' + name], inprocess=True) self.wipe() with mock.patch.dict(os.environ, {'XDG_DATA_DIRS': d}): os.environ.pop('XDG_DATA_HOME', None) - self.init(testdir, ['--cross-file=' + name], inprocess=True) + self.init(testdir, extra_args=['--cross-file=' + name], inprocess=True) self.wipe() with tempfile.TemporaryDirectory() as d: @@ -2776,7 +2832,7 @@ int main(int argc, char **argv) { with mock.patch.dict(os.environ): os.environ.pop('XDG_DATA_HOME', None) with mock.patch('mesonbuild.coredata.os.path.expanduser', lambda x: x.replace('~', d)): - self.init(testdir, ['--cross-file=' + name], inprocess=True) + self.init(testdir, extra_args=['--cross-file=' + name], inprocess=True) self.wipe() def test_compiler_run_command(self): @@ -2871,9 +2927,8 @@ recommended as it is not supported on some platforms''') # build user of library self.new_builddir() # replace is needed because meson mangles platform pathes passed via LDFLAGS - os.environ["LDFLAGS"] = '-L{}'.format(libdir.replace('\\', '/')) - self.init(os.path.join(testdirbase, 'exe')) - del os.environ["LDFLAGS"] + self.init(os.path.join(testdirbase, 'exe'), + override_envvars={"LDFLAGS": '-L{}'.format(libdir.replace('\\', '/'))}) self.build() self.assertBuildIsNoop() @@ -3027,9 +3082,10 @@ recommended as it is not supported on some platforms''') self.wipe() # c_args value should be parsed with shlex - self.init(testdir, extra_args=['-Dc_args=foo bar "one two"']) + self.init(testdir, extra_args=['-Dc_args=-Dfoo -Dbar "-Dthird=one two"']) obj = mesonbuild.coredata.load(self.builddir) - self.assertEqual(obj.compiler_options.host['c_args'].value, ['foo', 'bar', 'one two']) + self.assertEqual(obj.compiler_options.host['c_args'].value, ['-Dfoo', '-Dbar', '-Dthird=one two']) + self.setconf('-Dc_args="foo bar" one two') obj = mesonbuild.coredata.load(self.builddir) self.assertEqual(obj.compiler_options.host['c_args'].value, ['foo bar', 'one', 'two']) @@ -3040,21 +3096,21 @@ recommended as it is not supported on some platforms''') self.init(testdir, extra_args=['--bindir=foo', '--bindir=bar', '-Dbuildtype=plain', '-Dbuildtype=release', '-Db_sanitize=address', '-Db_sanitize=thread', - '-Dc_args=foo', '-Dc_args=bar']) + '-Dc_args=-Dfoo', '-Dc_args=-Dbar']) obj = mesonbuild.coredata.load(self.builddir) self.assertEqual(obj.builtins['bindir'].value, 'bar') self.assertEqual(obj.builtins['buildtype'].value, 'release') self.assertEqual(obj.base_options['b_sanitize'].value, 'thread') - self.assertEqual(obj.compiler_options.host['c_args'].value, ['bar']) + self.assertEqual(obj.compiler_options.host['c_args'].value, ['-Dbar']) self.setconf(['--bindir=bar', '--bindir=foo', '-Dbuildtype=release', '-Dbuildtype=plain', '-Db_sanitize=thread', '-Db_sanitize=address', - '-Dc_args=bar', '-Dc_args=foo']) + '-Dc_args=-Dbar', '-Dc_args=-Dfoo']) obj = mesonbuild.coredata.load(self.builddir) self.assertEqual(obj.builtins['bindir'].value, 'foo') self.assertEqual(obj.builtins['buildtype'].value, 'plain') self.assertEqual(obj.base_options['b_sanitize'].value, 'address') - self.assertEqual(obj.compiler_options.host['c_args'].value, ['foo']) + self.assertEqual(obj.compiler_options.host['c_args'].value, ['-Dfoo']) self.wipe() except KeyError: # Ignore KeyError, it happens on CI for compilers that does not @@ -3201,11 +3257,11 @@ recommended as it is not supported on some platforms''') crossfile.flush() self.meson_cross_file = crossfile.name - os.environ['PKG_CONFIG_LIBDIR'] = os.path.join(testdir, - 'native_pkgconfig') - self.init(testdir, extra_args=['-Dstart_native=false']) + env = {'PKG_CONFIG_LIBDIR': os.path.join(testdir, + 'native_pkgconfig')} + self.init(testdir, extra_args=['-Dstart_native=false'], override_envvars=env) self.wipe() - self.init(testdir, extra_args=['-Dstart_native=true']) + self.init(testdir, extra_args=['-Dstart_native=true'], override_envvars=env) def __reconfigure(self, change_minor=False): # Set an older version to force a reconfigure from scratch @@ -3342,7 +3398,7 @@ recommended as it is not supported on some platforms''') for entry in res: name = entry['name'] - self.assertEquals(entry['subproject'], expected[name]) + self.assertEqual(entry['subproject'], expected[name]) def test_introspect_projectinfo_subproject_dir(self): testdir = os.path.join(self.common_test_dir, '79 custom subproject dir') @@ -3712,7 +3768,12 @@ class FailureTests(BasePlatformTests): super().tearDown() windows_proof_rmtree(self.srcdir) - def assertMesonRaises(self, contents, match, extra_args=None, langs=None, meson_version=None, options=None): + def assertMesonRaises(self, contents, match, *, + extra_args=None, + langs=None, + meson_version=None, + options=None, + override_envvars=None): ''' Assert that running meson configure on the specified @contents raises a error message matching regex @match. @@ -3730,11 +3791,17 @@ class FailureTests(BasePlatformTests): if options is not None: with open(self.moptions, 'w') as f: f.write(options) + o = {'MESON_FORCE_BACKTRACE': '1'} + if override_envvars is None: + override_envvars = o + else: + override_envvars.update(o) # Force tracebacks so we can detect them properly - os.environ['MESON_FORCE_BACKTRACE'] = '1' with self.assertRaisesRegex(MesonException, match, msg=contents): # Must run in-process or we'll get a generic CalledProcessError - self.init(self.srcdir, extra_args=extra_args, inprocess=True) + self.init(self.srcdir, extra_args=extra_args, + inprocess=True, + override_envvars = override_envvars) def obtainMesonOutput(self, contents, match, extra_args, langs, meson_version=None): if langs is None: @@ -3844,9 +3911,9 @@ class FailureTests(BasePlatformTests): def test_boost_BOOST_ROOT_dependency(self): # Test BOOST_ROOT; can be run even if Boost is found or not - os.environ['BOOST_ROOT'] = 'relative/path' self.assertMesonRaises("dependency('boost')", - "(BOOST_ROOT.*absolute|{})".format(self.dnf)) + "(BOOST_ROOT.*absolute|{})".format(self.dnf), + override_envvars = {'BOOST_ROOT': 'relative/path'}) def test_dependency_invalid_method(self): code = '''zlib_dep = dependency('zlib', required : false) @@ -3905,9 +3972,8 @@ class FailureTests(BasePlatformTests): Test exit status on python exception ''' tdir = os.path.join(self.unit_test_dir, '21 exit status') - os.environ['MESON_UNIT_TEST'] = '1' with self.assertRaises(subprocess.CalledProcessError) as cm: - self.init(tdir, inprocess=False) + self.init(tdir, inprocess=False, override_envvars = {'MESON_UNIT_TEST': '1'}) self.assertEqual(cm.exception.returncode, 2) self.wipe() @@ -4178,11 +4244,10 @@ class DarwinTests(BasePlatformTests): # to ascertain that Meson does not call install_name_tool # with duplicate -delete_rpath arguments, which would # lead to erroring out on installation - os.environ["LDFLAGS"] = "-Wl,-rpath,/foo/bar" - self.init(testdir) + env = {"LDFLAGS": "-Wl,-rpath,/foo/bar"} + self.init(testdir, override_envvars=env) self.build() self.install() - del os.environ["LDFLAGS"] @unittest.skipUnless(not is_windows(), "requires something Unix-like") @@ -4265,13 +4330,13 @@ class LinuxlikeTests(BasePlatformTests): privatedir1 = self.privatedir self.new_builddir() - os.environ['PKG_CONFIG_LIBDIR'] = privatedir1 testdir = os.path.join(self.common_test_dir, '48 pkgconfig-gen', 'dependencies') - self.init(testdir) + self.init(testdir, override_envvars={'PKG_CONFIG_LIBDIR': privatedir1}) privatedir2 = self.privatedir - os.environ['PKG_CONFIG_LIBDIR'] = os.pathsep.join([privatedir1, privatedir2]) - self._run(['pkg-config', 'dependency-test', '--validate']) + os.environ + env = {'PKG_CONFIG_LIBDIR': os.pathsep.join([privatedir1, privatedir2])} + self._run(['pkg-config', 'dependency-test', '--validate'], override_envvars=env) # pkg-config strips some duplicated flags so we have to parse the # generated file ourself. @@ -4297,14 +4362,14 @@ class LinuxlikeTests(BasePlatformTests): self.assertEqual(len(expected), matched_lines) cmd = ['pkg-config', 'requires-test'] - out = self._run(cmd + ['--print-requires']).strip().split('\n') + out = self._run(cmd + ['--print-requires'], override_envvars=env).strip().split('\n') if not is_openbsd(): self.assertEqual(sorted(out), sorted(['libexposed', 'libfoo >= 1.0', 'libhello'])) else: self.assertEqual(sorted(out), sorted(['libexposed', 'libfoo>=1.0', 'libhello'])) cmd = ['pkg-config', 'requires-private-test'] - out = self._run(cmd + ['--print-requires-private']).strip().split('\n') + out = self._run(cmd + ['--print-requires-private'], override_envvars=env).strip().split('\n') if not is_openbsd(): self.assertEqual(sorted(out), sorted(['libexposed', 'libfoo >= 1.0', 'libhello'])) else: @@ -4363,7 +4428,7 @@ class LinuxlikeTests(BasePlatformTests): qt4 = subprocess.call(['pkg-config', '--exists', 'QtCore']) qt5 = subprocess.call(['pkg-config', '--exists', 'Qt5Core']) testdir = os.path.join(self.framework_test_dir, '4 qt') - self.init(testdir, ['-Dmethod=pkg-config']) + self.init(testdir, extra_args=['-Dmethod=pkg-config']) # Confirm that the dependency was found with pkg-config mesonlog = self.get_meson_log() if qt4 == 0: @@ -4381,7 +4446,7 @@ class LinuxlikeTests(BasePlatformTests): raise unittest.SkipTest('-fsanitize=address is not supported on OpenBSD') testdir = os.path.join(self.framework_test_dir, '7 gnome') - self.init(testdir, ['-Db_sanitize=address', '-Db_lundef=false']) + self.init(testdir, extra_args=['-Db_sanitize=address', '-Db_lundef=false']) self.build() def test_qt5dependency_qmake_detection(self): @@ -4398,7 +4463,7 @@ class LinuxlikeTests(BasePlatformTests): raise unittest.SkipTest('Qmake found, but it is not for Qt 5.') # Disable pkg-config codepath and force searching with qmake/qmake-qt5 testdir = os.path.join(self.framework_test_dir, '4 qt') - self.init(testdir, ['-Dmethod=qmake']) + self.init(testdir, extra_args=['-Dmethod=qmake']) # Confirm that the dependency was found with qmake mesonlog = self.get_meson_log() self.assertRegex('\n'.join(mesonlog), @@ -4463,9 +4528,10 @@ class LinuxlikeTests(BasePlatformTests): an ordinary test case because it needs the environment to be set. ''' Oflag = '-O3' - os.environ['CFLAGS'] = os.environ['CXXFLAGS'] = Oflag + env = {'CFLAGS': Oflag, + 'CXXFLAGS': Oflag} testdir = os.path.join(self.common_test_dir, '40 has function') - self.init(testdir) + self.init(testdir, override_envvars=env) cmds = self.get_meson_log_compiler_checks() for cmd in cmds: if cmd[0] == 'ccache': @@ -4493,7 +4559,7 @@ class LinuxlikeTests(BasePlatformTests): if (compiler.get_id() == 'gcc' and '2a' in v and version_compare(compiler.version, '<8.0.0')): continue std_opt = '{}={}'.format(lang_std, v) - self.init(testdir, ['-D' + std_opt]) + self.init(testdir, extra_args=['-D' + std_opt]) cmd = self.get_compdb()[0]['command'] # c++03 and gnu++03 are not understood by ICC, don't try to look for them skiplist = frozenset([ @@ -4511,11 +4577,17 @@ class LinuxlikeTests(BasePlatformTests): # Check that an invalid std option in CFLAGS/CPPFLAGS fails # Needed because by default ICC ignores invalid options cmd_std = '-std=FAIL' - env_flags = p.upper() + 'FLAGS' - os.environ[env_flags] = cmd_std + if p == 'c': + env_flag_name = 'CFLAGS' + elif p == 'cpp': + env_flag_name = 'CXXFLAGS' + else: + raise NotImplementedError('Language {} not defined.'.format(p)) + env = {} + env[env_flag_name] = cmd_std with self.assertRaises((subprocess.CalledProcessError, mesonbuild.mesonlib.EnvironmentException), msg='C compiler should have failed with -std=FAIL'): - self.init(testdir) + self.init(testdir, override_envvars = env) # ICC won't fail in the above because additional flags are needed to # make unknown -std=... options errors. self.build() @@ -4764,8 +4836,7 @@ class LinuxlikeTests(BasePlatformTests): @skipIfNoPkgconfig def test_order_of_l_arguments(self): testdir = os.path.join(self.unit_test_dir, '8 -L -l order') - os.environ['PKG_CONFIG_PATH'] = testdir - self.init(testdir) + self.init(testdir, override_envvars={'PKG_CONFIG_PATH': testdir}) # NOTE: .pc file has -Lfoo -lfoo -Lbar -lbar but pkg-config reorders # the flags before returning them to -Lfoo -Lbar -lfoo -lbar # but pkgconf seems to not do that. Sigh. Support both. @@ -4846,7 +4917,7 @@ class LinuxlikeTests(BasePlatformTests): raise unittest.SkipTest('-fsanitize=address is not supported on OpenBSD') testdir = os.path.join(self.common_test_dir, '13 pch') - self.init(testdir, ['-Db_sanitize=address']) + self.init(testdir, extra_args=['-Db_sanitize=address']) self.build() compdb = self.get_compdb() for i in compdb: @@ -4862,7 +4933,7 @@ class LinuxlikeTests(BasePlatformTests): # We need to use llvm-cov instead of gcovr with clang raise unittest.SkipTest('Coverage does not work with clang right now, help wanted!') testdir = os.path.join(self.common_test_dir, '1 trivial') - self.init(testdir, ['-Db_coverage=true']) + self.init(testdir, extra_args=['-Db_coverage=true']) self.build() self.run_tests() self.run_target('coverage-html') @@ -4892,7 +4963,7 @@ endian = 'little' def test_reconfigure(self): testdir = os.path.join(self.unit_test_dir, '13 reconfigure') - self.init(testdir, ['-Db_coverage=true'], default_args=False) + self.init(testdir, extra_args=['-Db_coverage=true'], default_args=False) self.build('reconfigure') def test_vala_generated_source_buildir_inside_source_tree(self): @@ -4923,11 +4994,15 @@ endian = 'little' also tested. ''' testdir = os.path.join(self.framework_test_dir, '7 gnome') - os.environ['MESON_UNIT_TEST_PRETEND_GLIB_OLD'] = "1" mesonbuild.modules.gnome.native_glib_version = '2.20' - self.init(testdir, inprocess=True) - self.build() - mesonbuild.modules.gnome.native_glib_version = None + env = {'MESON_UNIT_TEST_PRETEND_GLIB_OLD': "1"} + try: + self.init(testdir, + inprocess=True, + override_envvars=env) + self.build(override_envvars=env) + finally: + mesonbuild.modules.gnome.native_glib_version = None @skipIfNoPkgconfig def test_pkgconfig_usage(self): @@ -4938,23 +5013,24 @@ endian = 'little' stderr=subprocess.DEVNULL) != 0: raise unittest.SkipTest('Glib 2.0 dependency not available.') with tempfile.TemporaryDirectory() as tempdirname: - self.init(testdir1, ['--prefix=' + tempdirname, '--libdir=lib'], default_args=False) + self.init(testdir1, extra_args=['--prefix=' + tempdirname, '--libdir=lib'], default_args=False) self.install(use_destdir=False) shutil.rmtree(self.builddir) os.mkdir(self.builddir) pkg_dir = os.path.join(tempdirname, 'lib/pkgconfig') self.assertTrue(os.path.exists(os.path.join(pkg_dir, 'libpkgdep.pc'))) lib_dir = os.path.join(tempdirname, 'lib') - os.environ['PKG_CONFIG_PATH'] = pkg_dir + myenv = os.environ.copy() + myenv['PKG_CONFIG_PATH'] = pkg_dir # Private internal libraries must not leak out. - pkg_out = subprocess.check_output(['pkg-config', '--static', '--libs', 'libpkgdep']) + pkg_out = subprocess.check_output(['pkg-config', '--static', '--libs', 'libpkgdep'], env=myenv) self.assertFalse(b'libpkgdep-int' in pkg_out, 'Internal library leaked out.') # Dependencies must not leak to cflags when building only a shared library. - pkg_out = subprocess.check_output(['pkg-config', '--cflags', 'libpkgdep']) + pkg_out = subprocess.check_output(['pkg-config', '--cflags', 'libpkgdep'], env=myenv) self.assertFalse(b'glib' in pkg_out, 'Internal dependency leaked to headers.') # Test that the result is usable. - self.init(testdir2) - self.build() + self.init(testdir2, override_envvars=myenv) + self.build(override_envvars=myenv) myenv = os.environ.copy() myenv['LD_LIBRARY_PATH'] = ':'.join([lib_dir, myenv.get('LD_LIBRARY_PATH', '')]) if is_cygwin(): @@ -4998,9 +5074,9 @@ endian = 'little' # build user of library pkg_dir = os.path.join(tempdirname, 'lib/pkgconfig') - os.environ['PKG_CONFIG_PATH'] = pkg_dir self.new_builddir() - self.init(os.path.join(testdirbase, 'app')) + self.init(os.path.join(testdirbase, 'app'), + override_envvars={'PKG_CONFIG_PATH': pkg_dir}) self.build() @skipIfNoPkgconfig @@ -5105,16 +5181,16 @@ endian = 'little' self.install(use_destdir=False) ## New builddir for the consumer self.new_builddir() - os.environ['LIBRARY_PATH'] = os.path.join(installdir, self.libdir) - os.environ['PKG_CONFIG_PATH'] = os.path.join(installdir, self.libdir, 'pkgconfig') + env = {'LIBRARY_PATH': os.path.join(installdir, self.libdir), + 'PKG_CONFIG_PATH': os.path.join(installdir, self.libdir, 'pkgconfig')} testdir = os.path.join(self.unit_test_dir, '40 external, internal library rpath', 'built library') # install into installdir without using DESTDIR self.prefix = self.installdir - self.init(testdir) + self.init(testdir, override_envvars=env) self.prefix = oldprefix - self.build() + self.build(override_envvars=env) # test uninstalled - self.run_tests() + self.run_tests(override_envvars=env) if not is_osx(): # Rest of the workflow only works on macOS return @@ -5127,10 +5203,10 @@ endian = 'little' ## New builddir for testing that DESTDIR is not added to install_name self.new_builddir() # install into installdir with DESTDIR - self.init(testdir) - self.build() + self.init(testdir, override_envvars=env) + self.build(override_envvars=env) # test running after installation - self.install() + self.install(override_envvars=env) prog = self.installdir + os.path.join(self.prefix, 'bin', 'prog') lib = self.installdir + os.path.join(self.prefix, 'lib', 'libbar_built.dylib') for f in prog, lib: @@ -5220,14 +5296,14 @@ endian = 'little' def test_identity_cross(self): testdir = os.path.join(self.unit_test_dir, '58 identity cross') crossfile = tempfile.NamedTemporaryFile(mode='w') - os.environ['CC'] = '"' + os.path.join(testdir, 'build_wrapper.py') + '"' + env = {'CC': '"' + os.path.join(testdir, 'build_wrapper.py') + '"'} crossfile.write('''[binaries] c = ['{0}'] '''.format(os.path.join(testdir, 'host_wrapper.py'))) crossfile.flush() self.meson_cross_file = crossfile.name # TODO should someday be explicit about build platform only here - self.init(testdir) + self.init(testdir, override_envvars=env) def should_run_cross_arm_tests(): return shutil.which('arm-linux-gnueabihf-gcc') and not platform.machine().lower().startswith('arm') @@ -5249,8 +5325,7 @@ class LinuxCrossArmTests(BasePlatformTests): inspect the compiler database. ''' testdir = os.path.join(self.common_test_dir, '3 static') - os.environ['CFLAGS'] = '-DBUILD_ENVIRONMENT_ONLY' - self.init(testdir) + self.init(testdir, override_envvars={'CFLAGS': '-DBUILD_ENVIRONMENT_ONLY'}) compdb = self.get_compdb() self.assertNotIn('-DBUILD_ENVIRONMENT_ONLY', compdb[0]['command']) @@ -5319,18 +5394,23 @@ class LinuxCrossMingwTests(BasePlatformTests): # Change cross file to use a non-existing exe_wrapper and it should fail self.meson_cross_file = os.path.join(testdir, 'broken-cross.txt') # Force tracebacks so we can detect them properly - os.environ['MESON_FORCE_BACKTRACE'] = '1' + env = {'MESON_FORCE_BACKTRACE': '1'} with self.assertRaisesRegex(MesonException, 'exe_wrapper.*target.*use-exe-wrapper'): # Must run in-process or we'll get a generic CalledProcessError - self.init(testdir, extra_args='-Drun-target=false', inprocess=True) + self.init(testdir, extra_args='-Drun-target=false', + inprocess=True, + override_envvars=env) with self.assertRaisesRegex(MesonException, 'exe_wrapper.*run target.*run-prog'): # Must run in-process or we'll get a generic CalledProcessError - self.init(testdir, extra_args='-Dcustom-target=false', inprocess=True) - self.init(testdir, extra_args=['-Dcustom-target=false', '-Drun-target=false']) + self.init(testdir, extra_args='-Dcustom-target=false', + inprocess=True, + override_envvars=env) + self.init(testdir, extra_args=['-Dcustom-target=false', '-Drun-target=false'], + override_envvars=env) self.build() with self.assertRaisesRegex(MesonException, 'exe_wrapper.*PATH'): # Must run in-process or we'll get a generic CalledProcessError - self.run_tests(inprocess=True) + self.run_tests(inprocess=True, override_envvars=env) @skipIfNoPkgconfig def test_cross_pkg_config_option(self): @@ -5361,7 +5441,7 @@ class PythonTests(BasePlatformTests): # will also try 'python' as a fallback and use it if the major # version matches try: - self.init(testdir, ['-Dpython=python2']) + self.init(testdir, extra_args=['-Dpython=python2']) self.build() self.run_tests() except unittest.SkipTest: @@ -5377,7 +5457,7 @@ class PythonTests(BasePlatformTests): for py in ('pypy', 'pypy3'): try: - self.init(testdir, ['-Dpython=%s' % py]) + self.init(testdir, extra_args=['-Dpython=%s' % py]) except unittest.SkipTest: # Same as above, pypy2 and pypy3 are not expected to be present # on the test system, the test project only raises in these cases @@ -5391,13 +5471,13 @@ class PythonTests(BasePlatformTests): # The test is configured to error out with MESON_SKIP_TEST # in case it could not find python with self.assertRaises(unittest.SkipTest): - self.init(testdir, ['-Dpython=not-python']) + self.init(testdir, extra_args=['-Dpython=not-python']) self.wipe() # While dir is an external command on both Windows and Linux, # it certainly isn't python with self.assertRaises(unittest.SkipTest): - self.init(testdir, ['-Dpython=dir']) + self.init(testdir, extra_args=['-Dpython=dir']) self.wipe() @@ -5801,7 +5881,7 @@ class NativeFileTests(BasePlatformTests): if mesonbuild.environment.detect_msys2_arch(): f.write(r'@python3 {} %*'.format(filename)) else: - f.write('@py -3 {} %*'.format(filename)) + f.write('@{} {} %*'.format(sys.executable, filename)) return batfile def helper_for_compiler(self, lang, cb, for_machine = MachineChoice.HOST): @@ -6400,6 +6480,17 @@ def unset_envs(): def main(): unset_envs() + pytest_args = ['-n', 'auto', './run_unittests.py'] + if shutil.which('pytest-3'): + return subprocess.run(['pytest-3'] + pytest_args).returncode + elif shutil.which('pytest'): + return subprocess.run(['pytest'] + pytest_args).returncode + try: + import pytest # noqa: F401 + return subprocess.run(python_command + ['-m', 'pytest'] + pytest_args).returncode + except ImportError: + pass + # All attempts at locating pytest failed, fall back to plain unittest. cases = ['InternalTests', 'DataTests', 'AllPlatformTests', 'FailureTests', 'PythonTests', 'NativeFileTests', 'RewriterTests', 'CrossFileTests', 'TAPParserTests', diff --git a/test cases/frameworks/15 llvm/meson.build b/test cases/frameworks/15 llvm/meson.build index af94dae..4b2c88c 100644 --- a/test cases/frameworks/15 llvm/meson.build +++ b/test cases/frameworks/15 llvm/meson.build @@ -31,25 +31,29 @@ if not dep_tinfo.found() dep_tinfo = cpp.find_library('tinfo', required: false) endif -foreach static : [true, false] - llvm_dep = dependency( - 'llvm', - modules : ['bitwriter', 'asmprinter', 'executionengine', 'target', - 'mcjit', 'nativecodegen', 'amdgpu'], - required : false, - static : static, - ) - if llvm_dep.found() - name = static ? 'static' : 'dynamic' - executable( - 'sum-@0@'.format(name), - 'sum.c', - dependencies : [ - llvm_dep, dep_tinfo, - # zlib will be statically linked on windows - dependency('zlib', required : host_machine.system() != 'windows'), - meson.get_compiler('c').find_library('dl', required : false), - ] +foreach method : ['config-tool', 'cmake'] + foreach static : [true, false] + message('Trying method @0@ for @1@ link'.format(method, static ? 'static' : 'dynamic')) + llvm_dep = dependency( + 'llvm', + modules : ['bitwriter', 'asmprinter', 'executionengine', 'target', + 'mcjit', 'nativecodegen', 'amdgpu'], + required : false, + static : static, + method : method, ) - endif + if llvm_dep.found() + name = static ? 'static' : 'dynamic' + executable( + 'sum-@0@-@1@'.format(name, method), + 'sum.c', + dependencies : [ + llvm_dep, dep_tinfo, + # zlib will be statically linked on windows + dependency('zlib', required : host_machine.system() != 'windows'), + meson.get_compiler('c').find_library('dl', required : false), + ] + ) + endif + endforeach endforeach diff --git a/test cases/frameworks/23 hotdoc/installed_files.txt b/test cases/frameworks/23 hotdoc/installed_files.txt index 296dcf6..82597a2 100644 --- a/test cases/frameworks/23 hotdoc/installed_files.txt +++ b/test cases/frameworks/23 hotdoc/installed_files.txt @@ -66,6 +66,15 @@ usr/share/doc/foobar/html/assets/js/search/indecision usr/share/doc/foobar/html/assets/js/search/hotdoc_fragments/index.html-hello-world.fragment usr/share/doc/foobar/html/assets/js/search/hotdoc_fragments/dumped.trie usr/share/doc/foobar/html/assets/js/search/hotdoc_fragments/foo.html-FooIndecision.fragment +usr/share/doc/foobar/html/assets/js/search/Subpages +usr/share/doc/foobar/html/assets/js/search/foo +usr/share/doc/foobar/html/assets/js/search/API +usr/share/doc/foobar/html/assets/js/search/Reference +usr/share/doc/foobar/html/assets/js/search/api +usr/share/doc/foobar/html/assets/js/search/reference +usr/share/doc/foobar/html/assets/js/search/subpages +usr/share/doc/foobar/html/assets/js/search/hotdoc_fragments/index.html-subpages.fragment +usr/share/doc/foobar/html/assets/js/search/hotdoc_fragments/c-index.html-subpages.fragment usr/share/doc/foobar/html/assets/prism_components/prism-inform7.min.js usr/share/doc/foobar/html/assets/prism_components/prism-pascal.min.js usr/share/doc/foobar/html/assets/prism_components/prism-bro.js diff --git a/test cases/windows/5 resources/res/meson.build b/test cases/windows/5 resources/res/meson.build index 160d651..74e0778 100644 --- a/test cases/windows/5 resources/res/meson.build +++ b/test cases/windows/5 resources/res/meson.build @@ -2,7 +2,8 @@ win = import('windows') res = win.compile_resources('myres.rc', depend_files: 'sample.ico', - include_directories : inc) + include_directories : inc, + args : [['-DFOO'], '-DBAR']) # test that with MSVC tools, LIB/LINK invokes CVTRES with correct /MACHINE static_library('reslib', res, 'dummy.c') |