diff options
-rw-r--r-- | mesonbuild/backend/backends.py | 9 | ||||
-rw-r--r-- | mesonbuild/backend/ninjabackend.py | 39 | ||||
-rw-r--r-- | mesonbuild/coredata.py | 1 | ||||
-rw-r--r-- | mesonbuild/mesonmain.py | 3 | ||||
-rw-r--r-- | mesonbuild/scripts/cleantrees.py | 43 | ||||
-rwxr-xr-x | run_project_tests.py | 61 |
6 files changed, 120 insertions, 36 deletions
diff --git a/mesonbuild/backend/backends.py b/mesonbuild/backend/backends.py index ca013fa..b265a24 100644 --- a/mesonbuild/backend/backends.py +++ b/mesonbuild/backend/backends.py @@ -22,6 +22,15 @@ import json import subprocess from ..mesonlib import MesonException, get_compiler_for_source, classify_unity_sources +class CleanTrees(): + ''' + Directories outputted by custom targets that have to be manually cleaned + because on Linux `ninja clean` only deletes empty directories. + ''' + def __init__(self, build_dir, trees): + self.build_dir = build_dir + self.trees = trees + class InstallData(): def __init__(self, source_dir, build_dir, prefix): self.source_dir = source_dir diff --git a/mesonbuild/backend/ninjabackend.py b/mesonbuild/backend/ninjabackend.py index a20a35f..4c10e8d 100644 --- a/mesonbuild/backend/ninjabackend.py +++ b/mesonbuild/backend/ninjabackend.py @@ -20,7 +20,7 @@ from .. import mlog from .. import dependencies from .. import compilers from ..mesonlib import File, MesonException, get_compiler_for_source, Popen_safe -from .backends import InstallData +from .backends import CleanTrees, InstallData from ..build import InvalidArguments import os, sys, pickle, re import subprocess, shutil @@ -2109,6 +2109,22 @@ rule FORTRAN_DEP_HACK except OSError: mlog.debug("Library versioning disabled because we do not have symlink creation privileges.") + def generate_custom_target_clean(self, outfile, trees): + e = NinjaBuildElement(self.all_outputs, 'clean-ctlist', 'CUSTOM_COMMAND', 'PHONY') + d = CleanTrees(self.environment.get_build_dir(), trees) + d_file = os.path.join(self.environment.get_scratch_dir(), 'cleantrees.dat') + script_root = self.environment.get_script_dir() + clean_script = os.path.join(script_root, 'cleantrees.py') + e.add_item('COMMAND', [sys.executable, + self.environment.get_build_command(), + '--internal', 'cleantrees', d_file]) + e.add_item('description', 'Cleaning CustomTarget directories') + e.write(outfile) + # Write out the data file passed to the script + with open(d_file, 'wb') as ofile: + pickle.dump(d, ofile) + return 'clean-ctlist' + def generate_gcov_clean(self, outfile): gcno_elem = NinjaBuildElement(self.all_outputs, 'clean-gcno', 'CUSTOM_COMMAND', 'PHONY') script_root = self.environment.get_script_dir() @@ -2136,14 +2152,19 @@ rule FORTRAN_DEP_HACK def generate_ending(self, outfile): targetlist = [] + ctlist = [] for t in self.build.get_targets().values(): # RunTargets are meant to be invoked manually if isinstance(t, build.RunTarget): continue - # CustomTargets that aren't installed should only be built if they - # are used by something else or are meant to be always built - if isinstance(t, build.CustomTarget) and not (t.install or t.build_always): - continue + if isinstance(t, build.CustomTarget): + # Create a list of all custom target outputs + for o in t.get_outputs(): + ctlist.append(os.path.join(self.get_target_dir(t), o)) + # CustomTargets that aren't installed should only be built if + # they are used by something else or are to always be built + if not (t.install or t.build_always): + continue # Add the first output of each target to the 'all' target so that # they are all built targetlist.append(os.path.join(self.get_target_dir(t), t.get_outputs()[0])) @@ -2160,6 +2181,14 @@ rule FORTRAN_DEP_HACK elem = NinjaBuildElement(self.all_outputs, 'clean', 'CUSTOM_COMMAND', 'PHONY') elem.add_item('COMMAND', [ninja_command, '-t', 'clean']) elem.add_item('description', 'Cleaning') + # If we have custom targets in this project, add all their outputs to + # the list that is passed to the `cleantrees.py` script. The script + # will manually delete all custom_target outputs that are directories + # instead of files. This is needed because on platforms other than + # Windows, Ninja only deletes directories while cleaning if they are + # empty. https://github.com/mesonbuild/meson/issues/1220 + if ctlist: + elem.add_dep(self.generate_custom_target_clean(outfile, ctlist)) if 'b_coverage' in self.environment.coredata.base_options and \ self.environment.coredata.base_options['b_coverage'].value: self.generate_gcov_clean(outfile) diff --git a/mesonbuild/coredata.py b/mesonbuild/coredata.py index d804eda..4b0d0c4 100644 --- a/mesonbuild/coredata.py +++ b/mesonbuild/coredata.py @@ -236,6 +236,7 @@ builtin_options = { } forbidden_target_names = {'clean': None, + 'clean-ctlist': None, 'clean-gcno': None, 'clean-gcda': None, 'coverage-text': None, diff --git a/mesonbuild/mesonmain.py b/mesonbuild/mesonmain.py index 3c644a8..e85ef17 100644 --- a/mesonbuild/mesonmain.py +++ b/mesonbuild/mesonmain.py @@ -198,6 +198,9 @@ def run_script_command(args): if cmdname == 'exe': import mesonbuild.scripts.meson_exe as abc cmdfunc = abc.run + elif cmdname == 'cleantrees': + import mesonbuild.scripts.cleantrees as abc + cmdfunc = abc.run elif cmdname == 'install': import mesonbuild.scripts.meson_install as abc cmdfunc = abc.run diff --git a/mesonbuild/scripts/cleantrees.py b/mesonbuild/scripts/cleantrees.py new file mode 100644 index 0000000..0af8dd0 --- /dev/null +++ b/mesonbuild/scripts/cleantrees.py @@ -0,0 +1,43 @@ +# Copyright 2016 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. + +import os +import sys +import shutil +import pickle + +def rmtrees(build_dir, trees): + for t in trees: + # Never delete trees outside of the builddir + if os.path.isabs(t): + print('Cannot delete dir with absolute path {!r}'.format(t)) + continue + bt = os.path.join(build_dir, t) + # Skip if it doesn't exist, or if it is not a directory + if os.path.isdir(bt): + shutil.rmtree(bt, ignore_errors=True) + +def run(args): + if len(args) != 1: + print('Cleaner script for Meson. Do not run on your own please.') + print('cleantrees.py <data-file>') + return 1 + with open(args[0], 'rb') as f: + data = pickle.load(f) + rmtrees(data.build_dir, data.trees) + # Never fail cleaning + return 0 + +if __name__ == '__main__': + run(sys.argv[1:]) diff --git a/run_project_tests.py b/run_project_tests.py index da70bcb..07ee16a 100755 --- a/run_project_tests.py +++ b/run_project_tests.py @@ -24,7 +24,7 @@ from mesonbuild import environment from mesonbuild import mesonlib from mesonbuild import mlog from mesonbuild import mesonmain -from mesonbuild.mesonlib import stringlistify +from mesonbuild.mesonlib import stringlistify, Popen_safe import argparse import xml.etree.ElementTree as ET import time @@ -93,26 +93,20 @@ unity_flags = [] backend_flags = None compile_commands = None test_commands = None -install_commands = None +install_commands = [] +clean_commands = [] def setup_commands(backend): - global backend_flags, compile_commands, test_commands, install_commands + global backend_flags, compile_commands, test_commands, install_commands, clean_commands msbuild_exe = shutil.which('msbuild') - if backend == 'vs2010' or (backend is None and msbuild_exe is not None): - backend_flags = ['--backend=vs2010'] + if (backend and backend.startswith('vs')) or (backend is None and msbuild_exe is not None): + backend_flags = ['--backend=' + backend] compile_commands = ['msbuild'] test_commands = ['msbuild', 'RUN_TESTS.vcxproj'] - install_commands = [] - elif backend == 'vs2015': - backend_flags = ['--backend=vs2015'] - compile_commands = ['msbuild'] - test_commands = ['msbuild', 'RUN_TESTS.vcxproj'] - install_commands = [] elif backend == 'xcode' or (backend is None and mesonlib.is_osx()): backend_flags = ['--backend=xcode'] compile_commands = ['xcodebuild'] test_commands = ['xcodebuild', '-target', 'RUN_TESTS'] - install_commands = [] else: backend_flags = [] ninja_command = environment.detect_ninja() @@ -125,6 +119,7 @@ def setup_commands(backend): compile_commands += ['-w', 'dupbuild=err'] test_commands = [ninja_command, 'test', 'benchmark'] install_commands = [ninja_command, 'install'] + clean_commands = [ninja_command, 'clean'] def get_relative_files_list_from_dir(fromdir): paths = [] @@ -233,17 +228,18 @@ def parse_test_args(testdir): pass return args -def run_test(skipped, testdir, extra_args, flags, compile_commands, install_commands, should_fail): +def run_test(skipped, testdir, extra_args, flags, compile_commands, should_fail): if skipped: return None with AutoDeletedDir(tempfile.mkdtemp(prefix='b ', dir='.')) as build_dir: with AutoDeletedDir(tempfile.mkdtemp(prefix='i ', dir=os.getcwd())) as install_dir: try: - return _run_test(testdir, build_dir, install_dir, extra_args, flags, compile_commands, install_commands, should_fail) + return _run_test(testdir, build_dir, install_dir, extra_args, flags, compile_commands, should_fail) finally: mlog.shutdown() # Close the log file because otherwise Windows wets itself. -def _run_test(testdir, test_build_dir, install_dir, extra_args, flags, compile_commands, install_commands, should_fail): +def _run_test(testdir, test_build_dir, install_dir, extra_args, flags, compile_commands, should_fail): + global install_commands, clean_commands test_args = parse_test_args(testdir) gen_start = time.time() gen_command = [meson_command, '--prefix', '/usr', '--libdir', 'lib', testdir, test_build_dir]\ @@ -268,12 +264,10 @@ def _run_test(testdir, test_build_dir, install_dir, extra_args, flags, compile_c else: comp = compile_commands build_start = time.time() - pc = subprocess.Popen(comp, cwd=test_build_dir, - stdout=subprocess.PIPE, stderr=subprocess.PIPE) - (o, e) = pc.communicate() + pc, o, e = Popen_safe(comp, cwd=test_build_dir) build_time = time.time() - build_start - stdo += o.decode(sys.stdout.encoding) - stde += e.decode(sys.stdout.encoding) + stdo += o + stde += e if should_fail == 'build': if pc.returncode != 0: return TestResult('', stdo, stde, mesonlog, gen_time) @@ -294,19 +288,24 @@ def _run_test(testdir, test_build_dir, install_dir, extra_args, flags, compile_c return TestResult('Test that should have failed to run unit tests succeeded', stdo, stde, mesonlog, gen_time) if returncode != 0: return TestResult('Running unit tests failed.', stdo, stde, mesonlog, gen_time, build_time, test_time) + # Do installation if len(install_commands) == 0: return TestResult('', '', '', gen_time, build_time, test_time) - else: + env = os.environ.copy() + env['DESTDIR'] = install_dir + pi, o, e = Popen_safe(install_commands, cwd=test_build_dir, env=env) + stdo += o + stde += e + if pi.returncode != 0: + return TestResult('Running install failed.', stdo, stde, mesonlog, gen_time, build_time, test_time) + if len(clean_commands) != 0: env = os.environ.copy() - env['DESTDIR'] = install_dir - pi = subprocess.Popen(install_commands, cwd=test_build_dir, env=env, - stdout=subprocess.PIPE, stderr=subprocess.PIPE) - (o, e) = pi.communicate() - stdo += o.decode(sys.stdout.encoding) - stde += e.decode(sys.stdout.encoding) + pi, o, e = Popen_safe(clean_commands, cwd=test_build_dir, env=env) + stdo += o + stde += e if pi.returncode != 0: - return TestResult('Running install failed.', stdo, stde, mesonlog, gen_time, build_time, test_time) - return TestResult(validate_install(testdir, install_dir), stdo, stde, mesonlog, gen_time, build_time, test_time) + return TestResult('Running clean failed.', stdo, stde, mesonlog, gen_time, build_time, test_time) + return TestResult(validate_install(testdir, install_dir), stdo, stde, mesonlog, gen_time, build_time, test_time) def gather_tests(testdir): tests = [t.replace('\\', '/').split('/', 2)[2] for t in glob(os.path.join(testdir, '*'))] @@ -372,7 +371,7 @@ def detect_tests_to_run(): return all_tests def run_tests(extra_args): - global passing_tests, failing_tests, stop, executor, futures + global install_commands, passing_tests, failing_tests, stop, executor, futures all_tests = detect_tests_to_run() logfile = open('meson-test-run.txt', 'w', encoding="utf_8") junit_root = ET.Element('testsuites') @@ -404,7 +403,7 @@ def run_tests(extra_args): should_fail = False if name.startswith('failing'): should_fail = name.split('failing-')[1] - result = executor.submit(run_test, skipped, t, extra_args, unity_flags + backend_flags, compile_commands, install_commands, should_fail) + result = executor.submit(run_test, skipped, t, extra_args, unity_flags + backend_flags, compile_commands, should_fail) futures.append((testname, t, result)) for (testname, t, result) in futures: result = result.result() |