|
|
|
# Copyright 2016-2021 The Meson development team
|
|
|
|
|
|
|
|
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
|
|
# you may not use this file except in compliance with the License.
|
|
|
|
# You may obtain a copy of the License at
|
|
|
|
|
|
|
|
# http://www.apache.org/licenses/LICENSE-2.0
|
|
|
|
|
|
|
|
# Unless required by applicable law or agreed to in writing, software
|
|
|
|
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
|
|
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
|
|
# See the License for the specific language governing permissions and
|
|
|
|
# limitations under the License.
|
|
|
|
|
|
|
|
from pathlib import PurePath
|
|
|
|
from unittest import mock, TestCase, SkipTest
|
|
|
|
import json
|
|
|
|
import io
|
|
|
|
import os
|
|
|
|
import re
|
|
|
|
import subprocess
|
|
|
|
import sys
|
|
|
|
import tempfile
|
|
|
|
import typing as T
|
|
|
|
|
|
|
|
import mesonbuild.mlog
|
|
|
|
import mesonbuild.depfile
|
|
|
|
import mesonbuild.dependencies.base
|
|
|
|
import mesonbuild.dependencies.factory
|
|
|
|
import mesonbuild.compilers
|
|
|
|
import mesonbuild.envconfig
|
|
|
|
import mesonbuild.environment
|
|
|
|
import mesonbuild.coredata
|
|
|
|
import mesonbuild.modules.gnome
|
|
|
|
from mesonbuild.mesonlib import (
|
|
|
|
is_cygwin, join_args, split_args, windows_proof_rmtree, python_command
|
|
|
|
)
|
|
|
|
import mesonbuild.modules.pkgconfig
|
|
|
|
|
|
|
|
|
|
|
|
from run_tests import (
|
|
|
|
Backend, ensure_backend_detects_changes, get_backend_commands,
|
|
|
|
get_builddir_target_args, get_meson_script, run_configure_inprocess,
|
|
|
|
run_mtest_inprocess, handle_meson_skip_test,
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
# magic attribute used by unittest.result.TestResult._is_relevant_tb_level
|
|
|
|
# This causes tracebacks to hide these internal implementation details,
|
|
|
|
# e.g. for assertXXX helpers.
|
|
|
|
__unittest = True
|
|
|
|
|
|
|
|
class BasePlatformTests(TestCase):
|
|
|
|
prefix = '/usr'
|
|
|
|
libdir = 'lib'
|
|
|
|
|
|
|
|
def setUp(self):
|
|
|
|
super().setUp()
|
|
|
|
self.maxDiff = None
|
|
|
|
src_root = str(PurePath(__file__).parents[1])
|
|
|
|
self.src_root = src_root
|
|
|
|
# Get the backend
|
|
|
|
self.backend_name = os.environ['MESON_UNIT_TEST_BACKEND']
|
|
|
|
backend_type = 'vs' if self.backend_name.startswith('vs') else self.backend_name
|
|
|
|
self.backend = getattr(Backend, backend_type)
|
|
|
|
self.meson_args = ['--backend=' + self.backend_name]
|
|
|
|
self.meson_native_files = []
|
|
|
|
self.meson_cross_files = []
|
|
|
|
self.meson_command = python_command + [get_meson_script()]
|
deprecate running "meson builddir" without setup subcommand
This is ambiguous, if the build directory has the same name as a
subcommand then we end up running the subcommand. It also means we have
a hard time adding *new* subcommands, because if it is a popular name of
a build directory then suddenly scripts that try to set up a build
directory end up running a subcommand instead.
The fact that we support this at all is a legacy design. Back in the
day, the "meson" program was for setting up a build directory and all
other tools were their own entry points, e.g. `mesontest` or
`mesonconf`. Then in commit fa278f351fe3d6924b4d1961f77b5b4a36e133f8 we
migrated to the subcommand mechanism. So, for backwards compatibility,
we made those tools print a warning and then invoke `meson <tool>`. We
also made the `meson` tool default to setup.
However, we only warned for the other tools whose entry points were
eventually deleted. We never warned for setup itself, we just continued
to silently default to setup if no tool was provided.
`meson setup` has worked since 0.42, which is 5 years old this week.
It's available essentially everywhere. No one needs to use the old
backwards-compatible invocation method, but it continues to drag down
our ability to innovate. Let's finally do what we should have done a
long time ago, and sunset it.
2 years ago
|
|
|
self.setup_command = self.meson_command + ['setup'] + self.meson_args
|
|
|
|
self.mconf_command = self.meson_command + ['configure']
|
|
|
|
self.mintro_command = self.meson_command + ['introspect']
|
|
|
|
self.wrap_command = self.meson_command + ['wrap']
|
|
|
|
self.rewrite_command = self.meson_command + ['rewrite']
|
|
|
|
# Backend-specific build commands
|
|
|
|
self.build_command, self.clean_command, self.test_command, self.install_command, \
|
|
|
|
self.uninstall_command = get_backend_commands(self.backend)
|
|
|
|
# Test directories
|
|
|
|
self.common_test_dir = os.path.join(src_root, 'test cases/common')
|
|
|
|
self.python_test_dir = os.path.join(src_root, 'test cases/python')
|
|
|
|
self.rust_test_dir = os.path.join(src_root, 'test cases/rust')
|
|
|
|
self.vala_test_dir = os.path.join(src_root, 'test cases/vala')
|
|
|
|
self.framework_test_dir = os.path.join(src_root, 'test cases/frameworks')
|
|
|
|
self.unit_test_dir = os.path.join(src_root, 'test cases/unit')
|
|
|
|
self.rewrite_test_dir = os.path.join(src_root, 'test cases/rewrite')
|
|
|
|
self.linuxlike_test_dir = os.path.join(src_root, 'test cases/linuxlike')
|
|
|
|
self.objc_test_dir = os.path.join(src_root, 'test cases/objc')
|
|
|
|
self.objcpp_test_dir = os.path.join(src_root, 'test cases/objcpp')
|
|
|
|
|
|
|
|
# Misc stuff
|
|
|
|
self.orig_env = os.environ.copy()
|
|
|
|
if self.backend is Backend.ninja:
|
|
|
|
self.no_rebuild_stdout = ['ninja: no work to do.', 'samu: nothing to do']
|
|
|
|
else:
|
|
|
|
# VS doesn't have a stable output when no changes are done
|
|
|
|
# XCode backend is untested with unit tests, help welcome!
|
|
|
|
self.no_rebuild_stdout = [f'UNKNOWN BACKEND {self.backend.name!r}']
|
|
|
|
os.environ['COLUMNS'] = '80'
|
|
|
|
|
|
|
|
self.builddirs = []
|
|
|
|
self.new_builddir()
|
|
|
|
|
|
|
|
def change_builddir(self, newdir):
|
|
|
|
self.builddir = newdir
|
|
|
|
self.privatedir = os.path.join(self.builddir, 'meson-private')
|
|
|
|
self.logdir = os.path.join(self.builddir, 'meson-logs')
|
|
|
|
self.installdir = os.path.join(self.builddir, 'install')
|
|
|
|
self.distdir = os.path.join(self.builddir, 'meson-dist')
|
|
|
|
self.mtest_command = self.meson_command + ['test', '-C', self.builddir]
|
|
|
|
self.builddirs.append(self.builddir)
|
|
|
|
|
|
|
|
def new_builddir(self):
|
|
|
|
# Keep builddirs inside the source tree so that virus scanners
|
|
|
|
# don't complain
|
|
|
|
newdir = tempfile.mkdtemp(dir=os.getcwd())
|
|
|
|
# In case the directory is inside a symlinked directory, find the real
|
|
|
|
# path otherwise we might not find the srcdir from inside the builddir.
|
|
|
|
newdir = os.path.realpath(newdir)
|
|
|
|
self.change_builddir(newdir)
|
|
|
|
|
|
|
|
def new_builddir_in_tempdir(self):
|
|
|
|
# Can't keep the builddir inside the source tree for the umask tests:
|
|
|
|
# https://github.com/mesonbuild/meson/pull/5546#issuecomment-509666523
|
|
|
|
# And we can't do this for all tests because it causes the path to be
|
|
|
|
# a short-path which breaks other tests:
|
|
|
|
# https://github.com/mesonbuild/meson/pull/9497
|
|
|
|
newdir = tempfile.mkdtemp()
|
|
|
|
# In case the directory is inside a symlinked directory, find the real
|
|
|
|
# path otherwise we might not find the srcdir from inside the builddir.
|
|
|
|
newdir = os.path.realpath(newdir)
|
|
|
|
self.change_builddir(newdir)
|
|
|
|
|
|
|
|
def _open_meson_log(self) -> io.TextIOWrapper:
|
|
|
|
log = os.path.join(self.logdir, 'meson-log.txt')
|
|
|
|
return open(log, encoding='utf-8')
|
|
|
|
|
|
|
|
def _get_meson_log(self) -> T.Optional[str]:
|
|
|
|
try:
|
|
|
|
with self._open_meson_log() as f:
|
|
|
|
return f.read()
|
|
|
|
except FileNotFoundError as e:
|
|
|
|
print(f"{e.filename!r} doesn't exist", file=sys.stderr)
|
|
|
|
return None
|
|
|
|
|
|
|
|
def _print_meson_log(self) -> None:
|
|
|
|
log = self._get_meson_log()
|
|
|
|
if log:
|
|
|
|
print(log)
|
|
|
|
|
|
|
|
def tearDown(self):
|
|
|
|
for path in self.builddirs:
|
|
|
|
try:
|
|
|
|
windows_proof_rmtree(path)
|
|
|
|
except FileNotFoundError:
|
|
|
|
pass
|
|
|
|
os.environ.clear()
|
|
|
|
os.environ.update(self.orig_env)
|
|
|
|
super().tearDown()
|
|
|
|
|
|
|
|
def _run(self, command, *, workdir=None, override_envvars: T.Optional[T.Mapping[str, str]] = None, stderr=True):
|
|
|
|
'''
|
|
|
|
Run a command while printing the stdout and stderr to stdout,
|
|
|
|
and also return a copy of it
|
|
|
|
'''
|
|
|
|
# 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)
|
|
|
|
|
|
|
|
proc = subprocess.run(command, stdout=subprocess.PIPE,
|
|
|
|
stderr=subprocess.STDOUT if stderr else subprocess.PIPE,
|
|
|
|
env=env,
|
|
|
|
encoding='utf-8',
|
|
|
|
text=True, cwd=workdir, timeout=60 * 5)
|
|
|
|
print('$', join_args(command))
|
|
|
|
print('stdout:')
|
|
|
|
print(proc.stdout)
|
|
|
|
if not stderr:
|
|
|
|
print('stderr:')
|
|
|
|
print(proc.stderr)
|
|
|
|
if proc.returncode != 0:
|
|
|
|
skipped, reason = handle_meson_skip_test(proc.stdout)
|
|
|
|
if skipped:
|
|
|
|
raise SkipTest(f'Project requested skipping: {reason}')
|
|
|
|
raise subprocess.CalledProcessError(proc.returncode, command, output=proc.stdout)
|
|
|
|
return proc.stdout
|
|
|
|
|
|
|
|
def init(self, srcdir, *,
|
|
|
|
extra_args=None,
|
|
|
|
default_args=True,
|
|
|
|
inprocess=False,
|
|
|
|
override_envvars: T.Optional[T.Mapping[str, str]] = None,
|
|
|
|
workdir=None,
|
|
|
|
allow_fail: bool = False) -> str:
|
|
|
|
"""Call `meson setup`
|
|
|
|
|
|
|
|
:param allow_fail: If set to true initialization is allowed to fail.
|
|
|
|
When it does the log will be returned instead of stdout.
|
|
|
|
:return: the value of stdout on success, or the meson log on failure
|
|
|
|
when :param allow_fail: is true
|
|
|
|
"""
|
|
|
|
self.assertPathExists(srcdir)
|
|
|
|
if extra_args is None:
|
|
|
|
extra_args = []
|
|
|
|
if not isinstance(extra_args, list):
|
|
|
|
extra_args = [extra_args]
|
Experimental 'genvslite' WIP. (#11049)
* Capture all compile args from the first round of ninja backend generation for all languages used in building the targets so that these args, defines, and include paths can be applied to the .vcxproj's intellisense fields for all buildtypes/configurations.
Solution generation is now set up for mutiple build configurations (buildtypes) when using '--genvslite'.
All generated vcxprojs invoke the same high-level meson compile to build all targets; there's no selective target building (could add this later). Related to this, we skip pointlessly generating vcxprojs for targets that aren't buildable (BuildTarget-derived), which aren't of interest to the user anyway.
When using --genvslite, no longer inject '<ProjectReference ...>' dependencies on which a generated .vcxproj depends because that imposes a forced visual studio build dependency, which we don't want, since we're essentially bypassing VS's build in favour of running 'meson compile ...'.
When populating the vcxproj's shared intellisense defines, include paths, and compiler options fields, we choose the most frequent src file language, since this means more project src files can simply reference the project shared fields and fewer files of non-primary language types need to populate their full set of intellisense fields. This makes for smaller .vcxproj files.
Paths for generated source/header/etc files, left alone, would be added to solution projects relative to the '..._vs' build directory, where they're never generated; they're generated under the respective '..._[debug/opt/release]' ninja build directories that correspond to the solution build configuration. Although VS doesn't allow conditional src/header listings in vcxprojs (at least not in a simple way that I'm aware of), we can ensure these generated sources get adjusted to at least reference locations under one of the concrete build directories (I've chosen '..._debug') under which they will be generated.
Testing with --genvslite has revealed that, in some cases, the presence of 'c:\windows\system32;c:\windows' on the 'Path' environment variable (via the make-style project's ExecutablePath element) is critical to getting the 'meson compile ...' build to succeed. Not sure whether this is some 'find and guess' implicit defaults behaviour within meson or within the MSVC compiler that some projects may rely on. Feels weird but not sure of a better solution than forcibly adding these to the Path environment variable (the Executable Path property of the project).
Added a new windows-only test to windowstests.py ('test_genvslite') to exercise the --genvslite option along with checking that the 'msbuild' command invokes the 'meson compile ...' of the build-type-appropriate-suffixed temporary build dir and checks expected program output.
Check and report error if user specifies a non-ninja backend with a 'genvslite' setup, since that conflicts with the stated behaviour of genvslite. Also added this test case to 'WindowsTests.test_genvslite'
I had problems tracking down some problematic environment variable behaviour, which appears to need a work-around. See further notes on VSINSTALLDIR, in windowstests.py, test_genvslite.
'meson setup --help' clearly states that positional arguments are ... [builddir] [sourcedir]. However, BasePlatformTests.init(...) was passing these in the order [sourcedir] [builddir]. This was producing failures, saying, "ERROR: Neither directory contains a build file meson.build." but when using the correct ordering, setup now succeeds.
Changed regen, run_tests, and run_install utility projects to be simpler makefile projects instead, with commands to invoke the appropriate '...meson.py --internal regencheck ...' (or install/test) on the '[builddir]_[buildtype]' as appropriate for the curent VS configuration. Also, since the 'regen.vcxproj' utility didn't work correctly with '--genvslite' setup build dirs, and getting it to fully work would require more non-trivial intrusion into new parts of meson (i.e. '--internal regencheck', '--internal regenerate', and perhaps also 'setup --reconfigure'), for now, the REGEN project is replaced with a simpler, lighter-weight RECONFIGURE utility proj, which is unlinked from any solution build dependencies and which simply runs 'meson setup --reconfigure [builddir]_[buildtype] [srcdir]' on each of the ninja-backend build dirs for each buildtype.
Yes, although this will enable the building/compiling to be correctly configured, it can leave the solution/vcxprojs stale and out-of-date, it's simple for the user to 'meson setup --genvslite ...' to fully regenerate an updated, correct solution again. However, I've noted this down as a 'fixme' to consider implementing the full regen behaviour for the genvslite case.
* Review feedback changes -
- Avoid use of 'captured_compile_args_per_buildtype_and_target' as an 'out' param.
- Factored a little msetup.py, 'run(...)' macro/looping setup steps, for genvslite, out into a 'run_genvslite_setup' func.
* Review feedback: Fixed missing spaces between multi-line strings.
* 'backend_name' assignment gets immediately overwritten in 'genvslite' case so moved it into else/non-genvslite block.
* Had to bump up 'test cases/unit/113 genvslites/...' up to 114; it collided with a newly added test dir again.
* Changed validation of 'capture' and 'captured_compile_args_...' to use MesonBugException instead of MesonException.
* Changed some function param and closing brace indentation.
1 year ago
|
|
|
build_and_src_dir_args = [self.builddir, srcdir]
|
|
|
|
args = []
|
|
|
|
if default_args:
|
|
|
|
args += ['--prefix', self.prefix]
|
|
|
|
if self.libdir:
|
|
|
|
args += ['--libdir', self.libdir]
|
|
|
|
for f in self.meson_native_files:
|
|
|
|
args += ['--native-file', f]
|
|
|
|
for f in self.meson_cross_files:
|
|
|
|
args += ['--cross-file', f]
|
|
|
|
self.privatedir = os.path.join(self.builddir, 'meson-private')
|
|
|
|
if inprocess:
|
|
|
|
try:
|
Experimental 'genvslite' WIP. (#11049)
* Capture all compile args from the first round of ninja backend generation for all languages used in building the targets so that these args, defines, and include paths can be applied to the .vcxproj's intellisense fields for all buildtypes/configurations.
Solution generation is now set up for mutiple build configurations (buildtypes) when using '--genvslite'.
All generated vcxprojs invoke the same high-level meson compile to build all targets; there's no selective target building (could add this later). Related to this, we skip pointlessly generating vcxprojs for targets that aren't buildable (BuildTarget-derived), which aren't of interest to the user anyway.
When using --genvslite, no longer inject '<ProjectReference ...>' dependencies on which a generated .vcxproj depends because that imposes a forced visual studio build dependency, which we don't want, since we're essentially bypassing VS's build in favour of running 'meson compile ...'.
When populating the vcxproj's shared intellisense defines, include paths, and compiler options fields, we choose the most frequent src file language, since this means more project src files can simply reference the project shared fields and fewer files of non-primary language types need to populate their full set of intellisense fields. This makes for smaller .vcxproj files.
Paths for generated source/header/etc files, left alone, would be added to solution projects relative to the '..._vs' build directory, where they're never generated; they're generated under the respective '..._[debug/opt/release]' ninja build directories that correspond to the solution build configuration. Although VS doesn't allow conditional src/header listings in vcxprojs (at least not in a simple way that I'm aware of), we can ensure these generated sources get adjusted to at least reference locations under one of the concrete build directories (I've chosen '..._debug') under which they will be generated.
Testing with --genvslite has revealed that, in some cases, the presence of 'c:\windows\system32;c:\windows' on the 'Path' environment variable (via the make-style project's ExecutablePath element) is critical to getting the 'meson compile ...' build to succeed. Not sure whether this is some 'find and guess' implicit defaults behaviour within meson or within the MSVC compiler that some projects may rely on. Feels weird but not sure of a better solution than forcibly adding these to the Path environment variable (the Executable Path property of the project).
Added a new windows-only test to windowstests.py ('test_genvslite') to exercise the --genvslite option along with checking that the 'msbuild' command invokes the 'meson compile ...' of the build-type-appropriate-suffixed temporary build dir and checks expected program output.
Check and report error if user specifies a non-ninja backend with a 'genvslite' setup, since that conflicts with the stated behaviour of genvslite. Also added this test case to 'WindowsTests.test_genvslite'
I had problems tracking down some problematic environment variable behaviour, which appears to need a work-around. See further notes on VSINSTALLDIR, in windowstests.py, test_genvslite.
'meson setup --help' clearly states that positional arguments are ... [builddir] [sourcedir]. However, BasePlatformTests.init(...) was passing these in the order [sourcedir] [builddir]. This was producing failures, saying, "ERROR: Neither directory contains a build file meson.build." but when using the correct ordering, setup now succeeds.
Changed regen, run_tests, and run_install utility projects to be simpler makefile projects instead, with commands to invoke the appropriate '...meson.py --internal regencheck ...' (or install/test) on the '[builddir]_[buildtype]' as appropriate for the curent VS configuration. Also, since the 'regen.vcxproj' utility didn't work correctly with '--genvslite' setup build dirs, and getting it to fully work would require more non-trivial intrusion into new parts of meson (i.e. '--internal regencheck', '--internal regenerate', and perhaps also 'setup --reconfigure'), for now, the REGEN project is replaced with a simpler, lighter-weight RECONFIGURE utility proj, which is unlinked from any solution build dependencies and which simply runs 'meson setup --reconfigure [builddir]_[buildtype] [srcdir]' on each of the ninja-backend build dirs for each buildtype.
Yes, although this will enable the building/compiling to be correctly configured, it can leave the solution/vcxprojs stale and out-of-date, it's simple for the user to 'meson setup --genvslite ...' to fully regenerate an updated, correct solution again. However, I've noted this down as a 'fixme' to consider implementing the full regen behaviour for the genvslite case.
* Review feedback changes -
- Avoid use of 'captured_compile_args_per_buildtype_and_target' as an 'out' param.
- Factored a little msetup.py, 'run(...)' macro/looping setup steps, for genvslite, out into a 'run_genvslite_setup' func.
* Review feedback: Fixed missing spaces between multi-line strings.
* 'backend_name' assignment gets immediately overwritten in 'genvslite' case so moved it into else/non-genvslite block.
* Had to bump up 'test cases/unit/113 genvslites/...' up to 114; it collided with a newly added test dir again.
* Changed validation of 'capture' and 'captured_compile_args_...' to use MesonBugException instead of MesonException.
* Changed some function param and closing brace indentation.
1 year ago
|
|
|
returncode, out, err = run_configure_inprocess(['setup'] + self.meson_args + args + extra_args + build_and_src_dir_args, override_envvars)
|
|
|
|
except Exception as e:
|
|
|
|
if not allow_fail:
|
|
|
|
self._print_meson_log()
|
|
|
|
raise
|
|
|
|
out = self._get_meson_log() # Best we can do here
|
|
|
|
err = '' # type checkers can't figure out that on this path returncode will always be 0
|
|
|
|
returncode = 0
|
|
|
|
finally:
|
|
|
|
# Close log file to satisfy Windows file locking
|
|
|
|
mesonbuild.mlog.shutdown()
|
|
|
|
mesonbuild.mlog._logger.log_dir = None
|
|
|
|
mesonbuild.mlog._logger.log_file = None
|
|
|
|
|
|
|
|
skipped, reason = handle_meson_skip_test(out)
|
|
|
|
if skipped:
|
|
|
|
raise SkipTest(f'Project requested skipping: {reason}')
|
|
|
|
if returncode != 0:
|
|
|
|
self._print_meson_log()
|
|
|
|
print('Stdout:\n')
|
|
|
|
print(out)
|
|
|
|
print('Stderr:\n')
|
|
|
|
print(err)
|
|
|
|
if not allow_fail:
|
|
|
|
raise RuntimeError('Configure failed')
|
|
|
|
else:
|
|
|
|
try:
|
Experimental 'genvslite' WIP. (#11049)
* Capture all compile args from the first round of ninja backend generation for all languages used in building the targets so that these args, defines, and include paths can be applied to the .vcxproj's intellisense fields for all buildtypes/configurations.
Solution generation is now set up for mutiple build configurations (buildtypes) when using '--genvslite'.
All generated vcxprojs invoke the same high-level meson compile to build all targets; there's no selective target building (could add this later). Related to this, we skip pointlessly generating vcxprojs for targets that aren't buildable (BuildTarget-derived), which aren't of interest to the user anyway.
When using --genvslite, no longer inject '<ProjectReference ...>' dependencies on which a generated .vcxproj depends because that imposes a forced visual studio build dependency, which we don't want, since we're essentially bypassing VS's build in favour of running 'meson compile ...'.
When populating the vcxproj's shared intellisense defines, include paths, and compiler options fields, we choose the most frequent src file language, since this means more project src files can simply reference the project shared fields and fewer files of non-primary language types need to populate their full set of intellisense fields. This makes for smaller .vcxproj files.
Paths for generated source/header/etc files, left alone, would be added to solution projects relative to the '..._vs' build directory, where they're never generated; they're generated under the respective '..._[debug/opt/release]' ninja build directories that correspond to the solution build configuration. Although VS doesn't allow conditional src/header listings in vcxprojs (at least not in a simple way that I'm aware of), we can ensure these generated sources get adjusted to at least reference locations under one of the concrete build directories (I've chosen '..._debug') under which they will be generated.
Testing with --genvslite has revealed that, in some cases, the presence of 'c:\windows\system32;c:\windows' on the 'Path' environment variable (via the make-style project's ExecutablePath element) is critical to getting the 'meson compile ...' build to succeed. Not sure whether this is some 'find and guess' implicit defaults behaviour within meson or within the MSVC compiler that some projects may rely on. Feels weird but not sure of a better solution than forcibly adding these to the Path environment variable (the Executable Path property of the project).
Added a new windows-only test to windowstests.py ('test_genvslite') to exercise the --genvslite option along with checking that the 'msbuild' command invokes the 'meson compile ...' of the build-type-appropriate-suffixed temporary build dir and checks expected program output.
Check and report error if user specifies a non-ninja backend with a 'genvslite' setup, since that conflicts with the stated behaviour of genvslite. Also added this test case to 'WindowsTests.test_genvslite'
I had problems tracking down some problematic environment variable behaviour, which appears to need a work-around. See further notes on VSINSTALLDIR, in windowstests.py, test_genvslite.
'meson setup --help' clearly states that positional arguments are ... [builddir] [sourcedir]. However, BasePlatformTests.init(...) was passing these in the order [sourcedir] [builddir]. This was producing failures, saying, "ERROR: Neither directory contains a build file meson.build." but when using the correct ordering, setup now succeeds.
Changed regen, run_tests, and run_install utility projects to be simpler makefile projects instead, with commands to invoke the appropriate '...meson.py --internal regencheck ...' (or install/test) on the '[builddir]_[buildtype]' as appropriate for the curent VS configuration. Also, since the 'regen.vcxproj' utility didn't work correctly with '--genvslite' setup build dirs, and getting it to fully work would require more non-trivial intrusion into new parts of meson (i.e. '--internal regencheck', '--internal regenerate', and perhaps also 'setup --reconfigure'), for now, the REGEN project is replaced with a simpler, lighter-weight RECONFIGURE utility proj, which is unlinked from any solution build dependencies and which simply runs 'meson setup --reconfigure [builddir]_[buildtype] [srcdir]' on each of the ninja-backend build dirs for each buildtype.
Yes, although this will enable the building/compiling to be correctly configured, it can leave the solution/vcxprojs stale and out-of-date, it's simple for the user to 'meson setup --genvslite ...' to fully regenerate an updated, correct solution again. However, I've noted this down as a 'fixme' to consider implementing the full regen behaviour for the genvslite case.
* Review feedback changes -
- Avoid use of 'captured_compile_args_per_buildtype_and_target' as an 'out' param.
- Factored a little msetup.py, 'run(...)' macro/looping setup steps, for genvslite, out into a 'run_genvslite_setup' func.
* Review feedback: Fixed missing spaces between multi-line strings.
* 'backend_name' assignment gets immediately overwritten in 'genvslite' case so moved it into else/non-genvslite block.
* Had to bump up 'test cases/unit/113 genvslites/...' up to 114; it collided with a newly added test dir again.
* Changed validation of 'capture' and 'captured_compile_args_...' to use MesonBugException instead of MesonException.
* Changed some function param and closing brace indentation.
1 year ago
|
|
|
out = self._run(self.setup_command + args + extra_args + build_and_src_dir_args, override_envvars=override_envvars, workdir=workdir)
|
|
|
|
except Exception:
|
|
|
|
if not allow_fail:
|
|
|
|
self._print_meson_log()
|
|
|
|
raise
|
|
|
|
out = self._get_meson_log() # best we can do here
|
|
|
|
return out
|
|
|
|
|
|
|
|
def build(self, target=None, *, extra_args=None, override_envvars=None, stderr=True):
|
|
|
|
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, override_envvars=override_envvars, stderr=stderr)
|
|
|
|
|
|
|
|
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, override_envvars=override_envvars)
|
|
|
|
|
|
|
|
def run_tests(self, *, inprocess=False, override_envvars=None):
|
|
|
|
if not inprocess:
|
|
|
|
return self._run(self.test_command, workdir=self.builddir, override_envvars=override_envvars)
|
|
|
|
else:
|
|
|
|
with mock.patch.dict(os.environ, override_envvars):
|
|
|
|
return run_mtest_inprocess(['-C', self.builddir])[1]
|
|
|
|
|
|
|
|
def install(self, *, use_destdir=True, override_envvars=None):
|
|
|
|
if self.backend is not Backend.ninja:
|
|
|
|
raise SkipTest(f'{self.backend.name!r} backend can\'t install files')
|
|
|
|
if use_destdir:
|
|
|
|
destdir = {'DESTDIR': self.installdir}
|
|
|
|
if override_envvars is None:
|
|
|
|
override_envvars = destdir
|
|
|
|
else:
|
|
|
|
override_envvars.update(destdir)
|
|
|
|
return self._run(self.install_command, workdir=self.builddir, override_envvars=override_envvars)
|
|
|
|
|
|
|
|
def uninstall(self, *, override_envvars=None):
|
|
|
|
self._run(self.uninstall_command, workdir=self.builddir, override_envvars=override_envvars)
|
|
|
|
|
|
|
|
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, override_envvars=override_envvars)
|
|
|
|
|
|
|
|
def setconf(self, arg, will_build=True):
|
|
|
|
if not isinstance(arg, list):
|
|
|
|
arg = [arg]
|
|
|
|
if will_build:
|
|
|
|
ensure_backend_detects_changes(self.backend)
|
|
|
|
self._run(self.mconf_command + arg + [self.builddir])
|
|
|
|
|
|
|
|
def wipe(self):
|
|
|
|
windows_proof_rmtree(self.builddir)
|
|
|
|
|
|
|
|
def utime(self, f):
|
|
|
|
ensure_backend_detects_changes(self.backend)
|
|
|
|
os.utime(f)
|
|
|
|
|
|
|
|
def get_compdb(self):
|
|
|
|
if self.backend is not Backend.ninja:
|
|
|
|
raise SkipTest(f'Compiler db not available with {self.backend.name} backend')
|
|
|
|
try:
|
|
|
|
with open(os.path.join(self.builddir, 'compile_commands.json'), encoding='utf-8') as ifile:
|
|
|
|
contents = json.load(ifile)
|
|
|
|
except FileNotFoundError:
|
|
|
|
raise SkipTest('Compiler db not found')
|
|
|
|
# If Ninja is using .rsp files, generate them, read their contents, and
|
|
|
|
# replace it as the command for all compile commands in the parsed json.
|
|
|
|
if len(contents) > 0 and contents[0]['command'].endswith('.rsp'):
|
|
|
|
# Pretend to build so that the rsp files are generated
|
|
|
|
self.build(extra_args=['-d', 'keeprsp', '-n'])
|
|
|
|
for each in contents:
|
|
|
|
# Extract the actual command from the rsp file
|
|
|
|
compiler, rsp = each['command'].split(' @')
|
|
|
|
rsp = os.path.join(self.builddir, rsp)
|
|
|
|
# Replace the command with its contents
|
|
|
|
with open(rsp, encoding='utf-8') as f:
|
|
|
|
each['command'] = compiler + ' ' + f.read()
|
|
|
|
return contents
|
|
|
|
|
|
|
|
def get_meson_log_raw(self):
|
|
|
|
with self._open_meson_log() as f:
|
|
|
|
return f.read()
|
|
|
|
|
|
|
|
def get_meson_log(self):
|
|
|
|
with self._open_meson_log() as f:
|
|
|
|
return f.readlines()
|
|
|
|
|
|
|
|
def get_meson_log_compiler_checks(self):
|
|
|
|
'''
|
|
|
|
Fetch a list command-lines run by meson for compiler checks.
|
|
|
|
Each command-line is returned as a list of arguments.
|
|
|
|
'''
|
|
|
|
prefix = 'Command line: `'
|
|
|
|
suffix = '` -> 0\n'
|
|
|
|
with self._open_meson_log() as log:
|
|
|
|
cmds = [split_args(l[len(prefix):-len(suffix)]) for l in log if l.startswith(prefix)]
|
|
|
|
return cmds
|
|
|
|
|
|
|
|
def get_meson_log_sanitychecks(self):
|
|
|
|
'''
|
|
|
|
Same as above, but for the sanity checks that were run
|
|
|
|
'''
|
|
|
|
prefix = 'Sanity check compiler command line:'
|
|
|
|
with self._open_meson_log() as log:
|
|
|
|
cmds = [l[len(prefix):].split() for l in log if l.startswith(prefix)]
|
|
|
|
return cmds
|
|
|
|
|
|
|
|
def introspect(self, args):
|
|
|
|
if isinstance(args, str):
|
|
|
|
args = [args]
|
|
|
|
out = subprocess.check_output(self.mintro_command + args + [self.builddir],
|
|
|
|
universal_newlines=True)
|
|
|
|
return json.loads(out)
|
|
|
|
|
|
|
|
def introspect_directory(self, directory, args):
|
|
|
|
if isinstance(args, str):
|
|
|
|
args = [args]
|
|
|
|
out = subprocess.check_output(self.mintro_command + args + [directory],
|
|
|
|
universal_newlines=True)
|
|
|
|
try:
|
|
|
|
obj = json.loads(out)
|
|
|
|
except Exception as e:
|
|
|
|
print(out)
|
|
|
|
raise e
|
|
|
|
return obj
|
|
|
|
|
|
|
|
def assertPathEqual(self, path1, path2):
|
|
|
|
'''
|
|
|
|
Handles a lot of platform-specific quirks related to paths such as
|
|
|
|
separator, case-sensitivity, etc.
|
|
|
|
'''
|
|
|
|
self.assertEqual(PurePath(path1), PurePath(path2))
|
|
|
|
|
|
|
|
def assertPathListEqual(self, pathlist1, pathlist2):
|
|
|
|
self.assertEqual(len(pathlist1), len(pathlist2))
|
|
|
|
worklist = list(zip(pathlist1, pathlist2))
|
|
|
|
for i in worklist:
|
|
|
|
if i[0] is None:
|
|
|
|
self.assertEqual(i[0], i[1])
|
|
|
|
else:
|
|
|
|
self.assertPathEqual(i[0], i[1])
|
|
|
|
|
|
|
|
def assertPathBasenameEqual(self, path, basename):
|
|
|
|
msg = f'{path!r} does not end with {basename!r}'
|
|
|
|
# We cannot use os.path.basename because it returns '' when the path
|
|
|
|
# ends with '/' for some silly reason. This is not how the UNIX utility
|
|
|
|
# `basename` works.
|
|
|
|
path_basename = PurePath(path).parts[-1]
|
|
|
|
self.assertEqual(PurePath(path_basename), PurePath(basename), msg)
|
|
|
|
|
|
|
|
def assertReconfiguredBuildIsNoop(self):
|
|
|
|
'Assert that we reconfigured and then there was nothing to do'
|
|
|
|
ret = self.build(stderr=False)
|
|
|
|
self.assertIn('The Meson build system', ret)
|
|
|
|
if self.backend is Backend.ninja:
|
|
|
|
for line in ret.split('\n'):
|
|
|
|
if line in self.no_rebuild_stdout:
|
|
|
|
break
|
|
|
|
else:
|
|
|
|
raise AssertionError('build was reconfigured, but was not no-op')
|
|
|
|
elif self.backend is Backend.vs:
|
|
|
|
# Ensure that some target said that no rebuild was done
|
|
|
|
# XXX: Note CustomBuild did indeed rebuild, because of the regen checker!
|
|
|
|
self.assertIn('ClCompile:\n All outputs are up-to-date.', ret)
|
|
|
|
self.assertIn('Link:\n All outputs are up-to-date.', ret)
|
|
|
|
# Ensure that no targets were built
|
|
|
|
self.assertNotRegex(ret, re.compile('ClCompile:\n [^\n]*cl', flags=re.IGNORECASE))
|
|
|
|
self.assertNotRegex(ret, re.compile('Link:\n [^\n]*link', flags=re.IGNORECASE))
|
|
|
|
elif self.backend is Backend.xcode:
|
|
|
|
raise SkipTest('Please help us fix this test on the xcode backend')
|
|
|
|
else:
|
|
|
|
raise RuntimeError(f'Invalid backend: {self.backend.name!r}')
|
|
|
|
|
|
|
|
def assertBuildIsNoop(self):
|
|
|
|
ret = self.build(stderr=False)
|
|
|
|
if self.backend is Backend.ninja:
|
|
|
|
self.assertIn(ret.split('\n')[-2], self.no_rebuild_stdout)
|
|
|
|
elif self.backend is Backend.vs:
|
|
|
|
# Ensure that some target of each type said that no rebuild was done
|
|
|
|
# We always have at least one CustomBuild target for the regen checker
|
|
|
|
self.assertIn('CustomBuild:\n All outputs are up-to-date.', ret)
|
|
|
|
self.assertIn('ClCompile:\n All outputs are up-to-date.', ret)
|
|
|
|
self.assertIn('Link:\n All outputs are up-to-date.', ret)
|
|
|
|
# Ensure that no targets were built
|
|
|
|
self.assertNotRegex(ret, re.compile('CustomBuild:\n [^\n]*cl', flags=re.IGNORECASE))
|
|
|
|
self.assertNotRegex(ret, re.compile('ClCompile:\n [^\n]*cl', flags=re.IGNORECASE))
|
|
|
|
self.assertNotRegex(ret, re.compile('Link:\n [^\n]*link', flags=re.IGNORECASE))
|
|
|
|
elif self.backend is Backend.xcode:
|
|
|
|
raise SkipTest('Please help us fix this test on the xcode backend')
|
|
|
|
else:
|
|
|
|
raise RuntimeError(f'Invalid backend: {self.backend.name!r}')
|
|
|
|
|
|
|
|
def assertRebuiltTarget(self, target):
|
|
|
|
ret = self.build()
|
|
|
|
if self.backend is Backend.ninja:
|
|
|
|
self.assertIn(f'Linking target {target}', ret)
|
|
|
|
elif self.backend is Backend.vs:
|
|
|
|
# Ensure that this target was rebuilt
|
|
|
|
linkre = re.compile('Link:\n [^\n]*link[^\n]*' + target, flags=re.IGNORECASE)
|
|
|
|
self.assertRegex(ret, linkre)
|
|
|
|
elif self.backend is Backend.xcode:
|
|
|
|
raise SkipTest('Please help us fix this test on the xcode backend')
|
|
|
|
else:
|
|
|
|
raise RuntimeError(f'Invalid backend: {self.backend.name!r}')
|
|
|
|
|
|
|
|
@staticmethod
|
|
|
|
def get_target_from_filename(filename):
|
|
|
|
base = os.path.splitext(filename)[0]
|
|
|
|
if base.startswith(('lib', 'cyg')):
|
|
|
|
return base[3:]
|
|
|
|
return base
|
|
|
|
|
|
|
|
def assertBuildRelinkedOnlyTarget(self, target):
|
|
|
|
ret = self.build()
|
|
|
|
if self.backend is Backend.ninja:
|
|
|
|
linked_targets = []
|
|
|
|
for line in ret.split('\n'):
|
|
|
|
if 'Linking target' in line:
|
|
|
|
fname = line.rsplit('target ')[-1]
|
|
|
|
linked_targets.append(self.get_target_from_filename(fname))
|
|
|
|
self.assertEqual(linked_targets, [target])
|
|
|
|
elif self.backend is Backend.vs:
|
|
|
|
# Ensure that this target was rebuilt
|
|
|
|
linkre = re.compile(r'Link:\n [^\n]*link.exe[^\n]*/OUT:".\\([^"]*)"', flags=re.IGNORECASE)
|
|
|
|
matches = linkre.findall(ret)
|
|
|
|
self.assertEqual(len(matches), 1, msg=matches)
|
|
|
|
self.assertEqual(self.get_target_from_filename(matches[0]), target)
|
|
|
|
elif self.backend is Backend.xcode:
|
|
|
|
raise SkipTest('Please help us fix this test on the xcode backend')
|
|
|
|
else:
|
|
|
|
raise RuntimeError(f'Invalid backend: {self.backend.name!r}')
|
|
|
|
|
|
|
|
def assertPathExists(self, path):
|
|
|
|
m = f'Path {path!r} should exist'
|
|
|
|
self.assertTrue(os.path.exists(path), msg=m)
|
|
|
|
|
|
|
|
def assertPathDoesNotExist(self, path):
|
|
|
|
m = f'Path {path!r} should not exist'
|
|
|
|
self.assertFalse(os.path.exists(path), msg=m)
|
|
|
|
|
|
|
|
def assertLength(self, val, length):
|
|
|
|
assert len(val) == length, f'{val} is not length {length}'
|