diff options
27 files changed, 522 insertions, 119 deletions
@@ -4,9 +4,11 @@ MesonĀ® is a project to create the best possible next-generation build system. -####Build status +#### Status -[](https://travis-ci.org/mesonbuild/meson) [](https://ci.appveyor.com/project/jpakkane/meson) +[](https://pypi.python.org/pypi/meson) +[](https://travis-ci.org/mesonbuild/meson) +[](https://ci.appveyor.com/project/jpakkane/meson) ####Dependencies @@ -38,6 +40,10 @@ executable run the following command: Note that the source checkout may not be `meson` because it would clash with the generated binary name. +This will zip all files inside the source checkout into the script +which includes hundreds of tests, so you might want to temporarily +remove those before running it. + ####Running Meson requires that you have a source directory and a build directory diff --git a/authors.txt b/authors.txt index 9bf3e33..03e8478 100644 --- a/authors.txt +++ b/authors.txt @@ -56,3 +56,4 @@ Aurelien Jarno Mark Schulte Paulo Antonio Alvarez Olexa Bilaniuk +Daniel Stone @@ -14,10 +14,21 @@ # See the License for the specific language governing permissions and # limitations under the License. -from mesonbuild import mesonmain -import sys, os +from mesonbuild import mlog, mesonmain +import sys, os, locale def main(): + # Warn if the locale is not UTF-8. This can cause various unfixable issues + # such as os.stat not being able to decode filenames with unicode in them. + # There is no way to reset both the preferred encoding and the filesystem + # encoding, so we can just warn about it. + e = locale.getpreferredencoding() + if e.upper() != 'UTF-8': + mlog.warning('You are using {!r} which is not a a Unicode-compatible ' + 'locale.'.format(e)) + mlog.warning('You might see errors if you use UTF-8 strings as ' + 'filenames, as strings, or as file contents.') + mlog.warning('Please switch to a UTF-8 locale for your platform.') # Always resolve the command path so Ninja can find it for regen, tests, etc. launcher = os.path.realpath(sys.argv[0]) return mesonmain.run(launcher, sys.argv[1:]) diff --git a/mesonbuild/backend/backends.py b/mesonbuild/backend/backends.py index c37ae2a..49b6008 100644 --- a/mesonbuild/backend/backends.py +++ b/mesonbuild/backend/backends.py @@ -279,13 +279,9 @@ class Backend(): result = [] targetdir = self.get_target_private_dir(extobj.target) # With unity builds, there's just one object that contains all the - # sources, so if we want all the objects, just return that. + # sources, and we only support extracting all the objects in this mode, + # so just return that. if self.environment.coredata.get_builtin_option('unity'): - if not extobj.unity_compatible: - # This should never happen - msg = 'BUG: Meson must not allow extracting single objects ' \ - 'in Unity builds' - raise AssertionError(msg) comp = get_compiler_for_source(extobj.target.compilers.values(), extobj.srclist[0]) # The unity object name uses the full absolute path of the source file diff --git a/mesonbuild/build.py b/mesonbuild/build.py index 58a8433..462a55b 100644 --- a/mesonbuild/build.py +++ b/mesonbuild/build.py @@ -198,10 +198,11 @@ class ExtractedObjects(): ''' Holds a list of sources for which the objects must be extracted ''' - def __init__(self, target, srclist): + def __init__(self, target, srclist, is_unity): self.target = target self.srclist = srclist - self.check_unity_compatible() + if is_unity: + self.check_unity_compatible() def check_unity_compatible(self): # Figure out if the extracted object list is compatible with a Unity @@ -211,11 +212,9 @@ class ExtractedObjects(): # from each unified source file. # If the list of sources for which we want objects is the same as the # list of sources that go into each unified build, we're good. - self.unity_compatible = False srclist_set = set(self.srclist) # Objects for all the sources are required, so we're compatible if srclist_set == set(self.target.sources): - self.unity_compatible = True return # Check if the srclist is a subset (of the target's sources) that is # going to form a unified source file and a single object @@ -223,7 +222,6 @@ class ExtractedObjects(): self.target.sources) for srcs in compsrcs.values(): if srclist_set == set(srcs): - self.unity_compatible = True return msg = 'Single object files can not be extracted in Unity builds. ' \ 'You can only extract all the object files at once.' @@ -273,6 +271,7 @@ class BuildTarget(): self.subdir = subdir self.subproject = subproject # Can not be calculated from subdir as subproject dirname can be changed per project. self.is_cross = is_cross + self.is_unity = environment.coredata.get_builtin_option('unity') self.environment = environment self.sources = [] self.compilers = {} @@ -458,10 +457,10 @@ class BuildTarget(): if src not in self.sources: raise MesonException('Tried to extract unknown source %s.' % src) obj_src.append(src) - return ExtractedObjects(self, obj_src) + return ExtractedObjects(self, obj_src, self.is_unity) def extract_all_objects(self): - return ExtractedObjects(self, self.sources) + return ExtractedObjects(self, self.sources, self.is_unity) def get_all_link_deps(self): return self.get_transitive_link_deps() diff --git a/mesonbuild/compilers.py b/mesonbuild/compilers.py index ced2b6f..8f8851f 100644 --- a/mesonbuild/compilers.py +++ b/mesonbuild/compilers.py @@ -1846,11 +1846,7 @@ class VisualStudioCCompiler(CCompiler): } def get_option_link_args(self, options): - # FIXME: See GnuCCompiler.get_option_link_args - if 'c_winlibs' in options: - return options['c_winlibs'].value[:] - else: - return msvc_winlibs[:] + return options['c_winlibs'].value[:] def unix_link_flags_to_native(self, args): result = [] @@ -1955,11 +1951,7 @@ class VisualStudioCPPCompiler(VisualStudioCCompiler, CPPCompiler): return args def get_option_link_args(self, options): - # FIXME: See GnuCCompiler.get_option_link_args - if 'cpp_winlibs' in options: - return options['cpp_winlibs'].value[:] - else: - return msvc_winlibs[:] + return options['cpp_winlibs'].value[:] GCC_STANDARD = 0 GCC_OSX = 1 @@ -2071,17 +2063,7 @@ class GnuCCompiler(GnuCompiler, CCompiler): def get_option_link_args(self, options): if self.gcc_type == GCC_MINGW: - # FIXME: This check is needed because we currently pass - # cross-compiler options to the native compiler too and when - # cross-compiling from Windows to Linux, `options` will contain - # Linux-specific options which doesn't include `c_winlibs`. The - # proper fix is to allow cross-info files to specify compiler - # options and to maintain both cross and native compiler options in - # coredata: https://github.com/mesonbuild/meson/issues/1029 - if 'c_winlibs' in options: - return options['c_winlibs'].value[:] - else: - return gnu_winlibs[:] + return options['c_winlibs'].value[:] return [] class GnuCPPCompiler(GnuCompiler, CPPCompiler): @@ -2119,11 +2101,7 @@ class GnuCPPCompiler(GnuCompiler, CPPCompiler): def get_option_link_args(self, options): if self.gcc_type == GCC_MINGW: - # FIXME: See GnuCCompiler.get_option_link_args - if 'cpp_winlibs' in options: - return options['cpp_winlibs'].value[:] - else: - return gnu_winlibs[:] + return options['cpp_winlibs'].value[:] return [] def get_compiler_check_args(self): diff --git a/mesonbuild/dependencies.py b/mesonbuild/dependencies.py index 38945b4..4e87e4e 100644 --- a/mesonbuild/dependencies.py +++ b/mesonbuild/dependencies.py @@ -233,7 +233,7 @@ class PkgConfigDependency(Dependency): '(%s)' % out.decode().strip()) PkgConfigDependency.pkgconfig_found = True return - except Exception: + except (FileNotFoundError, PermissionError): pass PkgConfigDependency.pkgconfig_found = False if not self.silent: @@ -358,7 +358,7 @@ class WxDependency(Dependency): self.wxc = wxc WxDependency.wx_found = True return - except Exception: + except (FileNotFoundError, PermissionError): pass WxDependency.wxconfig_found = False mlog.log('Found wx-config:', mlog.red('NO')) @@ -1040,7 +1040,7 @@ class GnuStepDependency(Dependency): gp = subprocess.Popen([confprog, '--help'], stdout=subprocess.PIPE, stderr=subprocess.PIPE) gp.communicate() - except FileNotFoundError: + except (FileNotFoundError, PermissionError): self.args = None mlog.log('Dependency GnuStep found:', mlog.red('NO'), '(no gnustep-config)') return diff --git a/mesonbuild/environment.py b/mesonbuild/environment.py index cc62010..098f8ca 100644 --- a/mesonbuild/environment.py +++ b/mesonbuild/environment.py @@ -43,7 +43,8 @@ def detect_ninja(): for n in ['ninja', 'ninja-build']: try: p = subprocess.Popen([n, '--version'], stdout=subprocess.PIPE, stderr=subprocess.DEVNULL) - except FileNotFoundError: + except (FileNotFoundError, PermissionError): + # Doesn't exist in PATH or isn't executable continue version = p.communicate()[0].decode(errors='ignore') # Perhaps we should add a way for the caller to know the failure mode diff --git a/mesonbuild/interpreter.py b/mesonbuild/interpreter.py index 3b9f975..ef99511 100644 --- a/mesonbuild/interpreter.py +++ b/mesonbuild/interpreter.py @@ -1762,12 +1762,12 @@ class Interpreter(): raise InvalidCode('Tried to use unknown language "%s".' % lang) comp.sanity_check(self.environment.get_scratch_dir(), self.environment) self.coredata.compilers[lang] = comp + # Native compiler always exist so always add its options. + new_options = comp.get_options() if cross_comp is not None: cross_comp.sanity_check(self.environment.get_scratch_dir(), self.environment) self.coredata.cross_compilers[lang] = cross_comp - new_options = cross_comp.get_options() - else: - new_options = comp.get_options() + new_options.update(cross_comp.get_options()) optprefix = lang + '_' for i in new_options: if not i.startswith(optprefix): diff --git a/mesonbuild/mesonlib.py b/mesonbuild/mesonlib.py index b92be5f..4d9cc69 100644 --- a/mesonbuild/mesonlib.py +++ b/mesonbuild/mesonlib.py @@ -303,7 +303,7 @@ def do_conf_file(src, dst, confdata): replace_if_different(dst, dst_tmp) def dump_conf_header(ofilename, cdata): - with open(ofilename, 'w') as ofile: + with open(ofilename, 'w', encoding='utf-8') as ofile: ofile.write('''/* * Autogenerated by the Meson build system. * Do not edit, your changes will be lost. diff --git a/mesonbuild/mesonmain.py b/mesonbuild/mesonmain.py index 57c814c..1d4863c 100644 --- a/mesonbuild/mesonmain.py +++ b/mesonbuild/mesonmain.py @@ -240,7 +240,13 @@ def run(mainfile, args): return 1 if len(args) >= 2 and args[0] == '--internal': if args[1] != 'regenerate': - sys.exit(run_script_command(args[1:])) + script = args[1] + try: + sys.exit(run_script_command(args[1:])) + except MesonException as e: + mlog.log(mlog.red('\nError in {} helper script:'.format(script))) + mlog.log(e) + sys.exit(1) args = args[2:] handshake = True else: diff --git a/mesonbuild/modules/gnome.py b/mesonbuild/modules/gnome.py index 7c4fb1d..38b3319 100644 --- a/mesonbuild/modules/gnome.py +++ b/mesonbuild/modules/gnome.py @@ -37,6 +37,13 @@ native_glib_version = None girwarning_printed = False gresource_warning_printed = False +def find_program(program_name, target_name): + program = dependencies.ExternalProgram(program_name) + if not program.found(): + raise MesonException('%s can\'t be generated as %s could not be found' % ( + target_name, program_name)) + return program + class GnomeModule: @staticmethod @@ -308,6 +315,8 @@ can not be used with the current version of glib-compiled-resources, due to raise MesonException('Gir takes one argument') if kwargs.get('install_dir'): raise MesonException('install_dir is not supported with generate_gir(), see "install_dir_gir" and "install_dir_typelib"') + giscanner = find_program('g-ir-scanner', 'Gir') + gicompiler = find_program('g-ir-compiler', 'Gir') girtarget = args[0] while hasattr(girtarget, 'held_object'): girtarget = girtarget.held_object @@ -329,7 +338,7 @@ can not be used with the current version of glib-compiled-resources, due to depends = [girtarget] gir_inc_dirs = [] - scan_command = ['g-ir-scanner', '@INPUT@'] + scan_command = giscanner.get_command() + ['@INPUT@'] scan_command += pkgargs scan_command += ['--no-libtool', '--namespace='+ns, '--nsversion=' + nsversion, '--warn-all', '--output', '@OUTPUT@'] @@ -484,7 +493,7 @@ can not be used with the current version of glib-compiled-resources, due to scan_target = GirTarget(girfile, state.subdir, scankwargs) typelib_output = '%s-%s.typelib' % (ns, nsversion) - typelib_cmd = ['g-ir-compiler', scan_target, '--output', '@OUTPUT@'] + typelib_cmd = gicompiler.get_command() + [scan_target, '--output', '@OUTPUT@'] typelib_cmd += self._get_include_args(state, gir_inc_dirs, prefix='--includedir=') for incdir in typelib_includes: @@ -506,7 +515,9 @@ can not be used with the current version of glib-compiled-resources, due to raise MesonException('Compile_schemas does not take positional arguments.') srcdir = os.path.join(state.build_to_src, state.subdir) outdir = state.subdir - cmd = ['glib-compile-schemas', '--targetdir', outdir, srcdir] + + cmd = find_program('glib-compile-schemas', 'gsettings-compile').get_command() + cmd += ['--targetdir', outdir, srcdir] kwargs['command'] = cmd kwargs['input'] = [] kwargs['output'] = 'gschemas.compiled' @@ -684,7 +695,8 @@ can not be used with the current version of glib-compiled-resources, due to raise MesonException('Gdbus_codegen takes two arguments, name and xml file.') namebase = args[0] xml_file = args[1] - cmd = ['gdbus-codegen'] + target_name = namebase + '-gdbus' + cmd = find_program('gdbus-codegen', target_name).get_command() if 'interface_prefix' in kwargs: cmd += ['--interface-prefix', kwargs.pop('interface_prefix')] if 'namespace' in kwargs: @@ -695,7 +707,7 @@ can not be used with the current version of glib-compiled-resources, due to 'output' : outputs, 'command' : cmd } - return build.CustomTarget(namebase + '-gdbus', state.subdir, custom_kwargs) + return build.CustomTarget(target_name, state.subdir, custom_kwargs) def mkenums(self, state, args, kwargs): if len(args) != 1: @@ -741,7 +753,7 @@ can not be used with the current version of glib-compiled-resources, due to elif arg not in known_custom_target_kwargs: raise MesonException( 'Mkenums does not take a %s keyword argument.' % (arg, )) - cmd = ['glib-mkenums'] + cmd + cmd = find_program('glib-mkenums', 'mkenums').get_command() + cmd custom_kwargs = {} for arg in known_custom_target_kwargs: if arg in kwargs: @@ -822,7 +834,7 @@ can not be used with the current version of glib-compiled-resources, due to raise MesonException( 'Sources keyword argument must be a string or array.') - cmd = ['glib-genmarshal'] + cmd = find_program('glib-genmarshal', output + '_genmarshal').get_command() known_kwargs = ['internal', 'nostdinc', 'skip_source', 'stdinc', 'valist_marshallers'] known_custom_target_kwargs = ['build_always', 'depends', @@ -949,7 +961,8 @@ can not be used with the current version of glib-compiled-resources, due to build_dir = os.path.join(state.environment.get_build_dir(), state.subdir) source_dir = os.path.join(state.environment.get_source_dir(), state.subdir) pkg_cmd, vapi_depends, vapi_packages, vapi_includes = self._extract_vapi_packages(state, kwargs) - cmd = ['vapigen', '--quiet', '--library=' + library, '--directory=' + build_dir] + cmd = find_program('vapigen', 'Vaapi').get_command() + cmd += ['--quiet', '--library=' + library, '--directory=' + build_dir] cmd += self._vapi_args_to_command('--vapidir=', 'vapi_dirs', kwargs) cmd += self._vapi_args_to_command('--metadatadir=', 'metadata_dirs', kwargs) cmd += self._vapi_args_to_command('--girdir=', 'gir_dirs', kwargs) diff --git a/mesonbuild/modules/i18n.py b/mesonbuild/modules/i18n.py index 1ddb2fc..bb1b2f8 100644 --- a/mesonbuild/modules/i18n.py +++ b/mesonbuild/modules/i18n.py @@ -12,16 +12,26 @@ # See the License for the specific language governing permissions and # limitations under the License. +from os import path from .. import coredata, mesonlib, build import sys class I18nModule: + @staticmethod + def _read_linguas(state): + linguas = path.join(state.environment.get_source_dir(), state.subdir, 'LINGUAS') + try: + with open(linguas) as f: + return [line.strip() for line in f if not line.strip().startswith('#')] + except (FileNotFoundError, PermissionError): + return [] + def gettext(self, state, args, kwargs): if len(args) != 1: raise coredata.MesonException('Gettext requires one positional argument (package name).') packagename = args[0] - languages = mesonlib.stringlistify(kwargs.get('languages', [])) + languages = mesonlib.stringlistify(kwargs.get('languages', self._read_linguas(state))) if len(languages) == 0: raise coredata.MesonException('List of languages empty.') datadirs = mesonlib.stringlistify(kwargs.get('data_dirs', [])) diff --git a/mesonbuild/modules/pkgconfig.py b/mesonbuild/modules/pkgconfig.py index 9f50b0e..73b29ae 100644 --- a/mesonbuild/modules/pkgconfig.py +++ b/mesonbuild/modules/pkgconfig.py @@ -49,6 +49,7 @@ class PkgConfigModule: # 'os.path.join' for details) ofile.write('libdir=%s\n' % os.path.join('${prefix}', coredata.get_builtin_option('libdir'))) ofile.write('includedir=%s\n' % os.path.join('${prefix}', coredata.get_builtin_option('includedir'))) + ofile.write('\n') ofile.write('Name: %s\n' % name) if len(description) > 0: ofile.write('Description: %s\n' % description) diff --git a/mesonbuild/wrap/wrap.py b/mesonbuild/wrap/wrap.py index e05c641..37d6df7 100644 --- a/mesonbuild/wrap/wrap.py +++ b/mesonbuild/wrap/wrap.py @@ -93,9 +93,19 @@ class Resolver: def resolve(self, packagename): fname = os.path.join(self.subdir_root, packagename + '.wrap') dirname = os.path.join(self.subdir_root, packagename) - if os.path.isdir(dirname): - # The directory is there? Great, use it. - return packagename + try: + if os.listdir(dirname): + # The directory is there and not empty? Great, use it. + return packagename + else: + mlog.warning('Subproject directory %s is empty, possibly because of an unfinished' + 'checkout, removing to reclone' % dirname) + os.rmdir(checkoutdir) + except NotADirectoryError: + raise RuntimeError('%s is not a directory, can not use as subproject.' % dirname) + except FileNotFoundError: + pass + if not os.path.isfile(fname): # No wrap file with this name? Give up. return None @@ -118,6 +128,15 @@ class Resolver: revno = p.get('revision') is_there = os.path.isdir(checkoutdir) if is_there: + try: + subprocess.check_call(['git', 'rev-parse']) + is_there = True + except subprocess.CalledProcessError: + raise RuntimeError('%s is not empty but is not a valid ' + 'git repository, we can not work with it' + ' as a subproject directory.' % ( + checkoutdir)) + if revno.lower() == 'head': # Failure to do pull is not a fatal error, # because otherwise you can't develop without @@ -134,6 +153,11 @@ class Resolver: if revno.lower() != 'head': subprocess.check_call(['git', 'checkout', revno], cwd=checkoutdir) + push_url = p.values.get('push-url') + if push_url: + subprocess.check_call(['git', 'remote', 'set-url', + '--push', 'origin', push_url], + cwd=checkoutdir) def get_hg(self, p): checkoutdir = os.path.join(self.subdir_root, p.get('directory')) revno = p.get('revision') diff --git a/mesontest.py b/mesontest.py index 04f72df..73c92e4 100755 --- a/mesontest.py +++ b/mesontest.py @@ -18,15 +18,18 @@ import subprocess, sys, os, argparse import pickle -import mesonbuild from mesonbuild import build from mesonbuild import environment -import time, datetime, pickle, multiprocessing, json +import time, datetime, multiprocessing, json import concurrent.futures as conc import platform import signal +# GNU autotools interprets a return code of 77 from tests it executes to +# mean that the test should be skipped. +GNU_SKIP_RETURNCODE = 77 + def is_windows(): platname = platform.system().lower() return platname == 'windows' or 'mingw' in platname @@ -57,7 +60,7 @@ parser.add_argument('--list', default=False, dest='list', action='store_true', help='List available tests.') parser.add_argument('--wrapper', default=None, dest='wrapper', help='wrapper to run tests with (e.g. Valgrind)') -parser.add_argument('--wd', default=None, dest='wd', +parser.add_argument('-C', default='.', dest='wd', help='directory to cd into before running') parser.add_argument('--suite', default=None, dest='suite', help='Only run tests belonging to the given suite.') @@ -71,6 +74,8 @@ parser.add_argument('--logbase', default='testlog', help="Base name for log file.") parser.add_argument('--num-processes', default=determine_worker_count(), type=int, help='How many parallel processes to use.') +parser.add_argument('-v', '--verbose', default=False, action='store_true', + help='Do not redirect stdout and stderr') parser.add_argument('args', nargs='*') class TestRun(): @@ -107,6 +112,8 @@ class TestRun(): return res def decode(stream): + if stream is None: + return '' try: return stream.decode('utf-8') except UnicodeDecodeError: @@ -136,9 +143,10 @@ class TestHarness: self.error_count = 0 self.is_run = False if self.options.benchmark: - self.datafile = 'meson-private/meson_benchmark_setup.dat' + self.datafile = os.path.join(options.wd, 'meson-private/meson_benchmark_setup.dat') else: - self.datafile = 'meson-private/meson_test_setup.dat' + self.datafile = os.path.join(options.wd, 'meson-private/meson_test_setup.dat') + print(self.datafile) def run_single_test(self, wrap, test): if test.fname[0].endswith('.jar'): @@ -155,6 +163,7 @@ class TestHarness: cmd = [test.exe_runner] + test.fname else: cmd = test.fname + if cmd is None: res = 'SKIP' duration = 0.0 @@ -171,13 +180,20 @@ class TestHarness: child_env.update(test.env) if len(test.extra_paths) > 0: child_env['PATH'] = child_env['PATH'] + ';'.join([''] + test.extra_paths) - if is_windows(): - setsid = None - else: - setsid = os.setsid + + setsid = None + stdout = None + stderr = None + if not self.options.verbose: + stdout = subprocess.PIPE + stderr = subprocess.PIPE if self.options and self.options.split else subprocess.STDOUT + + if not is_windows(): + setsid = os.setsid + p = subprocess.Popen(cmd, - stdout=subprocess.PIPE, - stderr=subprocess.PIPE if self.options and self.options.split else subprocess.STDOUT, + stdout=stdout, + stderr=stderr, env=child_env, cwd=test.workdir, preexec_fn=setsid) @@ -201,6 +217,8 @@ class TestHarness: stde = decode(stde) if timed_out: res = 'TIMEOUT' + if p.returncode == GNU_SKIP_RETURNCODE: + res = 'SKIP' elif (not test.should_fail and p.returncode == 0) or \ (test.should_fail and p.returncode != 0): res = 'OK' @@ -218,7 +236,8 @@ class TestHarness: (num, name, padding1, result.res, padding2, result.duration) print(result_str) result_str += "\n\n" + result.get_log() - if (result.returncode != 0) != result.should_fail: + if (result.returncode != GNU_SKIP_RETURNCODE) and \ + (result.returncode != 0) != result.should_fail: self.error_count += 1 if self.options.print_errorlogs: self.collected_logs.append(result_str) @@ -250,7 +269,7 @@ class TestHarness: return self.error_count def run_tests(self, datafilename, log_base): - logfile_base = os.path.join('meson-logs', log_base) + logfile_base = os.path.join(self.options.wd, 'meson-logs', log_base) if self.options.wrapper is None: wrap = [] logfilename = logfile_base + '.txt' @@ -270,10 +289,15 @@ class TestHarness: futures = [] filtered_tests = filter_tests(self.options.suite, tests) - with open(jsonlogfilename, 'w') as jsonlogfile, \ - open(logfilename, 'w') as logfile: - logfile.write('Log of Meson test suite run on %s.\n\n' % - datetime.datetime.now().isoformat()) + jsonlogfile = None + logfile = None + try: + if not self.options.verbose: + jsonlogfile = open(jsonlogfilename, 'w') + logfile = open(logfilename, 'w') + logfile.write('Log of Meson test suite run on %s.\n\n' % + datetime.datetime.now().isoformat()) + for i, test in enumerate(filtered_tests): if test.suite[0] == '': visible_name = test.name @@ -287,20 +311,29 @@ class TestHarness: self.drain_futures(futures) futures = [] res = self.run_single_test(wrap, test) - self.print_stats(numlen, filtered_tests, visible_name, res, i, - logfile, jsonlogfile) + if not self.options.verbose: + self.print_stats(numlen, filtered_tests, visible_name, res, i, + logfile, jsonlogfile) else: f = executor.submit(self.run_single_test, wrap, test) - futures.append((f, numlen, filtered_tests, visible_name, i, - logfile, jsonlogfile)) - self.drain_futures(futures) + if not self.options.verbose: + futures.append((f, numlen, filtered_tests, visible_name, i, + logfile, jsonlogfile)) + self.drain_futures(futures, logfile, jsonlogfile) + finally: + if jsonlogfile: + jsonlogfile.close() + if logfile: + logfile.close() + return logfilename - def drain_futures(self, futures): + def drain_futures(self, futures, logfile, jsonlogfile): for i in futures: (result, numlen, tests, name, i, logfile, jsonlogfile) = i - self.print_stats(numlen, tests, name, result.result(), i, logfile, jsonlogfile) + if not self.options.verbose: + self.print_stats(numlen, tests, name, result.result(), i, logfile, jsonlogfile) def run_special(self): 'Tests run by the user, usually something like "under gdb 1000 times".' @@ -325,15 +358,20 @@ class TestHarness: for i in range(self.options.repeat): print('Running: %s %d/%d' % (t.name, i+1, self.options.repeat)) if self.options.gdb: - gdbrun(t) + wrap = ['gdb', '--quiet', '-ex', 'run', '-ex', 'quit'] + if len(t.cmd_args) > 0: + wrap.append('--args') + + res = self.run_single_test(wrap, t) else: res = self.run_single_test(wrap, t) if (res.returncode == 0 and res.should_fail) or \ - (res.returncode != 0 and not res.should_fail): - print('Test failed:\n\n-- stdout --\n') - print(res.stdo) - print('\n-- stderr --\n') - print(res.stde) + (res.returncode != 0 and not res.should_fail): + if not self.options.verbose: + print('Test failed:\n\n-- stdout --\n') + print(res.stdo) + print('\n-- stderr --\n') + print(res.stde) return 1 return 0 @@ -342,32 +380,19 @@ def filter_tests(suite, tests): return tests return [x for x in tests if suite in x.suite] -def gdbrun(test): - child_env = os.environ.copy() - child_env.update(test.env) - # On success will exit cleanly. On failure gdb will ask user - # if they really want to exit. - exe = test.fname - args = test.cmd_args - if len(args) > 0: - argset = ['-ex', 'set args ' + ' '.join(args)] - else: - argset = [] - cmd = ['gdb', '--quiet'] + argset + ['-ex', 'run', '-ex', 'quit'] + exe - # FIXME a ton of stuff. run_single_test grabs stdout & co, - # which we do not want to do when running under gdb. - p = subprocess.Popen(cmd, - env=child_env, - cwd=test.workdir, - ) - p.communicate() def run(args): options = parser.parse_args(args) if options.benchmark: options.num_processes = 1 + + if options.gdb: + options.verbose = True + th = TestHarness(options) - if len(options.args) == 0: + if options.list: + return th.run_special() + elif len(options.args) == 0: return th.doit() return th.run_special() diff --git a/run_project_tests.py b/run_project_tests.py index dcc6006..6f4d0a3 100755 --- a/run_project_tests.py +++ b/run_project_tests.py @@ -380,7 +380,14 @@ def run_tests(extra_args): build_time = 0 test_time = 0 - executor = conc.ProcessPoolExecutor(max_workers=multiprocessing.cpu_count()) + try: + # This fails in some CI environments for unknown reasons. + num_workers = multiprocessing.cpu_count() + except Exception as e: + print('Could not determine number of CPUs due to the following reason:' + str(e)) + print('Defaulting to using only one process') + num_workers = 1 + executor = conc.ProcessPoolExecutor(max_workers=num_workers) for name, test_cases, skipped in all_tests: current_suite = ET.SubElement(junit_root, 'testsuite', {'name' : name, 'tests' : str(len(test_cases))}) diff --git a/run_unittests.py b/run_unittests.py index c2ebc64..8b1f13f 100755 --- a/run_unittests.py +++ b/run_unittests.py @@ -193,9 +193,16 @@ class LinuxlikeTests(unittest.TestCase): self.run_target('check_exists') def test_qt5dependency_qmake_detection(self): - # Can't be sure that `qmake` is Qt5, so just try qmake-qt5. + # Verify that qmake is for Qt5 if not shutil.which('qmake-qt5'): - raise unittest.SkipTest('qt5 not found') + if not shutil.which('qmake'): + raise unittest.SkipTest('QMake not found') + # For some inexplicable reason qmake --version gives different + # results when run from the command line vs invoked by Python. + # Check for both cases in case this behaviour changes in the future. + output = subprocess.getoutput(['qmake', '--version']) + if 'Qt version 5' not in output and 'qt5' not in output: + raise unittest.SkipTest('Qmake found, but it is not for Qt 5.') # Disable pkg-config codepath and force searching with qmake/qmake-qt5 os.environ['PKG_CONFIG_LIBDIR'] = self.builddir os.environ['PKG_CONFIG_PATH'] = self.builddir @@ -203,8 +210,9 @@ class LinuxlikeTests(unittest.TestCase): self.init(testdir) # Confirm that the dependency was found with qmake msg = 'Qt5 native `qmake-qt5` dependency (modules: Core) found: YES\n' + msg2 = 'Qt5 native `qmake` dependency (modules: Core) found: YES\n' mesonlog = self.get_meson_log() - self.assertTrue(msg in mesonlog) + self.assertTrue(msg in mesonlog or msg2 in mesonlog) def get_soname(self, fname): output = subprocess.check_output(['readelf', '-a', fname]) @@ -70,6 +70,7 @@ setup(name='meson', 'mesonbuild.wrap'], scripts=['meson.py', 'mesonconf.py', + 'mesontest.py', 'mesonintrospect.py', 'wraptool.py'], cmdclass={'install_scripts': install_scripts}, diff --git a/syntax-highlighting/vim/README b/syntax-highlighting/vim/README new file mode 100644 index 0000000..1afa243 --- /dev/null +++ b/syntax-highlighting/vim/README @@ -0,0 +1,3 @@ +ftdetect sets the filetype +syntax does Meson syntax highlighting +plugin does Meson indentation diff --git a/syntax-highlighting/vim/ftdetect/meson.vim b/syntax-highlighting/vim/ftdetect/meson.vim new file mode 100644 index 0000000..84db70c --- /dev/null +++ b/syntax-highlighting/vim/ftdetect/meson.vim @@ -0,0 +1,2 @@ +au BufNewFile,BufRead meson.build set filetype=meson +au BufNewFile,BufRead meson_options.txt set filetype=meson diff --git a/syntax-highlighting/vim/plugin/meson.vim b/syntax-highlighting/vim/plugin/meson.vim new file mode 100644 index 0000000..b219bdc --- /dev/null +++ b/syntax-highlighting/vim/plugin/meson.vim @@ -0,0 +1,183 @@ +" Vim indent file +" Language: Meson +" Maintainer: Nirbheek Chauhan <nirbheek.chauhan@gmail.com> +" Original Authors: David Bustos <bustos@caltech.edu> +" Bram Moolenaar <Bram@vim.org> +" Last Change: 2015 Feb 23 + +" Only load this indent file when no other was loaded. +if exists("b:did_indent") + finish +endif +let b:did_indent = 1 + +" Some preliminary settings +setlocal nolisp " Make sure lisp indenting doesn't supersede us +setlocal autoindent " indentexpr isn't much help otherwise + +setlocal indentexpr=GetMesonIndent(v:lnum) +setlocal indentkeys+==elif,=else,=endforeach,=endif,0) + +" Only define the function once. +if exists("*GetMesonIndent") + finish +endif +let s:keepcpo= &cpo +set cpo&vim + +" Come here when loading the script the first time. + +let s:maxoff = 50 " maximum number of lines to look backwards for () + +" Force sw=2 sts=2 because that's required by convention +set shiftwidth=2 +set softtabstop=2 + +function GetMesonIndent(lnum) + echom getline(line(".")) + + " If this line is explicitly joined: If the previous line was also joined, + " line it up with that one, otherwise add two 'shiftwidth' + if getline(a:lnum - 1) =~ '\\$' + if a:lnum > 1 && getline(a:lnum - 2) =~ '\\$' + return indent(a:lnum - 1) + endif + return indent(a:lnum - 1) + (exists("g:mesonindent_continue") ? eval(g:mesonindent_continue) : (shiftwidth() * 2)) + endif + + " If the start of the line is in a string don't change the indent. + if has('syntax_items') + \ && synIDattr(synID(a:lnum, 1, 1), "name") =~ "String$" + return -1 + endif + + " Search backwards for the previous non-empty line. + let plnum = prevnonblank(v:lnum - 1) + + if plnum == 0 + " This is the first non-empty line, use zero indent. + return 0 + endif + + " If the previous line is inside parenthesis, use the indent of the starting + " line. + " Trick: use the non-existing "dummy" variable to break out of the loop when + " going too far back. + call cursor(plnum, 1) + let parlnum = searchpair('(\|{\|\[', '', ')\|}\|\]', 'nbW', + \ "line('.') < " . (plnum - s:maxoff) . " ? dummy :" + \ . " synIDattr(synID(line('.'), col('.'), 1), 'name')" + \ . " =~ '\\(Comment\\|Todo\\|String\\)$'") + if parlnum > 0 + let plindent = indent(parlnum) + let plnumstart = parlnum + else + let plindent = indent(plnum) + let plnumstart = plnum + endif + + + " When inside parenthesis: If at the first line below the parenthesis add + " two 'shiftwidth', otherwise same as previous line. + " i = (a + " + b + " + c) + call cursor(a:lnum, 1) + let p = searchpair('(\|{\|\[', '', ')\|}\|\]', 'bW', + \ "line('.') < " . (a:lnum - s:maxoff) . " ? dummy :" + \ . " synIDattr(synID(line('.'), col('.'), 1), 'name')" + \ . " =~ '\\(Comment\\|Todo\\|String\\)$'") + if p > 0 + if p == plnum + " When the start is inside parenthesis, only indent one 'shiftwidth'. + let pp = searchpair('(\|{\|\[', '', ')\|}\|\]', 'bW', + \ "line('.') < " . (a:lnum - s:maxoff) . " ? dummy :" + \ . " synIDattr(synID(line('.'), col('.'), 1), 'name')" + \ . " =~ '\\(Comment\\|Todo\\|String\\)$'") + if pp > 0 + return indent(plnum) + (exists("g:pyindent_nested_paren") ? eval(g:pyindent_nested_paren) : shiftwidth()) + endif + return indent(plnum) + (exists("g:pyindent_open_paren") ? eval(g:pyindent_open_paren) : (shiftwidth() * 2)) + endif + if plnumstart == p + return indent(plnum) + endif + return plindent + endif + + + " Get the line and remove a trailing comment. + " Use syntax highlighting attributes when possible. + let pline = getline(plnum) + let pline_len = strlen(pline) + if has('syntax_items') + " If the last character in the line is a comment, do a binary search for + " the start of the comment. synID() is slow, a linear search would take + " too long on a long line. + if synIDattr(synID(plnum, pline_len, 1), "name") =~ "\\(Comment\\|Todo\\)$" + let min = 1 + let max = pline_len + while min < max + let col = (min + max) / 2 + if synIDattr(synID(plnum, col, 1), "name") =~ "\\(Comment\\|Todo\\)$" + let max = col + else + let min = col + 1 + endif + endwhile + let pline = strpart(pline, 0, min - 1) + endif + else + let col = 0 + while col < pline_len + if pline[col] == '#' + let pline = strpart(pline, 0, col) + break + endif + let col = col + 1 + endwhile + endif + + " If the previous line ended the conditional/loop + if getline(plnum) =~ '^\s*\(endif\|endforeach\)\>\s*' + " Maintain indent + return -1 + endif + + " If the previous line ended with a builtin, indent this line + if pline =~ '^\s*\(foreach\|if\|else\|elif\)\>\s*' + return plindent + shiftwidth() + endif + + " If the current line begins with a header keyword, deindent + if getline(a:lnum) =~ '^\s*\(else\|elif\|endif\|endforeach\)' + + " Unless the previous line was a one-liner + if getline(plnumstart) =~ '^\s*\(foreach\|if\)\>\s*' + return plindent + endif + + " Or the user has already dedented + if indent(a:lnum) <= plindent - shiftwidth() + return -1 + endif + + return plindent - shiftwidth() + endif + + " When after a () construct we probably want to go back to the start line. + " a = (b + " + c) + " here + if parlnum > 0 + return plindent + endif + + return -1 + +endfunction + +let &cpo = s:keepcpo +unlet s:keepcpo + +" vim:sw=2 diff --git a/syntax-highlighting/vim/syntax/meson.vim b/syntax-highlighting/vim/syntax/meson.vim new file mode 100644 index 0000000..c2653ab --- /dev/null +++ b/syntax-highlighting/vim/syntax/meson.vim @@ -0,0 +1,117 @@ +" Vim syntax file +" Language: Meson +" Maintainer: Nirbheek Chauhan <nirbheek.chauhan@gmail.com> +" Last Change: 2015 Feb 23 +" Credits: Zvezdan Petkovic <zpetkovic@acm.org> +" Neil Schemenauer <nas@meson.ca> +" Dmitry Vasiliev +" +" This version is copied and edited from python.vim +" It's very basic, and doesn't do many things I'd like it to +" For instance, it should show errors for syntax that is valid in +" Python but not in Meson. +" +" Optional highlighting can be controlled using these variables. +" +" let meson_space_error_highlight = 1 +" + +" For version 5.x: Clear all syntax items. +" For version 6.x: Quit when a syntax file was already loaded. +if version < 600 + syntax clear +elseif exists("b:current_syntax") + finish +endif + +" We need nocompatible mode in order to continue lines with backslashes. +" Original setting will be restored. +let s:cpo_save = &cpo +set cpo&vim + +" https://github.com/mesonbuild/meson/wiki/Syntax +syn keyword mesonConditional elif else if endif +syn keyword mesonRepeat foreach endforeach +syn keyword mesonOperator and not or + +syn match mesonComment "#.*$" contains=mesonTodo,@Spell +syn keyword mesonTodo FIXME NOTE NOTES TODO XXX contained + +" Strings can either be single quoted or triple counted across multiple lines, +" but always with a ' +syn region mesonString + \ start="\z('\)" end="\z1" skip="\\\\\|\\\z1" + \ contains=mesonEscape,@Spell +syn region mesonString + \ start="\z('''\)" end="\z1" keepend + \ contains=mesonEscape,mesonSpaceError,@Spell + +syn match mesonEscape "\\[abfnrtv'\\]" contained +syn match mesonEscape "\\\o\{1,3}" contained +syn match mesonEscape "\\x\x\{2}" contained +syn match mesonEscape "\%(\\u\x\{4}\|\\U\x\{8}\)" contained +" Meson allows case-insensitive Unicode IDs: http://www.unicode.org/charts/ +syn match mesonEscape "\\N{\a\+\%(\s\a\+\)*}" contained +syn match mesonEscape "\\$" + +" Meson only supports integer numbers +" https://github.com/mesonbuild/meson/wiki/Syntax#numbers +syn match mesonNumber "\<\d\+\>" + +" booleans +syn keyword mesonConstant false true + +" Built-in functions +syn keyword mesonBuiltin add_global_arguments add_languages benchmark +syn keyword mesonBuiltin build_target configuration_data configure_file +syn keyword mesonBuiltin custom_target declare_dependency dependency +syn keyword mesonBuiltin error executable find_program find_library +syn keyword mesonBuiltin files generator get_option get_variable +syn keyword mesonBuiltin gettext import include_directories install_data +syn keyword mesonBuiltin install_headers install_man install_subdir +syn keyword mesonBuiltin is_subproject is_variable jar library message +syn keyword mesonBuiltin project run_command run_target set_variable +syn keyword mesonBuiltin shared_library static_library subdir subproject +syn keyword mesonBuiltin test vcs_tag + +if exists("meson_space_error_highlight") + " trailing whitespace + syn match mesonSpaceError display excludenl "\s\+$" + " mixed tabs and spaces + syn match mesonSpaceError display " \+\t" + syn match mesonSpaceError display "\t\+ " +endif + +if version >= 508 || !exists("did_meson_syn_inits") + if version <= 508 + let did_meson_syn_inits = 1 + command -nargs=+ HiLink hi link <args> + else + command -nargs=+ HiLink hi def link <args> + endif + + " The default highlight links. Can be overridden later. + HiLink mesonStatement Statement + HiLink mesonConditional Conditional + HiLink mesonRepeat Repeat + HiLink mesonOperator Operator + HiLink mesonComment Comment + HiLink mesonTodo Todo + HiLink mesonString String + HiLink mesonEscape Special + HiLink mesonNumber Number + HiLink mesonBuiltin Function + HiLink mesonConstant Number + if exists("meson_space_error_highlight") + HiLink mesonSpaceError Error + endif + + delcommand HiLink +endif + +let b:current_syntax = "meson" + +let &cpo = s:cpo_save +unlet s:cpo_save + +" vim:set sw=2 sts=2 ts=8 noet: diff --git a/test cases/common/124 test skip/meson.build b/test cases/common/124 test skip/meson.build new file mode 100644 index 0000000..568527f --- /dev/null +++ b/test cases/common/124 test skip/meson.build @@ -0,0 +1,4 @@ +project('test skip', 'c') + +exe_test_skip = executable('test_skip', 'test_skip.c') +test('test_skip', exe_test_skip) diff --git a/test cases/common/124 test skip/test_skip.c b/test cases/common/124 test skip/test_skip.c new file mode 100644 index 0000000..d050a61 --- /dev/null +++ b/test cases/common/124 test skip/test_skip.c @@ -0,0 +1,4 @@ +int main(int argc, char *argv[]) +{ + return 77; +} diff --git a/test cases/common/25 object extraction/lib2.c b/test cases/common/25 object extraction/lib2.c new file mode 100644 index 0000000..c30dde2 --- /dev/null +++ b/test cases/common/25 object extraction/lib2.c @@ -0,0 +1,3 @@ +int retval() { + return 43; +} diff --git a/test cases/common/25 object extraction/meson.build b/test cases/common/25 object extraction/meson.build index 7c5ab90..c76b0db 100644 --- a/test cases/common/25 object extraction/meson.build +++ b/test cases/common/25 object extraction/meson.build @@ -4,7 +4,7 @@ if meson.is_unity() message('Skipping extraction test because this is a Unity build.') else lib1 = shared_library('somelib', 'src/lib.c') - lib2 = shared_library('somelib2', 'lib.c') + lib2 = shared_library('somelib2', 'lib.c', 'lib2.c') obj1 = lib1.extract_objects('src/lib.c') obj2 = lib2.extract_objects(['lib.c']) |