aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJussi Pakkanen <jpakkane@gmail.com>2020-12-15 21:21:50 +0000
committerGitHub <noreply@github.com>2020-12-15 21:21:50 +0000
commitd32d0d6b53dc84e35f6b26fbcfaf23a37ab6b002 (patch)
tree437d20b8b08459dcb14289bf185ef2a2c003ecc1
parentc9685ac5612eb040961efe5dc60a91fc5aa686b4 (diff)
parent79e2c52a15e896e46ff3cfa3ec16fbf3f132ee01 (diff)
downloadmeson-d32d0d6b53dc84e35f6b26fbcfaf23a37ab6b002.zip
meson-d32d0d6b53dc84e35f6b26fbcfaf23a37ab6b002.tar.gz
meson-d32d0d6b53dc84e35f6b26fbcfaf23a37ab6b002.tar.bz2
Merge pull request #7902 from bonzini/mtest-build-depends-only
mtest: only build what is needed for the tests
-rw-r--r--docs/markdown/snippets/meson_test_depends.md17
-rw-r--r--mesonbuild/minstall.py18
-rw-r--r--mesonbuild/mintro.py47
-rw-r--r--mesonbuild/mtest.py38
4 files changed, 91 insertions, 29 deletions
diff --git a/docs/markdown/snippets/meson_test_depends.md b/docs/markdown/snippets/meson_test_depends.md
new file mode 100644
index 0000000..09d78f1
--- /dev/null
+++ b/docs/markdown/snippets/meson_test_depends.md
@@ -0,0 +1,17 @@
+## `meson test` only rebuilds test dependencies
+
+Until now, `meson test` rebuilt the whole project independent of the
+requested tests and their dependencies. With this release, `meson test`
+will only rebuild what is needed for the tests or suites that will be run.
+This feature can be used, for example, to speed up bisecting regressions
+using commands like the following:
+
+ git bisect start <broken commit> <working commit>
+ git bisect run meson test <failing test name>
+
+This would find the broken commit automatically while at each step
+rebuilding only those pieces of code needed to run the test.
+
+However, this change could cause failures if dependencies are not
+specified correctly in `meson.build`.
+
diff --git a/mesonbuild/minstall.py b/mesonbuild/minstall.py
index 47bb88b..a8ec8f3 100644
--- a/mesonbuild/minstall.py
+++ b/mesonbuild/minstall.py
@@ -19,7 +19,6 @@ from glob import glob
from .scripts import depfixer
from .scripts import destdir_join
from .mesonlib import is_windows, Popen_safe
-from .mtest import rebuild_all
from .backend.backends import InstallData
from .coredata import major_versions_differ, MesonVersionMismatchException
from .coredata import version as coredata_version
@@ -532,6 +531,23 @@ class Installer:
else:
raise
+def rebuild_all(wd: str) -> bool:
+ if not (Path(wd) / 'build.ninja').is_file():
+ print('Only ninja backend is supported to rebuild the project before installation.')
+ return True
+
+ ninja = environment.detect_ninja()
+ if not ninja:
+ print("Can't find ninja, can't rebuild test.")
+ return False
+
+ ret = subprocess.run(ninja + ['-C', wd]).returncode
+ if ret != 0:
+ print('Could not rebuild {}'.format(wd))
+ return False
+
+ return True
+
def run(opts):
datafilename = 'meson-private/install.dat'
private_dir = os.path.dirname(datafilename)
diff --git a/mesonbuild/mintro.py b/mesonbuild/mintro.py
index d36927f..f6262c3 100644
--- a/mesonbuild/mintro.py
+++ b/mesonbuild/mintro.py
@@ -385,12 +385,25 @@ def print_results(options: argparse.Namespace, results: T.Sequence[T.Tuple[str,
print(json.dumps(out, indent=indent))
return 0
+def get_infodir(builddir: T.Optional[str] = None) -> str:
+ infodir = 'meson-info'
+ if builddir is not None:
+ infodir = os.path.join(builddir, infodir)
+ return infodir
+
+def get_info_file(infodir: str, kind: T.Optional[str] = None) -> str:
+ return os.path.join(infodir,
+ 'meson-info.json' if not kind else 'intro-{}.json'.format(kind))
+
+def load_info_file(infodir: str, kind: T.Optional[str] = None) -> T.Any:
+ with open(get_info_file(infodir, kind), 'r') as fp:
+ return json.load(fp)
+
def run(options: argparse.Namespace) -> int:
datadir = 'meson-private'
- infodir = 'meson-info'
+ infodir = get_infodir(options.builddir)
if options.builddir is not None:
datadir = os.path.join(options.builddir, datadir)
- infodir = os.path.join(options.builddir, infodir)
indent = 4 if options.indent else None
results = [] # type: T.List[T.Tuple[str, T.Union[dict, T.List[T.Any]]]]
sourcedir = '.' if options.builddir == 'meson.build' else options.builddir[:-11]
@@ -411,17 +424,18 @@ def run(options: argparse.Namespace) -> int:
results += [(key, val.no_bd(intr))]
return print_results(options, results, indent)
- infofile = get_meson_info_file(infodir)
- if not os.path.isdir(datadir) or not os.path.isdir(infodir) or not os.path.isfile(infofile):
- print('Current directory is not a meson build directory.\n'
- 'Please specify a valid build dir or change the working directory to it.\n'
- 'It is also possible that the build directory was generated with an old\n'
- 'meson version. Please regenerate it in this case.')
- return 1
-
- with open(infofile, 'r') as fp:
- raw = json.load(fp)
+ try:
+ raw = load_info_file(infodir)
intro_vers = raw.get('introspection', {}).get('version', {}).get('full', '0.0.0')
+ except FileNotFoundError:
+ if not os.path.isdir(datadir) or not os.path.isdir(infodir):
+ print('Current directory is not a meson build directory.\n'
+ 'Please specify a valid build dir or change the working directory to it.')
+ else:
+ print('Introspection file {} does not exist.\n'
+ 'It is also possible that the build directory was generated with an old\n'
+ 'meson version. Please regenerate it in this case.'.format(get_info_file(infodir)))
+ return 1
vers_to_check = get_meson_introspection_required_version()
for i in vers_to_check:
@@ -437,12 +451,11 @@ def run(options: argparse.Namespace) -> int:
continue
if not options.all and not getattr(options, i, False):
continue
- curr = os.path.join(infodir, 'intro-{}.json'.format(i))
- if not os.path.isfile(curr):
- print('Introspection file {} does not exist.'.format(curr))
+ try:
+ results += [(i, load_info_file(infodir, i))]
+ except FileNotFoundError:
+ print('Introspection file {} does not exist.'.format(get_info_file(infodir, i)))
return 1
- with open(curr, 'r') as fp:
- results += [(i, json.load(fp))]
return print_results(options, results, indent)
diff --git a/mesonbuild/mtest.py b/mesonbuild/mtest.py
index a92b5cc..4f687a5 100644
--- a/mesonbuild/mtest.py
+++ b/mesonbuild/mtest.py
@@ -45,6 +45,7 @@ from .coredata import major_versions_differ, MesonVersionMismatchException
from .coredata import version as coredata_version
from .dependencies import ExternalProgram
from .mesonlib import MesonException, get_wine_shortpath, split_args, join_args
+from .mintro import get_infodir, load_info_file
from .backend.backends import TestProtocol, TestSerialisation
# GNU autotools interprets a return code of 77 from tests it executes to
@@ -1019,13 +1020,21 @@ class TestHarness:
def total_failure_count(self) -> int:
return self.fail_count + self.unexpectedpass_count + self.timeout_count
- def doit(self) -> int:
+ def doit(self, options: argparse.Namespace) -> int:
if self.is_run:
raise RuntimeError('Test harness object can only be used once.')
self.is_run = True
tests = self.get_tests()
if not tests:
return 0
+ if not options.no_rebuild and not rebuild_deps(options.wd, tests):
+ # We return 125 here in case the build failed.
+ # The reason is that exit code 125 tells `git bisect run` that the current
+ # commit should be skipped. Thus users can directly use `meson test` to
+ # bisect without needing to handle the does-not-build case separately in a
+ # wrapper script.
+ sys.exit(125)
+
self.run_tests(tests)
return self.total_failure_count()
@@ -1272,7 +1281,7 @@ def list_tests(th: TestHarness) -> bool:
print(th.get_pretty_suite(t))
return not tests
-def rebuild_all(wd: str) -> bool:
+def rebuild_deps(wd: str, tests: T.List[TestSerialisation]) -> bool:
if not (Path(wd) / 'build.ninja').is_file():
print('Only ninja backend is supported to rebuild tests before running them.')
return True
@@ -1282,7 +1291,21 @@ def rebuild_all(wd: str) -> bool:
print("Can't find ninja, can't rebuild test.")
return False
- ret = subprocess.run(ninja + ['-C', wd]).returncode
+ depends = set() # type: T.Set[str]
+ targets = set() # type: T.Set[str]
+ intro_targets = dict() # type: T.Dict[str, T.List[str]]
+ for target in load_info_file(get_infodir(wd), kind='targets'):
+ intro_targets[target['id']] = [
+ os.path.relpath(f, wd)
+ for f in target['filename']]
+ for t in tests:
+ for d in t.depends:
+ if d in depends:
+ continue
+ depends.update(d)
+ targets.update(intro_targets[d])
+
+ ret = subprocess.run(ninja + ['-C', wd] + sorted(targets)).returncode
if ret != 0:
print('Could not rebuild {}'.format(wd))
return False
@@ -1318,18 +1341,11 @@ def run(options: argparse.Namespace) -> int:
print('Could not find requested program: {!r}'.format(check_bin))
return 1
- if not options.list and not options.no_rebuild:
- if not rebuild_all(options.wd):
- # We return 125 here in case the build failed.
- # The reason is that exit code 125 tells `git bisect run` that the current commit should be skipped.
- # Thus users can directly use `meson test` to bisect without needing to handle the does-not-build case separately in a wrapper script.
- return 125
-
with TestHarness(options) as th:
try:
if options.list:
return list_tests(th)
- return th.doit()
+ return th.doit(options)
except TestException as e:
print('Meson test encountered an error:\n')
if os.environ.get('MESON_FORCE_BACKTRACE'):