diff options
91 files changed, 1670 insertions, 755 deletions
diff --git a/.travis.yml b/.travis.yml index 2acb908..b3346a1 100644 --- a/.travis.yml +++ b/.travis.yml @@ -40,5 +40,5 @@ script: - if [[ "$TRAVIS_OS_NAME" == "linux" ]]; then echo FROM jpakkane/mesonci:yakkety > Dockerfile; fi - if [[ "$TRAVIS_OS_NAME" == "linux" ]]; then echo ADD . /root >> Dockerfile; fi - if [[ "$TRAVIS_OS_NAME" == "linux" ]]; then docker build -t withgit .; fi - - if [[ "$TRAVIS_OS_NAME" == "linux" ]]; then docker run withgit /bin/sh -c "cd /root && TRAVIS=true CC=$CC CXX=$CXX ./run_tests.py -- $MESON_ARGS"; fi - - if [[ "$TRAVIS_OS_NAME" == "osx" ]]; then SDKROOT=$(xcodebuild -version -sdk macosx Path) ./run_tests.py --backend=ninja -- $MESON_ARGS ; fi + - if [[ "$TRAVIS_OS_NAME" == "linux" ]]; then docker run withgit /bin/sh -c "cd /root && TRAVIS=true CC=$CC CXX=$CXX OBJC=$CC OBJCXX=$CXX ./run_tests.py -- $MESON_ARGS"; fi + - if [[ "$TRAVIS_OS_NAME" == "osx" ]]; then SDKROOT=$(xcodebuild -version -sdk macosx Path) OBJC=$CC OBJCXX=$CXX ./run_tests.py --backend=ninja -- $MESON_ARGS ; fi diff --git a/authors.txt b/authors.txt index 2f36256..80c4bbb 100644 --- a/authors.txt +++ b/authors.txt @@ -63,3 +63,7 @@ Kseniia Vasilchuk Philipp Geier Mike Sinkovsky Dima Krasner +Fabio Porcedda +Rodrigo Lourenço +Sebastian Stang +Marc Becker diff --git a/mesonbuild/backend/backends.py b/mesonbuild/backend/backends.py index 46f8563..26052d3 100644 --- a/mesonbuild/backend/backends.py +++ b/mesonbuild/backend/backends.py @@ -33,10 +33,11 @@ class CleanTrees: self.trees = trees class InstallData: - def __init__(self, source_dir, build_dir, prefix): + def __init__(self, source_dir, build_dir, prefix, strip_bin): self.source_dir = source_dir self.build_dir = build_dir self.prefix = prefix + self.strip_bin = strip_bin self.targets = [] self.headers = [] self.man = [] @@ -215,13 +216,13 @@ class Backend: exe_data = os.path.join(self.environment.get_scratch_dir(), scratch_file) with open(exe_data, 'wb') as f: if isinstance(exe, dependencies.ExternalProgram): - exe_fullpath = exe.fullpath + exe_cmd = exe.get_command() exe_needs_wrapper = False elif isinstance(exe, (build.BuildTarget, build.CustomTarget)): - exe_fullpath = [self.get_target_filename_abs(exe)] + exe_cmd = [self.get_target_filename_abs(exe)] exe_needs_wrapper = exe.is_cross else: - exe_fullpath = [exe] + exe_cmd = [exe] exe_needs_wrapper = False is_cross = exe_needs_wrapper and \ self.environment.is_cross_build() and \ @@ -235,7 +236,7 @@ class Backend: extra_paths = self.determine_windows_extra_paths(exe) else: extra_paths = [] - es = ExecutableSerialisation(basename, exe_fullpath, cmd_args, env, + es = ExecutableSerialisation(basename, exe_cmd, cmd_args, env, is_cross, exe_wrapper, workdir, extra_paths, capture) pickle.dump(es, f) @@ -444,9 +445,9 @@ class Backend: for t in tests: exe = t.get_exe() if isinstance(exe, dependencies.ExternalProgram): - fname = exe.fullpath + cmd = exe.get_command() else: - fname = [os.path.join(self.environment.get_build_dir(), self.get_target_filename(t.get_exe()))] + cmd = [os.path.join(self.environment.get_build_dir(), self.get_target_filename(t.get_exe()))] is_cross = self.environment.is_cross_build() and \ self.environment.cross_info.need_cross_compiler() and \ self.environment.cross_info.need_exe_wrapper() @@ -471,7 +472,7 @@ class Backend: cmd_args.append(self.get_target_filename(a)) else: raise MesonException('Bad object in test command.') - ts = TestSerialisation(t.get_name(), t.suite, fname, is_cross, exe_wrapper, + ts = TestSerialisation(t.get_name(), t.suite, cmd, is_cross, exe_wrapper, t.is_parallel, cmd_args, t.env, t.should_fail, t.timeout, t.workdir, extra_paths) arr.append(ts) @@ -603,19 +604,15 @@ class Backend: return srcs def eval_custom_target_command(self, target, absolute_outputs=False): - # We only want the outputs to be absolute when using the VS backend - if not absolute_outputs: - ofilenames = [os.path.join(self.get_target_dir(target), i) for i in target.output] - else: - ofilenames = [os.path.join(self.environment.get_build_dir(), self.get_target_dir(target), i) - for i in target.output] - srcs = self.get_custom_target_sources(target) + # We want the outputs to be absolute only when using the VS backend outdir = self.get_target_dir(target) - # Many external programs fail on empty arguments. - if outdir == '': - outdir = '.' - if target.absolute_paths: + if absolute_outputs: outdir = os.path.join(self.environment.get_build_dir(), outdir) + outputs = [] + for i in target.output: + outputs.append(os.path.join(outdir, i)) + inputs = self.get_custom_target_sources(target) + # Evaluate the command list cmd = [] for i in target.command: if isinstance(i, build.Executable): @@ -631,37 +628,10 @@ class Backend: if target.absolute_paths: i = os.path.join(self.environment.get_build_dir(), i) # FIXME: str types are blindly added ignoring 'target.absolute_paths' + # because we can't know if they refer to a file or just a string elif not isinstance(i, str): err_msg = 'Argument {0} is of unknown type {1}' raise RuntimeError(err_msg.format(str(i), str(type(i)))) - for (j, src) in enumerate(srcs): - i = i.replace('@INPUT%d@' % j, src) - for (j, res) in enumerate(ofilenames): - i = i.replace('@OUTPUT%d@' % j, res) - if '@INPUT@' in i: - msg = 'Custom target {} has @INPUT@ in the command, but'.format(target.name) - if len(srcs) == 0: - raise MesonException(msg + ' no input files') - if i == '@INPUT@': - cmd += srcs - continue - else: - if len(srcs) > 1: - raise MesonException(msg + ' more than one input file') - i = i.replace('@INPUT@', srcs[0]) - elif '@OUTPUT@' in i: - msg = 'Custom target {} has @OUTPUT@ in the command, but'.format(target.name) - if len(ofilenames) == 0: - raise MesonException(msg + ' no output files') - if i == '@OUTPUT@': - cmd += ofilenames - continue - else: - if len(ofilenames) > 1: - raise MesonException(msg + ' more than one output file') - i = i.replace('@OUTPUT@', ofilenames[0]) - elif '@OUTDIR@' in i: - i = i.replace('@OUTDIR@', outdir) elif '@DEPFILE@' in i: if target.depfile is None: msg = 'Custom target {!r} has @DEPFILE@ but no depfile ' \ @@ -680,10 +650,11 @@ class Backend: lead_dir = '' else: lead_dir = self.environment.get_build_dir() - i = i.replace(source, - os.path.join(lead_dir, - outdir)) + i = i.replace(source, os.path.join(lead_dir, outdir)) cmd.append(i) + # Substitute the rest of the template strings + values = mesonlib.get_filenames_templates_dict(inputs, outputs) + cmd = mesonlib.substitute_values(cmd, values) # This should not be necessary but removing it breaks # building GStreamer on Windows. The underlying issue # is problems with quoting backslashes on Windows @@ -703,7 +674,7 @@ class Backend: # # https://github.com/mesonbuild/meson/pull/737 cmd = [i.replace('\\', '/') for i in cmd] - return srcs, ofilenames, cmd + return inputs, outputs, cmd def run_postconf_scripts(self): env = {'MESON_SOURCE_ROOT': self.environment.get_source_dir(), diff --git a/mesonbuild/backend/ninjabackend.py b/mesonbuild/backend/ninjabackend.py index 8d5d2e0..5e137ca 100644 --- a/mesonbuild/backend/ninjabackend.py +++ b/mesonbuild/backend/ninjabackend.py @@ -30,9 +30,11 @@ from collections import OrderedDict if mesonlib.is_windows(): quote_char = '"' execute_wrapper = 'cmd /c' + rmfile_prefix = 'del /f /s /q {} &&' else: quote_char = "'" execute_wrapper = '' + rmfile_prefix = 'rm -f {} &&' def ninja_quote(text): return text.replace(' ', '$ ').replace(':', '$:') @@ -208,9 +210,11 @@ int dummy; # http://clang.llvm.org/docs/JSONCompilationDatabase.html def generate_compdb(self): ninja_exe = environment.detect_ninja() + ninja_compdb = [ninja_exe, '-t', 'compdb', 'c_COMPILER', 'cpp_COMPILER', 'c_CROSS_COMPILER', + 'cpp_CROSS_COMPILER'] builddir = self.environment.get_build_dir() try: - jsondb = subprocess.check_output([ninja_exe, '-t', 'compdb', 'c_COMPILER', 'cpp_COMPILER'], cwd=builddir) + jsondb = subprocess.check_output(ninja_compdb, cwd=builddir) with open(os.path.join(builddir, 'compile_commands.json'), 'wb') as f: f.write(jsondb) except Exception: @@ -590,9 +594,19 @@ int dummy; def generate_install(self, outfile): install_data_file = os.path.join(self.environment.get_scratch_dir(), 'install.dat') + if self.environment.is_cross_build(): + bins = self.environment.cross_info.config['binaries'] + if 'strip' not in bins: + mlog.warning('Cross file does not specify strip binary, result will not be stripped.') + strip_bin = None + else: + strip_bin = mesonlib.stringlistify(bins['strip']) + else: + strip_bin = self.environment.native_strip_bin d = InstallData(self.environment.get_source_dir(), self.environment.get_build_dir(), - self.environment.get_prefix()) + self.environment.get_prefix(), + strip_bin) elem = NinjaBuildElement(self.all_outputs, 'install', 'CUSTOM_COMMAND', 'PHONY') elem.add_dep('all') elem.add_item('DESC', 'Installing files.') @@ -1232,15 +1246,22 @@ int dummy; return rule = 'rule STATIC%s_LINKER\n' % crstr if mesonlib.is_windows(): - command_templ = ''' command = %s @$out.rsp + command_template = ''' command = {executable} @$out.rsp rspfile = $out.rsp - rspfile_content = $LINK_ARGS %s $in + rspfile_content = $LINK_ARGS {output_args} $in ''' else: - command_templ = ' command = %s $LINK_ARGS %s $in\n' - command = command_templ % ( - ' '.join(static_linker.get_exelist()), - ' '.join(static_linker.get_output_args('$out'))) + command_template = ' command = {executable} $LINK_ARGS {output_args} $in\n' + cmdlist = [] + if isinstance(static_linker, compilers.ArLinker): + # `ar` has no options to overwrite archives. It always appends, + # which is never what we want. Delete an existing library first if + # it exists. https://github.com/mesonbuild/meson/issues/1355 + cmdlist = [execute_wrapper, rmfile_prefix.format('$out')] + cmdlist += static_linker.get_exelist() + command = command_template.format( + executable=' '.join(cmdlist), + output_args=' '.join(static_linker.get_output_args('$out'))) description = ' description = Static linking library $out\n\n' outfile.write(rule) outfile.write(command) @@ -1273,16 +1294,17 @@ int dummy; pass rule = 'rule %s%s_LINKER\n' % (langname, crstr) if mesonlib.is_windows(): - command_template = ''' command = %s @$out.rsp + command_template = ''' command = {executable} @$out.rsp rspfile = $out.rsp - rspfile_content = %s $ARGS %s $in $LINK_ARGS $aliasing + rspfile_content = $ARGS {output_args} $in $LINK_ARGS {cross_args} $aliasing ''' else: - command_template = ' command = %s %s $ARGS %s $in $LINK_ARGS $aliasing\n' - command = command_template % ( - ' '.join(compiler.get_linker_exelist()), - ' '.join(cross_args), - ' '.join(compiler.get_linker_output_args('$out'))) + command_template = ' command = {executable} $ARGS {output_args} $in $LINK_ARGS {cross_args} $aliasing\n' + command = command_template.format( + executable=' '.join(compiler.get_linker_exelist()), + cross_args=' '.join(cross_args), + output_args=' '.join(compiler.get_linker_output_args('$out')) + ) description = ' description = Linking target $out' outfile.write(rule) outfile.write(command) @@ -1386,17 +1408,18 @@ rule FORTRAN_DEP_HACK if getattr(self, 'created_llvm_ir_rule', False): return rule = 'rule llvm_ir{}_COMPILER\n'.format('_CROSS' if is_cross else '') - args = [' '.join([ninja_quote(i) for i in compiler.get_exelist()]), - ' '.join(self.get_cross_info_lang_args(compiler.language, is_cross)), - ' '.join(compiler.get_output_args('$out')), - ' '.join(compiler.get_compile_only_args())] if mesonlib.is_windows(): - command_template = ' command = {} @$out.rsp\n' \ + command_template = ' command = {executable} @$out.rsp\n' \ ' rspfile = $out.rsp\n' \ - ' rspfile_content = {} $ARGS {} {} $in\n' + ' rspfile_content = {cross_args} $ARGS {output_args} {compile_only_args} $in\n' else: - command_template = ' command = {} {} $ARGS {} {} $in\n' - command = command_template.format(*args) + command_template = ' command = {executable} {cross_args} $ARGS {output_args} {compile_only_args} $in\n' + command = command_template.format( + executable=' '.join([ninja_quote(i) for i in compiler.get_exelist()]), + cross_args=' '.join(self.get_cross_info_lang_args(compiler.language, is_cross)), + output_args=' '.join(compiler.get_output_args('$out')), + compile_only_args=' '.join(compiler.get_compile_only_args()) + ) description = ' description = Compiling LLVM IR object $in.\n' outfile.write(rule) outfile.write(command) @@ -1448,18 +1471,19 @@ rule FORTRAN_DEP_HACK quoted_depargs.append(d) cross_args = self.get_cross_info_lang_args(langname, is_cross) if mesonlib.is_windows(): - command_template = ''' command = %s @$out.rsp + command_template = ''' command = {executable} @$out.rsp rspfile = $out.rsp - rspfile_content = %s $ARGS %s %s %s $in + rspfile_content = {cross_args} $ARGS {dep_args} {output_args} {compile_only_args} $in ''' else: - command_template = ' command = %s %s $ARGS %s %s %s $in\n' - command = command_template % ( - ' '.join([ninja_quote(i) for i in compiler.get_exelist()]), - ' '.join(cross_args), - ' '.join(quoted_depargs), - ' '.join(compiler.get_output_args('$out')), - ' '.join(compiler.get_compile_only_args())) + command_template = ' command = {executable} {cross_args} $ARGS {dep_args} {output_args} {compile_only_args} $in\n' + command = command_template.format( + executable=' '.join([ninja_quote(i) for i in compiler.get_exelist()]), + cross_args=' '.join(cross_args), + dep_args=' '.join(quoted_depargs), + output_args=' '.join(compiler.get_output_args('$out')), + compile_only_args=' '.join(compiler.get_compile_only_args()) + ) description = ' description = Compiling %s object $out\n' % langname if compiler.get_id() == 'msvc': deps = ' deps = msvc\n' @@ -1497,12 +1521,13 @@ rule FORTRAN_DEP_HACK output = '' else: output = ' '.join(compiler.get_output_args('$out')) - command = " command = %s %s $ARGS %s %s %s $in\n" % ( - ' '.join(compiler.get_exelist()), - ' '.join(cross_args), - ' '.join(quoted_depargs), - output, - ' '.join(compiler.get_compile_only_args())) + command = " command = {executable} {cross_args} $ARGS {dep_args} {output_args} {compile_only_args} $in\n".format( + executable=' '.join(compiler.get_exelist()), + cross_args=' '.join(cross_args), + dep_args=' '.join(quoted_depargs), + output_args=output, + compile_only_args=' '.join(compiler.get_compile_only_args()) + ) description = ' description = Precompiling header %s\n' % '$in' if compiler.get_id() == 'msvc': deps = ' deps = msvc\n' diff --git a/mesonbuild/backend/vs2010backend.py b/mesonbuild/backend/vs2010backend.py index 666da7d..547889c 100644 --- a/mesonbuild/backend/vs2010backend.py +++ b/mesonbuild/backend/vs2010backend.py @@ -395,7 +395,7 @@ class Vs2010Backend(backends.Backend): if isinstance(i, build.BuildTarget): cmd.append(os.path.join(self.environment.get_build_dir(), self.get_target_filename(i))) elif isinstance(i, dependencies.ExternalProgram): - cmd += i.fullpath + cmd += i.get_command() else: cmd.append(i) cmd_templ = '''"%s" ''' * len(cmd) diff --git a/mesonbuild/build.py b/mesonbuild/build.py index 5466431..bdb1dc3 100644 --- a/mesonbuild/build.py +++ b/mesonbuild/build.py @@ -16,7 +16,9 @@ from . import environment from . import dependencies from . import mlog import copy, os, re -from .mesonlib import File, flatten, MesonException, stringlistify, classify_unity_sources +from .mesonlib import File, MesonException +from .mesonlib import flatten, stringlistify, classify_unity_sources +from .mesonlib import get_filenames_templates_dict, substitute_values from .environment import for_windows, for_darwin from .compilers import is_object, clike_langs, lang_suffixes @@ -227,6 +229,10 @@ class EnvironmentVariables: def __init__(self): self.envvars = [] + def __repr__(self): + repr_str = "<{0}: {1}>" + return repr_str.format(self.__class__.__name__, self.envvars) + def get_value(self, name, values, kwargs): separator = kwargs.get('separator', os.pathsep) @@ -318,11 +324,16 @@ class BuildTarget(Target): raise InvalidArguments('Build target %s has no sources.' % name) self.process_compilers() self.validate_sources() + self.validate_cross_install(environment) def __repr__(self): repr_str = "<{0} {1}: {2}>" return repr_str.format(self.__class__.__name__, self.get_id(), self.filename) + def validate_cross_install(self, environment): + if environment.is_cross_build() and not self.is_cross and self.install: + raise InvalidArguments('Tried to install a natively built target in a cross build.') + def get_id(self): # This ID must also be a valid file name on all OSs. # It should also avoid shell metacharacters for obvious @@ -1298,6 +1309,29 @@ class CustomTarget(Target): deps.append(c) return deps + def flatten_command(self, cmd): + if not isinstance(cmd, list): + cmd = [cmd] + final_cmd = [] + for c in cmd: + if hasattr(c, 'held_object'): + c = c.held_object + if isinstance(c, (str, File)): + final_cmd.append(c) + elif isinstance(c, dependencies.ExternalProgram): + if not c.found(): + m = 'Tried to use not-found external program {!r} in "command"' + raise InvalidArguments(m.format(c.name)) + final_cmd += c.get_command() + elif isinstance(c, (BuildTarget, CustomTarget)): + self.dependencies.append(c) + final_cmd.append(c) + elif isinstance(c, list): + final_cmd += self.flatten_command(c) + else: + raise InvalidArguments('Argument {!r} in "command" is invalid'.format(c)) + return final_cmd + def process_kwargs(self, kwargs): super().process_kwargs(kwargs) self.sources = kwargs.get('input', []) @@ -1308,11 +1342,25 @@ class CustomTarget(Target): self.output = kwargs['output'] if not isinstance(self.output, list): self.output = [self.output] + # This will substitute values from the input into output and return it. + inputs = get_sources_string_names(self.sources) + values = get_filenames_templates_dict(inputs, []) for i in self.output: if not(isinstance(i, str)): raise InvalidArguments('Output argument not a string.') if '/' in i: raise InvalidArguments('Output must not contain a path segment.') + if '@INPUT@' in i or '@INPUT0@' in i: + m = 'Output cannot contain @INPUT@ or @INPUT0@, did you ' \ + 'mean @PLAINNAME@ or @BASENAME@?' + raise InvalidArguments(m) + # We already check this during substitution, but the error message + # will be unclear/confusing, so check it here. + if len(inputs) != 1 and ('@PLAINNAME@' in i or '@BASENAME@' in i): + m = "Output cannot contain @PLAINNAME@ or @BASENAME@ when " \ + "there is more than one input (we can't know which to use)" + raise InvalidArguments(m) + self.output = substitute_values(self.output, values) self.capture = kwargs.get('capture', False) if self.capture and len(self.output) != 1: raise InvalidArguments('Capturing can only output to a single file.') @@ -1325,32 +1373,7 @@ class CustomTarget(Target): if os.path.split(depfile)[1] != depfile: raise InvalidArguments('Depfile must be a plain filename without a subdirectory.') self.depfile = depfile - cmd = kwargs['command'] - if not(isinstance(cmd, list)): - cmd = [cmd] - final_cmd = [] - for i, c in enumerate(cmd): - if hasattr(c, 'held_object'): - c = c.held_object - if isinstance(c, (str, File)): - final_cmd.append(c) - elif isinstance(c, dependencies.ExternalProgram): - if not c.found(): - raise InvalidArguments('Tried to use not found external program {!r} in a build rule.'.format(c.name)) - final_cmd += c.get_command() - elif isinstance(c, (BuildTarget, CustomTarget)): - self.dependencies.append(c) - final_cmd.append(c) - elif isinstance(c, list): - # Hackety hack, only supports one level of flattening. Should really - # work to arbtrary depth. - for s in c: - if not isinstance(s, str): - raise InvalidArguments('Array as argument %d contains a non-string.' % i) - final_cmd.append(s) - else: - raise InvalidArguments('Argument %s in "command" is invalid.' % i) - self.command = final_cmd + self.command = self.flatten_command(kwargs['command']) if self.capture: for c in self.command: if isinstance(c, str) and '@OUTPUT@' in c: @@ -1463,6 +1486,11 @@ class Jar(BuildTarget): def get_java_args(self): return self.java_args + def validate_cross_install(self, environment): + # All jar targets are installable. + pass + + class ConfigureFile: def __init__(self, subdir, sourcename, targetname, configuration_data): @@ -1532,3 +1560,22 @@ class TestSetup: self.gdb = gdb self.timeout_multiplier = timeout_multiplier self.env = env + +def get_sources_string_names(sources): + ''' + For the specified list of @sources which can be strings, Files, or targets, + get all the output basenames. + ''' + names = [] + for s in sources: + if hasattr(s, 'held_object'): + s = s.held_object + if isinstance(s, str): + names.append(s) + elif isinstance(s, (BuildTarget, CustomTarget, GeneratedList)): + names += s.get_outputs() + elif isinstance(s, File): + names.append(s.fname) + else: + raise AssertionError('Unknown source type: {!r}'.format(s)) + return names diff --git a/mesonbuild/compilers.py b/mesonbuild/compilers.py index 5351111..8c2bb92 100644 --- a/mesonbuild/compilers.py +++ b/mesonbuild/compilers.py @@ -188,6 +188,11 @@ gnu_color_args = {'auto': ['-fdiagnostics-color=auto'], 'never': ['-fdiagnostics-color=never'], } +clang_color_args = {'auto': ['-Xclang', '-fcolor-diagnostics'], + 'always': ['-Xclang', '-fcolor-diagnostics'], + 'never': ['-Xclang', '-fno-color-diagnostics'], + } + base_options = {'b_pch': coredata.UserBooleanOption('b_pch', 'Use precompiled headers', True), 'b_lto': coredata.UserBooleanOption('b_lto', 'Use link time optimization', False), 'b_sanitize': coredata.UserComboOption('b_sanitize', @@ -2399,11 +2404,9 @@ class GnuCPPCompiler(GnuCompiler, CPPCompiler): class GnuObjCCompiler(GnuCompiler, ObjCCompiler): - def __init__(self, exelist, version, is_cross, exe_wrapper=None, defines=None): + def __init__(self, exelist, version, gcc_type, is_cross, exe_wrapper=None, defines=None): ObjCCompiler.__init__(self, exelist, version, is_cross, exe_wrapper) - # Not really correct, but GNU objc is only used on non-OSX non-win. File a bug - # if this breaks your use case. - GnuCompiler.__init__(self, GCC_STANDARD, defines) + GnuCompiler.__init__(self, gcc_type, defines) default_warn_args = ['-Wall', '-Winvalid-pch'] self.warn_args = {'1': default_warn_args, '2': default_warn_args + ['-Wextra'], @@ -2411,11 +2414,9 @@ class GnuObjCCompiler(GnuCompiler, ObjCCompiler): class GnuObjCPPCompiler(GnuCompiler, ObjCPPCompiler): - def __init__(self, exelist, version, is_cross, exe_wrapper=None, defines=None): + def __init__(self, exelist, version, gcc_type, is_cross, exe_wrapper=None, defines=None): ObjCPPCompiler.__init__(self, exelist, version, is_cross, exe_wrapper) - # Not really correct, but GNU objc is only used on non-OSX non-win. File a bug - # if this breaks your use case. - GnuCompiler.__init__(self, GCC_STANDARD, defines) + GnuCompiler.__init__(self, gcc_type, defines) default_warn_args = ['-Wall', '-Winvalid-pch', '-Wnon-virtual-dtor'] self.warn_args = {'1': default_warn_args, '2': default_warn_args + ['-Wextra'], @@ -2427,7 +2428,7 @@ class ClangCompiler: self.id = 'clang' self.clang_type = clang_type self.base_options = ['b_pch', 'b_lto', 'b_pgo', 'b_sanitize', 'b_coverage', - 'b_ndebug', 'b_staticpic'] + 'b_ndebug', 'b_staticpic', 'b_colorout'] if self.clang_type != CLANG_OSX: self.base_options.append('b_lundef') self.base_options.append('b_asneeded') @@ -2439,6 +2440,9 @@ class ClangCompiler: return [] # On Window and OS X, pic is always on. return ['-fPIC'] + def get_colorout_args(self, colortype): + return clang_color_args[colortype][:] + def get_buildtype_args(self, buildtype): return gnulike_buildtype_args[buildtype] @@ -2541,13 +2545,13 @@ class ClangCPPCompiler(ClangCompiler, CPPCompiler): class ClangObjCCompiler(ClangCompiler, GnuObjCCompiler): def __init__(self, exelist, version, cltype, is_cross, exe_wrapper=None): - GnuObjCCompiler.__init__(self, exelist, version, is_cross, exe_wrapper) + GnuObjCCompiler.__init__(self, exelist, version, cltype, is_cross, exe_wrapper) ClangCompiler.__init__(self, cltype) self.base_options = ['b_pch', 'b_lto', 'b_pgo', 'b_sanitize', 'b_coverage'] class ClangObjCPPCompiler(ClangCompiler, GnuObjCPPCompiler): def __init__(self, exelist, version, cltype, is_cross, exe_wrapper=None): - GnuObjCPPCompiler.__init__(self, exelist, version, is_cross, exe_wrapper) + GnuObjCPPCompiler.__init__(self, exelist, version, cltype, is_cross, exe_wrapper) ClangCompiler.__init__(self, cltype) self.base_options = ['b_pch', 'b_lto', 'b_pgo', 'b_sanitize', 'b_coverage'] diff --git a/mesonbuild/coredata.py b/mesonbuild/coredata.py index d39f161..0e6685c 100644 --- a/mesonbuild/coredata.py +++ b/mesonbuild/coredata.py @@ -304,7 +304,6 @@ forbidden_target_names = {'clean': None, 'PHONY': None, 'all': None, 'test': None, - 'test:': None, 'benchmark': None, 'install': None, 'uninstall': None, diff --git a/mesonbuild/dependencies.py b/mesonbuild/dependencies.py index c894b0e..e4317f1 100644 --- a/mesonbuild/dependencies.py +++ b/mesonbuild/dependencies.py @@ -1,4 +1,4 @@ -# Copyright 2013-2015 The Meson development team +# Copyright 2013-2017 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. @@ -120,8 +120,8 @@ class PkgConfigDependency(Dependency): if self.required: raise DependencyException('Pkg-config binary missing from cross file') else: - potential_pkgbin = ExternalProgram(environment.cross_info.config['binaries'].get('pkgconfig', 'non_existing_binary'), - silent=True) + pkgname = environment.cross_info.config['binaries']['pkgconfig'] + potential_pkgbin = ExternalProgram(pkgname, silent=True) if potential_pkgbin.found(): # FIXME, we should store all pkg-configs in ExternalPrograms. # However that is too destabilizing a change to do just before release. @@ -402,24 +402,28 @@ class WxDependency(Dependency): return self.is_found class ExternalProgram: - windows_exts = ('exe', 'com', 'bat') + windows_exts = ('exe', 'msc', 'com', 'bat') - def __init__(self, name, fullpath=None, silent=False, search_dir=None): + def __init__(self, name, command=None, silent=False, search_dir=None): self.name = name - if fullpath is not None: - if not isinstance(fullpath, list): - self.fullpath = [fullpath] + if command is not None: + if not isinstance(command, list): + self.command = [command] else: - self.fullpath = fullpath + self.command = command else: - self.fullpath = self._search(name, search_dir) + self.command = self._search(name, search_dir) if not silent: if self.found(): mlog.log('Program', mlog.bold(name), 'found:', mlog.green('YES'), - '(%s)' % ' '.join(self.fullpath)) + '(%s)' % ' '.join(self.command)) else: mlog.log('Program', mlog.bold(name), 'found:', mlog.red('NO')) + def __repr__(self): + r = '<{} {!r} -> {!r}>' + return r.format(self.__class__.__name__, self.name, self.command) + @staticmethod def _shebang_to_cmd(script): """ @@ -473,34 +477,63 @@ class ExternalProgram: return self._shebang_to_cmd(trial) def _search(self, name, search_dir): + ''' + Search in the specified dir for the specified executable by name + and if not found search in PATH + ''' commands = self._search_dir(name, search_dir) if commands: return commands # Do a standard search in PATH - fullpath = shutil.which(name) - if fullpath or not mesonlib.is_windows(): + command = shutil.which(name) + if not mesonlib.is_windows(): # On UNIX-like platforms, the standard PATH search is enough - return [fullpath] - # On Windows, if name is an absolute path, we need the extension too - for ext in self.windows_exts: - fullpath = '{}.{}'.format(name, ext) - if os.path.exists(fullpath): - return [fullpath] - # On Windows, interpreted scripts must have an extension otherwise they - # cannot be found by a standard PATH search. So we do a custom search - # where we manually search for a script with a shebang in PATH. - search_dirs = os.environ.get('PATH', '').split(';') - for search_dir in search_dirs: - commands = self._search_dir(name, search_dir) + return [command] + # HERE BEGINS THE TERROR OF WINDOWS + if command: + # On Windows, even if the PATH search returned a full path, we can't be + # sure that it can be run directly if it's not a native executable. + # For instance, interpreted scripts sometimes need to be run explicitly + # with an interpreter if the file association is not done properly. + name_ext = os.path.splitext(command)[1] + if name_ext[1:].lower() in self.windows_exts: + # Good, it can be directly executed + return [command] + # Try to extract the interpreter from the shebang + commands = self._shebang_to_cmd(command) if commands: return commands + else: + # Maybe the name is an absolute path to a native Windows + # executable, but without the extension. This is technically wrong, + # but many people do it because it works in the MinGW shell. + if os.path.isabs(name): + for ext in self.windows_exts: + command = '{}.{}'.format(name, ext) + if os.path.exists(command): + return [command] + # On Windows, interpreted scripts must have an extension otherwise they + # cannot be found by a standard PATH search. So we do a custom search + # where we manually search for a script with a shebang in PATH. + search_dirs = os.environ.get('PATH', '').split(';') + for search_dir in search_dirs: + commands = self._search_dir(name, search_dir) + if commands: + return commands return [None] def found(self): - return self.fullpath[0] is not None + return self.command[0] is not None def get_command(self): - return self.fullpath[:] + return self.command[:] + + def get_path(self): + # Assume that the last element is the full path to the script + # If it's not a script, this will be an array of length 1 + if self.found(): + return self.command[-1] + return None def get_name(self): return self.name @@ -531,6 +564,9 @@ class ExternalLibrary(Dependency): def found(self): return self.is_found + def get_name(self): + return self.name + def get_link_args(self): return self.link_args @@ -589,6 +625,9 @@ class BoostDependency(Dependency): mlog.log('Dependency Boost (%s) found:' % module_str, mlog.green('YES'), info) else: mlog.log("Dependency Boost (%s) found:" % module_str, mlog.red('NO')) + if 'cpp' not in self.environment.coredata.compilers: + raise DependencyException('Tried to use Boost but a C++ compiler is not defined.') + self.cpp_compiler = self.environment.coredata.compilers['cpp'] def detect_win_root(self): globtext = 'c:\\local\\boost_*' @@ -721,8 +760,19 @@ class BoostDependency(Dependency): args.append('-L' + os.path.join(self.boost_root, 'lib')) for module in self.requested_modules: module = BoostDependency.name2lib.get(module, module) - if module in self.lib_modules or module in self.lib_modules_mt: - linkcmd = '-lboost_' + module + libname = 'boost_' + module + # The compiler's library detector is the most reliable so use that first. + default_detect = self.cpp_compiler.find_library(libname, self.environment, []) + if default_detect is not None: + if module == 'unit_testing_framework': + emon_args = self.cpp_compiler.find_library('boost_test_exec_monitor') + else: + emon_args = None + args += default_detect + if emon_args is not None: + args += emon_args + elif module in self.lib_modules or module in self.lib_modules_mt: + linkcmd = '-l' + libname args.append(linkcmd) # FIXME a hack, but Boost's testing framework has a lot of # different options and it's hard to determine what to do @@ -980,7 +1030,7 @@ class QtBaseDependency(Dependency): if not self.qmake.found(): continue # Check that the qmake is for qt5 - pc, stdo = Popen_safe(self.qmake.fullpath + ['-v'])[0:2] + pc, stdo = Popen_safe(self.qmake.get_command() + ['-v'])[0:2] if pc.returncode != 0: continue if not 'Qt version ' + self.qtver in stdo: @@ -993,7 +1043,7 @@ class QtBaseDependency(Dependency): return self.version = re.search(self.qtver + '(\.\d+)+', stdo).group(0) # Query library path, header path, and binary path - stdo = Popen_safe(self.qmake.fullpath + ['-query'])[1] + stdo = Popen_safe(self.qmake.get_command() + ['-query'])[1] qvars = {} for line in stdo.split('\n'): line = line.strip() diff --git a/mesonbuild/environment.py b/mesonbuild/environment.py index e143b0b..5217626 100644 --- a/mesonbuild/environment.py +++ b/mesonbuild/environment.py @@ -19,6 +19,7 @@ from . import mlog from .compilers import * from .mesonlib import EnvironmentException, Popen_safe import configparser +import shlex import shutil build_filename = 'meson.build' @@ -121,6 +122,18 @@ def detect_cpu_family(compilers): if trial.startswith('arm'): return 'arm' if trial in ('amd64', 'x64'): + trial = 'x86_64' + if trial == 'x86_64': + # On Linux (and maybe others) there can be any mixture of 32/64 bit + # code in the kernel, Python, system etc. The only reliable way + # to know is to check the compiler defines. + for c in compilers.values(): + try: + if c.has_define('__i386__'): + return 'x86' + except mesonlib.MesonException: + # Ignore compilers that do not support has_define. + pass return 'x86_64' # Add fixes here as bugs are reported. return trial @@ -131,6 +144,15 @@ def detect_cpu(compilers): else: trial = platform.machine().lower() if trial in ('amd64', 'x64'): + trial = 'x86_64' + if trial == 'x86_64': + # Same check as above for cpu_family + for c in compilers.values(): + try: + if c.has_define('__i386__'): + return 'i686' # All 64 bit cpus have at least this level of x86 support. + except mesonlib.MesonException: + pass return 'x86_64' # Add fixes here as bugs are reported. return trial @@ -239,6 +261,10 @@ class Environment: self.exe_suffix = '' self.object_suffix = 'o' self.win_libdir_layout = False + if 'STRIP' in os.environ: + self.native_strip_bin = shlex.split('STRIP') + else: + self.native_strip_bin = ['strip'] def is_cross_build(self): return self.cross_info is not None @@ -345,10 +371,13 @@ class Environment: # We ignore Cygwin for now, and treat it as a standard GCC return GCC_STANDARD - def detect_c_compiler(self, want_cross): - evar = 'CC' + def _get_compilers(self, lang, evar, want_cross): + ''' + The list of compilers is detected in the exact same way for + C, C++, ObjC, ObjC++, Fortran so consolidate it here. + ''' if self.is_cross_build() and want_cross: - compilers = [self.cross_info.config['binaries']['c']] + compilers = [mesonlib.stringlistify(self.cross_info.config['binaries'][lang])] ccache = [] is_cross = True if self.cross_info.need_exe_wrapper(): @@ -356,122 +385,122 @@ class Environment: else: exe_wrap = [] elif evar in os.environ: - compilers = os.environ[evar].split() + compilers = [shlex.split(os.environ[evar])] ccache = [] is_cross = False exe_wrap = None else: - compilers = self.default_c + compilers = getattr(self, 'default_' + lang) ccache = self.detect_ccache() is_cross = False exe_wrap = None + return compilers, ccache, is_cross, exe_wrap + + def _handle_compiler_exceptions(self, exceptions, compilers): + errmsg = 'Unknown compiler(s): ' + str(compilers) + if exceptions: + errmsg += '\nThe follow exceptions were encountered:' + for (c, e) in exceptions.items(): + errmsg += '\nRunning "{0}" gave "{1}"'.format(c, e) + raise EnvironmentException(errmsg) + + def _detect_c_or_cpp_compiler(self, lang, evar, want_cross): popen_exceptions = {} + compilers, ccache, is_cross, exe_wrap = self._get_compilers(lang, evar, want_cross) for compiler in compilers: + if isinstance(compiler, str): + compiler = [compiler] try: - basename = os.path.basename(compiler).lower() - if basename == 'cl' or basename == 'cl.exe': + if 'cl' in compiler or 'cl.exe' in compiler: arg = '/?' else: arg = '--version' - p, out, err = Popen_safe([compiler, arg]) + p, out, err = Popen_safe(compiler + [arg]) except OSError as e: - popen_exceptions[' '.join([compiler, arg])] = e + popen_exceptions[' '.join(compiler + [arg])] = e continue version = search_version(out) if 'Free Software Foundation' in out: - defines = self.get_gnu_compiler_defines([compiler]) + defines = self.get_gnu_compiler_defines(compiler) if not defines: popen_exceptions[compiler] = 'no pre-processor defines' continue gtype = self.get_gnu_compiler_type(defines) version = self.get_gnu_version_from_defines(defines) - return GnuCCompiler(ccache + [compiler], version, gtype, is_cross, exe_wrap, defines) + cls = GnuCCompiler if lang == 'c' else GnuCPPCompiler + return cls(ccache + compiler, version, gtype, is_cross, exe_wrap, defines) if 'clang' in out: if 'Apple' in out or for_darwin(want_cross, self): cltype = CLANG_OSX else: cltype = CLANG_STANDARD - return ClangCCompiler(ccache + [compiler], version, cltype, is_cross, exe_wrap) + cls = ClangCCompiler if lang == 'c' else ClangCPPCompiler + return cls(ccache + compiler, version, cltype, is_cross, exe_wrap) if 'Microsoft' in out or 'Microsoft' in err: # Visual Studio prints version number to stderr but # everything else to stdout. Why? Lord only knows. version = search_version(err) - return VisualStudioCCompiler([compiler], version, is_cross, exe_wrap) + cls = VisualStudioCCompiler if lang == 'c' else VisualStudioCPPCompiler + return cls(compiler, version, is_cross, exe_wrap) if '(ICC)' in out: # TODO: add microsoft add check OSX inteltype = ICC_STANDARD - return IntelCCompiler(ccache + [compiler], version, inteltype, is_cross, exe_wrap) - errmsg = 'Unknown compiler(s): "' + ', '.join(compilers) + '"' - if popen_exceptions: - errmsg += '\nThe follow exceptions were encountered:' - for (c, e) in popen_exceptions.items(): - errmsg += '\nRunning "{0}" gave "{1}"'.format(c, e) - raise EnvironmentException(errmsg) + cls = IntelCCompiler if lang == 'c' else IntelCPPCompiler + return cls(ccache + compiler, version, inteltype, is_cross, exe_wrap) + self._handle_compiler_exceptions(popen_exceptions, compilers) + + def detect_c_compiler(self, want_cross): + return self._detect_c_or_cpp_compiler('c', 'CC', want_cross) + + def detect_cpp_compiler(self, want_cross): + return self._detect_c_or_cpp_compiler('cpp', 'CXX', want_cross) def detect_fortran_compiler(self, want_cross): - evar = 'FC' - if self.is_cross_build() and want_cross: - compilers = [self.cross_info['fortran']] - is_cross = True - if self.cross_info.need_exe_wrapper(): - exe_wrap = self.cross_info.get('exe_wrapper', None) - else: - exe_wrap = [] - elif evar in os.environ: - compilers = os.environ[evar].split() - is_cross = False - exe_wrap = None - else: - compilers = self.default_fortran - is_cross = False - exe_wrap = None popen_exceptions = {} + compilers, ccache, is_cross, exe_wrap = self._get_compilers('fortran', 'FC', want_cross) for compiler in compilers: + if isinstance(compiler, str): + compiler = [compiler] for arg in ['--version', '-V']: try: - p, out, err = Popen_safe([compiler, arg]) + p, out, err = Popen_safe(compiler + [arg]) except OSError as e: - popen_exceptions[' '.join([compiler, arg])] = e + popen_exceptions[' '.join(compiler + [arg])] = e continue version = search_version(out) if 'GNU Fortran' in out: - defines = self.get_gnu_compiler_defines([compiler]) + defines = self.get_gnu_compiler_defines(compiler) if not defines: popen_exceptions[compiler] = 'no pre-processor defines' continue gtype = self.get_gnu_compiler_type(defines) version = self.get_gnu_version_from_defines(defines) - return GnuFortranCompiler([compiler], version, gtype, is_cross, exe_wrap, defines) + return GnuFortranCompiler(compiler, version, gtype, is_cross, exe_wrap, defines) if 'G95' in out: - return G95FortranCompiler([compiler], version, is_cross, exe_wrap) + return G95FortranCompiler(compiler, version, is_cross, exe_wrap) if 'Sun Fortran' in err: version = search_version(err) - return SunFortranCompiler([compiler], version, is_cross, exe_wrap) + return SunFortranCompiler(compiler, version, is_cross, exe_wrap) if 'ifort (IFORT)' in out: - return IntelFortranCompiler([compiler], version, is_cross, exe_wrap) + return IntelFortranCompiler(compiler, version, is_cross, exe_wrap) if 'PathScale EKOPath(tm)' in err: - return PathScaleFortranCompiler([compiler], version, is_cross, exe_wrap) + return PathScaleFortranCompiler(compiler, version, is_cross, exe_wrap) if 'PGI Compilers' in out: - return PGIFortranCompiler([compiler], version, is_cross, exe_wrap) + return PGIFortranCompiler(compiler, version, is_cross, exe_wrap) if 'Open64 Compiler Suite' in err: - return Open64FortranCompiler([compiler], version, is_cross, exe_wrap) + return Open64FortranCompiler(compiler, version, is_cross, exe_wrap) if 'NAG Fortran' in err: - return NAGFortranCompiler([compiler], version, is_cross, exe_wrap) - errmsg = 'Unknown compiler(s): "' + ', '.join(compilers) + '"' - if popen_exceptions: - errmsg += '\nThe follow exceptions were encountered:' - for (c, e) in popen_exceptions.items(): - errmsg += '\nRunning "{0}" gave "{1}"'.format(c, e) - raise EnvironmentException(errmsg) + return NAGFortranCompiler(compiler, version, is_cross, exe_wrap) + self._handle_compiler_exceptions(popen_exceptions, compilers) def get_scratch_dir(self): return self.scratch_dir @@ -480,115 +509,57 @@ class Environment: path = os.path.split(__file__)[0] return os.path.join(path, 'depfixer.py') - def detect_cpp_compiler(self, want_cross): - evar = 'CXX' - if self.is_cross_build() and want_cross: - compilers = [self.cross_info.config['binaries']['cpp']] - ccache = [] - is_cross = True - if self.cross_info.need_exe_wrapper(): - exe_wrap = self.cross_info.config['binaries'].get('exe_wrapper', None) - else: - exe_wrap = [] - elif evar in os.environ: - compilers = os.environ[evar].split() - ccache = [] - is_cross = False - exe_wrap = None - else: - compilers = self.default_cpp - ccache = self.detect_ccache() - is_cross = False - exe_wrap = None + def detect_objc_compiler(self, want_cross): popen_exceptions = {} + compilers, ccache, is_cross, exe_wrap = self._get_compilers('objc', 'OBJC', want_cross) for compiler in compilers: - basename = os.path.basename(compiler).lower() - if basename == 'cl' or basename == 'cl.exe': - arg = '/?' - else: - arg = '--version' + if isinstance(compiler, str): + compiler = [compiler] + arg = ['--version'] try: - p, out, err = Popen_safe([compiler, arg]) + p, out, err = Popen_safe(compiler + arg) except OSError as e: - popen_exceptions[' '.join([compiler, arg])] = e - continue + popen_exceptions[' '.join(compiler + arg)] = e version = search_version(out) if 'Free Software Foundation' in out: - defines = self.get_gnu_compiler_defines([compiler]) + defines = self.get_gnu_compiler_defines(compiler) if not defines: popen_exceptions[compiler] = 'no pre-processor defines' continue gtype = self.get_gnu_compiler_type(defines) version = self.get_gnu_version_from_defines(defines) - return GnuCPPCompiler(ccache + [compiler], version, gtype, is_cross, exe_wrap, defines) - if 'clang' in out: - if 'Apple' in out: - cltype = CLANG_OSX - else: - cltype = CLANG_STANDARD - return ClangCPPCompiler(ccache + [compiler], version, cltype, is_cross, exe_wrap) - if 'Microsoft' in out or 'Microsoft' in err: - version = search_version(err) - return VisualStudioCPPCompiler([compiler], version, is_cross, exe_wrap) - if '(ICC)' in out: - # TODO: add microsoft add check OSX - inteltype = ICC_STANDARD - return IntelCPPCompiler(ccache + [compiler], version, inteltype, is_cross, exe_wrap) - errmsg = 'Unknown compiler(s): "' + ', '.join(compilers) + '"' - if popen_exceptions: - errmsg += '\nThe follow exceptions were encountered:' - for (c, e) in popen_exceptions.items(): - errmsg += '\nRunning "{0}" gave "{1}"'.format(c, e) - raise EnvironmentException(errmsg) - - def detect_objc_compiler(self, want_cross): - if self.is_cross_build() and want_cross: - exelist = [self.cross_info['objc']] - is_cross = True - if self.cross_info.need_exe_wrapper(): - exe_wrap = self.cross_info.get('exe_wrapper', None) - else: - exe_wrap = [] - else: - exelist = self.get_objc_compiler_exelist() - is_cross = False - exe_wrap = None - try: - p, out, err = Popen_safe(exelist + ['--version']) - except OSError: - raise EnvironmentException('Could not execute ObjC compiler "%s"' % ' '.join(exelist)) - version = search_version(out) - if 'Free Software Foundation' in out: - defines = self.get_gnu_compiler_defines(exelist) - version = self.get_gnu_version_from_defines(defines) - return GnuObjCCompiler(exelist, version, is_cross, exe_wrap, defines) - if out.startswith('Apple LLVM'): - return ClangObjCCompiler(exelist, version, CLANG_OSX, is_cross, exe_wrap) - raise EnvironmentException('Unknown compiler "' + ' '.join(exelist) + '"') + return GnuObjCCompiler(ccache + compiler, version, gtype, is_cross, exe_wrap, defines) + if out.startswith('Apple LLVM'): + return ClangObjCCompiler(ccache + compiler, version, CLANG_OSX, is_cross, exe_wrap) + if out.startswith('clang'): + return ClangObjCCompiler(ccache + compiler, version, CLANG_STANDARD, is_cross, exe_wrap) + self._handle_compiler_exceptions(popen_exceptions, compilers) def detect_objcpp_compiler(self, want_cross): - if self.is_cross_build() and want_cross: - exelist = [self.cross_info['objcpp']] - is_cross = True - if self.cross_info.need_exe_wrapper(): - exe_wrap = self.cross_info.get('exe_wrapper', None) - else: - exe_wrap = [] - else: - exelist = self.get_objcpp_compiler_exelist() - is_cross = False - exe_wrap = None - try: - p, out, err = Popen_safe(exelist + ['--version']) - except OSError: - raise EnvironmentException('Could not execute ObjC++ compiler "%s"' % ' '.join(exelist)) - version = search_version(out) - if 'Free Software Foundation' in out: - defines = self.get_gnu_compiler_defines(exelist) - return GnuObjCPPCompiler(exelist, version, is_cross, exe_wrap, defines) - if out.startswith('Apple LLVM'): - return ClangObjCPPCompiler(exelist, version, CLANG_OSX, is_cross, exe_wrap) - raise EnvironmentException('Unknown compiler "' + ' '.join(exelist) + '"') + popen_exceptions = {} + compilers, ccache, is_cross, exe_wrap = self._get_compilers('objcpp', 'OBJCXX', want_cross) + for compiler in compilers: + if isinstance(compiler, str): + compiler = [compiler] + arg = ['--version'] + try: + p, out, err = Popen_safe(compiler + arg) + except OSError as e: + popen_exceptions[' '.join(compiler + arg)] = e + version = search_version(out) + if 'Free Software Foundation' in out: + defines = self.get_gnu_compiler_defines(compiler) + if not defines: + popen_exceptions[compiler] = 'no pre-processor defines' + continue + gtype = self.get_gnu_compiler_type(defines) + version = self.get_gnu_version_from_defines(defines) + return GnuObjCPPCompiler(ccache + compiler, version, gtype, is_cross, exe_wrap, defines) + if out.startswith('Apple LLVM'): + return ClangObjCPPCompiler(ccache + compiler, version, CLANG_OSX, is_cross, exe_wrap) + if out.startswith('clang'): + return ClangObjCPPCompiler(ccache + compiler, version, CLANG_STANDARD, is_cross, exe_wrap) + self._handle_compiler_exceptions(popen_exceptions, compilers) def detect_java_compiler(self): exelist = ['javac'] @@ -641,9 +612,9 @@ class Environment: # environment variable because LDC has a much more # up to date language version at time (2016). if 'DC' in os.environ: - exelist = os.environ['DC'].split() + exelist = shlex.split(os.environ['DC']) elif self.is_cross_build() and want_cross: - exelist = [self.cross_info.config['binaries']['d']] + exelist = mesonlib.stringlistify(self.cross_info.config['binaries']['d']) is_cross = True elif shutil.which("ldc2"): exelist = ['ldc2'] @@ -683,30 +654,31 @@ class Environment: def detect_static_linker(self, compiler): if compiler.is_cross: linker = self.cross_info.config['binaries']['ar'] + if isinstance(linker, str): + linker = [linker] else: evar = 'AR' if evar in os.environ: - linker = os.environ[evar].strip() + linker = shlex.split(os.environ[evar]) elif isinstance(compiler, VisualStudioCCompiler): - linker = self.vs_static_linker + linker = [self.vs_static_linker] else: - linker = self.default_static_linker - basename = os.path.basename(linker).lower() - if basename == 'lib' or basename == 'lib.exe': + linker = [self.default_static_linker] + if 'lib' in linker or 'lib.exe' in linker: arg = '/?' else: arg = '--version' try: - p, out, err = Popen_safe([linker, arg]) + p, out, err = Popen_safe(linker + [arg]) except OSError: - raise EnvironmentException('Could not execute static linker "%s".' % linker) + raise EnvironmentException('Could not execute static linker "%s".' % ' '.join(linker)) if '/OUT:' in out or '/OUT:' in err: - return VisualStudioLinker([linker]) + return VisualStudioLinker(linker) if p.returncode == 0: - return ArLinker([linker]) + return ArLinker(linker) if p.returncode == 1 and err.startswith('usage'): # OSX - return ArLinker([linker]) - raise EnvironmentException('Unknown static linker "%s"' % linker) + return ArLinker(linker) + raise EnvironmentException('Unknown static linker "%s"' % ' '.join(linker)) def detect_ccache(self): try: @@ -719,20 +691,6 @@ class Environment: cmdlist = [] return cmdlist - def get_objc_compiler_exelist(self): - ccachelist = self.detect_ccache() - evar = 'OBJCC' - if evar in os.environ: - return os.environ[evar].split() - return ccachelist + self.default_objc - - def get_objcpp_compiler_exelist(self): - ccachelist = self.detect_ccache() - evar = 'OBJCXX' - if evar in os.environ: - return os.environ[evar].split() - return ccachelist + self.default_objcpp - def get_source_dir(self): return self.source_dir diff --git a/mesonbuild/interpreter.py b/mesonbuild/interpreter.py index 4466f22..07b5c40 100644 --- a/mesonbuild/interpreter.py +++ b/mesonbuild/interpreter.py @@ -134,6 +134,10 @@ class EnvironmentVariablesHolder(MutableInterpreterObject): 'prepend': self.prepend_method, }) + def __repr__(self): + repr_str = "<{0}: {1}>" + return repr_str.format(self.__class__.__name__, self.held_object.envvars) + @stringArgs def add_var(self, method, args, kwargs): if not isinstance(kwargs.get("separator", ""), str): @@ -285,16 +289,16 @@ class ExternalProgramHolder(InterpreterObject): return self.found() def path_method(self, args, kwargs): - return self.get_command() + return self.held_object.get_path() def found(self): return self.held_object.found() def get_command(self): - return self.held_object.fullpath + return self.held_object.get_command() def get_name(self): - return self.held_object.name + return self.held_object.get_name() class ExternalLibraryHolder(InterpreterObject): def __init__(self, el): @@ -308,9 +312,6 @@ class ExternalLibraryHolder(InterpreterObject): def found_method(self, args, kwargs): return self.found() - def get_filename(self): - return self.held_object.fullpath - def get_name(self): return self.held_object.name @@ -1424,7 +1425,8 @@ class Interpreter(InterpreterBase): elif isinstance(cmd, str): cmd = [cmd] else: - raise InterpreterException('First argument is of incorrect type.') + raise InterpreterException('First argument should be find_program() ' + 'or string, not {!r}'.format(cmd)) expanded_args = [] for a in mesonlib.flatten(cargs): if isinstance(a, str): @@ -1759,7 +1761,6 @@ class Interpreter(InterpreterBase): break self.coredata.base_options[optname] = oobj - @stringArgs def func_find_program(self, node, args, kwargs): if len(args) == 0: raise InterpreterException('No program name specified.') @@ -1769,8 +1770,21 @@ class Interpreter(InterpreterBase): # Search for scripts relative to current subdir. # Do not cache found programs because find_program('foobar') # might give different results when run from different source dirs. - search_dir = os.path.join(self.environment.get_source_dir(), self.subdir) + source_dir = os.path.join(self.environment.get_source_dir(), self.subdir) for exename in args: + if isinstance(exename, mesonlib.File): + if exename.is_built: + search_dir = os.path.join(self.environment.get_build_dir(), + exename.subdir) + else: + search_dir = os.path.join(self.environment.get_source_dir(), + exename.subdir) + exename = exename.fname + elif isinstance(exename, str): + search_dir = source_dir + else: + raise InvalidArguments('find_program only accepts strings and ' + 'files, not {!r}'.format(exename)) extprog = dependencies.ExternalProgram(exename, search_dir=search_dir) progobj = ExternalProgramHolder(extprog) if progobj.found(): @@ -2046,7 +2060,7 @@ requirements use the version keyword argument instead.''') self.add_test(node, args, kwargs, True) def unpack_env_kwarg(self, kwargs): - envlist = kwargs.get('env', []) + envlist = kwargs.get('env', EnvironmentVariablesHolder()) if isinstance(envlist, EnvironmentVariablesHolder): env = envlist.held_object else: @@ -2132,6 +2146,8 @@ requirements use the version keyword argument instead.''') raise InvalidArguments('Must not go into subprojects dir with subdir(), use subproject() instead.') prev_subdir = self.subdir subdir = os.path.join(prev_subdir, args[0]) + if os.path.isabs(subdir): + raise InvalidArguments('Subdir argument must be a relative path.') if subdir in self.visited_subdirs: raise InvalidArguments('Tried to enter directory "%s", which has already been visited.' % subdir) @@ -2212,12 +2228,28 @@ requirements use the version keyword argument instead.''') raise InterpreterException("configure_file takes only keyword arguments.") if 'output' not in kwargs: raise InterpreterException('Required keyword argument "output" not defined.') - inputfile = kwargs.get('input', None) + if 'configuration' in kwargs and 'command' in kwargs: + raise InterpreterException('Must not specify both "configuration" ' + 'and "command" keyword arguments since ' + 'they are mutually exclusive.') + # Validate input + inputfile = None + if 'input' in kwargs: + inputfile = kwargs['input'] + if isinstance(inputfile, list): + if len(inputfile) != 1: + m = "Keyword argument 'input' requires exactly one file" + raise InterpreterException(m) + inputfile = inputfile[0] + if not isinstance(inputfile, (str, mesonlib.File)): + raise InterpreterException('Input must be a string or a file') + ifile_abs = os.path.join(self.environment.source_dir, self.subdir, inputfile) + elif 'command' in kwargs: + raise InterpreterException('Required keyword argument \'input\' missing') + # Validate output output = kwargs['output'] - if not isinstance(inputfile, (str, type(None))): - raise InterpreterException('Input must be a string.') if not isinstance(output, str): - raise InterpreterException('Output must be a string.') + raise InterpreterException('Output file name must be a string') if os.path.split(output)[0] != '': raise InterpreterException('Output file name must not contain a subdirectory.') (ofile_path, ofile_fname) = os.path.split(os.path.join(self.subdir, output)) @@ -2226,6 +2258,7 @@ requirements use the version keyword argument instead.''') conf = kwargs['configuration'] if not isinstance(conf, ConfigurationDataHolder): raise InterpreterException('Argument "configuration" is not of type configuration_data') + mlog.log('Configuring', mlog.bold(output), 'using configuration') if inputfile is not None: # Normalize the path of the conffile to avoid duplicates # This is especially important to convert '/' to '\' on Windows @@ -2233,15 +2266,19 @@ requirements use the version keyword argument instead.''') if conffile not in self.build_def_files: self.build_def_files.append(conffile) os.makedirs(os.path.join(self.environment.build_dir, self.subdir), exist_ok=True) - ifile_abs = os.path.join(self.environment.source_dir, self.subdir, inputfile) mesonlib.do_conf_file(ifile_abs, ofile_abs, conf.held_object) else: mesonlib.dump_conf_header(ofile_abs, conf.held_object) conf.mark_used() elif 'command' in kwargs: - if 'input' not in kwargs: - raise InterpreterException('Required keyword input missing.') - res = self.func_run_command(node, kwargs['command'], {}) + # We use absolute paths for input and output here because the cwd + # that the command is run from is 'unspecified', so it could change. + # Currently it's builddir/subdir for in_builddir else srcdir/subdir. + values = mesonlib.get_filenames_templates_dict([ifile_abs], [ofile_abs]) + # Substitute @INPUT@, @OUTPUT@, etc here. + cmd = mesonlib.substitute_values(kwargs['command'], values) + mlog.log('Configuring', mlog.bold(output), 'with command') + res = self.func_run_command(node, cmd, {'in_builddir': True}) if res.returncode != 0: raise InterpreterException('Running configure command failed.\n%s\n%s' % (res.stdout, res.stderr)) @@ -2255,8 +2292,27 @@ requirements use the version keyword argument instead.''') @stringArgs def func_include_directories(self, node, args, kwargs): - absbase = os.path.join(self.environment.get_source_dir(), self.subdir) + src_root = self.environment.get_source_dir() + absbase = os.path.join(src_root, self.subdir) for a in args: + if a.startswith(src_root): + raise InvalidArguments('''Tried to form an absolute path to a source dir. You should not do that but use +relative paths instead. + +To get include path to any directory relative to the current dir do + +incdir = include_directories(dirname) + +After this incdir will contain both the current source dir as well as the +corresponding build dir. It can then be used in any subdirectory and +Meson will take care of all the busywork to make paths work. + +Dirname can even be '.' to mark the current directory. Though you should +remember that the current source and build directories are always +put in the include directories by default so you only need to do +include_directories('.') if you intend to use the result in a +different subdirectory. +''') absdir = os.path.join(absbase, a) if not os.path.isdir(absdir): raise InvalidArguments('Include dir %s does not exist.' % a) diff --git a/mesonbuild/mesonlib.py b/mesonbuild/mesonlib.py index f0b20e1..c7368d5 100644 --- a/mesonbuild/mesonlib.py +++ b/mesonbuild/mesonlib.py @@ -521,3 +521,154 @@ def commonpath(paths): new = os.path.join(*new) common = pathlib.PurePath(new) return str(common) + +def iter_regexin_iter(regexiter, initer): + ''' + Takes each regular expression in @regexiter and tries to search for it in + every item in @initer. If there is a match, returns that match. + Else returns False. + ''' + for regex in regexiter: + for ii in initer: + if not isinstance(ii, str): + continue + match = re.search(regex, ii) + if match: + return match.group() + return False + +def _substitute_values_check_errors(command, values): + # Error checking + inregex = ('@INPUT([0-9]+)?@', '@PLAINNAME@', '@BASENAME@') + outregex = ('@OUTPUT([0-9]+)?@', '@OUTDIR@') + if '@INPUT@' not in values: + # Error out if any input-derived templates are present in the command + match = iter_regexin_iter(inregex, command) + if match: + m = 'Command cannot have {!r}, since no input files were specified' + raise MesonException(m.format(match)) + else: + if len(values['@INPUT@']) > 1: + # Error out if @PLAINNAME@ or @BASENAME@ is present in the command + match = iter_regexin_iter(inregex[1:], command) + if match: + raise MesonException('Command cannot have {!r} when there is ' + 'more than one input file'.format(match)) + # Error out if an invalid @INPUTnn@ template was specified + for each in command: + if not isinstance(each, str): + continue + match = re.search(inregex[0], each) + if match and match.group() not in values: + m = 'Command cannot have {!r} since there are only {!r} inputs' + raise MesonException(m.format(match.group(), len(values['@INPUT@']))) + if '@OUTPUT@' not in values: + # Error out if any output-derived templates are present in the command + match = iter_regexin_iter(outregex, command) + if match: + m = 'Command cannot have {!r} since there are no outputs' + raise MesonException(m.format(match)) + else: + # Error out if an invalid @OUTPUTnn@ template was specified + for each in command: + if not isinstance(each, str): + continue + match = re.search(outregex[0], each) + if match and match.group() not in values: + m = 'Command cannot have {!r} since there are only {!r} outputs' + raise MesonException(m.format(match.group(), len(values['@OUTPUT@']))) + +def substitute_values(command, values): + ''' + Substitute the template strings in the @values dict into the list of + strings @command and return a new list. For a full list of the templates, + see get_filenames_templates_dict() + + If multiple inputs/outputs are given in the @values dictionary, we + substitute @INPUT@ and @OUTPUT@ only if they are the entire string, not + just a part of it, and in that case we substitute *all* of them. + ''' + # Error checking + _substitute_values_check_errors(command, values) + # Substitution + outcmd = [] + for vv in command: + if not isinstance(vv, str): + outcmd.append(vv) + elif '@INPUT@' in vv: + inputs = values['@INPUT@'] + if vv == '@INPUT@': + outcmd += inputs + elif len(inputs) == 1: + outcmd.append(vv.replace('@INPUT@', inputs[0])) + else: + raise MesonException("Command has '@INPUT@' as part of a " + "string and more than one input file") + elif '@OUTPUT@' in vv: + outputs = values['@OUTPUT@'] + if vv == '@OUTPUT@': + outcmd += outputs + elif len(outputs) == 1: + outcmd.append(vv.replace('@OUTPUT@', outputs[0])) + else: + raise MesonException("Command has '@OUTPUT@' as part of a " + "string and more than one output file") + # Append values that are exactly a template string. + # This is faster than a string replace. + elif vv in values: + outcmd.append(values[vv]) + # Substitute everything else with replacement + else: + for key, value in values.items(): + if key in ('@INPUT@', '@OUTPUT@'): + # Already done above + continue + vv = vv.replace(key, value) + outcmd.append(vv) + return outcmd + +def get_filenames_templates_dict(inputs, outputs): + ''' + Create a dictionary with template strings as keys and values as values for + the following templates: + + @INPUT@ - the full path to one or more input files, from @inputs + @OUTPUT@ - the full path to one or more output files, from @outputs + @OUTDIR@ - the full path to the directory containing the output files + + If there is only one input file, the following keys are also created: + + @PLAINNAME@ - the filename of the input file + @BASENAME@ - the filename of the input file with the extension removed + + If there is more than one input file, the following keys are also created: + + @INPUT0@, @INPUT1@, ... one for each input file + + If there is more than one output file, the following keys are also created: + + @OUTPUT0@, @OUTPUT1@, ... one for each output file + ''' + values = {} + # Gather values derived from the input + if inputs: + # We want to substitute all the inputs. + values['@INPUT@'] = inputs + for (ii, vv) in enumerate(inputs): + # Write out @INPUT0@, @INPUT1@, ... + values['@INPUT{}@'.format(ii)] = vv + if len(inputs) == 1: + # Just one value, substitute @PLAINNAME@ and @BASENAME@ + values['@PLAINNAME@'] = plain = os.path.split(inputs[0])[1] + values['@BASENAME@'] = os.path.splitext(plain)[0] + if outputs: + # Gather values derived from the outputs, similar to above. + values['@OUTPUT@'] = outputs + for (ii, vv) in enumerate(outputs): + values['@OUTPUT{}@'.format(ii)] = vv + # Outdir should be the same for all outputs + values['@OUTDIR@'] = os.path.split(outputs[0])[0] + # Many external programs fail on empty arguments. + if values['@OUTDIR@'] == '': + values['@OUTDIR@'] = '.' + return values diff --git a/mesonbuild/mintro.py b/mesonbuild/mintro.py index e30500f..6eab76e 100644 --- a/mesonbuild/mintro.py +++ b/mesonbuild/mintro.py @@ -23,6 +23,7 @@ import json, pickle from . import coredata, build import argparse import sys, os +import pathlib parser = argparse.ArgumentParser() parser.add_argument('--targets', action='store_true', dest='list_targets', default=False, @@ -56,7 +57,9 @@ def determine_installed_path(target, installdata): fname = i[0] outdir = i[1] outname = os.path.join(installdata.prefix, outdir, os.path.split(fname)[-1]) - return outname + # Normalize the path by using os.path.sep consistently, etc. + # Does not change the effective path. + return str(pathlib.PurePath(outname)) def list_installed(installdata): @@ -111,23 +114,11 @@ def list_target_files(target_name, coredata, builddata): print(json.dumps(sources)) def list_buildoptions(coredata, builddata): - buildtype = {'choices': ['plain', 'debug', 'debugoptimized', 'release', 'minsize'], - 'type': 'combo', - 'value': coredata.get_builtin_option('buildtype'), - 'description': 'Build type', - 'name': 'type'} - strip = {'value': coredata.get_builtin_option('strip'), - 'type': 'boolean', - 'description': 'Strip on install', - 'name': 'strip'} - unity = {'value': coredata.get_builtin_option('unity'), - 'type': 'boolean', - 'description': 'Unity build', - 'name': 'unity'} - optlist = [buildtype, strip, unity] + optlist = [] add_keys(optlist, coredata.user_options) add_keys(optlist, coredata.compiler_options) add_keys(optlist, coredata.base_options) + add_keys(optlist, coredata.builtins) print(json.dumps(optlist)) def add_keys(optlist, options): diff --git a/mesonbuild/mlog.py b/mesonbuild/mlog.py index a4f93d0..843f366 100644 --- a/mesonbuild/mlog.py +++ b/mesonbuild/mlog.py @@ -12,7 +12,7 @@ # See the License for the specific language governing permissions and # limitations under the License. -import sys, os, platform +import sys, os, platform, io """This is (mostly) a standalone module used to write logging information about Meson runs. Some output goes to screen, @@ -25,7 +25,7 @@ log_file = None def initialize(logdir): global log_dir, log_file log_dir = logdir - log_file = open(os.path.join(logdir, 'meson-log.txt'), 'w') + log_file = open(os.path.join(logdir, 'meson-log.txt'), 'w', encoding='utf8') def shutdown(): global log_file @@ -70,6 +70,17 @@ def process_markup(args, keep): arr.append(str(arg)) return arr +def force_print(*args, **kwargs): + # _Something_ is going to get printed. + try: + print(*args, **kwargs) + except UnicodeEncodeError: + iostr = io.StringIO() + kwargs['file'] = iostr + print(*args, **kwargs) + cleaned = iostr.getvalue().encode('ascii', 'replace').decode('ascii') + print(cleaned) + def debug(*args, **kwargs): arr = process_markup(args, False) if log_file is not None: @@ -83,7 +94,7 @@ def log(*args, **kwargs): log_file.flush() if colorize_console: arr = process_markup(args, True) - print(*arr, **kwargs) + force_print(*arr, **kwargs) def warning(*args, **kwargs): log(yellow('WARNING:'), *args, **kwargs) diff --git a/mesonbuild/modules/gnome.py b/mesonbuild/modules/gnome.py index 2a54f3a..8cf89e1 100644 --- a/mesonbuild/modules/gnome.py +++ b/mesonbuild/modules/gnome.py @@ -37,7 +37,7 @@ from . import ExtensionModule # # https://github.com/ninja-build/ninja/issues/1184 # https://bugzilla.gnome.org/show_bug.cgi?id=774368 -gresource_dep_needed_version = '>= 2.52.0' +gresource_dep_needed_version = '>= 2.51.1' native_glib_version = None girwarning_printed = False @@ -74,14 +74,15 @@ class GnomeModule(ExtensionModule): global gresource_warning_printed if not gresource_warning_printed: if not mesonlib.version_compare(self._get_native_glib_version(state), gresource_dep_needed_version): - mlog.warning('''GLib compiled dependencies do not work reliably with -the current version of GLib. See the following upstream issue:''', + mlog.warning('GLib compiled dependencies do not work reliably with \n' + 'the current version of GLib. See the following upstream issue:', mlog.bold('https://bugzilla.gnome.org/show_bug.cgi?id=774368')) gresource_warning_printed = True return [] def compile_resources(self, state, args, kwargs): self.__print_gresources_warning(state) + glib_version = self._get_native_glib_version(state) cmd = ['glib-compile-resources', '@INPUT@'] @@ -90,26 +91,48 @@ the current version of GLib. See the following upstream issue:''', source_dirs = [source_dirs] if len(args) < 2: - raise MesonException('Not enough arguments; The name of the resource and the path to the XML file are required') + raise MesonException('Not enough arguments; the name of the resource ' + 'and the path to the XML file are required') dependencies = kwargs.pop('dependencies', []) if not isinstance(dependencies, list): dependencies = [dependencies] - - glib_version = self._get_native_glib_version(state) - if not mesonlib.version_compare(glib_version, gresource_dep_needed_version): - if len(dependencies) > 0: - raise MesonException('''The "dependencies" argument of gnome.compile_resources() -can not be used with the current version of glib-compiled-resources, due to -<https://bugzilla.gnome.org/show_bug.cgi?id=774368>''') + # Validate dependencies + for (ii, dep) in enumerate(dependencies): + if hasattr(dep, 'held_object'): + dependencies[ii] = dep = dep.held_object + if not isinstance(dep, (mesonlib.File, build.CustomTarget)): + m = 'Unexpected dependency type {!r} for gnome.compile_resources() ' \ + '"dependencies" argument.\nPlease pass the return value of ' \ + 'custom_target() or configure_file()' + raise MesonException(m.format(dep)) + if isinstance(dep, build.CustomTarget): + if not mesonlib.version_compare(glib_version, gresource_dep_needed_version): + m = 'The "dependencies" argument of gnome.compile_resources() can not\n' \ + 'be used with the current version of glib-compile-resources due to\n' \ + '<https://bugzilla.gnome.org/show_bug.cgi?id=774368>' + raise MesonException(m) ifile = args[1] if isinstance(ifile, mesonlib.File): - ifile = os.path.join(ifile.subdir, ifile.fname) + # glib-compile-resources will be run inside the source dir, + # so we need either 'src_to_build' or the absolute path. + # Absolute path is the easiest choice. + if ifile.is_built: + ifile = os.path.join(state.environment.get_build_dir(), ifile.subdir, ifile.fname) + else: + ifile = os.path.join(ifile.subdir, ifile.fname) elif isinstance(ifile, str): ifile = os.path.join(state.subdir, ifile) + elif isinstance(ifile, (interpreter.CustomTargetHolder, + interpreter.GeneratedObjectsHolder)): + m = 'Resource xml files generated at build-time cannot be used ' \ + 'with gnome.compile_resources() because we need to scan ' \ + 'the xml for dependencies. Use configure_file() instead ' \ + 'to generate it at configure-time.' + raise MesonException(m) else: - raise RuntimeError('Unreachable code.') + raise MesonException('Invalid file argument: {!r}'.format(ifile)) depend_files, depends, subdirs = self._get_gresource_dependencies( state, ifile, source_dirs, dependencies) @@ -183,13 +206,6 @@ can not be used with the current version of glib-compiled-resources, due to return ModuleReturnValue(rv, rv) def _get_gresource_dependencies(self, state, input_file, source_dirs, dependencies): - for dep in dependencies: - if not isinstance(dep, interpreter.CustomTargetHolder) and not \ - isinstance(dep, mesonlib.File): - raise MesonException( - 'Unexpected dependency type for gnome.compile_resources() ' - '"dependencies" argument. Please pass the output of ' - 'custom_target() or configure_file().') cmd = ['glib-compile-resources', input_file, @@ -199,9 +215,10 @@ can not be used with the current version of glib-compiled-resources, due to cmd += ['--sourcedir', os.path.join(state.subdir, source_dir)] cmd += ['--sourcedir', state.subdir] # Current dir - pc, stdout = Popen_safe(cmd, cwd=state.environment.get_source_dir())[0:2] + pc, stdout, stderr = Popen_safe(cmd, cwd=state.environment.get_source_dir()) if pc.returncode != 0: - mlog.warning('glib-compile-resources has failed to get the dependencies for {}'.format(cmd[1])) + m = 'glib-compile-resources failed to get dependencies for {}:\n{}' + mlog.warning(m.format(cmd[1], stderr)) raise subprocess.CalledProcessError(pc.returncode, cmd) dep_files = stdout.split('\n')[:-1] @@ -354,11 +371,7 @@ can not be used with the current version of glib-compiled-resources, due to {'native': True}) pkgargs = self.gir_dep.get_compile_args() except Exception: - global girwarning_printed - if not girwarning_printed: - mlog.warning('gobject-introspection dependency was not found, disabling gir generation.') - girwarning_printed = True - return ModuleReturnValue(None, []) + raise MesonException('gobject-introspection dependency was not found, gir cannot be generated.') ns = kwargs.pop('namespace') nsversion = kwargs.pop('nsversion') libsources = kwargs.pop('sources') @@ -366,7 +379,7 @@ can not be used with the current version of glib-compiled-resources, due to depends = [girtarget] gir_inc_dirs = [] - scan_command = giscanner.get_command() + ['@INPUT@'] + scan_command = [giscanner, '@INPUT@'] scan_command += pkgargs scan_command += ['--no-libtool', '--namespace=' + ns, '--nsversion=' + nsversion, '--warn-all', '--output', '@OUTPUT@'] @@ -522,7 +535,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 = gicompiler.get_command() + [scan_target, '--output', '@OUTPUT@'] + typelib_cmd = [gicompiler, scan_target, '--output', '@OUTPUT@'] typelib_cmd += get_include_args(state.environment, gir_inc_dirs, prefix='--includedir=') for incdir in typelib_includes: @@ -546,7 +559,7 @@ can not be used with the current version of glib-compiled-resources, due to srcdir = os.path.join(state.build_to_src, state.subdir) outdir = state.subdir - cmd = find_program('glib-compile-schemas', 'gsettings-compile').get_command() + cmd = [find_program('glib-compile-schemas', 'gsettings-compile')] cmd += ['--targetdir', outdir, srcdir] kwargs['command'] = cmd kwargs['input'] = [] @@ -740,7 +753,7 @@ can not be used with the current version of glib-compiled-resources, due to namebase = args[0] xml_file = args[1] target_name = namebase + '-gdbus' - cmd = find_program('gdbus-codegen', target_name).get_command() + cmd = [find_program('gdbus-codegen', target_name)] if 'interface_prefix' in kwargs: cmd += ['--interface-prefix', kwargs.pop('interface_prefix')] if 'namespace' in kwargs: @@ -798,7 +811,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 = find_program('glib-mkenums', 'mkenums').get_command() + cmd + cmd = [find_program('glib-mkenums', 'mkenums')] + cmd custom_kwargs = {} for arg in known_custom_target_kwargs: if arg in kwargs: @@ -863,6 +876,7 @@ can not be used with the current version of glib-compiled-resources, due to } custom_kwargs.update(kwargs) return build.CustomTarget(output, state.subdir, custom_kwargs, + # https://github.com/mesonbuild/meson/issues/973 absolute_paths=True) def genmarshal(self, state, args, kwargs): @@ -880,7 +894,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 = find_program('glib-genmarshal', output + '_genmarshal').get_command() + cmd = [find_program('glib-genmarshal', output + '_genmarshal')] known_kwargs = ['internal', 'nostdinc', 'skip_source', 'stdinc', 'valist_marshallers'] known_custom_target_kwargs = ['build_always', 'depends', @@ -1009,7 +1023,7 @@ 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 = find_program('vapigen', 'Vaapi').get_command() + cmd = [find_program('vapigen', 'Vaapi')] 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) diff --git a/mesonbuild/modules/qt4.py b/mesonbuild/modules/qt4.py index 33c9f80..7146739 100644 --- a/mesonbuild/modules/qt4.py +++ b/mesonbuild/modules/qt4.py @@ -48,7 +48,7 @@ class Qt4Module(ExtensionModule): raise MesonException('Moc preprocessor is not for Qt 4. Output:\n%s\n%s' % (stdout, stderr)) mlog.log(' moc:', mlog.green('YES'), '(%s, %s)' % - (' '.join(self.moc.fullpath), moc_ver.split()[-1])) + (self.moc.get_path(), moc_ver.split()[-1])) else: mlog.log(' moc:', mlog.red('NO')) if self.uic.found(): @@ -61,7 +61,7 @@ class Qt4Module(ExtensionModule): raise MesonException('Uic compiler is not for Qt4. Output:\n%s\n%s' % (stdout, stderr)) mlog.log(' uic:', mlog.green('YES'), '(%s, %s)' % - (' '.join(self.uic.fullpath), uic_ver.split()[-1])) + (self.uic.get_path(), uic_ver.split()[-1])) else: mlog.log(' uic:', mlog.red('NO')) if self.rcc.found(): @@ -74,7 +74,7 @@ class Qt4Module(ExtensionModule): raise MesonException('Rcc compiler is not for Qt 4. Output:\n%s\n%s' % (stdout, stderr)) mlog.log(' rcc:', mlog.green('YES'), '(%s, %s)' - % (' '.join(self.rcc.fullpath), rcc_ver.split()[-1])) + % (self.rcc.get_path(), rcc_ver.split()[-1])) else: mlog.log(' rcc:', mlog.red('NO')) self.tools_detected = True diff --git a/mesonbuild/modules/qt5.py b/mesonbuild/modules/qt5.py index b4f1475..2a87a80 100644 --- a/mesonbuild/modules/qt5.py +++ b/mesonbuild/modules/qt5.py @@ -50,7 +50,7 @@ class Qt5Module(ExtensionModule): raise MesonException('Moc preprocessor is not for Qt 5. Output:\n%s\n%s' % (stdout, stderr)) mlog.log(' moc:', mlog.green('YES'), '(%s, %s)' % - (' '.join(self.moc.fullpath), moc_ver.split()[-1])) + (self.moc.get_path(), moc_ver.split()[-1])) else: mlog.log(' moc:', mlog.red('NO')) if self.uic.found(): @@ -65,7 +65,7 @@ class Qt5Module(ExtensionModule): raise MesonException('Uic compiler is not for Qt 5. Output:\n%s\n%s' % (stdout, stderr)) mlog.log(' uic:', mlog.green('YES'), '(%s, %s)' % - (' '.join(self.uic.fullpath), uic_ver.split()[-1])) + (self.uic.get_path(), uic_ver.split()[-1])) else: mlog.log(' uic:', mlog.red('NO')) if self.rcc.found(): @@ -80,7 +80,7 @@ class Qt5Module(ExtensionModule): raise MesonException('Rcc compiler is not for Qt 5. Output:\n%s\n%s' % (stdout, stderr)) mlog.log(' rcc:', mlog.green('YES'), '(%s, %s)' - % (' '.join(self.rcc.fullpath), rcc_ver.split()[-1])) + % (self.rcc.get_path(), rcc_ver.split()[-1])) else: mlog.log(' rcc:', mlog.red('NO')) self.tools_detected = True diff --git a/mesonbuild/modules/rpm.py b/mesonbuild/modules/rpm.py index bd8a3c4..17396ae 100644 --- a/mesonbuild/modules/rpm.py +++ b/mesonbuild/modules/rpm.py @@ -98,17 +98,18 @@ class RPMModule(ExtensionModule): for dep in state.environment.coredata.deps: fn.write('BuildRequires: pkgconfig(%s)\n' % dep[0]) for lib in state.environment.coredata.ext_libs.values(): - fn.write('BuildRequires: %s # FIXME\n' % lib.fullpath) - mlog.warning('replace', mlog.bold(lib.fullpath), 'with real package.', + name = lib.get_name() + fn.write('BuildRequires: {} # FIXME\n'.format(name)) + mlog.warning('replace', mlog.bold(name), 'with the real package.', 'You can use following command to find package which ' 'contains this lib:', - mlog.bold('dnf provides %s' % lib.fullpath)) + mlog.bold("dnf provides '*/lib{}.so'".format(name))) for prog in state.environment.coredata.ext_progs.values(): if not prog.found(): fn.write('BuildRequires: %%{_bindir}/%s # FIXME\n' % prog.get_name()) else: - fn.write('BuildRequires: %s\n' % ' '.join(prog.fullpath)) + fn.write('BuildRequires: {}\n'.format(prog.get_path())) fn.write('BuildRequires: meson\n') fn.write('\n') fn.write('%description\n') diff --git a/mesonbuild/scripts/meson_install.py b/mesonbuild/scripts/meson_install.py index 2ffc505..a025b0c 100644 --- a/mesonbuild/scripts/meson_install.py +++ b/mesonbuild/scripts/meson_install.py @@ -236,12 +236,12 @@ def install_targets(d): raise RuntimeError('File {!r} could not be found'.format(fname)) elif os.path.isfile(fname): do_copyfile(fname, outname) - if should_strip: + if should_strip and d.strip_bin is not None: if fname.endswith('.jar'): print('Not stripping jar target:', os.path.split(fname)[1]) continue print('Stripping target {!r}'.format(fname)) - ps, stdo, stde = Popen_safe(['strip', outname]) + ps, stdo, stde = Popen_safe(d.strip_bin + [outname]) if ps.returncode != 0: print('Could not strip file.\n') print('Stdout:\n%s\n' % stdo) diff --git a/mesonbuild/wrap/wrap.py b/mesonbuild/wrap/wrap.py index 7acbc76..b1efc13 100644 --- a/mesonbuild/wrap/wrap.py +++ b/mesonbuild/wrap/wrap.py @@ -129,7 +129,7 @@ class Resolver: is_there = os.path.isdir(checkoutdir) if is_there: try: - subprocess.check_call(['git', 'rev-parse']) + subprocess.check_call(['git', 'rev-parse'], cwd=checkoutdir) is_there = True except subprocess.CalledProcessError: raise RuntimeError('%s is not empty but is not a valid ' diff --git a/mesontest.py b/mesontest.py index f5da103..a1708e3 100755 --- a/mesontest.py +++ b/mesontest.py @@ -16,6 +16,7 @@ # A tool to run tests in many different ways. +import shlex import subprocess, sys, os, argparse import pickle from mesonbuild import build @@ -25,6 +26,7 @@ import time, datetime, multiprocessing, json import concurrent.futures as conc import platform import signal +import random # GNU autotools interprets a return code of 77 from tests it executes to # mean that the test should be skipped. @@ -60,7 +62,7 @@ parser.add_argument('--gdb', default=False, dest='gdb', action='store_true', help='Run test under gdb.') parser.add_argument('--list', default=False, dest='list', action='store_true', help='List available tests.') -parser.add_argument('--wrapper', default=None, dest='wrapper', +parser.add_argument('--wrapper', default=None, dest='wrapper', type=shlex.split, help='wrapper to run tests with (e.g. Valgrind)') parser.add_argument('-C', default='.', dest='wd', help='directory to cd into before running') @@ -82,13 +84,16 @@ parser.add_argument('-v', '--verbose', default=False, action='store_true', help='Do not redirect stdout and stderr') parser.add_argument('-q', '--quiet', default=False, action='store_true', help='Produce less output to the terminal.') -parser.add_argument('-t', '--timeout-multiplier', type=float, default=1.0, +parser.add_argument('-t', '--timeout-multiplier', type=float, default=None, help='Define a multiplier for test timeout, for example ' ' when running tests in particular conditions they might take' ' more time to execute.') parser.add_argument('--setup', default=None, dest='setup', help='Which test setup to use.') -parser.add_argument('args', nargs='*') +parser.add_argument('--test-args', default=[], type=shlex.split, + help='Arguments to pass to the specified test(s) or all tests') +parser.add_argument('args', nargs='*', + help='Optional list of tests to run') class TestRun: def __init__(self, res, returncode, should_fail, duration, stdo, stde, cmd, @@ -158,7 +163,6 @@ class TestHarness: self.skip_count = 0 self.timeout_count = 0 self.is_run = False - self.cant_rebuild = False self.tests = None self.suites = None if self.options.benchmark: @@ -166,27 +170,6 @@ class TestHarness: else: self.load_datafile(os.path.join(options.wd, 'meson-private', 'meson_test_setup.dat')) - def rebuild_all(self): - if not os.path.isfile(os.path.join(self.options.wd, 'build.ninja')): - print("Only ninja backend is supported to rebuilt tests before running them.") - self.cant_rebuild = True - return True - - ninja = environment.detect_ninja() - if not ninja: - print("Can't find ninja, can't rebuild test.") - self.cant_rebuild = True - return False - - p = subprocess.Popen([ninja, '-C', self.options.wd]) - (stdo, stde) = p.communicate() - - if p.returncode != 0: - print("Could not rebuild") - return False - - return True - def run_single_test(self, wrap, test): if test.fname[0].endswith('.jar'): cmd = ['java', '-jar'] + test.fname @@ -210,7 +193,7 @@ class TestHarness: stde = None returncode = GNU_SKIP_RETURNCODE else: - cmd = wrap + cmd + test.cmd_args + cmd = wrap + cmd + test.cmd_args + self.options.test_args starttime = time.time() child_env = os.environ.copy() child_env.update(self.options.global_env.get_env(child_env)) @@ -221,6 +204,14 @@ class TestHarness: if len(test.extra_paths) > 0: child_env['PATH'] += ';'.join([''] + test.extra_paths) + # If MALLOC_PERTURB_ is not set, or if it is set to an empty value, + # (i.e., the test or the environment don't explicitly set it), set + # it ourselves. We do this unconditionally because it is extremely + # useful to have in tests. + # Setting MALLOC_PERTURB_="0" will completely disable this feature. + if 'MALLOC_PERTURB_' not in child_env or not child_env['MALLOC_PERTURB_']: + child_env['MALLOC_PERTURB_'] = str(random.randint(1, 255)) + setsid = None stdout = None stderr = None @@ -406,15 +397,18 @@ TIMEOUT: %4d if not self.options.logbase or self.options.verbose: return None, None, None, None + namebase = None logfile_base = os.path.join(self.options.wd, 'meson-logs', self.options.logbase) - if self.options.wrapper is None: - logfilename = logfile_base + '.txt' - jsonlogfilename = logfile_base + '.json' - else: + if self.options.wrapper: namebase = os.path.split(self.get_wrapper()[0])[1] - logfilename = logfile_base + '-' + namebase.replace(' ', '_') + '.txt' - jsonlogfilename = logfile_base + '-' + namebase.replace(' ', '_') + '.json' + elif self.options.setup: + namebase = self.options.setup + + if namebase: + logfile_base += '-' + namebase.replace(' ', '_') + logfilename = logfile_base + '.txt' + jsonlogfilename = logfile_base + '.json' jsonlogfile = open(jsonlogfilename, 'w') logfile = open(logfilename, 'w') @@ -430,11 +424,10 @@ TIMEOUT: %4d wrap = ['gdb', '--quiet', '--nh'] if self.options.repeat > 1: wrap += ['-ex', 'run', '-ex', 'quit'] - elif self.options.wrapper: - if isinstance(self.options.wrapper, str): - wrap = self.options.wrapper.split() - else: - wrap = self.options.wrapper + # Signal the end of arguments to gdb + wrap += ['--args'] + if self.options.wrapper: + wrap += self.options.wrapper assert(isinstance(wrap, list)) return wrap @@ -464,8 +457,6 @@ TIMEOUT: %4d if self.options.gdb: test.timeout = None - if len(test.cmd_args): - wrap.append('--args') if not test.is_parallel or self.options.gdb: self.drain_futures(futures, logfile, jsonlogfile) @@ -510,6 +501,8 @@ TIMEOUT: %4d if os.path.isfile('build.ninja'): subprocess.check_call([environment.detect_ninja(), 'all']) tests = self.get_tests() + if not tests: + return 0 self.run_tests(tests) return self.fail_count @@ -539,6 +532,25 @@ def merge_suite_options(options): options.wrapper = current.exe_wrapper return current.env +def rebuild_all(wd): + if not os.path.isfile(os.path.join(wd, 'build.ninja')): + print("Only ninja backend is supported to rebuild tests before running them.") + return True + + ninja = environment.detect_ninja() + if not ninja: + print("Can't find ninja, can't rebuild test.") + return False + + p = subprocess.Popen([ninja, '-C', wd]) + (stdo, stde) = p.communicate() + + if p.returncode != 0: + print("Could not rebuild") + return False + + return True + def run(args): options = parser.parse_args(args) @@ -549,6 +561,8 @@ def run(args): global_env = merge_suite_options(options) else: global_env = build.EnvironmentVariables() + if options.timeout_multiplier is None: + options.timeout_multiplier = 1 setattr(options, 'global_env', global_env) @@ -564,13 +578,14 @@ def run(args): options.wd = os.path.abspath(options.wd) + if not options.no_rebuild: + if not rebuild_all(options.wd): + sys.exit(-1) + th = TestHarness(options) if options.list: list_tests(th) return 0 - if not options.no_rebuild: - if not th.rebuild_all(): - sys.exit(-1) if len(options.args) == 0: return th.doit() return th.run_special() diff --git a/run_tests.py b/run_tests.py index 005717e..5025057 100755 --- a/run_tests.py +++ b/run_tests.py @@ -1,6 +1,6 @@ #!/usr/bin/env python3 -# Copyright 2012-2016 The Meson development team +# Copyright 2012-2017 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. @@ -14,17 +14,25 @@ # See the License for the specific language governing permissions and # limitations under the License. -import subprocess, sys, shutil +import os +import sys +import shutil +import subprocess import platform from mesonbuild import mesonlib if __name__ == '__main__': returncode = 0 + # Running on a developer machine? Be nice! + if not mesonlib.is_windows() and 'TRAVIS' not in os.environ: + os.nice(20) print('Running unittests.\n') + units = ['InternalTests', 'AllPlatformTests'] if mesonlib.is_linux(): - returncode += subprocess.call([sys.executable, 'run_unittests.py', '-v']) - else: - returncode += subprocess.call([sys.executable, 'run_unittests.py', '-v', 'InternalTests']) + units += ['LinuxlikeTests'] + elif mesonlib.is_windows(): + units += ['WindowsTests'] + returncode += subprocess.call([sys.executable, 'run_unittests.py', '-v'] + units) # Ubuntu packages do not have a binary without -6 suffix. if shutil.which('arm-linux-gnueabihf-gcc-6') and not platform.machine().startswith('arm'): print('Running cross compilation tests.\n') diff --git a/run_unittests.py b/run_unittests.py index aed1412..f800d03 100755 --- a/run_unittests.py +++ b/run_unittests.py @@ -1,5 +1,5 @@ #!/usr/bin/env python3 -# Copyright 2016 The Meson development team +# Copyright 2016-2017 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. @@ -18,14 +18,20 @@ import shlex import subprocess import re, json import tempfile -import pathlib import unittest, os, sys, shutil, time from glob import glob +from pathlib import PurePath import mesonbuild.compilers import mesonbuild.environment import mesonbuild.mesonlib +from mesonbuild.mesonlib import is_windows, is_osx from mesonbuild.environment import detect_ninja, Environment -from mesonbuild.dependencies import PkgConfigDependency +from mesonbuild.dependencies import PkgConfigDependency, ExternalProgram + +if is_windows(): + exe_suffix = '.exe' +else: + exe_suffix = '' def get_soname(fname): # HACK, fix to not use shell. @@ -172,15 +178,169 @@ class InternalTests(unittest.TestCase): self.assertEqual(commonpath(['blam', 'bin']), '') prefix = '/some/path/to/prefix' libdir = '/some/path/to/prefix/libdir' - self.assertEqual(commonpath([prefix, libdir]), str(pathlib.PurePath(prefix))) - - -class LinuxlikeTests(unittest.TestCase): + self.assertEqual(commonpath([prefix, libdir]), str(PurePath(prefix))) + + def test_string_templates_substitution(self): + dictfunc = mesonbuild.mesonlib.get_filenames_templates_dict + substfunc = mesonbuild.mesonlib.substitute_values + ME = mesonbuild.mesonlib.MesonException + + # Identity + self.assertEqual(dictfunc([], []), {}) + + # One input, no outputs + inputs = ['bar/foo.c.in'] + outputs = [] + ret = dictfunc(inputs, outputs) + d = {'@INPUT@': inputs, '@INPUT0@': inputs[0], + '@PLAINNAME@': 'foo.c.in', '@BASENAME@': 'foo.c'} + # Check dictionary + self.assertEqual(ret, d) + # Check substitutions + cmd = ['some', 'ordinary', 'strings'] + self.assertEqual(substfunc(cmd, d), cmd) + cmd = ['@INPUT@.out', 'ordinary', 'strings'] + self.assertEqual(substfunc(cmd, d), [inputs[0] + '.out'] + cmd[1:]) + cmd = ['@INPUT0@.out', '@PLAINNAME@.ok', 'strings'] + self.assertEqual(substfunc(cmd, d), + [inputs[0] + '.out'] + [d['@PLAINNAME@'] + '.ok'] + cmd[2:]) + cmd = ['@INPUT@', '@BASENAME@.hah', 'strings'] + self.assertEqual(substfunc(cmd, d), + inputs + [d['@BASENAME@'] + '.hah'] + cmd[2:]) + cmd = ['@OUTPUT@'] + self.assertRaises(ME, substfunc, cmd, d) + + # One input, one output + inputs = ['bar/foo.c.in'] + outputs = ['out.c'] + ret = dictfunc(inputs, outputs) + d = {'@INPUT@': inputs, '@INPUT0@': inputs[0], + '@PLAINNAME@': 'foo.c.in', '@BASENAME@': 'foo.c', + '@OUTPUT@': outputs, '@OUTPUT0@': outputs[0], '@OUTDIR@': '.'} + # Check dictionary + self.assertEqual(ret, d) + # Check substitutions + cmd = ['some', 'ordinary', 'strings'] + self.assertEqual(substfunc(cmd, d), cmd) + cmd = ['@INPUT@.out', '@OUTPUT@', 'strings'] + self.assertEqual(substfunc(cmd, d), + [inputs[0] + '.out'] + outputs + cmd[2:]) + cmd = ['@INPUT0@.out', '@PLAINNAME@.ok', '@OUTPUT0@'] + self.assertEqual(substfunc(cmd, d), + [inputs[0] + '.out', d['@PLAINNAME@'] + '.ok'] + outputs) + cmd = ['@INPUT@', '@BASENAME@.hah', 'strings'] + self.assertEqual(substfunc(cmd, d), + inputs + [d['@BASENAME@'] + '.hah'] + cmd[2:]) + + # One input, one output with a subdir + outputs = ['dir/out.c'] + ret = dictfunc(inputs, outputs) + d = {'@INPUT@': inputs, '@INPUT0@': inputs[0], + '@PLAINNAME@': 'foo.c.in', '@BASENAME@': 'foo.c', + '@OUTPUT@': outputs, '@OUTPUT0@': outputs[0], '@OUTDIR@': 'dir'} + # Check dictionary + self.assertEqual(ret, d) + + # Two inputs, no outputs + inputs = ['bar/foo.c.in', 'baz/foo.c.in'] + outputs = [] + ret = dictfunc(inputs, outputs) + d = {'@INPUT@': inputs, '@INPUT0@': inputs[0], '@INPUT1@': inputs[1]} + # Check dictionary + self.assertEqual(ret, d) + # Check substitutions + cmd = ['some', 'ordinary', 'strings'] + self.assertEqual(substfunc(cmd, d), cmd) + cmd = ['@INPUT@', 'ordinary', 'strings'] + self.assertEqual(substfunc(cmd, d), inputs + cmd[1:]) + cmd = ['@INPUT0@.out', 'ordinary', 'strings'] + self.assertEqual(substfunc(cmd, d), [inputs[0] + '.out'] + cmd[1:]) + cmd = ['@INPUT0@.out', '@INPUT1@.ok', 'strings'] + self.assertEqual(substfunc(cmd, d), [inputs[0] + '.out', inputs[1] + '.ok'] + cmd[2:]) + cmd = ['@INPUT0@', '@INPUT1@', 'strings'] + self.assertEqual(substfunc(cmd, d), inputs + cmd[2:]) + # Many inputs, can't use @INPUT@ like this + cmd = ['@INPUT@.out', 'ordinary', 'strings'] + # Not enough inputs + cmd = ['@INPUT2@.out', 'ordinary', 'strings'] + self.assertRaises(ME, substfunc, cmd, d) + # Too many inputs + cmd = ['@PLAINNAME@'] + self.assertRaises(ME, substfunc, cmd, d) + cmd = ['@BASENAME@'] + self.assertRaises(ME, substfunc, cmd, d) + # No outputs + cmd = ['@OUTPUT@'] + self.assertRaises(ME, substfunc, cmd, d) + cmd = ['@OUTPUT0@'] + self.assertRaises(ME, substfunc, cmd, d) + cmd = ['@OUTDIR@'] + self.assertRaises(ME, substfunc, cmd, d) + + # Two inputs, one output + outputs = ['dir/out.c'] + ret = dictfunc(inputs, outputs) + d = {'@INPUT@': inputs, '@INPUT0@': inputs[0], '@INPUT1@': inputs[1], + '@OUTPUT@': outputs, '@OUTPUT0@': outputs[0], '@OUTDIR@': 'dir'} + # Check dictionary + self.assertEqual(ret, d) + # Check substitutions + cmd = ['some', 'ordinary', 'strings'] + self.assertEqual(substfunc(cmd, d), cmd) + cmd = ['@OUTPUT@', 'ordinary', 'strings'] + self.assertEqual(substfunc(cmd, d), outputs + cmd[1:]) + cmd = ['@OUTPUT@.out', 'ordinary', 'strings'] + self.assertEqual(substfunc(cmd, d), [outputs[0] + '.out'] + cmd[1:]) + cmd = ['@OUTPUT0@.out', '@INPUT1@.ok', 'strings'] + self.assertEqual(substfunc(cmd, d), [outputs[0] + '.out', inputs[1] + '.ok'] + cmd[2:]) + # Many inputs, can't use @INPUT@ like this + cmd = ['@INPUT@.out', 'ordinary', 'strings'] + # Not enough inputs + cmd = ['@INPUT2@.out', 'ordinary', 'strings'] + self.assertRaises(ME, substfunc, cmd, d) + # Not enough outputs + cmd = ['@OUTPUT2@.out', 'ordinary', 'strings'] + self.assertRaises(ME, substfunc, cmd, d) + + # Two inputs, two outputs + outputs = ['dir/out.c', 'dir/out2.c'] + ret = dictfunc(inputs, outputs) + d = {'@INPUT@': inputs, '@INPUT0@': inputs[0], '@INPUT1@': inputs[1], + '@OUTPUT@': outputs, '@OUTPUT0@': outputs[0], '@OUTPUT1@': outputs[1], + '@OUTDIR@': 'dir'} + # Check dictionary + self.assertEqual(ret, d) + # Check substitutions + cmd = ['some', 'ordinary', 'strings'] + self.assertEqual(substfunc(cmd, d), cmd) + cmd = ['@OUTPUT@', 'ordinary', 'strings'] + self.assertEqual(substfunc(cmd, d), outputs + cmd[1:]) + cmd = ['@OUTPUT0@', '@OUTPUT1@', 'strings'] + self.assertEqual(substfunc(cmd, d), outputs + cmd[2:]) + cmd = ['@OUTPUT0@.out', '@INPUT1@.ok', '@OUTDIR@'] + self.assertEqual(substfunc(cmd, d), [outputs[0] + '.out', inputs[1] + '.ok', 'dir']) + # Many inputs, can't use @INPUT@ like this + cmd = ['@INPUT@.out', 'ordinary', 'strings'] + # Not enough inputs + cmd = ['@INPUT2@.out', 'ordinary', 'strings'] + self.assertRaises(ME, substfunc, cmd, d) + # Not enough outputs + cmd = ['@OUTPUT2@.out', 'ordinary', 'strings'] + self.assertRaises(ME, substfunc, cmd, d) + # Many outputs, can't use @OUTPUT@ like this + cmd = ['@OUTPUT@.out', 'ordinary', 'strings'] + self.assertRaises(ME, substfunc, cmd, d) + + +class BasePlatformTests(unittest.TestCase): def setUp(self): super().setUp() src_root = os.path.dirname(__file__) src_root = os.path.join(os.getcwd(), src_root) - self.builddir = tempfile.mkdtemp() + self.src_root = src_root + # In case the directory is inside a symlinked directory, find the real + # path otherwise we might not find the srcdir from inside the builddir. + self.builddir = os.path.realpath(tempfile.mkdtemp()) self.logdir = os.path.join(self.builddir, 'meson-logs') self.prefix = '/usr' self.libdir = os.path.join(self.prefix, 'lib') @@ -194,7 +354,6 @@ class LinuxlikeTests(unittest.TestCase): 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.output = b'' self.orig_env = os.environ.copy() def tearDown(self): @@ -203,20 +362,26 @@ class LinuxlikeTests(unittest.TestCase): super().tearDown() def _run(self, command): - self.output += subprocess.check_output(command, stderr=subprocess.STDOUT, - env=os.environ.copy()) + output = subprocess.check_output(command, stderr=subprocess.STDOUT, + env=os.environ.copy(), + universal_newlines=True) + print(output) + return output - def init(self, srcdir, extra_args=None): + def init(self, srcdir, extra_args=None, default_args=True): if extra_args is None: extra_args = [] - args = [srcdir, self.builddir, - '--prefix', self.prefix, - '--libdir', self.libdir] + args = [srcdir, self.builddir] + if default_args: + args += ['--prefix', self.prefix, + '--libdir', self.libdir] self._run(self.meson_command + args + extra_args) self.privatedir = os.path.join(self.builddir, 'meson-private') - def build(self): - self._run(self.ninja_command) + def build(self, extra_args=None): + if extra_args is None: + extra_args = [] + self._run(self.ninja_command + extra_args) def run_tests(self): self._run(self.ninja_command + ['test']) @@ -229,9 +394,18 @@ class LinuxlikeTests(unittest.TestCase): self._run(self.ninja_command + ['uninstall']) def run_target(self, target): - self.output += subprocess.check_output(self.ninja_command + [target]) + output = subprocess.check_output(self.ninja_command + [target], + stderr=subprocess.STDOUT, + universal_newlines=True) + print(output) + return output - def setconf(self, arg): + def setconf(self, arg, will_build=True): + # This is needed to increase the difference between build.ninja's + # timestamp and coredata.dat's timestamp due to a Ninja bug. + # https://github.com/ninja-build/ninja/issues/371 + if will_build: + time.sleep(1) self._run(self.mconf_command + [arg, self.builddir]) def wipe(self): @@ -260,6 +434,447 @@ class LinuxlikeTests(unittest.TestCase): universal_newlines=True) return json.loads(out) + 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 assertPathBasenameEqual(self, path, basename): + msg = '{!r} does not end with {!r}'.format(path, basename) + # 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) + + +class AllPlatformTests(BasePlatformTests): + ''' + Tests that should run on all platforms + ''' + def test_default_options_prefix(self): + ''' + Tests that setting a prefix in default_options in project() works. + Can't be an ordinary test because we pass --prefix to meson there. + https://github.com/mesonbuild/meson/issues/1349 + ''' + testdir = os.path.join(self.common_test_dir, '94 default options') + self.init(testdir, default_args=False) + opts = self.introspect('--buildoptions') + for opt in opts: + if opt['name'] == 'prefix': + prefix = opt['value'] + self.assertEqual(prefix, '/absoluteprefix') + + def test_absolute_prefix_libdir(self): + ''' + Tests that setting absolute paths for --prefix and --libdir work. Can't + be an ordinary test because these are set via the command-line. + https://github.com/mesonbuild/meson/issues/1341 + https://github.com/mesonbuild/meson/issues/1345 + ''' + testdir = os.path.join(self.common_test_dir, '94 default options') + prefix = '/someabs' + libdir = 'libdir' + extra_args = ['--prefix=' + prefix, + # This can just be a relative path, but we want to test + # that passing this as an absolute path also works + '--libdir=' + prefix + '/' + libdir] + self.init(testdir, extra_args, default_args=False) + opts = self.introspect('--buildoptions') + for opt in opts: + if opt['name'] == 'prefix': + self.assertEqual(prefix, opt['value']) + elif opt['name'] == 'libdir': + self.assertEqual(libdir, opt['value']) + + def test_libdir_must_be_inside_prefix(self): + ''' + Tests that libdir is forced to be inside prefix no matter how it is set. + Must be a unit test for obvious reasons. + ''' + testdir = os.path.join(self.common_test_dir, '1 trivial') + # libdir being inside prefix is ok + args = ['--prefix', '/opt', '--libdir', '/opt/lib32'] + self.init(testdir, args) + self.wipe() + # libdir not being inside prefix is not ok + args = ['--prefix', '/usr', '--libdir', '/opt/lib32'] + self.assertRaises(subprocess.CalledProcessError, self.init, testdir, args) + self.wipe() + # libdir must be inside prefix even when set via mesonconf + self.init(testdir) + self.assertRaises(subprocess.CalledProcessError, self.setconf, '-Dlibdir=/opt', False) + + def test_static_library_overwrite(self): + ''' + Tests that static libraries are never appended to, always overwritten. + Has to be a unit test because this involves building a project, + reconfiguring, and building it again so that `ar` is run twice on the + same static library. + https://github.com/mesonbuild/meson/issues/1355 + ''' + testdir = os.path.join(self.common_test_dir, '3 static') + env = Environment(testdir, self.builddir, self.meson_command, + get_fake_options(self.prefix), []) + cc = env.detect_c_compiler(False) + static_linker = env.detect_static_linker(cc) + if not isinstance(static_linker, mesonbuild.compilers.ArLinker): + raise unittest.SkipTest('static linker is not `ar`') + # Configure + self.init(testdir) + # Get name of static library + targets = self.introspect('--targets') + self.assertEqual(len(targets), 1) + libname = targets[0]['filename'] + # Build and get contents of static library + self.build() + before = self._run(['ar', 't', os.path.join(self.builddir, libname)]).split() + # Filter out non-object-file contents + before = [f for f in before if f.endswith(('.o', '.obj'))] + # Static library should contain only one object + self.assertEqual(len(before), 1, msg=before) + # Change the source to be built into the static library + self.setconf('-Dsource=libfile2.c') + self.build() + after = self._run(['ar', 't', os.path.join(self.builddir, libname)]).split() + # Filter out non-object-file contents + after = [f for f in after if f.endswith(('.o', '.obj'))] + # Static library should contain only one object + self.assertEqual(len(after), 1, msg=after) + # and the object must have changed + self.assertNotEqual(before, after) + + def test_static_compile_order(self): + ''' + Test that the order of files in a compiler command-line while compiling + and linking statically is deterministic. This can't be an ordinary test + case because we need to inspect the compiler database. + https://github.com/mesonbuild/meson/pull/951 + ''' + testdir = os.path.join(self.common_test_dir, '5 linkstatic') + self.init(testdir) + compdb = self.get_compdb() + # Rules will get written out in this order + self.assertTrue(compdb[0]['file'].endswith("libfile.c")) + self.assertTrue(compdb[1]['file'].endswith("libfile2.c")) + self.assertTrue(compdb[2]['file'].endswith("libfile3.c")) + self.assertTrue(compdb[3]['file'].endswith("libfile4.c")) + # FIXME: We don't have access to the linker command + + def test_run_target_files_path(self): + ''' + Test that run_targets are run from the correct directory + https://github.com/mesonbuild/meson/issues/957 + ''' + testdir = os.path.join(self.common_test_dir, '58 run target') + self.init(testdir) + self.run_target('check_exists') + + def test_install_introspection(self): + ''' + Tests that the Meson introspection API exposes install filenames correctly + https://github.com/mesonbuild/meson/issues/829 + ''' + testdir = os.path.join(self.common_test_dir, '8 install') + self.init(testdir) + intro = self.introspect('--targets') + if intro[0]['type'] == 'executable': + intro = intro[::-1] + self.assertPathEqual(intro[0]['install_filename'], '/usr/lib/libstat.a') + self.assertPathEqual(intro[1]['install_filename'], '/usr/bin/prog' + exe_suffix) + + def test_uninstall(self): + exename = os.path.join(self.installdir, 'usr/bin/prog' + exe_suffix) + testdir = os.path.join(self.common_test_dir, '8 install') + self.init(testdir) + self.assertFalse(os.path.exists(exename)) + self.install() + self.assertTrue(os.path.exists(exename)) + self.uninstall() + self.assertFalse(os.path.exists(exename)) + + def test_testsetups(self): + if not shutil.which('valgrind'): + raise unittest.SkipTest('Valgrind not installed.') + testdir = os.path.join(self.unit_test_dir, '2 testsetups') + self.init(testdir) + self.build() + # Run tests without setup + self.run_tests() + with open(os.path.join(self.logdir, 'testlog.txt')) as f: + basic_log = f.read() + # Run buggy test with setup that has env that will make it fail + self.assertRaises(subprocess.CalledProcessError, + self._run, self.mtest_command + ['--setup=valgrind']) + with open(os.path.join(self.logdir, 'testlog-valgrind.txt')) as f: + vg_log = f.read() + self.assertFalse('TEST_ENV is set' in basic_log) + self.assertFalse('Memcheck' in basic_log) + self.assertTrue('TEST_ENV is set' in vg_log) + self.assertTrue('Memcheck' in vg_log) + # Run buggy test with setup without env that will pass + self._run(self.mtest_command + ['--setup=wrapper']) + # Setup with no properties works + self._run(self.mtest_command + ['--setup=empty']) + # Setup with only env works + self._run(self.mtest_command + ['--setup=onlyenv']) + # Setup with only a timeout works + self._run(self.mtest_command + ['--setup=timeout']) + + def assertFailedTestCount(self, failure_count, command): + try: + self._run(command) + self.assertEqual(0, failure_count, 'Expected %d tests to fail.' % failure_count) + except subprocess.CalledProcessError as e: + self.assertEqual(e.returncode, failure_count) + + def test_suite_selection(self): + testdir = os.path.join(self.unit_test_dir, '4 suite selection') + self.init(testdir) + self.build() + + self.assertFailedTestCount(3, self.mtest_command) + + self.assertFailedTestCount(0, self.mtest_command + ['--suite', ':success']) + self.assertFailedTestCount(3, self.mtest_command + ['--suite', ':fail']) + self.assertFailedTestCount(3, self.mtest_command + ['--no-suite', ':success']) + self.assertFailedTestCount(0, self.mtest_command + ['--no-suite', ':fail']) + + self.assertFailedTestCount(1, self.mtest_command + ['--suite', 'mainprj']) + self.assertFailedTestCount(0, self.mtest_command + ['--suite', 'subprjsucc']) + self.assertFailedTestCount(1, self.mtest_command + ['--suite', 'subprjfail']) + self.assertFailedTestCount(1, self.mtest_command + ['--suite', 'subprjmix']) + self.assertFailedTestCount(2, self.mtest_command + ['--no-suite', 'mainprj']) + self.assertFailedTestCount(3, self.mtest_command + ['--no-suite', 'subprjsucc']) + self.assertFailedTestCount(2, self.mtest_command + ['--no-suite', 'subprjfail']) + self.assertFailedTestCount(2, self.mtest_command + ['--no-suite', 'subprjmix']) + + self.assertFailedTestCount(1, self.mtest_command + ['--suite', 'mainprj:fail']) + self.assertFailedTestCount(0, self.mtest_command + ['--suite', 'mainprj:success']) + self.assertFailedTestCount(2, self.mtest_command + ['--no-suite', 'mainprj:fail']) + self.assertFailedTestCount(3, self.mtest_command + ['--no-suite', 'mainprj:success']) + + self.assertFailedTestCount(1, self.mtest_command + ['--suite', 'subprjfail:fail']) + self.assertFailedTestCount(0, self.mtest_command + ['--suite', 'subprjfail:success']) + self.assertFailedTestCount(2, self.mtest_command + ['--no-suite', 'subprjfail:fail']) + self.assertFailedTestCount(3, self.mtest_command + ['--no-suite', 'subprjfail:success']) + + self.assertFailedTestCount(0, self.mtest_command + ['--suite', 'subprjsucc:fail']) + self.assertFailedTestCount(0, self.mtest_command + ['--suite', 'subprjsucc:success']) + self.assertFailedTestCount(3, self.mtest_command + ['--no-suite', 'subprjsucc:fail']) + self.assertFailedTestCount(3, self.mtest_command + ['--no-suite', 'subprjsucc:success']) + + self.assertFailedTestCount(1, self.mtest_command + ['--suite', 'subprjmix:fail']) + self.assertFailedTestCount(0, self.mtest_command + ['--suite', 'subprjmix:success']) + self.assertFailedTestCount(2, self.mtest_command + ['--no-suite', 'subprjmix:fail']) + self.assertFailedTestCount(3, self.mtest_command + ['--no-suite', 'subprjmix:success']) + + self.assertFailedTestCount(2, self.mtest_command + ['--suite', 'subprjfail', '--suite', 'subprjmix:fail']) + self.assertFailedTestCount(3, self.mtest_command + ['--suite', 'subprjfail', '--suite', 'subprjmix', '--suite', 'mainprj']) + self.assertFailedTestCount(2, self.mtest_command + ['--suite', 'subprjfail', '--suite', 'subprjmix', '--suite', 'mainprj', '--no-suite', 'subprjmix:fail']) + self.assertFailedTestCount(1, self.mtest_command + ['--suite', 'subprjfail', '--suite', 'subprjmix', '--suite', 'mainprj', '--no-suite', 'subprjmix:fail', 'mainprj-failing_test']) + + self.assertFailedTestCount(1, self.mtest_command + ['--no-suite', 'subprjfail:fail', '--no-suite', 'subprjmix:fail']) + + def test_build_by_default(self): + testdir = os.path.join(self.common_test_dir, '137 build by default') + self.init(testdir) + self.build() + genfile = os.path.join(self.builddir, 'generated.dat') + exe = os.path.join(self.builddir, 'fooprog' + exe_suffix) + self.assertTrue(os.path.exists(genfile)) + self.assertFalse(os.path.exists(exe)) + self._run(self.ninja_command + ['fooprog' + exe_suffix]) + self.assertTrue(os.path.exists(exe)) + + def test_internal_include_order(self): + testdir = os.path.join(self.common_test_dir, '138 include order') + self.init(testdir) + for cmd in self.get_compdb(): + if cmd['file'].endswith('/main.c'): + cmd = cmd['command'] + break + else: + raise Exception('Could not find main.c command') + if cmd.endswith('.rsp'): + # Pretend to build so that the rsp files are generated + self.build(['-d', 'keeprsp', '-n']) + # Extract the actual command from the rsp file + rsp = os.path.join(self.builddir, cmd.split('cl @')[1]) + with open(rsp, 'r', encoding='utf-8') as f: + cmd = f.read() + incs = [a for a in shlex.split(cmd) if a.startswith("-I")] + self.assertEqual(len(incs), 8) + # target private dir + self.assertPathEqual(incs[0], "-Isub4/someexe@exe") + # target build subdir + self.assertPathEqual(incs[1], "-Isub4") + # target source subdir + self.assertPathBasenameEqual(incs[2], 'sub4') + # include paths added via per-target c_args: ['-I'...] + self.assertPathBasenameEqual(incs[3], 'sub3') + # target include_directories: build dir + self.assertPathEqual(incs[4], "-Isub2") + # target include_directories: source dir + self.assertPathBasenameEqual(incs[5], 'sub2') + # target internal dependency include_directories: build dir + self.assertPathEqual(incs[6], "-Isub1") + # target internal dependency include_directories: source dir + self.assertPathBasenameEqual(incs[7], 'sub1') + + def test_compiler_detection(self): + ''' + Test that automatic compiler detection and setting from the environment + both work just fine. This is needed because while running project tests + and other unit tests, we always read CC/CXX/etc from the environment. + ''' + gnu = mesonbuild.compilers.GnuCompiler + clang = mesonbuild.compilers.ClangCompiler + intel = mesonbuild.compilers.IntelCompiler + msvc = mesonbuild.compilers.VisualStudioCCompiler + ar = mesonbuild.compilers.ArLinker + lib = mesonbuild.compilers.VisualStudioLinker + langs = [('c', 'CC'), ('cpp', 'CXX')] + if not is_windows(): + langs += [('objc', 'OBJC'), ('objcpp', 'OBJCXX')] + testdir = os.path.join(self.unit_test_dir, '5 compiler detection') + env = Environment(testdir, self.builddir, self.meson_command, + get_fake_options(self.prefix), []) + for lang, evar in langs: + evalue = None + # Detect with evar and do sanity checks on that + if evar in os.environ: + ecc = getattr(env, 'detect_{}_compiler'.format(lang))(False) + elinker = env.detect_static_linker(ecc) + # Pop it so we don't use it for the next detection + evalue = os.environ.pop(evar) + # Very rough/strict heuristics. Would never work for actual + # compiler detection, but should be ok for the tests. + if os.path.basename(evalue).startswith('g'): + self.assertIsInstance(ecc, gnu) + self.assertIsInstance(elinker, ar) + elif 'clang' in os.path.basename(evalue): + self.assertIsInstance(ecc, clang) + self.assertIsInstance(elinker, ar) + elif os.path.basename(evalue).startswith('ic'): + self.assertIsInstance(ecc, intel) + self.assertIsInstance(elinker, ar) + elif os.path.basename(evalue).startswith('cl'): + self.assertIsInstance(ecc, msvc) + self.assertIsInstance(elinker, lib) + else: + raise AssertionError('Unknown compiler {!r}'.format(evalue)) + # Check that we actually used the evalue correctly as the compiler + self.assertEqual(ecc.get_exelist(), shlex.split(evalue)) + # Do auto-detection of compiler based on platform, PATH, etc. + cc = getattr(env, 'detect_{}_compiler'.format(lang))(False) + linker = env.detect_static_linker(cc) + # Check compiler type + if isinstance(cc, gnu): + self.assertIsInstance(linker, ar) + if is_osx(): + self.assertEqual(cc.gcc_type, mesonbuild.compilers.GCC_OSX) + elif is_windows(): + self.assertEqual(cc.gcc_type, mesonbuild.compilers.GCC_MINGW) + else: + self.assertEqual(cc.gcc_type, mesonbuild.compilers.GCC_STANDARD) + if isinstance(cc, clang): + self.assertIsInstance(linker, ar) + if is_osx(): + self.assertEqual(cc.clang_type, mesonbuild.compilers.CLANG_OSX) + elif is_windows(): + # Not implemented yet + self.assertEqual(cc.clang_type, mesonbuild.compilers.CLANG_WIN) + else: + self.assertEqual(cc.clang_type, mesonbuild.compilers.CLANG_STANDARD) + if isinstance(cc, intel): + self.assertIsInstance(linker, ar) + if is_osx(): + self.assertEqual(cc.icc_type, mesonbuild.compilers.ICC_OSX) + elif is_windows(): + self.assertEqual(cc.icc_type, mesonbuild.compilers.ICC_WIN) + else: + self.assertEqual(cc.icc_type, mesonbuild.compilers.ICC_STANDARD) + if isinstance(cc, msvc): + self.assertTrue(is_windows()) + self.assertIsInstance(linker, lib) + self.assertEqual(cc.id, 'msvc') + # Set evar ourselves to a wrapper script that just calls the same + # exelist + some argument. This is meant to test that setting + # something like `ccache gcc -pipe` or `distcc ccache gcc` works. + wrapper = os.path.join(testdir, 'compiler wrapper.py') + wrappercc = [sys.executable, wrapper] + cc.get_exelist() + cc.get_always_args() + wrappercc_s = '' + for w in wrappercc: + wrappercc_s += shlex.quote(w) + ' ' + os.environ[evar] = wrappercc_s + wcc = getattr(env, 'detect_{}_compiler'.format(lang))(False) + # Check static linker too + wrapperlinker = [sys.executable, wrapper] + linker.get_exelist() + linker.get_always_args() + wrapperlinker_s = '' + for w in wrapperlinker: + wrapperlinker_s += shlex.quote(w) + ' ' + os.environ['AR'] = wrapperlinker_s + wlinker = env.detect_static_linker(wcc) + # Must be the same type since it's a wrapper around the same exelist + self.assertIs(type(cc), type(wcc)) + self.assertIs(type(linker), type(wlinker)) + # Ensure that the exelist is correct + self.assertEqual(wcc.get_exelist(), wrappercc) + self.assertEqual(wlinker.get_exelist(), wrapperlinker) + + +class WindowsTests(BasePlatformTests): + ''' + Tests that should run on Cygwin, MinGW, and MSVC + ''' + def setUp(self): + super().setUp() + self.platform_test_dir = os.path.join(self.src_root, 'test cases/windows') + + def test_find_program(self): + ''' + Test that Windows-specific edge-cases in find_program are functioning + correctly. Cannot be an ordinary test because it involves manipulating + PATH to point to a directory with Python scripts. + ''' + testdir = os.path.join(self.platform_test_dir, '9 find program') + # Find `cmd` and `cmd.exe` + prog1 = ExternalProgram('cmd') + self.assertTrue(prog1.found(), msg='cmd not found') + prog2 = ExternalProgram('cmd.exe') + self.assertTrue(prog2.found(), msg='cmd.exe not found') + self.assertPathEqual(prog1.get_path(), prog2.get_path()) + # Find cmd with an absolute path that's missing the extension + cmd_path = prog2.get_path()[:-4] + prog = ExternalProgram(cmd_path) + self.assertTrue(prog.found(), msg='{!r} not found'.format(cmd_path)) + # Finding a script with no extension inside a directory works + prog = ExternalProgram(os.path.join(testdir, 'test-script')) + self.assertTrue(prog.found(), msg='test-script not found') + # Finding a script with an extension inside a directory works + prog = ExternalProgram(os.path.join(testdir, 'test-script-ext.py')) + self.assertTrue(prog.found(), msg='test-script-ext.py not found') + # Finding a script in PATH w/o extension works and adds the interpreter + os.environ['PATH'] += os.pathsep + testdir + prog = ExternalProgram('test-script-ext') + self.assertTrue(prog.found(), msg='test-script-ext not found in PATH') + self.assertPathEqual(prog.get_command()[0], sys.executable) + self.assertPathBasenameEqual(prog.get_path(), 'test-script-ext.py') + # Finding a script in PATH with extension works and adds the interpreter + prog = ExternalProgram('test-script-ext.py') + self.assertTrue(prog.found(), msg='test-script-ext.py not found in PATH') + self.assertPathEqual(prog.get_command()[0], sys.executable) + self.assertPathBasenameEqual(prog.get_path(), 'test-script-ext.py') + + +class LinuxlikeTests(BasePlatformTests): + ''' + Tests that should run on Linux and *BSD + ''' def test_basic_soname(self): ''' Test that the soname is set correctly for shared libraries. This can't @@ -298,10 +913,6 @@ class LinuxlikeTests(unittest.TestCase): self.init(testdir) compdb = self.get_compdb() self.assertIn('-fPIC', compdb[0]['command']) - # This is needed to increase the difference between build.ninja's - # timestamp and coredata.dat's timestamp due to a Ninja bug. - # https://github.com/ninja-build/ninja/issues/371 - time.sleep(1) self.setconf('-Db_staticpic=false') # Regenerate build self.build() @@ -358,45 +969,6 @@ class LinuxlikeTests(unittest.TestCase): self.assertIn("'-Werror'", vala_command) self.assertIn("'-Werror'", c_command) - def test_static_compile_order(self): - ''' - Test that the order of files in a compiler command-line while compiling - and linking statically is deterministic. This can't be an ordinary test - case because we need to inspect the compiler database. - https://github.com/mesonbuild/meson/pull/951 - ''' - testdir = os.path.join(self.common_test_dir, '5 linkstatic') - self.init(testdir) - compdb = self.get_compdb() - # Rules will get written out in this order - self.assertTrue(compdb[0]['file'].endswith("libfile.c")) - self.assertTrue(compdb[1]['file'].endswith("libfile2.c")) - self.assertTrue(compdb[2]['file'].endswith("libfile3.c")) - self.assertTrue(compdb[3]['file'].endswith("libfile4.c")) - # FIXME: We don't have access to the linker command - - def test_install_introspection(self): - ''' - Tests that the Meson introspection API exposes install filenames correctly - https://github.com/mesonbuild/meson/issues/829 - ''' - testdir = os.path.join(self.common_test_dir, '8 install') - self.init(testdir) - intro = self.introspect('--targets') - if intro[0]['type'] == 'executable': - intro = intro[::-1] - self.assertEqual(intro[0]['install_filename'], '/usr/lib/libstat.a') - self.assertEqual(intro[1]['install_filename'], '/usr/bin/prog') - - def test_run_target_files_path(self): - ''' - Test that run_targets are run from the correct directory - https://github.com/mesonbuild/meson/issues/957 - ''' - testdir = os.path.join(self.common_test_dir, '58 run target') - self.init(testdir) - self.run_target('check_exists') - def test_qt5dependency_qmake_detection(self): ''' Test that qt5 detection with qmake works. This can't be an ordinary @@ -500,16 +1072,6 @@ class LinuxlikeTests(unittest.TestCase): Oargs = [arg for arg in cmd if arg.startswith('-O')] self.assertEqual(Oargs, [Oflag, '-O0']) - def test_uninstall(self): - exename = os.path.join(self.installdir, 'usr/bin/prog') - testdir = os.path.join(self.common_test_dir, '8 install') - self.init(testdir) - self.assertFalse(os.path.exists(exename)) - self.install() - self.assertTrue(os.path.exists(exename)) - self.uninstall() - self.assertFalse(os.path.exists(exename)) - def test_custom_target_exe_data_deterministic(self): testdir = os.path.join(self.common_test_dir, '117 custom target capture') self.init(testdir) @@ -519,79 +1081,6 @@ class LinuxlikeTests(unittest.TestCase): meson_exe_dat2 = glob(os.path.join(self.privatedir, 'meson_exe*.dat')) self.assertListEqual(meson_exe_dat1, meson_exe_dat2) - def test_testsetups(self): - if not shutil.which('valgrind'): - raise unittest.SkipTest('Valgrind not installed.') - testdir = os.path.join(self.unit_test_dir, '2 testsetups') - self.init(testdir) - self.build() - self.run_tests() - with open(os.path.join(self.logdir, 'testlog.txt')) as f: - basic_log = f.read() - self.assertRaises(subprocess.CalledProcessError, - self._run, self.mtest_command + ['--setup=valgrind']) - with open(os.path.join(self.logdir, 'testlog-valgrind.txt')) as f: - vg_log = f.read() - self.assertFalse('TEST_ENV is set' in basic_log) - self.assertFalse('Memcheck' in basic_log) - self.assertTrue('TEST_ENV is set' in vg_log) - self.assertTrue('Memcheck' in vg_log) - - def assertFailedTestCount(self, failure_count, command): - try: - self._run(command) - self.assertEqual(0, failure_count, 'Expected %d tests to fail.' % failure_count) - except subprocess.CalledProcessError as e: - self.assertEqual(e.returncode, failure_count) - - def test_suite_selection(self): - testdir = os.path.join(self.unit_test_dir, '4 suite selection') - self.init(testdir) - self.build() - - self.assertFailedTestCount(3, self.mtest_command) - - self.assertFailedTestCount(0, self.mtest_command + ['--suite', ':success']) - self.assertFailedTestCount(3, self.mtest_command + ['--suite', ':fail']) - self.assertFailedTestCount(3, self.mtest_command + ['--no-suite', ':success']) - self.assertFailedTestCount(0, self.mtest_command + ['--no-suite', ':fail']) - - self.assertFailedTestCount(1, self.mtest_command + ['--suite', 'mainprj']) - self.assertFailedTestCount(0, self.mtest_command + ['--suite', 'subprjsucc']) - self.assertFailedTestCount(1, self.mtest_command + ['--suite', 'subprjfail']) - self.assertFailedTestCount(1, self.mtest_command + ['--suite', 'subprjmix']) - self.assertFailedTestCount(2, self.mtest_command + ['--no-suite', 'mainprj']) - self.assertFailedTestCount(3, self.mtest_command + ['--no-suite', 'subprjsucc']) - self.assertFailedTestCount(2, self.mtest_command + ['--no-suite', 'subprjfail']) - self.assertFailedTestCount(2, self.mtest_command + ['--no-suite', 'subprjmix']) - - self.assertFailedTestCount(1, self.mtest_command + ['--suite', 'mainprj:fail']) - self.assertFailedTestCount(0, self.mtest_command + ['--suite', 'mainprj:success']) - self.assertFailedTestCount(2, self.mtest_command + ['--no-suite', 'mainprj:fail']) - self.assertFailedTestCount(3, self.mtest_command + ['--no-suite', 'mainprj:success']) - - self.assertFailedTestCount(1, self.mtest_command + ['--suite', 'subprjfail:fail']) - self.assertFailedTestCount(0, self.mtest_command + ['--suite', 'subprjfail:success']) - self.assertFailedTestCount(2, self.mtest_command + ['--no-suite', 'subprjfail:fail']) - self.assertFailedTestCount(3, self.mtest_command + ['--no-suite', 'subprjfail:success']) - - self.assertFailedTestCount(0, self.mtest_command + ['--suite', 'subprjsucc:fail']) - self.assertFailedTestCount(0, self.mtest_command + ['--suite', 'subprjsucc:success']) - self.assertFailedTestCount(3, self.mtest_command + ['--no-suite', 'subprjsucc:fail']) - self.assertFailedTestCount(3, self.mtest_command + ['--no-suite', 'subprjsucc:success']) - - self.assertFailedTestCount(1, self.mtest_command + ['--suite', 'subprjmix:fail']) - self.assertFailedTestCount(0, self.mtest_command + ['--suite', 'subprjmix:success']) - self.assertFailedTestCount(2, self.mtest_command + ['--no-suite', 'subprjmix:fail']) - self.assertFailedTestCount(3, self.mtest_command + ['--no-suite', 'subprjmix:success']) - - self.assertFailedTestCount(2, self.mtest_command + ['--suite', 'subprjfail', '--suite', 'subprjmix:fail']) - self.assertFailedTestCount(3, self.mtest_command + ['--suite', 'subprjfail', '--suite', 'subprjmix', '--suite', 'mainprj']) - self.assertFailedTestCount(2, self.mtest_command + ['--suite', 'subprjfail', '--suite', 'subprjmix', '--suite', 'mainprj', '--no-suite', 'subprjmix:fail']) - self.assertFailedTestCount(1, self.mtest_command + ['--suite', 'subprjfail', '--suite', 'subprjmix', '--suite', 'mainprj', '--no-suite', 'subprjmix:fail', 'mainprj-failing_test']) - - self.assertFailedTestCount(1, self.mtest_command + ['--no-suite', 'subprjfail:fail', '--no-suite', 'subprjmix:fail']) - def _test_stds_impl(self, testdir, compiler, p): lang_std = p + '_std' # Check that all the listed -std=xxx options for this compiler work @@ -644,31 +1133,6 @@ class LinuxlikeTests(unittest.TestCase): cpp = env.detect_cpp_compiler(False) self._test_stds_impl(testdir, cpp, 'cpp') - def test_build_by_default(self): - testdir = os.path.join(self.common_test_dir, '137 build by default') - self.init(testdir) - self.build() - genfile = os.path.join(self.builddir, 'generated.dat') - exe = os.path.join(self.builddir, 'fooprog') - self.assertTrue(os.path.exists(genfile)) - self.assertFalse(os.path.exists(exe)) - self._run(self.ninja_command + ['fooprog']) - self.assertTrue(os.path.exists(exe)) - - def test_libdir_must_be_inside_prefix(self): - testdir = os.path.join(self.common_test_dir, '1 trivial') - # libdir being inside prefix is ok - args = ['--prefix', '/opt', '--libdir', '/opt/lib32'] - self.init(testdir, args) - self.wipe() - # libdir not being inside prefix is not ok - args = ['--prefix', '/usr', '--libdir', '/opt/lib32'] - self.assertRaises(subprocess.CalledProcessError, self.init, testdir, args) - self.wipe() - # libdir must be inside prefix even when set via mesonconf - self.init(testdir) - self.assertRaises(subprocess.CalledProcessError, self.setconf, '-Dlibdir=/opt') - def test_installed_modes(self): ''' Test that files installed by these tests have the correct permissions. @@ -722,47 +1186,15 @@ class LinuxlikeTests(unittest.TestCase): # The chown failed nonfatally if we're not root self.assertEqual(0, statf.st_uid) - def test_internal_include_order(self): - testdir = os.path.join(self.common_test_dir, '138 include order') - self.init(testdir) - for cmd in self.get_compdb(): - if cmd['file'].endswith('/main.c'): - cmd = cmd['command'] - break - else: - raise Exception('Could not find main.c command') - incs = [a for a in shlex.split(cmd) if a.startswith("-I")] - self.assertEqual(len(incs), 8) - # target private dir - self.assertEqual(incs[0], "-Isub4/someexe@exe") - # target build subdir - self.assertEqual(incs[1], "-Isub4") - # target source subdir - msg = "{!r} does not end with '/sub4'".format(incs[2]) - self.assertTrue(incs[2].endswith("/sub4"), msg) - # include paths added via per-target c_args: ['-I'...] - msg = "{!r} does not end with '/sub3'".format(incs[3]) - self.assertTrue(incs[3].endswith("/sub3"), msg) - # target include_directories: build dir - self.assertEqual(incs[4], "-Isub2") - # target include_directories: source dir - msg = "{!r} does not end with '/sub2'".format(incs[5]) - self.assertTrue(incs[5].endswith("/sub2"), msg) - # target internal dependency include_directories: build dir - self.assertEqual(incs[6], "-Isub1") - # target internal dependency include_directories: source dir - msg = "{!r} does not end with '/sub1'".format(incs[7]) - self.assertTrue(incs[7].endswith("/sub1"), msg) - class RewriterTests(unittest.TestCase): def setUp(self): super().setUp() src_root = os.path.dirname(__file__) - self.testroot = tempfile.mkdtemp() + self.testroot = os.path.realpath(tempfile.mkdtemp()) self.rewrite_command = [sys.executable, os.path.join(src_root, 'mesonrewriter.py')] - self.tmpdir = tempfile.mkdtemp() + self.tmpdir = os.path.realpath(tempfile.mkdtemp()) self.workdir = os.path.join(self.tmpdir, 'foo') self.test_dir = os.path.join(src_root, 'test cases/rewrite') @@ -785,34 +1217,38 @@ class RewriterTests(unittest.TestCase): def test_basic(self): self.prime('1 basic') - subprocess.check_output(self.rewrite_command + ['remove', - '--target=trivialprog', - '--filename=notthere.c', - '--sourcedir', self.workdir]) + subprocess.check_call(self.rewrite_command + ['remove', + '--target=trivialprog', + '--filename=notthere.c', + '--sourcedir', self.workdir], + universal_newlines=True) self.check_effectively_same('meson.build', 'removed.txt') - subprocess.check_output(self.rewrite_command + ['add', - '--target=trivialprog', - '--filename=notthere.c', - '--sourcedir', self.workdir]) + subprocess.check_call(self.rewrite_command + ['add', + '--target=trivialprog', + '--filename=notthere.c', + '--sourcedir', self.workdir], + universal_newlines=True) self.check_effectively_same('meson.build', 'added.txt') - subprocess.check_output(self.rewrite_command + ['remove', - '--target=trivialprog', - '--filename=notthere.c', - '--sourcedir', self.workdir]) + subprocess.check_call(self.rewrite_command + ['remove', + '--target=trivialprog', + '--filename=notthere.c', + '--sourcedir', self.workdir], + universal_newlines=True) self.check_effectively_same('meson.build', 'removed.txt') def test_subdir(self): self.prime('2 subdirs') top = self.read_contents('meson.build') s2 = self.read_contents('sub2/meson.build') - subprocess.check_output(self.rewrite_command + ['remove', - '--target=something', - '--filename=second.c', - '--sourcedir', self.workdir]) + subprocess.check_call(self.rewrite_command + ['remove', + '--target=something', + '--filename=second.c', + '--sourcedir', self.workdir], + universal_newlines=True) self.check_effectively_same('sub1/meson.build', 'sub1/after.txt') self.assertEqual(top, self.read_contents('meson.build')) self.assertEqual(s2, self.read_contents('sub2/meson.build')) if __name__ == '__main__': - unittest.main() + unittest.main(buffer=True) diff --git a/syntax-highlighting/vim/syntax/meson.vim b/syntax-highlighting/vim/syntax/meson.vim index 0799237..49921c1 100644 --- a/syntax-highlighting/vim/syntax/meson.vim +++ b/syntax-highlighting/vim/syntax/meson.vim @@ -67,8 +67,8 @@ syn keyword mesonBuiltin \ add_global_link_arguments \ add_languages \ add_project_arguments - \ add_project_arguments \ add_project_link_arguments + \ add_test_setup \ benchmark \ build_machine \ build_target @@ -94,7 +94,6 @@ syn keyword mesonBuiltin \ install_headers \ install_man \ install_subdir - \ is_subproject \ is_variable \ jar \ join_paths diff --git a/test cases/common/105 find program path/meson.build b/test cases/common/105 find program path/meson.build index ba6030b..e1e6d2e 100644 --- a/test cases/common/105 find program path/meson.build +++ b/test cases/common/105 find program path/meson.build @@ -1,10 +1,25 @@ project('find program', 'c') -prog = find_program('program.py') - python = find_program('python3', required : false) if not python.found() python = find_program('python') endif -run_command(python, prog.path()) +# Source file via string +prog = find_program('program.py') +# Source file via files() +progf = files('program.py') +# Built file +py = configure_file(input : 'program.py', + output : 'builtprogram.py', + configuration : configuration_data()) + +foreach f : [prog, find_program(py), find_program(progf)] + ret = run_command(python, f.path()) + assert(ret.returncode() == 0, 'can\'t manually run @0@'.format(prog.path())) + assert(ret.stdout().strip() == 'Found', 'wrong output from manually-run @0@'.format(prog.path())) + + ret = run_command(f) + assert(ret.returncode() == 0, 'can\'t run @0@'.format(prog.path())) + assert(ret.stdout().strip() == 'Found', 'wrong output from @0@'.format(prog.path())) +endforeach diff --git a/test cases/common/105 find program path/program.py b/test cases/common/105 find program path/program.py index b910718..2ebc564 100644 --- a/test cases/common/105 find program path/program.py +++ b/test cases/common/105 find program path/program.py @@ -1,3 +1,3 @@ -#!/usr/bin/env python +#!/usr/bin/env python3 print("Found") diff --git a/test cases/common/107 postconf/postconf.py b/test cases/common/107 postconf/postconf.py index 9a23cfa..950c706 100644 --- a/test cases/common/107 postconf/postconf.py +++ b/test cases/common/107 postconf/postconf.py @@ -1,4 +1,4 @@ -#!/usr/bin/env python +#!/usr/bin/env python3 import os diff --git a/test cases/common/108 postconf with args/postconf.py b/test cases/common/108 postconf with args/postconf.py index 3ed0450..cef7f79 100644 --- a/test cases/common/108 postconf with args/postconf.py +++ b/test cases/common/108 postconf with args/postconf.py @@ -1,4 +1,4 @@ -#!/usr/bin/env python +#!/usr/bin/env python3 import sys, os diff --git a/test cases/common/113 generatorcustom/catter.py b/test cases/common/113 generatorcustom/catter.py index a79b739..198fa98 100755 --- a/test cases/common/113 generatorcustom/catter.py +++ b/test cases/common/113 generatorcustom/catter.py @@ -1,4 +1,4 @@ -#!/usr/bin/env python +#!/usr/bin/env python3 import sys diff --git a/test cases/common/113 generatorcustom/gen.py b/test cases/common/113 generatorcustom/gen.py index f9efb47..c1e34ed 100755 --- a/test cases/common/113 generatorcustom/gen.py +++ b/test cases/common/113 generatorcustom/gen.py @@ -1,4 +1,4 @@ -#!/usr/bin/env python +#!/usr/bin/env python3 import sys diff --git a/test cases/common/118 allgenerate/converter.py b/test cases/common/118 allgenerate/converter.py index cc2c574..f8e2ca0 100755 --- a/test cases/common/118 allgenerate/converter.py +++ b/test cases/common/118 allgenerate/converter.py @@ -1,4 +1,4 @@ -#!/usr/bin/env python +#!/usr/bin/env python3 import sys diff --git a/test cases/common/118 allgenerate/meson.build b/test cases/common/118 allgenerate/meson.build index 36abbe9..049e849 100644 --- a/test cases/common/118 allgenerate/meson.build +++ b/test cases/common/118 allgenerate/meson.build @@ -13,7 +13,7 @@ c = g.process('foobar.cpp.in') prog = executable('genexe', c) c2 = custom_target('c2gen', - output : 'c2gen.cpp', + output : '@BASENAME@', input : 'foobar.cpp.in', command : [comp, '@INPUT@', '@OUTPUT@']) diff --git a/test cases/common/119 pathjoin/meson.build b/test cases/common/119 pathjoin/meson.build index 7f33791..751ca68 100644 --- a/test cases/common/119 pathjoin/meson.build +++ b/test cases/common/119 pathjoin/meson.build @@ -1,8 +1,17 @@ project('pathjoin', 'c') +# Test string-args form since that is the canonical way assert(join_paths('foo') == 'foo', 'Single argument join is broken') assert(join_paths('foo', 'bar') == 'foo/bar', 'Path joining is broken') assert(join_paths('foo', 'bar', 'baz') == 'foo/bar/baz', 'Path joining is broken') assert(join_paths('/foo', 'bar') == '/foo/bar', 'Path joining is broken') assert(join_paths('foo', '/bar') == '/bar', 'Absolute path joining is broken') assert(join_paths('/foo', '/bar') == '/bar', 'Absolute path joining is broken') + +# Test array form since people are using that too +assert(join_paths(['foo']) == 'foo', 'Single argument join is broken') +assert(join_paths(['foo', 'bar']) == 'foo/bar', 'Path joining is broken') +assert(join_paths(['foo', 'bar', 'baz']) == 'foo/bar/baz', 'Path joining is broken') +assert(join_paths(['/foo', 'bar']) == '/foo/bar', 'Path joining is broken') +assert(join_paths(['foo', '/bar']) == '/bar', 'Absolute path joining is broken') +assert(join_paths(['/foo', '/bar']) == '/bar', 'Absolute path joining is broken') diff --git a/test cases/common/129 object only target/meson.build b/test cases/common/129 object only target/meson.build index 58d01d9..d83a658 100644 --- a/test cases/common/129 object only target/meson.build +++ b/test cases/common/129 object only target/meson.build @@ -16,7 +16,7 @@ cc = meson.get_compiler('c').cmd_array().get(-1) # provided by the source tree source1 = configure_file(input : 'source.c', output : 'source' + ext, - command : [comp, cc, 'source.c', + command : [comp, cc, files('source.c'), join_paths(meson.current_build_dir(), 'source' + ext)]) obj = static_library('obj', objects : source1) diff --git a/test cases/common/129 object only target/obj_generator.py b/test cases/common/129 object only target/obj_generator.py index f0239b4..a33872a 100755 --- a/test cases/common/129 object only target/obj_generator.py +++ b/test cases/common/129 object only target/obj_generator.py @@ -1,4 +1,4 @@ -#!/usr/bin/env python +#!/usr/bin/env python3 # Mimic a binary that generates an object file (e.g. windres). diff --git a/test cases/common/131 custom target directory install/docgen.py b/test cases/common/131 custom target directory install/docgen.py index 4d80124..245f370 100644 --- a/test cases/common/131 custom target directory install/docgen.py +++ b/test cases/common/131 custom target directory install/docgen.py @@ -1,4 +1,4 @@ -#!/usr/bin/env python +#!/usr/bin/env python3 import os import sys diff --git a/test cases/common/133 configure file in generator/src/gen.py b/test cases/common/133 configure file in generator/src/gen.py index 5bccece..99b7cdd 100755 --- a/test cases/common/133 configure file in generator/src/gen.py +++ b/test cases/common/133 configure file in generator/src/gen.py @@ -1,4 +1,4 @@ -#!/usr/bin/env python +#!/usr/bin/env python3 import sys diff --git a/test cases/common/134 generated llvm ir/copyfile.py b/test cases/common/134 generated llvm ir/copyfile.py index da503e2..ff42ac3 100644 --- a/test cases/common/134 generated llvm ir/copyfile.py +++ b/test cases/common/134 generated llvm ir/copyfile.py @@ -1,4 +1,4 @@ -#!/usr/bin/env python +#!/usr/bin/env python3 import sys import shutil diff --git a/test cases/common/135 generated assembly/copyfile.py b/test cases/common/135 generated assembly/copyfile.py index da503e2..ff42ac3 100644 --- a/test cases/common/135 generated assembly/copyfile.py +++ b/test cases/common/135 generated assembly/copyfile.py @@ -1,4 +1,4 @@ -#!/usr/bin/env python +#!/usr/bin/env python3 import sys import shutil diff --git a/test cases/common/16 configure file/check_file.py b/test cases/common/16 configure file/check_file.py new file mode 100644 index 0000000..449b77a --- /dev/null +++ b/test cases/common/16 configure file/check_file.py @@ -0,0 +1,6 @@ +#!/usr/bin/env python3 + +import os +import sys + +assert(os.path.exists(sys.argv[1])) diff --git a/test cases/common/16 configure file/generator.py b/test cases/common/16 configure file/generator.py index 2c7f2f8..e3cc881 100755 --- a/test cases/common/16 configure file/generator.py +++ b/test cases/common/16 configure file/generator.py @@ -1,15 +1,17 @@ -#!/usr/bin/env python +#!/usr/bin/env python3 -# On some platforms "python" points to Python 2 -# on others to Python 3. Work with both. - -from __future__ import print_function import sys, os +from pathlib import Path if len(sys.argv) != 3: print("Wrong amount of parameters.") -assert(os.path.exists(sys.argv[1])) +build_dir = Path(os.environ['MESON_BUILD_ROOT']) +subdir = Path(os.environ['MESON_SUBDIR']) +inputf = Path(sys.argv[1]) +outputf = Path(sys.argv[2]) + +assert(inputf.exists()) -with open(sys.argv[2], 'w') as ofile: +with outputf.open('w') as ofile: ofile.write("#define ZERO_RESULT 0\n") diff --git a/test cases/common/16 configure file/installed_files.txt b/test cases/common/16 configure file/installed_files.txt index 219b4c0..d9fee12 100644 --- a/test cases/common/16 configure file/installed_files.txt +++ b/test cases/common/16 configure file/installed_files.txt @@ -1 +1,3 @@ usr/share/appdir/config2.h +usr/share/appdireh/config2-1.h +usr/share/appdirok/config2-2.h diff --git a/test cases/common/16 configure file/meson.build b/test cases/common/16 configure file/meson.build index b764c5a..bff041b 100644 --- a/test cases/common/16 configure file/meson.build +++ b/test cases/common/16 configure file/meson.build @@ -23,18 +23,22 @@ cfile) test('inctest', e) # Now generate a header file with an external script. -genprog = find_program('python3', required : false) -if not genprog.found() - genprog = find_program('python') -endif +genprog = import('python3').find_python() scriptfile = '@0@/generator.py'.format(meson.current_source_dir()) ifile = '@0@/dummy.dat'.format(meson.current_source_dir()) ofile = '@0@/config2.h'.format(meson.current_build_dir()) +check_file = find_program('check_file.py') +# Configure in source root with command and absolute paths configure_file(input : 'dummy.dat', -output : 'config2.h', -command : [genprog, scriptfile, ifile, ofile], -install_dir : 'share/appdir') + output : 'config2.h', + command : [genprog, scriptfile, ifile, ofile], + install_dir : 'share/appdir') +run_command(check_file, join_paths(meson.current_build_dir(), 'config2.h')) + +found_script = find_program('generator.py') +# More configure_file tests in here +subdir('subdir') test('inctest2', executable('prog2', 'prog2.c')) diff --git a/test cases/common/16 configure file/subdir/meson.build b/test cases/common/16 configure file/subdir/meson.build new file mode 100644 index 0000000..d802c1d --- /dev/null +++ b/test cases/common/16 configure file/subdir/meson.build @@ -0,0 +1,19 @@ +# Configure in subdir with absolute paths for input and relative for output +configure_file(input : '../dummy.dat', + output : 'config2-1.h', + command : [genprog, scriptfile, ifile, 'config2-1.h'], + install_dir : 'share/appdireh') +run_command(check_file, join_paths(meson.current_build_dir(), 'config2-1.h')) + +# Configure in subdir with files() for input and relative for output +configure_file(input : '../dummy.dat', + output : 'config2-2.h', + command : [genprog, scriptfile, files('../dummy.dat'), 'config2-2.h'], + install_dir : 'share/appdirok') +run_command(check_file, join_paths(meson.current_build_dir(), 'config2-2.h')) + +# Configure in subdir with string templates for input and output +configure_file(input : '../dummy.dat', + output : 'config2-3.h', + command : [found_script, '@INPUT@', '@OUTPUT@']) +run_command(check_file, join_paths(meson.current_build_dir(), 'config2-3.h')) diff --git a/test cases/common/3 static/libfile2.c b/test cases/common/3 static/libfile2.c new file mode 100644 index 0000000..86bbb2c --- /dev/null +++ b/test cases/common/3 static/libfile2.c @@ -0,0 +1,3 @@ +int libfunc2() { + return 4; +} diff --git a/test cases/common/3 static/meson.build b/test cases/common/3 static/meson.build index 3dee93b..e539956 100644 --- a/test cases/common/3 static/meson.build +++ b/test cases/common/3 static/meson.build @@ -1,3 +1,4 @@ project('static library test', 'c') -lib = static_library('mylib', 'libfile.c', + +lib = static_library('mylib', get_option('source'), link_args : '-THISMUSTNOBEUSED') # Static linker needs to ignore all link args. diff --git a/test cases/common/3 static/meson_options.txt b/test cases/common/3 static/meson_options.txt new file mode 100644 index 0000000..7261a19 --- /dev/null +++ b/test cases/common/3 static/meson_options.txt @@ -0,0 +1 @@ +option('source', type : 'combo', choices : ['libfile.c', 'libfile2.c'], value : 'libfile.c') diff --git a/test cases/common/48 test args/tester.py b/test cases/common/48 test args/tester.py index c3c1edc..0b4010a 100755 --- a/test cases/common/48 test args/tester.py +++ b/test cases/common/48 test args/tester.py @@ -1,4 +1,4 @@ -#!/usr/bin/env python +#!/usr/bin/env python3 import sys diff --git a/test cases/common/56 custom target/meson.build b/test cases/common/56 custom target/meson.build index fd59fbd..2e6f69c 100644 --- a/test cases/common/56 custom target/meson.build +++ b/test cases/common/56 custom target/meson.build @@ -8,11 +8,13 @@ endif # Note that this will not add a dependency to the compiler executable. # Code will not be rebuilt if it changes. comp = '@0@/@1@'.format(meson.current_source_dir(), 'my_compiler.py') +# Test that files() in command: works. The compiler just discards it. +useless = files('installed_files.txt') mytarget = custom_target('bindat', output : 'data.dat', input : 'data_source.txt', -command : [python, comp, '--input=@INPUT@', '--output=@OUTPUT@'], +command : [python, comp, '--input=@INPUT@', '--output=@OUTPUT@', useless], install : true, install_dir : 'subdir' ) diff --git a/test cases/common/56 custom target/my_compiler.py b/test cases/common/56 custom target/my_compiler.py index 4ba2da6..f46d23a 100755 --- a/test cases/common/56 custom target/my_compiler.py +++ b/test cases/common/56 custom target/my_compiler.py @@ -1,16 +1,21 @@ #!/usr/bin/env python3 +import os import sys +assert(os.path.exists(sys.argv[3])) + +args = sys.argv[:-1] + if __name__ == '__main__': - if len(sys.argv) != 3 or not sys.argv[1].startswith('--input') or \ - not sys.argv[2].startswith('--output'): - print(sys.argv[0], '--input=input_file --output=output_file') + if len(args) != 3 or not args[1].startswith('--input') or \ + not args[2].startswith('--output'): + print(args[0], '--input=input_file --output=output_file') sys.exit(1) - with open(sys.argv[1].split('=')[1]) as f: + with open(args[1].split('=')[1]) as f: ifile = f.read() if ifile != 'This is a text only input file.\n': print('Malformed input') sys.exit(1) - with open(sys.argv[2].split('=')[1], 'w') as ofile: + with open(args[2].split('=')[1], 'w') as ofile: ofile.write('This is a binary output file.\n') diff --git a/test cases/common/58 run target/check_exists.py b/test cases/common/58 run target/check_exists.py index 62cbe23..b6fc967 100755 --- a/test cases/common/58 run target/check_exists.py +++ b/test cases/common/58 run target/check_exists.py @@ -1,4 +1,4 @@ -#!/usr/bin/env python +#!/usr/bin/env python3 import os import sys diff --git a/test cases/common/58 run target/converter.py b/test cases/common/58 run target/converter.py index 9f47ba5..8dd31fe 100644 --- a/test cases/common/58 run target/converter.py +++ b/test cases/common/58 run target/converter.py @@ -1,4 +1,4 @@ -#!/usr/bin/env python +#!/usr/bin/env python3 import sys diff --git a/test cases/common/58 run target/fakeburner.py b/test cases/common/58 run target/fakeburner.py index 7f505d6..da3d0ac 100755 --- a/test cases/common/58 run target/fakeburner.py +++ b/test cases/common/58 run target/fakeburner.py @@ -1,4 +1,4 @@ -#!/usr/bin/env python +#!/usr/bin/env python3 from __future__ import print_function diff --git a/test cases/common/58 run target/meson.build b/test cases/common/58 run target/meson.build index 8a06490..a1c5ad8 100644 --- a/test cases/common/58 run target/meson.build +++ b/test cases/common/58 run target/meson.build @@ -19,15 +19,17 @@ hex = custom_target('exe.hex', ], ) +fakeburner = find_program('fakeburner.py') + # These emulates the Arduino flasher application. It sandwiches the filename inside # a packed argument. Thus we need to declare it manually. run_target('upload', - command : ['fakeburner.py', 'x:@0@:y'.format(exe.full_path())], + command : [fakeburner, 'x:@0@:y'.format(exe.full_path())], depends : exe, ) run_target('upload2', - command : ['fakeburner.py', 'x:@0@:y'.format(hex.full_path())], + command : [fakeburner, 'x:@0@:y'.format(hex.full_path())], depends : hex, ) diff --git a/test cases/common/60 install script/myinstall.py b/test cases/common/60 install script/myinstall.py index 969aba5..812561e 100644 --- a/test cases/common/60 install script/myinstall.py +++ b/test cases/common/60 install script/myinstall.py @@ -1,4 +1,4 @@ -#!/usr/bin/env python +#!/usr/bin/env python3 import os import sys diff --git a/test cases/common/60 install script/src/myinstall.py b/test cases/common/60 install script/src/myinstall.py index d8a5714..3b7ce37 100644 --- a/test cases/common/60 install script/src/myinstall.py +++ b/test cases/common/60 install script/src/myinstall.py @@ -1,4 +1,4 @@ -#!/usr/bin/env python +#!/usr/bin/env python3 import os import sys diff --git a/test cases/common/61 custom target source output/generator.py b/test cases/common/61 custom target source output/generator.py index 42532ca..3464b0a 100755 --- a/test cases/common/61 custom target source output/generator.py +++ b/test cases/common/61 custom target source output/generator.py @@ -1,4 +1,4 @@ -#!/usr/bin/env python +#!/usr/bin/env python3 import sys, os diff --git a/test cases/common/64 custom header generator/makeheader.py b/test cases/common/64 custom header generator/makeheader.py index 0c5a228..f156834 100644 --- a/test cases/common/64 custom header generator/makeheader.py +++ b/test cases/common/64 custom header generator/makeheader.py @@ -1,4 +1,4 @@ -#!/usr/bin/env python +#!/usr/bin/env python3 # NOTE: this file does not have the executable bit set. This tests that # Meson can automatically parse shebang lines. diff --git a/test cases/common/65 multiple generators/mygen.py b/test cases/common/65 multiple generators/mygen.py index 020a389..99dc331 100755 --- a/test cases/common/65 multiple generators/mygen.py +++ b/test cases/common/65 multiple generators/mygen.py @@ -1,4 +1,4 @@ -#!/usr/bin/env python +#!/usr/bin/env python3 import sys, os diff --git a/test cases/common/72 build always/version_gen.py b/test cases/common/72 build always/version_gen.py index 3973e61..d7b01ca 100755 --- a/test cases/common/72 build always/version_gen.py +++ b/test cases/common/72 build always/version_gen.py @@ -1,4 +1,4 @@ -#!/usr/bin/env python +#!/usr/bin/env python3 import sys, os, subprocess diff --git a/test cases/common/76 configure file in custom target/src/mycompiler.py b/test cases/common/76 configure file in custom target/src/mycompiler.py index e1750f8..b00c862 100644 --- a/test cases/common/76 configure file in custom target/src/mycompiler.py +++ b/test cases/common/76 configure file in custom target/src/mycompiler.py @@ -1,4 +1,4 @@ -#!/usr/bin/env python +#!/usr/bin/env python3 import sys diff --git a/test cases/common/77 external test program/mytest.py b/test cases/common/77 external test program/mytest.py index 7cdaf09..9947773 100755 --- a/test cases/common/77 external test program/mytest.py +++ b/test cases/common/77 external test program/mytest.py @@ -1,4 +1,4 @@ -#!/usr/bin/env python +#!/usr/bin/env python3 from __future__ import print_function diff --git a/test cases/common/78 ctarget dependency/gen1.py b/test cases/common/78 ctarget dependency/gen1.py index f920e53..0fa6ea1 100755 --- a/test cases/common/78 ctarget dependency/gen1.py +++ b/test cases/common/78 ctarget dependency/gen1.py @@ -1,4 +1,4 @@ -#!/usr/bin/env python +#!/usr/bin/env python3 import time, sys diff --git a/test cases/common/78 ctarget dependency/gen2.py b/test cases/common/78 ctarget dependency/gen2.py index fc60e1e..b087b02 100755 --- a/test cases/common/78 ctarget dependency/gen2.py +++ b/test cases/common/78 ctarget dependency/gen2.py @@ -1,4 +1,4 @@ -#!/usr/bin/env python +#!/usr/bin/env python3 import sys, os from glob import glob diff --git a/test cases/common/93 private include/stlib/compiler.py b/test cases/common/93 private include/stlib/compiler.py index 0555c16..98dbe46 100755 --- a/test cases/common/93 private include/stlib/compiler.py +++ b/test cases/common/93 private include/stlib/compiler.py @@ -1,4 +1,4 @@ -#!/usr/bin/env python +#!/usr/bin/env python3 import sys, os diff --git a/test cases/common/94 default options/meson.build b/test cases/common/94 default options/meson.build index a9176e0..9f45df0 100644 --- a/test cases/common/94 default options/meson.build +++ b/test cases/common/94 default options/meson.build @@ -1,4 +1,5 @@ project('default options', 'cpp', 'c', default_options : [ + 'prefix=/absoluteprefix', 'buildtype=debugoptimized', 'cpp_std=c++11', 'cpp_eh=none', diff --git a/test cases/common/95 dep fallback/gensrc.py b/test cases/common/95 dep fallback/gensrc.py index da503e2..ff42ac3 100644 --- a/test cases/common/95 dep fallback/gensrc.py +++ b/test cases/common/95 dep fallback/gensrc.py @@ -1,4 +1,4 @@ -#!/usr/bin/env python +#!/usr/bin/env python3 import sys import shutil diff --git a/test cases/common/95 dep fallback/subprojects/boblib/genbob.py b/test cases/common/95 dep fallback/subprojects/boblib/genbob.py index 7da3233..34af779 100644 --- a/test cases/common/95 dep fallback/subprojects/boblib/genbob.py +++ b/test cases/common/95 dep fallback/subprojects/boblib/genbob.py @@ -1,4 +1,4 @@ -#!/usr/bin/env python +#!/usr/bin/env python3 import sys diff --git a/test cases/common/98 gen extra/srcgen.py b/test cases/common/98 gen extra/srcgen.py index 86fd698..8988cd9 100755 --- a/test cases/common/98 gen extra/srcgen.py +++ b/test cases/common/98 gen extra/srcgen.py @@ -1,4 +1,4 @@ -#!/usr/bin/env python +#!/usr/bin/env python3 import sys import argparse diff --git a/test cases/failing/42 abs subdir/bob/meson.build b/test cases/failing/42 abs subdir/bob/meson.build new file mode 100644 index 0000000..7bbf4b2 --- /dev/null +++ b/test cases/failing/42 abs subdir/bob/meson.build @@ -0,0 +1,2 @@ +# This file is never reached. +x = 3 diff --git a/test cases/failing/42 abs subdir/meson.build b/test cases/failing/42 abs subdir/meson.build new file mode 100644 index 0000000..8c23224 --- /dev/null +++ b/test cases/failing/42 abs subdir/meson.build @@ -0,0 +1,6 @@ +project('abs subdir', 'c') + +# For some reason people insist on doing this, probably +# because Make has taught them to never rely on anything. +subdir(join_paths(meson.source_root(), 'bob')) + diff --git a/test cases/failing/42 abspath to srcdir/meson.build b/test cases/failing/42 abspath to srcdir/meson.build new file mode 100644 index 0000000..964a19b --- /dev/null +++ b/test cases/failing/42 abspath to srcdir/meson.build @@ -0,0 +1,3 @@ +project('meson', 'c') + +include_directories(meson.current_source_dir()) diff --git a/test cases/failing/42 custom target plainname many inputs/1.txt b/test cases/failing/42 custom target plainname many inputs/1.txt new file mode 100644 index 0000000..d00491f --- /dev/null +++ b/test cases/failing/42 custom target plainname many inputs/1.txt @@ -0,0 +1 @@ +1 diff --git a/test cases/failing/42 custom target plainname many inputs/2.txt b/test cases/failing/42 custom target plainname many inputs/2.txt new file mode 100644 index 0000000..0cfbf08 --- /dev/null +++ b/test cases/failing/42 custom target plainname many inputs/2.txt @@ -0,0 +1 @@ +2 diff --git a/test cases/failing/42 custom target plainname many inputs/catfiles.py b/test cases/failing/42 custom target plainname many inputs/catfiles.py new file mode 100644 index 0000000..1c53e24 --- /dev/null +++ b/test cases/failing/42 custom target plainname many inputs/catfiles.py @@ -0,0 +1,9 @@ +#!/usr/bin/env python3 + +import sys + +out = sys.argv[-1] +with open(out, 'wb') as o: + for infile in sys.argv[1:-1]: + with open(infile, 'rb') as f: + o.write(f.read()) diff --git a/test cases/failing/42 custom target plainname many inputs/meson.build b/test cases/failing/42 custom target plainname many inputs/meson.build new file mode 100644 index 0000000..1bcfc06 --- /dev/null +++ b/test cases/failing/42 custom target plainname many inputs/meson.build @@ -0,0 +1,8 @@ +project('plain name many inputs', 'c') + +catfiles = find_program('catfiles.py') + +custom_target('plainname-inputs', + input : ['1.txt', '2.txt'], + output : '@PLAINNAME@.dat', + command : [catfiles, '@INPUT@', '@OUTPUT@']) diff --git a/test cases/frameworks/7 gnome/resources-data/meson.build b/test cases/frameworks/7 gnome/resources-data/meson.build index fd8cb0a..9458c2d 100644 --- a/test cases/frameworks/7 gnome/resources-data/meson.build +++ b/test cases/frameworks/7 gnome/resources-data/meson.build @@ -1,5 +1,7 @@ subdir('subdir') +python3 = import('python3').find_python() + fake_generator_script = ''' import os, sys assert os.path.exists(sys.argv[1]), "File %s not found" % sys.argv[1] @@ -11,6 +13,6 @@ print("This is a generated resource.") res3_txt = custom_target('res3.txt', input: 'res3.txt.in', output: 'res3.txt', - command: ['python3', '-c', fake_generator_script, '@INPUT@'], + command: [python3, '-c', fake_generator_script, '@INPUT@'], capture: true, ) diff --git a/test cases/frameworks/7 gnome/resources/copyfile.py b/test cases/frameworks/7 gnome/resources/copyfile.py new file mode 100644 index 0000000..7e44c48 --- /dev/null +++ b/test cases/frameworks/7 gnome/resources/copyfile.py @@ -0,0 +1,6 @@ +#!/usr/bin/env python3 + +import sys +import shutil + +shutil.copy(sys.argv[1], sys.argv[2]) diff --git a/test cases/frameworks/7 gnome/resources/meson.build b/test cases/frameworks/7 gnome/resources/meson.build index 2e72501..fdf6f63 100644 --- a/test cases/frameworks/7 gnome/resources/meson.build +++ b/test cases/frameworks/7 gnome/resources/meson.build @@ -1,8 +1,15 @@ # There are two tests here, because the 2nd one depends on a version of -# GLib (2.48.2) that is very recent at the time of writing. +# GLib (2.51.1) that is very recent at the time of writing. + +copyfile = find_program('copyfile.py') + +simple_gresource = configure_file( + input : 'simple.gresource.xml', + output : 'simple-gen.gresource.xml', + command : [copyfile, '@INPUT@', '@OUTPUT@']) simple_resources = gnome.compile_resources('simple-resources', - 'simple.gresource.xml', + simple_gresource, install_header : true, export : true, source_dir : '../resources-data', diff --git a/test cases/linuxlike/5 dependency versions/meson.build b/test cases/linuxlike/5 dependency versions/meson.build index 20b3df5..1b01cd6 100644 --- a/test cases/linuxlike/5 dependency versions/meson.build +++ b/test cases/linuxlike/5 dependency versions/meson.build @@ -1,4 +1,4 @@ -project('dep versions', 'c') +project('dep versions', 'c', 'cpp') # Find external dependency without version zlib = dependency('zlib') diff --git a/test cases/objc/2 nsstring/meson.build b/test cases/objc/2 nsstring/meson.build index bc997bc..ec496a2 100644 --- a/test cases/objc/2 nsstring/meson.build +++ b/test cases/objc/2 nsstring/meson.build @@ -4,6 +4,9 @@ if host_machine.system() == 'darwin' dep = dependency('appleframeworks', modules : 'foundation') else dep = dependency('gnustep') + if host_machine.system() == 'linux' and meson.get_compiler('objc').get_id() == 'clang' + error('MESON_SKIP_TEST: GNUstep is broken on Linux with Clang') + endif endif exe = executable('stringprog', 'stringprog.m', dependencies : dep) test('stringtest', exe) diff --git a/test cases/unit/2 testsetups/buggy.c b/test cases/unit/2 testsetups/buggy.c index 5d20a24..d238830 100644 --- a/test cases/unit/2 testsetups/buggy.c +++ b/test cases/unit/2 testsetups/buggy.c @@ -5,10 +5,10 @@ int main(int argc, char **argv) { char *ten = malloc(10); - do_nasty(ten); - free(ten); if(getenv("TEST_ENV")) { + do_nasty(ten); printf("TEST_ENV is set.\n"); } + free(ten); return 0; } diff --git a/test cases/unit/2 testsetups/meson.build b/test cases/unit/2 testsetups/meson.build index a65548e..488cf21 100644 --- a/test cases/unit/2 testsetups/meson.build +++ b/test cases/unit/2 testsetups/meson.build @@ -14,3 +14,7 @@ add_test_setup('valgrind', buggy = executable('buggy', 'buggy.c', 'impl.c') test('Test buggy', buggy) +add_test_setup('empty') +add_test_setup('onlyenv', env : env) +add_test_setup('wrapper', exe_wrapper : [vg, '--error-exitcode=1']) +add_test_setup('timeout', timeout_multiplier : 20) diff --git a/test cases/unit/5 compiler detection/compiler wrapper.py b/test cases/unit/5 compiler detection/compiler wrapper.py new file mode 100644 index 0000000..fedd343 --- /dev/null +++ b/test cases/unit/5 compiler detection/compiler wrapper.py @@ -0,0 +1,6 @@ +#!/usr/bin/env python3 + +import sys +import subprocess + +sys.exit(subprocess.call(sys.argv[1:])) diff --git a/test cases/unit/5 compiler detection/meson.build b/test cases/unit/5 compiler detection/meson.build new file mode 100644 index 0000000..5491c64 --- /dev/null +++ b/test cases/unit/5 compiler detection/meson.build @@ -0,0 +1,8 @@ +project('trivial test', + ['c', 'cpp', 'objc', 'objcpp'], + meson_version : '>=0.27.0') + +executable('trivialc', 'trivial.c') +executable('trivialcpp', 'trivial.cpp') +executable('trivialobjc', 'trivial.m') +executable('trivialobjcpp', 'trivial.mm') diff --git a/test cases/unit/5 compiler detection/trivial.c b/test cases/unit/5 compiler detection/trivial.c new file mode 100644 index 0000000..24ac454 --- /dev/null +++ b/test cases/unit/5 compiler detection/trivial.c @@ -0,0 +1,6 @@ +#include<stdio.h> + +int main(int argc, char **argv) { + printf("Trivial test is working.\n"); + return 0; +} diff --git a/test cases/unit/5 compiler detection/trivial.cc b/test cases/unit/5 compiler detection/trivial.cc new file mode 100644 index 0000000..8aa907b --- /dev/null +++ b/test cases/unit/5 compiler detection/trivial.cc @@ -0,0 +1,6 @@ +#include<iostream> + +int main(int argc, char **argv) { + std::cout << "C++ seems to be working." << std::endl; + return 0; +} diff --git a/test cases/unit/5 compiler detection/trivial.m b/test cases/unit/5 compiler detection/trivial.m new file mode 100644 index 0000000..f2e2315 --- /dev/null +++ b/test cases/unit/5 compiler detection/trivial.m @@ -0,0 +1,5 @@ +#import<stdio.h> + +int main(int argc, char **argv) { + return 0; +}
\ No newline at end of file diff --git a/test cases/unit/5 compiler detection/trivial.mm b/test cases/unit/5 compiler detection/trivial.mm new file mode 100644 index 0000000..927e810 --- /dev/null +++ b/test cases/unit/5 compiler detection/trivial.mm @@ -0,0 +1,9 @@ +#import<stdio.h> + +class MyClass { +}; + +int main(int argc, char **argv) { + return 0; +} + diff --git a/test cases/windows/9 find program/meson.build b/test cases/windows/9 find program/meson.build index ef34586..565fb62 100644 --- a/test cases/windows/9 find program/meson.build +++ b/test cases/windows/9 find program/meson.build @@ -1,4 +1,12 @@ project('find program', 'c') +# Test that we can find native windows executables +find_program('cmd') +find_program('cmd.exe') + +# Test that a script file with an extension can be found +ext = find_program('test-script-ext.py') +test('ext', ext) +# Test that a script file without an extension can be found prog = find_program('test-script') test('script', prog) diff --git a/test cases/windows/9 find program/test-script-ext.py b/test cases/windows/9 find program/test-script-ext.py new file mode 100644 index 0000000..ae9adfb --- /dev/null +++ b/test cases/windows/9 find program/test-script-ext.py @@ -0,0 +1,3 @@ +#!/usr/bin/env python3 + +print('ext/noext') |