diff options
113 files changed, 1740 insertions, 665 deletions
diff --git a/.appveyor.yml b/.appveyor.yml index 9264f49..96a4ed8 100644 --- a/.appveyor.yml +++ b/.appveyor.yml @@ -5,6 +5,10 @@ os: Visual Studio 2015 environment: matrix: - arch: x86 + compiler: msys2-mingw + backend: ninja + + - arch: x86 compiler: msvc2010 backend: ninja @@ -20,7 +24,11 @@ environment: compiler: msvc2015 backend: vs2015 - - arch: x86 + - arch: x64 + compiler: cygwin + backend: ninja + + - arch: x64 compiler: msys2-mingw backend: ninja @@ -34,10 +42,6 @@ environment: backend: vs2017 APPVEYOR_BUILD_WORKER_IMAGE: Visual Studio 2017 - - arch: x64 - compiler: msys2-mingw - backend: ninja - platform: - x64 @@ -55,15 +59,22 @@ install: - cmd: if %compiler%==msvc2015 ( call "C:\Program Files (x86)\Microsoft Visual Studio 14.0\VC\vcvarsall.bat" %arch% ) - cmd: if %compiler%==msvc2017 ( call "C:\Program Files (x86)\Microsoft Visual Studio\2017\Community\Common7\Tools\VsDevCmd.bat" -arch=%arch% ) - cmd: if %compiler%==msys2-mingw (if %arch%==x86 (set "PATH=C:\msys64\mingw32\bin;%PATH%") else (set "PATH=C:\msys64\mingw64\bin;%PATH%")) + - cmd: if %compiler%==cygwin ( call ci\appveyor-install.bat ) build_script: - cmd: echo No build step. - - cmd: if %backend%==ninja ( ninja.exe --version ) else ( MSBuild /version & echo. ) + - cmd: if not %compiler%==cygwin if %backend%==ninja ( ninja.exe --version ) else ( MSBuild /version & echo. ) test_script: - cmd: echo Running tests for %arch% and %compiler% with the %backend% backend - - cmd: PATH=%cd%;%MESON_PYTHON_PATH%;%PATH%; && python run_tests.py --backend=%backend% + - cmd: set "ORIG_PATH=%PATH%" + - cmd: if %compiler%==cygwin ( set "PATH=%CYGWIN_ROOT%\bin;%SYSTEMROOT%\system32" && bash -lc "cd $APPVEYOR_BUILD_FOLDER && ci/appveyor-test.sh" ) + - cmd: if not %compiler%==cygwin ( set "PATH=%cd%;%MESON_PYTHON_PATH%;%PATH%;" && python run_tests.py --backend=%backend% ) on_finish: + - set "PATH=%ORIG_PATH%" - appveyor PushArtifact meson-test-run.txt -DeploymentName "Text test logs" - appveyor PushArtifact meson-test-run.xml -DeploymentName "XML test logs" + +cache: + - C:\cache diff --git a/authors.txt b/authors.txt index 0c575e7..9b2ea72 100644 --- a/authors.txt +++ b/authors.txt @@ -73,3 +73,11 @@ Joe Baldino Peter Harris Roger Boerdijk melak47 +Philipp Ittershagen +Dylan Baker +Aaron Plattner +Jon Turney +Wade Berrier +Richard Hughes +Rafael Fontenelle +Michael Olbrich diff --git a/ci/appveyor-install.bat b/ci/appveyor-install.bat new file mode 100644 index 0000000..0c1ce44 --- /dev/null +++ b/ci/appveyor-install.bat @@ -0,0 +1,11 @@ +set CACHE=C:\cache +set CYGWIN_MIRROR="http://cygwin.mirror.constant.com" + +if _%arch%_ == _x64_ set SETUP=setup-x86_64.exe && set CYGWIN_ROOT=C:\cygwin64 +if _%arch%_ == _x86_ set SETUP=setup-x86.exe && set CYGWIN_ROOT=C:\cygwin + +if not exist %CACHE% mkdir %CACHE% + +echo Updating Cygwin and installing ninja and test prerequisites +%CYGWIN_ROOT%\%SETUP% -qnNdO -R "%CYGWIN_ROOT%" -s "%CYGWIN_MIRROR%" -l "%CACHE%" -g -P "ninja,gcc-objc,gcc-objc++,libglib2.0-devel,zlib-devel" +echo Install done diff --git a/ci/appveyor-test.sh b/ci/appveyor-test.sh new file mode 100755 index 0000000..2f29630 --- /dev/null +++ b/ci/appveyor-test.sh @@ -0,0 +1,6 @@ +#!/bin/sh + +echo ninja $(ninja --version) +python3 --version -V + +python3 run_tests.py --backend=${backend} diff --git a/mesonbuild/backend/backends.py b/mesonbuild/backend/backends.py index e37b0df..a77047b 100644 --- a/mesonbuild/backend/backends.py +++ b/mesonbuild/backend/backends.py @@ -20,7 +20,8 @@ from .. import mlog from .. import compilers import json import subprocess -from ..mesonlib import MesonException, get_compiler_for_source, classify_unity_sources +from ..mesonlib import MesonException, get_meson_script +from ..mesonlib import get_compiler_for_source, classify_unity_sources from ..compilers import CompilerArgs from collections import OrderedDict @@ -34,7 +35,7 @@ class CleanTrees: self.trees = trees class InstallData: - def __init__(self, source_dir, build_dir, prefix, strip_bin): + def __init__(self, source_dir, build_dir, prefix, strip_bin, mesonintrospect): self.source_dir = source_dir self.build_dir = build_dir self.prefix = prefix @@ -47,6 +48,7 @@ class InstallData: self.po = [] self.install_scripts = [] self.install_subdirs = [] + self.mesonintrospect = mesonintrospect class ExecutableSerialisation: def __init__(self, name, fname, cmd_args, env, is_cross, exe_wrapper, @@ -77,6 +79,24 @@ class TestSerialisation: self.workdir = workdir self.extra_paths = extra_paths +class OptionProxy: + def __init__(self, name, value): + self.name = name + self.value = value + +class OptionOverrideProxy: + '''Mimic an option list but transparently override + selected option values.''' + def __init__(self, overrides, options): + self.overrides = overrides + self.options = options + + def __getitem__(self, option_name): + base_opt = self.options[option_name] + if option_name in self.overrides: + return OptionProxy(base_opt.name, base_opt.validate_value(self.overrides[option_name])) + return base_opt + # This class contains the basic functionality that is needed by all backends. # Feel free to move stuff in and out of it as you see fit. class Backend: @@ -104,6 +124,12 @@ class Backend: def get_target_filename_abs(self, target): return os.path.join(self.environment.get_build_dir(), self.get_target_filename(target)) + def get_option_for_target(self, option_name, target): + if option_name in target.option_overrides: + override = target.option_overrides[option_name] + return self.environment.coredata.validate_option_value(option_name, override) + return self.environment.coredata.get_builtin_option(option_name) + def get_target_filename_for_linking(self, target): # On some platforms (msvc for instance), the file that is used for # dynamic linking is not the same as the dynamic library itself. This @@ -153,8 +179,10 @@ class Backend: compsrcs = classify_unity_sources(target.compilers.values(), unity_src) def init_language_file(suffix): - outfilename = os.path.join(self.get_target_private_dir_abs(target), - self.get_unity_source_filename(target, suffix)) + unity_src_name = self.get_unity_source_filename(target, suffix) + unity_src_subdir = self.get_target_private_dir_abs(target) + outfilename = os.path.join(unity_src_subdir, + unity_src_name) outfileabs = os.path.join(self.environment.get_build_dir(), outfilename) outfileabs_tmp = outfileabs + '.tmp' @@ -162,7 +190,7 @@ class Backend: outfileabs_tmp_dir = os.path.dirname(outfileabs_tmp) if not os.path.exists(outfileabs_tmp_dir): os.makedirs(outfileabs_tmp_dir) - result.append(outfilename) + result.append(mesonlib.File(True, unity_src_subdir, unity_src_name)) return open(outfileabs_tmp, 'w') # For each language, generate a unity source file and return the list @@ -187,7 +215,7 @@ class Backend: elif isinstance(obj, mesonlib.File): obj_list.append(obj.rel_to_builddir(self.build_to_src)) elif isinstance(obj, build.ExtractedObjects): - obj_list += self.determine_ext_objs(obj, proj_dir_to_build_root) + obj_list += self.determine_ext_objs(target, obj, proj_dir_to_build_root) else: raise MesonException('Unknown data type in object list.') return obj_list @@ -227,7 +255,7 @@ class Backend: exe_wrapper = self.environment.cross_info.config['binaries'].get('exe_wrapper', None) else: exe_wrapper = None - if mesonlib.is_windows(): + if mesonlib.is_windows() or mesonlib.is_cygwin(): extra_paths = self.determine_windows_extra_paths(exe) else: extra_paths = [] @@ -262,32 +290,34 @@ class Backend: raise MesonException(m.format(target.name)) return l - def object_filename_from_source(self, target, source): + def object_filename_from_source(self, target, source, is_unity): if isinstance(source, mesonlib.File): source = source.fname # foo.vala files compile down to foo.c and then foo.c.o, not foo.vala.o if source.endswith('.vala'): + if is_unity: + return source[:-5] + '.c.' + self.environment.get_object_suffix() source = os.path.join(self.get_target_private_dir(target), source[:-5] + '.c') return source.replace('/', '_').replace('\\', '_') + '.' + self.environment.get_object_suffix() - def determine_ext_objs(self, extobj, proj_dir_to_build_root): + def determine_ext_objs(self, target, extobj, proj_dir_to_build_root): result = [] targetdir = self.get_target_private_dir(extobj.target) # With unity builds, there's just one object that contains all the # sources, and we only support extracting all the objects in this mode, # so just return that. - if self.environment.coredata.get_builtin_option('unity'): + if self.get_option_for_target('unity', target): comp = get_compiler_for_source(extobj.target.compilers.values(), extobj.srclist[0]) - # The unity object name uses the full absolute path of the source file - osrc = os.path.join(self.get_target_private_dir_abs(extobj.target), - self.get_unity_source_filename(extobj.target, - comp.get_default_suffix())) - objname = self.object_filename_from_source(extobj.target, osrc) + # There is a potential conflict here, but it is unlikely that + # anyone both enables unity builds and has a file called foo-unity.cpp. + osrc = self.get_unity_source_filename(extobj.target, + comp.get_default_suffix()) + objname = self.object_filename_from_source(extobj.target, osrc, True) objpath = os.path.join(proj_dir_to_build_root, targetdir, objname) return [objpath] for osrc in extobj.srclist: - objname = self.object_filename_from_source(extobj.target, osrc) + objname = self.object_filename_from_source(extobj.target, osrc, False) objpath = os.path.join(proj_dir_to_build_root, targetdir, objname) result.append(objpath) return result @@ -339,6 +369,8 @@ class Backend: # various sources in the order in which they must override each other # starting from hard-coded defaults followed by build options and so on. commands = CompilerArgs(compiler) + + copt_proxy = OptionOverrideProxy(target.option_overrides, self.environment.coredata.compiler_options) # First, the trivial ones that are impossible to override. # # Add -nostdinc/-nostdinc++ if needed; can't be overriden @@ -349,19 +381,19 @@ class Backend: # we weren't explicitly asked to not emit warnings (for Vala, f.ex) if no_warn_args: commands += compiler.get_no_warn_args() - elif self.environment.coredata.get_builtin_option('buildtype') != 'plain': - commands += compiler.get_warn_args(self.environment.coredata.get_builtin_option('warning_level')) + elif self.get_option_for_target('buildtype', target) != 'plain': + commands += compiler.get_warn_args(self.get_option_for_target('warning_level', target)) # Add -Werror if werror=true is set in the build options set on the # command-line or default_options inside project(). This only sets the # action to be done for warnings if/when they are emitted, so it's ok # to set it after get_no_warn_args() or get_warn_args(). - if self.environment.coredata.get_builtin_option('werror'): + if self.get_option_for_target('werror', target): commands += compiler.get_werror_args() # Add compile args for c_* or cpp_* build options set on the # command-line or default_options inside project(). - commands += compiler.get_option_compile_args(self.environment.coredata.compiler_options) + commands += compiler.get_option_compile_args(copt_proxy) # Add buildtype args: optimization level, debugging, etc. - commands += compiler.get_buildtype_args(self.environment.coredata.get_builtin_option('buildtype')) + commands += compiler.get_buildtype_args(self.get_option_for_target('buildtype', target)) # Add compile args added using add_project_arguments() commands += self.build.get_project_args(compiler, target.subproject) # Add compile args added using add_global_arguments() @@ -452,7 +484,7 @@ class Backend: exe_wrapper = self.environment.cross_info.config['binaries'].get('exe_wrapper', None) else: exe_wrapper = None - if mesonlib.is_windows(): + if mesonlib.is_windows() or mesonlib.is_cygwin(): extra_paths = self.determine_windows_extra_paths(exe) else: extra_paths = [] @@ -570,7 +602,7 @@ class Backend: for t in target.get_generated_sources(): if not isinstance(t, build.CustomTarget): continue - for f in t.output: + for f in t.get_outputs(): if self.environment.is_library(f): libs.append(os.path.join(self.get_target_dir(t), f)) return libs @@ -600,6 +632,22 @@ class Backend: srcs += fname return srcs + def get_custom_target_depend_files(self, target, absolute_paths=False): + deps = [] + for i in target.depend_files: + if isinstance(i, mesonlib.File): + if absolute_paths: + deps.append(i.absolute_path(self.environment.get_source_dir(), + self.environment.get_build_dir())) + else: + deps.append(i.rel_to_builddir(self.build_to_src)) + else: + if absolute_paths: + deps.append(os.path.join(self.environment.get_build_dir(), i)) + else: + deps.append(os.path.join(self.build_to_src, i)) + return deps + def eval_custom_target_command(self, target, absolute_outputs=False): # We want the outputs to be absolute only when using the VS backend # XXX: Maybe allow the vs backend to use relative paths too? @@ -611,7 +659,7 @@ class Backend: build_root = self.environment.get_source_dir() outdir = os.path.join(self.environment.get_build_dir(), outdir) outputs = [] - for i in target.output: + for i in target.get_outputs(): outputs.append(os.path.join(outdir, i)) inputs = self.get_custom_target_sources(target) # Evaluate the command list @@ -685,7 +733,7 @@ class Backend: def run_postconf_scripts(self): env = {'MESON_SOURCE_ROOT': self.environment.get_source_dir(), 'MESON_BUILD_ROOT': self.environment.get_build_dir(), - } + 'MESONINTROSPECT': get_meson_script(self.environment, 'mesonintrospect')} child_env = os.environ.copy() child_env.update(env) diff --git a/mesonbuild/backend/ninjabackend.py b/mesonbuild/backend/ninjabackend.py index 63db854..98a2110 100644 --- a/mesonbuild/backend/ninjabackend.py +++ b/mesonbuild/backend/ninjabackend.py @@ -20,7 +20,8 @@ from .. import mlog from .. import dependencies from .. import compilers from ..compilers import CompilerArgs -from ..mesonlib import File, MesonException, get_compiler_for_source, Popen_safe +from ..mesonlib import File, MesonException +from ..mesonlib import get_meson_script, get_compiler_for_source, Popen_safe from .backends import CleanTrees, InstallData from ..build import InvalidArguments import os, sys, pickle, re @@ -165,14 +166,20 @@ class NinjaBackend(backends.Backend): int dummy; ''') - pc, stdo = Popen_safe(['cl', '/showIncludes', '/c', 'incdetect.c'], - cwd=self.environment.get_scratch_dir())[0:2] - - for line in stdo.split('\n'): - if line.endswith('stdio.h'): - matchstr = ':'.join(line.split(':')[0:2]) + ':' - with open(tempfilename, 'a') as binfile: - binfile.write('msvc_deps_prefix = ' + matchstr + '\n') + # The output of cl dependency information is language + # and locale dependent. Any attempt at converting it to + # Python strings leads to failure. We _must_ do this detection + # in raw byte mode and write the result in raw bytes. + pc = subprocess.Popen(['cl', '/showIncludes', '/c', 'incdetect.c'], + cwd=self.environment.get_scratch_dir(), + stdout=subprocess.PIPE, stderr=subprocess.PIPE) + (stdo, _) = pc.communicate() + + for line in stdo.split(b'\r\n'): + if line.endswith(b'stdio.h'): + matchstr = b':'.join(line.split(b':')[0:2]) + b':' + with open(tempfilename, 'ab') as binfile: + binfile.write(b'msvc_deps_prefix = ' + matchstr + b'\n') return open(tempfilename, 'a') raise MesonException('Could not determine vs dep dependency prefix string.') @@ -336,7 +343,7 @@ int dummy; outname = self.get_target_filename(target) obj_list = [] use_pch = self.environment.coredata.base_options.get('b_pch', False) - is_unity = self.environment.coredata.get_builtin_option('unity') + is_unity = self.get_option_for_target('unity', target) if use_pch and target.has_pch(): pch_objects = self.generate_pch(target, outfile) else: @@ -433,7 +440,7 @@ int dummy; obj_list += self.flatten_object_list(target) if is_unity: for src in self.generate_unity_files(target, unity_src): - obj_list.append(self.generate_single_compile(target, outfile, RawFilename(src), True, unity_deps + header_deps)) + obj_list.append(self.generate_single_compile(target, outfile, src, True, unity_deps + header_deps)) linker = self.determine_linker(target) elem = self.generate_link(target, outfile, outname, obj_list, linker, pch_objects) self.generate_shlib_aliases(target, self.get_target_dir(target)) @@ -467,6 +474,7 @@ int dummy; self.custom_target_generator_inputs(target, outfile) (srcs, ofilenames, cmd) = self.eval_custom_target_command(target) deps = self.unwrap_dep_list(target) + deps += self.get_custom_target_depend_files(target) desc = 'Generating {0} with a {1} command.' if target.build_always: deps.append('PHONY') @@ -475,11 +483,6 @@ int dummy; else: rulename = 'CUSTOM_COMMAND_DEP' elem = NinjaBuildElement(self.all_outputs, ofilenames, rulename, srcs) - for i in target.depend_files: - if isinstance(i, mesonlib.File): - deps.append(i.rel_to_builddir(self.build_to_src)) - else: - deps.append(os.path.join(self.build_to_src, i)) elem.add_dep(deps) for d in target.extra_depends: # Add a dependency on all the outputs of this target @@ -492,7 +495,7 @@ int dummy; # the project, we need to set PATH so the DLLs are found. We use # a serialized executable wrapper for that and check if the # CustomTarget command needs extra paths first. - if target.capture or (mesonlib.is_windows() and + if target.capture or ((mesonlib.is_windows() or mesonlib.is_cygwin()) and self.determine_windows_extra_paths(target.command[0])): exe_data = self.serialise_executable(target.command[0], cmd[1:], # All targets are built from the build dir @@ -515,7 +518,7 @@ int dummy; self.processed_targets[target.name + target.type_suffix()] = True def generate_run_target(self, target, outfile): - runnerscript = [sys.executable, self.environment.get_build_command(), '--internal', 'commandrunner'] + cmd = [sys.executable, self.environment.get_build_command(), '--internal', 'commandrunner'] deps = self.unwrap_dep_list(target) arg_strings = [] for i in target.args: @@ -531,7 +534,10 @@ int dummy; else: raise AssertionError('Unreachable code in generate_run_target: ' + str(i)) elem = NinjaBuildElement(self.all_outputs, target.name, 'CUSTOM_COMMAND', []) - cmd = runnerscript + [self.environment.get_source_dir(), self.environment.get_build_dir(), target.subdir] + cmd += [self.environment.get_source_dir(), + self.environment.get_build_dir(), + target.subdir, + get_meson_script(self.environment, 'mesonintrospect')] texe = target.command try: texe = texe.held_object @@ -608,7 +614,8 @@ int dummy; d = InstallData(self.environment.get_source_dir(), self.environment.get_build_dir(), self.environment.get_prefix(), - strip_bin) + strip_bin, + get_meson_script(self.environment, 'mesonintrospect')) elem = NinjaBuildElement(self.all_outputs, 'install', 'CUSTOM_COMMAND', 'PHONY') elem.add_dep('all') elem.add_item('DESC', 'Installing files.') @@ -627,41 +634,89 @@ int dummy; pickle.dump(d, ofile) def generate_target_install(self, d): - should_strip = self.environment.coredata.get_builtin_option('strip') for t in self.build.get_targets().values(): - if t.should_install(): - # Find the installation directory. FIXME: Currently only one - # installation directory is supported for each target - outdir = t.get_custom_install_dir() - if outdir is not None: - pass - elif isinstance(t, build.SharedLibrary): - # For toolchains/platforms that need an import library for + if not t.should_install(): + continue + # Find the installation directory. + outdirs = t.get_custom_install_dir() + custom_install_dir = False + if outdirs[0] is not None and outdirs[0] is not True: + # Either the value is set, or is set to False which means + # we want this specific output out of many outputs to not + # be installed. + custom_install_dir = True + elif isinstance(t, build.SharedModule): + outdirs[0] = self.environment.get_shared_module_dir() + elif isinstance(t, build.SharedLibrary): + outdirs[0] = self.environment.get_shared_lib_dir() + elif isinstance(t, build.StaticLibrary): + outdirs[0] = self.environment.get_static_lib_dir() + elif isinstance(t, build.Executable): + outdirs[0] = self.environment.get_bindir() + else: + assert(isinstance(t, build.BuildTarget)) + # XXX: Add BuildTarget-specific install dir cases here + outdirs[0] = self.environment.get_libdir() + # Sanity-check the outputs and install_dirs + num_outdirs, num_out = len(outdirs), len(t.get_outputs()) + if num_outdirs != 1 and num_outdirs != num_out: + m = 'Target {!r} has {} outputs: {!r}, but only {} "install_dir"s were found.\n' \ + "Pass 'false' for outputs that should not be installed and 'true' for\n" \ + 'using the default installation directory for an output.' + raise MesonException(m.format(t.name, num_out, t.get_outputs(), num_outdirs)) + # Install the target output(s) + if isinstance(t, build.BuildTarget): + should_strip = self.get_option_for_target('strip', t) + # Install primary build output (library/executable/jar, etc) + # Done separately because of strip/aliases/rpath + if outdirs[0] is not False: + i = [self.get_target_filename(t), outdirs[0], + t.get_aliases(), should_strip, t.install_rpath] + d.targets.append(i) + # On toolchains/platforms that use an import library for # linking (separate from the shared library with all the - # code), we need to install the import library (dll.a/.lib) - if t.get_import_filename(): + # code), we need to install that too (dll.a/.lib). + if isinstance(t, build.SharedLibrary) and t.get_import_filename(): + if custom_install_dir: + # If the DLL is installed into a custom directory, + # install the import library into the same place so + # it doesn't go into a surprising place + implib_install_dir = outdirs[0] + else: + implib_install_dir = self.environment.get_import_lib_dir() # Install the import library. i = [self.get_target_filename_for_linking(t), - self.environment.get_import_lib_dir(), + implib_install_dir, # It has no aliases, should not be stripped, and # doesn't have an install_rpath {}, False, ''] d.targets.append(i) - outdir = self.environment.get_shared_lib_dir() - elif isinstance(t, build.StaticLibrary): - outdir = self.environment.get_static_lib_dir() - elif isinstance(t, build.Executable): - outdir = self.environment.get_bindir() - else: - # XXX: Add BuildTarget-specific install dir cases here - outdir = self.environment.get_libdir() - if isinstance(t, build.BuildTarget): - i = [self.get_target_filename(t), outdir, t.get_aliases(), - should_strip, t.install_rpath] - d.targets.append(i) - elif isinstance(t, build.CustomTarget): + # Install secondary outputs. Only used for Vala right now. + if num_outdirs > 1: + for output, outdir in zip(t.get_outputs()[1:], outdirs[1:]): + # User requested that we not install this output + if outdir is False: + continue + f = os.path.join(self.get_target_dir(t), output) + d.targets.append([f, outdir, {}, False, None]) + elif isinstance(t, build.CustomTarget): + # If only one install_dir is specified, assume that all + # outputs will be installed into it. This is for + # backwards-compatibility and because it makes sense to + # avoid repetition since this is a common use-case. + # + # To selectively install only some outputs, pass `false` as + # the install_dir for the corresponding output by index + if num_outdirs == 1 and num_out > 1: for output in t.get_outputs(): f = os.path.join(self.get_target_dir(t), output) + d.targets.append([f, outdirs[0], {}, False, None]) + else: + for output, outdir in zip(t.get_outputs(), outdirs): + # User requested that we not install this output + if outdir is False: + continue + f = os.path.join(self.get_target_dir(t), output) d.targets.append([f, outdir, {}, False, None]) def generate_custom_install_script(self, d): @@ -729,9 +784,7 @@ int dummy; def generate_tests(self, outfile): self.serialise_tests() - meson_exe = self.environment.get_build_command() - (base, ext) = os.path.splitext(meson_exe) - test_exe = base + 'test' + ext + test_exe = get_meson_script(self.environment, 'mesontest') cmd = [sys.executable, test_exe, '--no-rebuild'] if not self.environment.coredata.get_builtin_option('stdsplit'): cmd += ['--no-stdsplit'] @@ -843,7 +896,7 @@ int dummy; return args, deps def generate_cs_target(self, target, outfile): - buildtype = self.environment.coredata.get_builtin_option('buildtype') + buildtype = self.get_option_for_target('buildtype', target) fname = target.get_filename() outname_rel = os.path.join(self.get_target_dir(target), fname) src_list = target.get_sources() @@ -877,7 +930,7 @@ int dummy; def generate_single_java_compile(self, src, target, compiler, outfile): args = [] - args += compiler.get_buildtype_args(self.environment.coredata.get_builtin_option('buildtype')) + args += compiler.get_buildtype_args(self.get_option_for_target('buildtype', target)) args += self.build.get_global_args(compiler) args += self.build.get_project_args(compiler, target.subproject) args += target.get_java_args() @@ -1010,7 +1063,7 @@ int dummy; args = [] args += self.build.get_global_args(valac) args += self.build.get_project_args(valac, target.subproject) - args += valac.get_buildtype_args(self.environment.coredata.get_builtin_option('buildtype')) + args += valac.get_buildtype_args(self.get_option_for_target('buildtype', target)) # Tell Valac to output everything in our private directory. Sadly this # means it will also preserve the directory components of Vala sources # found inside the build tree (generated sources). @@ -1029,11 +1082,22 @@ int dummy; # Without this, it will write it inside c_out_dir args += ['--vapi', os.path.join('..', target.vala_vapi)] valac_outputs.append(vapiname) + target.outputs += [target.vala_header, target.vala_vapi] + # Install header and vapi to default locations if user requests this + if len(target.install_dir) > 1 and target.install_dir[1] is True: + target.install_dir[1] = self.environment.get_includedir() + if len(target.install_dir) > 2 and target.install_dir[2] is True: + target.install_dir[2] = os.path.join(self.environment.get_datadir(), 'vala', 'vapi') + # Generate GIR if requested if isinstance(target.vala_gir, str): girname = os.path.join(self.get_target_dir(target), target.vala_gir) args += ['--gir', os.path.join('..', target.vala_gir)] valac_outputs.append(girname) - if self.environment.coredata.get_builtin_option('werror'): + target.outputs.append(target.vala_gir) + # Install GIR to default location if requested by user + if len(target.install_dir) > 3 and target.install_dir[3] is True: + target.install_dir[3] = os.path.join(self.environment.get_datadir(), 'gir-1.0') + if self.get_option_for_target('werror', target): args += valac.get_werror_args() for d in target.get_external_deps(): if isinstance(d, dependencies.PkgConfigDependency): @@ -1088,7 +1152,7 @@ int dummy; else: raise InvalidArguments('Unknown target type for rustc.') args.append(cratetype) - args += rustc.get_buildtype_args(self.environment.coredata.get_builtin_option('buildtype')) + args += rustc.get_buildtype_args(self.get_option_for_target('buildtype', target)) depfile = os.path.join(target.subdir, target.name + '.d') args += ['--emit', 'dep-info={}'.format(depfile), '--emit', 'link'] args += ['-o', os.path.join(target.subdir, target.get_filename())] @@ -1810,13 +1874,15 @@ rule FORTRAN_DEP_HACK return incs def _generate_single_compile(self, target, compiler, is_generated=False): + base_proxy = backends.OptionOverrideProxy(target.option_overrides, + self.environment.coredata.base_options) # Create an empty commands list, and start adding arguments from # various sources in the order in which they must override each other commands = CompilerArgs(compiler) # Add compiler args for compiling this target derived from 'base' build # options passed on the command-line, in default_options, etc. # These have the lowest priority. - commands += compilers.get_base_compile_args(self.environment.coredata.base_options, + commands += compilers.get_base_compile_args(base_proxy, compiler) # The code generated by valac is usually crap and has tons of unused # variables and such, so disable warnings for Vala C sources. @@ -1888,6 +1954,11 @@ rule FORTRAN_DEP_HACK raise AssertionError('BUG: sources should not contain headers {!r}'.format(src)) if isinstance(src, RawFilename) and src.fname.endswith('.h'): raise AssertionError('BUG: sources should not contain headers {!r}'.format(src.fname)) + + if isinstance(src, str) and src.endswith('.h'): + raise AssertionError('BUG: sources should not contain headers {!r}'.format(src)) + if isinstance(src, RawFilename) and src.fname.endswith('.h'): + raise AssertionError('BUG: sources should not contain headers {!r}'.format(src.fname)) compiler = get_compiler_for_source(target.compilers.values(), src) key = (target, compiler, is_generated) if key in self.target_arg_cache: @@ -1905,6 +1976,10 @@ rule FORTRAN_DEP_HACK abs_src = src.fname else: abs_src = os.path.join(self.environment.get_build_dir(), src.fname) + elif isinstance(src, mesonlib.File): + rel_src = src.rel_to_builddir(self.build_to_src) + abs_src = src.absolute_path(self.environment.get_source_dir(), + self.environment.get_build_dir()) elif is_generated: raise AssertionError('BUG: broken generated source file handling for {!r}'.format(src)) else: @@ -2145,7 +2220,7 @@ rule FORTRAN_DEP_HACK # Add things like /NOLOGO; usually can't be overriden commands += linker.get_linker_always_args() # Add buildtype linker args: optimization level, etc. - commands += linker.get_buildtype_linker_args(self.environment.coredata.get_builtin_option('buildtype')) + commands += linker.get_buildtype_linker_args(self.get_option_for_target('buildtype', target)) # Add /DEBUG and the pdb filename when using MSVC commands += self.get_link_debugfile_args(linker, target, outname) # Add link args specific to this BuildTarget type, such as soname args, diff --git a/mesonbuild/backend/vs2010backend.py b/mesonbuild/backend/vs2010backend.py index 8c0cce6..46eab11 100644 --- a/mesonbuild/backend/vs2010backend.py +++ b/mesonbuild/backend/vs2010backend.py @@ -24,7 +24,7 @@ from .. import mlog from .. import compilers from ..build import BuildTarget from ..compilers import CompilerArgs -from ..mesonlib import MesonException, File +from ..mesonlib import MesonException, File, get_meson_script from ..environment import Environment def autodetect_vs_version(build): @@ -87,7 +87,7 @@ class Vs2010Backend(backends.Backend): self.vs_version = '2010' self.windows_target_platform_version = None - def object_filename_from_source(self, target, source): + def object_filename_from_source(self, target, source, is_unity=False): basename = os.path.basename(source.fname) filename_without_extension = '.'.join(basename.split('.')[:-1]) if basename in self.sources_conflicts[target.get_id()]: @@ -409,21 +409,25 @@ class Vs2010Backend(backends.Backend): customstep = ET.SubElement(action, 'PostBuildEvent') cmd_raw = [target.command] + target.args cmd = [sys.executable, os.path.join(self.environment.get_script_dir(), 'commandrunner.py'), - self.environment.get_build_dir(), self.environment.get_source_dir(), - self.get_target_dir(target)] + self.environment.get_build_dir(), + self.environment.get_source_dir(), + self.get_target_dir(target), + get_meson_script(self.environment, 'mesonintrospect')] for i in cmd_raw: 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.get_command() + elif isinstance(i, File): + relfname = i.rel_to_builddir(self.build_to_src) + cmd.append(os.path.join(self.environment.get_build_dir(), relfname)) else: cmd.append(i) cmd_templ = '''"%s" ''' * len(cmd) ET.SubElement(customstep, 'Command').text = cmd_templ % tuple(cmd) ET.SubElement(customstep, 'Message').text = 'Running custom command.' ET.SubElement(root, 'Import', Project='$(VCTargetsPath)\Microsoft.Cpp.targets') - tree = ET.ElementTree(root) - tree.write(ofname, encoding='utf-8', xml_declaration=True) + self._prettyprint_vcxproj_xml(ET.ElementTree(root), ofname) def gen_custom_target_vcxproj(self, target, ofname, guid): root = self.create_basic_crap(target) @@ -433,6 +437,7 @@ class Vs2010Backend(backends.Backend): # from the target dir, not the build root. target.absolute_paths = True (srcs, ofilenames, cmd) = self.eval_custom_target_command(target, True) + depend_files = self.get_custom_target_depend_files(target, True) # Always use a wrapper because MSBuild eats random characters when # there are many arguments. tdir_abs = os.path.join(self.environment.get_build_dir(), self.get_target_dir(target)) @@ -444,11 +449,10 @@ class Vs2010Backend(backends.Backend): '--internal', 'exe', exe_data] ET.SubElement(customstep, 'Command').text = ' '.join(self.quote_arguments(wrapper_cmd)) ET.SubElement(customstep, 'Outputs').text = ';'.join(ofilenames) - ET.SubElement(customstep, 'Inputs').text = ';'.join(srcs) + ET.SubElement(customstep, 'Inputs').text = ';'.join([exe_data] + srcs + depend_files) ET.SubElement(root, 'Import', Project='$(VCTargetsPath)\Microsoft.Cpp.targets') self.generate_custom_generator_commands(target, root) - tree = ET.ElementTree(root) - tree.write(ofname, encoding='utf-8', xml_declaration=True) + self._prettyprint_vcxproj_xml(ET.ElementTree(root), ofname) @classmethod def lang_from_source_file(cls, src): @@ -574,6 +578,13 @@ class Vs2010Backend(backends.Backend): return c raise MesonException('Could not find a C or C++ compiler. MSVC can only build C/C++ projects.') + def _prettyprint_vcxproj_xml(self, tree, ofname): + tree.write(ofname, encoding='utf-8', xml_declaration=True) + # ElementTree can not do prettyprinting so do it manually + doc = xml.dom.minidom.parse(ofname) + with open(ofname, 'w') as of: + of.write(doc.toprettyxml()) + def gen_vcxproj(self, target, ofname, guid): mlog.debug('Generating vcxproj %s.' % target.name) entrypoint = 'WinMainCRTStartup' @@ -601,6 +612,8 @@ class Vs2010Backend(backends.Backend): # Prefix to use to access the source tree's subdir from the vcxproj dir proj_to_src_dir = os.path.join(proj_to_src_root, target.subdir) (sources, headers, objects, languages) = self.split_sources(target.sources) + if self.get_option_for_target('unity', target): + sources = self.generate_unity_files(target, sources) compiler = self._get_cl_compiler(target) buildtype_args = compiler.get_buildtype_args(self.buildtype) buildtype_link_args = compiler.get_buildtype_linker_args(self.buildtype) @@ -690,7 +703,7 @@ class Vs2010Backend(backends.Backend): elif '/Od' in o_flags: ET.SubElement(type_config, 'Optimization').text = 'Disabled' # Warning level - warning_level = self.environment.coredata.get_builtin_option('warning_level') + warning_level = self.get_option_for_target('warning_level', target) ET.SubElement(type_config, 'WarningLevel').text = 'Level' + warning_level # End configuration ET.SubElement(root, 'Import', Project='$(VCTargetsPath)\Microsoft.Cpp.props') @@ -844,7 +857,7 @@ class Vs2010Backend(backends.Backend): ET.SubElement(clconf, 'MinimalRebuild').text = 'true' ET.SubElement(clconf, 'FunctionLevelLinking').text = 'true' pch_node = ET.SubElement(clconf, 'PrecompiledHeader') - if self.environment.coredata.get_builtin_option('werror'): + if self.get_option_for_target('werror', target): ET.SubElement(clconf, 'TreatWarningAsError').text = 'true' # Note: SuppressStartupBanner is /NOLOGO and is 'true' by default pch_sources = {} @@ -1016,19 +1029,7 @@ class Vs2010Backend(backends.Backend): ig = ET.SubElement(root, 'ItemGroup') pref = ET.SubElement(ig, 'ProjectReference', Include=os.path.join(self.environment.get_build_dir(), 'REGEN.vcxproj')) ET.SubElement(pref, 'Project').text = self.environment.coredata.regen_guid - tree = ET.ElementTree(root) - tree.write(ofname, encoding='utf-8', xml_declaration=True) - # ElementTree can not do prettyprinting so do it manually - doc = xml.dom.minidom.parse(ofname) - with open(ofname, 'w') as of: - of.write(doc.toprettyxml()) - # World of horror! Python insists on not quoting quotes and - # fixing the escaped " into &quot; whereas MSVS - # requires quoted but not fixed elements. Enter horrible hack. - with open(ofname, 'r') as of: - txt = of.read() - with open(ofname, 'w') as of: - of.write(txt.replace('&quot;', '"')) + self._prettyprint_vcxproj_xml(ET.ElementTree(root), ofname) def gen_regenproj(self, project_name, ofname): root = ET.Element('Project', {'DefaultTargets': 'Build', @@ -1107,8 +1108,7 @@ if %%errorlevel%% neq 0 goto :VCEnd''' ET.SubElement(custombuild, 'AdditionalInputs').text = ';'.join(deps) ET.SubElement(root, 'Import', Project='$(VCTargetsPath)\Microsoft.Cpp.targets') ET.SubElement(root, 'ImportGroup', Label='ExtensionTargets') - tree = ET.ElementTree(root) - tree.write(ofname, encoding='utf-8', xml_declaration=True) + self._prettyprint_vcxproj_xml(ET.ElementTree(root), ofname) def gen_testproj(self, target_name, ofname): project_name = target_name @@ -1162,11 +1162,8 @@ if %%errorlevel%% neq 0 goto :VCEnd''' postbuild = ET.SubElement(action, 'PostBuildEvent') ET.SubElement(postbuild, 'Message') # FIXME: No benchmarks? - meson_py = self.environment.get_build_command() - (base, ext) = os.path.splitext(meson_py) - mesontest_py = base + 'test' + ext test_command = [sys.executable, - mesontest_py, + get_meson_script(self.environment, 'mesontest'), '--no-rebuild'] if not self.environment.coredata.get_builtin_option('stdsplit'): test_command += ['--no-stdsplit'] @@ -1185,8 +1182,4 @@ if %%errorlevel%% neq 0 goto :VCEnd''' ET.SubElement(postbuild, 'Command').text =\ cmd_templ % ('" "'.join(test_command)) ET.SubElement(root, 'Import', Project='$(VCTargetsPath)\Microsoft.Cpp.targets') - tree = ET.ElementTree(root) - tree.write(ofname, encoding='utf-8', xml_declaration=True) - # ElementTree can not do prettyprinting so do it manually - # doc = xml.dom.minidom.parse(ofname) - # open(ofname, 'w').write(doc.toprettyxml()) + self._prettyprint_vcxproj_xml(ET.ElementTree(root), ofname) diff --git a/mesonbuild/build.py b/mesonbuild/build.py index ed0abc4..6c16cf9 100644 --- a/mesonbuild/build.py +++ b/mesonbuild/build.py @@ -19,9 +19,9 @@ from . import environment from . import dependencies from . import mlog from .mesonlib import File, MesonException -from .mesonlib import flatten, stringlistify, classify_unity_sources +from .mesonlib import flatten, typeslistify, stringlistify, classify_unity_sources from .mesonlib import get_filenames_templates_dict, substitute_values -from .environment import for_windows, for_darwin +from .environment import for_windows, for_darwin, for_cygwin from .compilers import is_object, clike_langs, sort_clike, lang_suffixes known_basic_kwargs = {'install': True, @@ -49,6 +49,7 @@ known_basic_kwargs = {'install': True, 'objects': True, 'native': True, 'build_by_default': True, + 'override_options': True, } # These contain kwargs supported by both static and shared libraries. These are @@ -272,6 +273,7 @@ class Target: self.build_by_default = build_by_default self.install = False self.build_always = False + self.option_overrides = {} def get_basename(self): return self.name @@ -284,6 +286,20 @@ class Target: self.build_by_default = kwargs['build_by_default'] if not isinstance(self.build_by_default, bool): raise InvalidArguments('build_by_default must be a boolean value.') + self.option_overrides = self.parse_overrides(kwargs) + + def parse_overrides(self, kwargs): + result = {} + overrides = stringlistify(kwargs.get('override_options', [])) + for o in overrides: + if '=' not in o: + raise InvalidArguments('Overrides must be of form "key=value"') + k, v = o.split('=', 1) + k = k.strip() + v = v.strip() + result[k] = v + return result + class BuildTarget(Target): def __init__(self, name, subdir, subproject, is_cross, sources, objects, environment, kwargs): @@ -302,6 +318,9 @@ class BuildTarget(Target): self.name_prefix_set = False self.name_suffix_set = False self.filename = 'no_name' + # The list of all files outputted by this target. Useful in cases such + # as Vala which generates .vapi and .h besides the compiled output. + self.outputs = [self.filename] self.need_install = False self.pch = {} self.extra_args = {} @@ -531,7 +550,7 @@ class BuildTarget(Target): return result def get_custom_install_dir(self): - return self.custom_install_dir + return self.install_dir def process_kwargs(self, kwargs, environment): super().process_kwargs(kwargs) @@ -578,7 +597,7 @@ class BuildTarget(Target): if not isinstance(self, Executable): self.vala_header = kwargs.get('vala_header', self.name + '.h') self.vala_vapi = kwargs.get('vala_vapi', self.name + '.vapi') - self.vala_gir = kwargs.get('vala_gir', None) + self.vala_gir = kwargs.get('vala_gir', None) dlist = stringlistify(kwargs.get('d_args', [])) self.add_compiler_args('d', dlist) self.link_args = kwargs.get('link_args', []) @@ -604,10 +623,10 @@ class BuildTarget(Target): if not isinstance(deplist, list): deplist = [deplist] self.add_deps(deplist) - self.custom_install_dir = kwargs.get('install_dir', None) - if self.custom_install_dir is not None: - if not isinstance(self.custom_install_dir, str): - raise InvalidArguments('Custom_install_dir must be a string') + # If an item in this list is False, the output corresponding to + # the list index of that item will not be installed + self.install_dir = typeslistify(kwargs.get('install_dir', [None]), + (str, bool)) main_class = kwargs.get('main_class', '') if not isinstance(main_class, str): raise InvalidArguments('Main class must be a string') @@ -678,7 +697,7 @@ class BuildTarget(Target): return self.filename def get_outputs(self): - return [self.filename] + return self.outputs def get_extra_args(self, language): return self.extra_args.get(language, []) @@ -984,13 +1003,15 @@ class Executable(BuildTarget): self.prefix = '' if not hasattr(self, 'suffix'): # Executable for Windows or C#/Mono - if for_windows(is_cross, environment) or 'cs' in self.compilers: + if (for_windows(is_cross, environment) or + for_cygwin(is_cross, environment) or 'cs' in self.compilers): self.suffix = 'exe' else: self.suffix = '' self.filename = self.name if self.suffix: self.filename += '.' + self.suffix + self.outputs = [self.filename] def type_suffix(self): return "@exe" @@ -1018,6 +1039,7 @@ class StaticLibrary(BuildTarget): else: self.suffix = 'a' self.filename = self.prefix + self.name + '.' + self.suffix + self.outputs = [self.filename] def type_suffix(self): return "@sta" @@ -1107,6 +1129,18 @@ class SharedLibrary(BuildTarget): self.filename_tpl = '{0.prefix}{0.name}-{0.soversion}.{0.suffix}' else: self.filename_tpl = '{0.prefix}{0.name}.{0.suffix}' + elif for_cygwin(is_cross, env): + suffix = 'dll' + self.gcc_import_filename = 'lib{0}.dll.a'.format(self.name) + # Shared library is of the form cygfoo.dll + # (ld --dll-search-prefix=cyg is the default) + prefix = 'cyg' + # Import library is called libfoo.dll.a + self.import_filename = self.gcc_import_filename + if self.soversion: + self.filename_tpl = '{0.prefix}{0.name}-{0.soversion}.{0.suffix}' + else: + self.filename_tpl = '{0.prefix}{0.name}.{0.suffix}' elif for_darwin(is_cross, env): prefix = 'lib' suffix = 'dylib' @@ -1134,6 +1168,7 @@ class SharedLibrary(BuildTarget): if self.suffix is None: self.suffix = suffix self.filename = self.filename_tpl.format(self) + self.outputs = [self.filename] def process_kwargs(self, kwargs, environment): super().process_kwargs(kwargs, environment) @@ -1159,12 +1194,22 @@ class SharedLibrary(BuildTarget): # Visual Studio module-definitions file if 'vs_module_defs' in kwargs: path = kwargs['vs_module_defs'] - if os.path.isabs(path): - self.vs_module_defs = File.from_absolute_file(path) + if isinstance(path, str): + if os.path.isabs(path): + self.vs_module_defs = File.from_absolute_file(path) + else: + self.vs_module_defs = File.from_source_file(environment.source_dir, self.subdir, path) + # link_depends can be an absolute path or relative to self.subdir + self.link_depends.append(path) + elif isinstance(path, File): + # When passing a generated file. + self.vs_module_defs = path + # link_depends can be an absolute path or relative to self.subdir + self.link_depends.append(path.absolute_path(environment.source_dir, environment.build_dir)) else: - self.vs_module_defs = File.from_source_file(environment.source_dir, self.subdir, path) - # link_depends can be an absolute path or relative to self.subdir - self.link_depends.append(path) + raise InvalidArguments( + 'Shared library vs_module_defs must be either a string, ' + 'or a file object') def check_unknown_kwargs(self, kwargs): self.check_unknown_kwargs_int(kwargs, known_lib_kwargs) @@ -1227,6 +1272,7 @@ class SharedModule(SharedLibrary): if 'soversion' in kwargs: raise MesonException('Shared modules must not specify the soversion kwarg.') super().__init__(name, subdir, subproject, is_cross, sources, objects, environment, kwargs) + self.import_filename = None class CustomTarget(Target): known_kwargs = {'input': True, @@ -1240,6 +1286,7 @@ class CustomTarget(Target): 'depend_files': True, 'depfile': True, 'build_by_default': True, + 'override_options': True, } def __init__(self, name, subdir, kwargs, absolute_paths=False): @@ -1287,12 +1334,16 @@ class CustomTarget(Target): for c in cmd: if hasattr(c, 'held_object'): c = c.held_object - if isinstance(c, (str, File)): + if isinstance(c, str): + final_cmd.append(c) + elif isinstance(c, File): + self.depend_files.append(c) 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)) + self.depend_files.append(File.from_absolute_file(c.get_path())) final_cmd += c.get_command() elif isinstance(c, (BuildTarget, CustomTarget)): self.dependencies.append(c) @@ -1310,13 +1361,13 @@ class CustomTarget(Target): self.sources = [self.sources] if 'output' not in kwargs: raise InvalidArguments('Missing keyword argument "output".') - self.output = kwargs['output'] - if not isinstance(self.output, list): - self.output = [self.output] + self.outputs = kwargs['output'] + if not isinstance(self.outputs, list): + self.outputs = [self.outputs] # 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: + for i in self.outputs: if not(isinstance(i, str)): raise InvalidArguments('Output argument not a string.') if '/' in i: @@ -1331,9 +1382,9 @@ class CustomTarget(Target): 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.outputs = substitute_values(self.outputs, values) self.capture = kwargs.get('capture', False) - if self.capture and len(self.output) != 1: + if self.capture and len(self.outputs) != 1: raise InvalidArguments('Capturing can only output to a single file.') if 'command' not in kwargs: raise InvalidArguments('Missing keyword argument "command".') @@ -1355,12 +1406,14 @@ class CustomTarget(Target): raise InvalidArguments('"install" must be boolean.') if self.install: if 'install_dir' not in kwargs: - raise InvalidArguments('"install_dir" not specified.') - self.install_dir = kwargs['install_dir'] - if not(isinstance(self.install_dir, str)): - raise InvalidArguments('"install_dir" must be a string.') + raise InvalidArguments('"install_dir" must be specified ' + 'when installing a target') + # If an item in this list is False, the output corresponding to + # the list index of that item will not be installed + self.install_dir = typeslistify(kwargs['install_dir'], (str, bool)) else: self.install = False + self.install_dir = [None] self.build_always = kwargs.get('build_always', False) if not isinstance(self.build_always, bool): raise InvalidArguments('Argument build_always must be a boolean.') @@ -1393,10 +1446,10 @@ class CustomTarget(Target): return self.install_dir def get_outputs(self): - return self.output + return self.outputs def get_filename(self): - return self.output[0] + return self.outputs[0] def get_sources(self): return self.sources @@ -1458,6 +1511,7 @@ class Jar(BuildTarget): if not s.endswith('.java'): raise InvalidArguments('Jar source %s is not a java file.' % s) self.filename = self.name + '.jar' + self.outputs = [self.filename] self.java_args = kwargs.get('java_args', []) def get_main_class(self): diff --git a/mesonbuild/compilers.py b/mesonbuild/compilers.py index 5e7db24..e6be8b1 100644 --- a/mesonbuild/compilers.py +++ b/mesonbuild/compilers.py @@ -550,11 +550,11 @@ class Compiler: def get_exelist(self): return self.exelist[:] - def get_define(self, *args, **kwargs): - raise EnvironmentException('%s does not support get_define.' % self.id) + def get_builtin_define(self, *args, **kwargs): + raise EnvironmentException('%s does not support get_builtin_define.' % self.id) - def has_define(self, *args, **kwargs): - raise EnvironmentException('%s does not support has_define.' % self.id) + def has_builtin_define(self, *args, **kwargs): + raise EnvironmentException('%s does not support has_builtin_define.' % self.id) def get_always_args(self): return [] @@ -906,8 +906,6 @@ class CCompiler(Compiler): return self.sanity_check_impl(work_dir, environment, 'sanitycheckc.c', code) def has_header(self, hname, prefix, env, extra_args=None, dependencies=None): - if extra_args is None: - extra_args = [] fargs = {'prefix': prefix, 'header': hname} code = '''{prefix} #ifdef __has_include @@ -921,8 +919,6 @@ class CCompiler(Compiler): dependencies, 'preprocess') def has_header_symbol(self, hname, symbol, prefix, env, extra_args=None, dependencies=None): - if extra_args is None: - extra_args = [] fargs = {'prefix': prefix, 'header': hname, 'symbol': symbol} t = '''{prefix} #include <{header}> @@ -934,7 +930,7 @@ class CCompiler(Compiler): }}''' return self.compiles(t.format(**fargs), env, extra_args, dependencies) - def compiles(self, code, env, extra_args=None, dependencies=None, mode='compile'): + def _get_compiler_check_args(self, env, extra_args, dependencies, mode='compile'): if extra_args is None: extra_args = [] elif isinstance(extra_args, str): @@ -943,49 +939,43 @@ class CCompiler(Compiler): dependencies = [] elif not isinstance(dependencies, list): dependencies = [dependencies] - # Add compile flags needed by dependencies + # Collect compiler arguments args = CompilerArgs(self) for d in dependencies: + # Add compile flags needed by dependencies args += d.get_compile_args() + if mode == 'link': + # Add link flags needed to find dependencies + args += d.get_link_args() + # Select a CRT if needed since we're linking + if mode == 'link': + args += self.get_linker_debug_crt_args() # Read c_args/cpp_args/etc from the cross-info file (if needed) - args += self.get_cross_extra_flags(env, compile=True, link=False) - # Add CFLAGS/CXXFLAGS/OBJCFLAGS/OBJCXXFLAGS from the env - # We assume that the user has ensured these are compiler-specific - args += env.coredata.external_args[self.language] + args += self.get_cross_extra_flags(env, compile=(mode != 'preprocess'), + link=(mode == 'link')) + if mode == 'preprocess': + # Add CPPFLAGS from the env. + args += env.coredata.external_preprocess_args[self.language] + elif mode == 'compile': + # Add CFLAGS/CXXFLAGS/OBJCFLAGS/OBJCXXFLAGS from the env + args += env.coredata.external_args[self.language] + elif mode == 'link': + # Add LDFLAGS from the env + args += env.coredata.external_link_args[self.language] args += self.get_compiler_check_args() # extra_args must override all other arguments, so we add them last args += extra_args + return args + + def compiles(self, code, env, extra_args=None, dependencies=None, mode='compile'): + args = self._get_compiler_check_args(env, extra_args, dependencies, mode) # We only want to compile; not link with self.compile(code, args.to_native(), mode) as p: return p.returncode == 0 def _links_wrapper(self, code, env, extra_args, dependencies): "Shares common code between self.links and self.run" - if extra_args is None: - extra_args = [] - elif isinstance(extra_args, str): - extra_args = [extra_args] - if dependencies is None: - dependencies = [] - elif not isinstance(dependencies, list): - dependencies = [dependencies] - # Add compile and link flags needed by dependencies - args = CompilerArgs(self) - for d in dependencies: - args += d.get_compile_args() - args += d.get_link_args() - # Select a CRT if needed since we're linking - args += self.get_linker_debug_crt_args() - # Read c_args/c_link_args/cpp_args/cpp_link_args/etc from the - # cross-info file (if needed) - args += self.get_cross_extra_flags(env, compile=True, link=True) - # Add LDFLAGS from the env. We assume that the user has ensured these - # are compiler-specific - args += env.coredata.external_link_args[self.language] - # Add compiler check args such that they override - args += self.get_compiler_check_args() - # extra_args must override all other arguments, so we add them last - args += extra_args + args = self._get_compiler_check_args(env, extra_args, dependencies, mode='link') return self.compile(code, args.to_native()) def links(self, code, env, extra_args=None, dependencies=None): @@ -1141,6 +1131,24 @@ class CCompiler(Compiler): raise EnvironmentException('Could not determine alignment of %s. Sorry. You might want to file a bug.' % typename) return align + def get_define(self, dname, prefix, env, extra_args, dependencies): + delim = '"MESON_GET_DEFINE_DELIMITER"' + fargs = {'prefix': prefix, 'define': dname, 'delim': delim} + code = ''' + #ifndef {define} + # define {define} + #endif + {prefix} + {delim}\n{define}''' + args = self._get_compiler_check_args(env, extra_args, dependencies, + mode='preprocess').to_native() + with self.compile(code.format(**fargs), args, 'preprocess') as p: + if p.returncode != 0: + raise EnvironmentException('Could not get define {!r}'.format(dname)) + # Get the preprocessed value after the delimiter, + # minus the extra newline at the end + return p.stdo.split(delim + '\n')[-1][:-1] + @staticmethod def _no_prototype_templ(): """ @@ -2301,6 +2309,7 @@ class VisualStudioCPPCompiler(VisualStudioCCompiler, CPPCompiler): GCC_STANDARD = 0 GCC_OSX = 1 GCC_MINGW = 2 +GCC_CYGWIN = 3 CLANG_STANDARD = 0 CLANG_OSX = 1 @@ -2316,7 +2325,7 @@ def get_gcc_soname_args(gcc_type, prefix, shlib_name, suffix, path, soversion, i sostr = '' else: sostr = '.' + soversion - if gcc_type == GCC_STANDARD or gcc_type == GCC_MINGW: + if gcc_type in (GCC_STANDARD, GCC_MINGW, GCC_CYGWIN): # Might not be correct for mingw but seems to work. return ['-Wl,-soname,%s%s.%s%s' % (prefix, shlib_name, suffix, sostr)] elif gcc_type == GCC_OSX: @@ -2382,15 +2391,15 @@ class GnuCompiler: args[args.index('-Wpedantic')] = '-pedantic' return args - def has_define(self, define): + def has_builtin_define(self, define): return define in self.defines - def get_define(self, define): + def get_builtin_define(self, define): if define in self.defines: return self.defines[define] def get_pic_args(self): - if self.gcc_type in (GCC_MINGW, GCC_OSX): + if self.gcc_type in (GCC_CYGWIN, GCC_MINGW, GCC_OSX): return [] # On Window and OS X, pic is always on. return ['-fPIC'] @@ -2788,7 +2797,7 @@ class FortranCompiler(Compiler): return ' '.join(self.exelist) def get_pic_args(self): - if self.gcc_type in (GCC_MINGW, GCC_OSX): + if self.gcc_type in (GCC_CYGWIN, GCC_MINGW, GCC_OSX): return [] # On Window and OS X, pic is always on. return ['-fPIC'] @@ -2896,10 +2905,10 @@ class GnuFortranCompiler(FortranCompiler): self.defines = defines or {} self.id = 'gcc' - def has_define(self, define): + def has_builtin_define(self, define): return define in self.defines - def get_define(self, define): + def get_builtin_define(self, define): if define in self.defines: return self.defines[define] diff --git a/mesonbuild/coredata.py b/mesonbuild/coredata.py index 51eeaff..27f1dd7 100644 --- a/mesonbuild/coredata.py +++ b/mesonbuild/coredata.py @@ -17,6 +17,7 @@ from pathlib import PurePath from collections import OrderedDict from .mesonlib import MesonException, commonpath from .mesonlib import default_libdir, default_libexecdir, default_prefix +import ast version = '0.40.0.dev1' backendlist = ['ninja', 'vs', 'vs2010', 'vs2015', 'vs2017', 'xcode'] @@ -31,6 +32,12 @@ class UserOption: def parse_string(self, valuestring): return valuestring + # Check that the input is a valid value and return the + # "cleaned" or "native" version. For example the Boolean + # option could take the string "true" and return True. + def validate_value(self, value): + raise RuntimeError('Derived option class did not override validate_value.') + class UserStringOption(UserOption): def __init__(self, name, description, value, choices=None): super().__init__(name, description, choices) @@ -44,6 +51,10 @@ class UserStringOption(UserOption): self.validate(newvalue) self.value = newvalue + def validate_value(self, value): + self.validate(value) + return value + class UserBooleanOption(UserOption): def __init__(self, name, description, value): super().__init__(name, description, [True, False]) @@ -71,6 +82,9 @@ class UserBooleanOption(UserOption): def __bool__(self): return self.value + def validate_value(self, value): + return self.tobool(value) + class UserComboOption(UserOption): def __init__(self, name, description, choices, value): super().__init__(name, description, choices) @@ -87,22 +101,36 @@ class UserComboOption(UserOption): raise MesonException('Value "%s" for combo option "%s" is not one of the choices. Possible choices are: %s.' % (newvalue, self.name, optionsstring)) self.value = newvalue + def validate_value(self, value): + if value not in self.choices: + raise MesonException('Value %s not one of accepted values.' % value) + return value + class UserStringArrayOption(UserOption): def __init__(self, name, description, value, **kwargs): super().__init__(name, description, kwargs.get('choices', [])) self.set_value(value) - def set_value(self, newvalue): - if isinstance(newvalue, str): - if not newvalue.startswith('['): - raise MesonException('Valuestring does not define an array: ' + newvalue) - newvalue = eval(newvalue, {}, {}) # Yes, it is unsafe. + def validate(self, value): + if isinstance(value, str): + if not value.startswith('['): + raise MesonException('Valuestring does not define an array: ' + value) + newvalue = ast.literal_eval(value) + else: + newvalue = value if not isinstance(newvalue, list): raise MesonException('"{0}" should be a string array, but it is not'.format(str(newvalue))) for i in newvalue: if not isinstance(i, str): raise MesonException('String array element "{0}" is not a string.'.format(str(newvalue))) - self.value = newvalue + return newvalue + + def set_value(self, newvalue): + self.value = self.validate(newvalue) + + def validate_value(self, value): + self.validate(value) + return value # This class contains all data that must persist over multiple # invocations of Meson. It is roughly the same thing as @@ -120,10 +148,11 @@ class CoreData: self.user_options = {} self.compiler_options = {} self.base_options = {} - # These two, external_*args, are set via env vars CFLAGS, LDFLAGS, etc + # These external_*args, are set via env vars CFLAGS, LDFLAGS, etc # but only when not cross-compiling. - self.external_args = {} - self.external_link_args = {} + self.external_preprocess_args = {} # CPPFLAGS only + self.external_args = {} # CPPFLAGS + CFLAGS + self.external_link_args = {} # CFLAGS + LDFLAGS (with MSVC: only LDFLAGS) if options.cross_file is not None: self.cross_file = os.path.join(os.getcwd(), options.cross_file) else: @@ -204,6 +233,13 @@ class CoreData: raise RuntimeError('Tried to set unknown builtin option %s.' % optname) self.builtins[optname].set_value(value) + def validate_option_value(self, option_name, override_value): + for opts in (self.builtins, self.base_options, self.compiler_options, self.user_options): + if option_name in opts: + opt = opts[option_name] + return opt.validate_value(override_value) + raise MesonException('Tried to validate unknown option %s.' % option_name) + def load(filename): load_fail_msg = 'Coredata file {!r} is corrupted. Try with a fresh build tree.'.format(filename) try: diff --git a/mesonbuild/dependencies.py b/mesonbuild/dependencies.py index eacb15b..25ca411 100644 --- a/mesonbuild/dependencies.py +++ b/mesonbuild/dependencies.py @@ -24,6 +24,7 @@ import sys import os, stat, glob, shutil import subprocess import sysconfig +from enum import Enum from collections import OrderedDict from . mesonlib import MesonException, version_compare, version_compare_many, Popen_safe from . import mlog @@ -33,11 +34,35 @@ from .environment import detect_cpu_family, for_windows class DependencyException(MesonException): '''Exceptions raised while trying to find dependencies''' +class DependencyMethods(Enum): + # Auto means to use whatever dependency checking mechanisms in whatever order meson thinks is best. + AUTO = 'auto' + PKGCONFIG = 'pkg-config' + QMAKE = 'qmake' + # Just specify the standard link arguments, assuming the operating system provides the library. + SYSTEM = 'system' + # Detect using sdl2-config + SDLCONFIG = 'sdlconfig' + # This is only supported on OSX - search the frameworks directory by name. + EXTRAFRAMEWORK = 'extraframework' + # Detect using the sysconfig module. + SYSCONFIG = 'sysconfig' + class Dependency: - def __init__(self, type_name='unknown'): + def __init__(self, type_name, kwargs): self.name = "null" self.is_found = False self.type_name = type_name + method = DependencyMethods(kwargs.get('method', 'auto')) + + # Set the detection method. If the method is set to auto, use any available method. + # If method is set to a specific string, allow only that detection method. + if method == DependencyMethods.AUTO: + self.methods = self.get_methods() + elif method in self.get_methods(): + self.methods = [method] + else: + raise MesonException('Unsupported detection method: {}, allowed methods are {}'.format(method.value, mlog.format_list(map(lambda x: x.value, [DependencyMethods.AUTO] + self.get_methods())))) def __repr__(self): s = '<{0} {1}: {2}>' @@ -57,6 +82,9 @@ class Dependency: As an example, gtest-all.cc when using GTest.""" return [] + def get_methods(self): + return [DependencyMethods.AUTO] + def get_name(self): return self.name @@ -71,7 +99,7 @@ class Dependency: class InternalDependency(Dependency): def __init__(self, version, incdirs, compile_args, link_args, libraries, sources, ext_deps): - super().__init__('internal') + super().__init__('internal', {}) self.version = version self.include_directories = incdirs self.compile_args = compile_args @@ -95,7 +123,7 @@ class PkgConfigDependency(Dependency): class_pkgbin = None def __init__(self, name, environment, kwargs): - Dependency.__init__(self, 'pkgconfig') + Dependency.__init__(self, 'pkgconfig', kwargs) self.is_libtool = False self.required = kwargs.get('required', True) self.static = kwargs.get('static', False) @@ -254,6 +282,9 @@ class PkgConfigDependency(Dependency): def get_link_args(self): return self.libs + def get_methods(self): + return [DependencyMethods.PKGCONFIG] + def check_pkgconfig(self): evar = 'PKG_CONFIG' if evar in os.environ: @@ -322,7 +353,7 @@ class WxDependency(Dependency): wx_found = None def __init__(self, environment, kwargs): - Dependency.__init__(self, 'wx') + Dependency.__init__(self, 'wx', kwargs) self.is_found = False self.modversion = 'none' if WxDependency.wx_found is None: @@ -542,7 +573,7 @@ class ExternalLibrary(Dependency): # TODO: Add `lang` to the parent Dependency object so that dependencies can # be expressed for languages other than C-like def __init__(self, name, link_args=None, language=None, silent=False): - super().__init__('external') + super().__init__('external', {}) self.name = name self.is_found = False self.link_args = [] @@ -582,7 +613,7 @@ class BoostDependency(Dependency): name2lib = {'test': 'unit_test_framework'} def __init__(self, environment, kwargs): - Dependency.__init__(self, 'boost') + Dependency.__init__(self, 'boost', kwargs) self.name = 'boost' self.environment = environment self.libdir = '' @@ -598,12 +629,18 @@ class BoostDependency(Dependency): self.boost_root = None if self.boost_root is None: if self.want_cross: - raise DependencyException('BOOST_ROOT is needed while cross-compiling') + if 'BOOST_INCLUDEDIR' in os.environ: + self.incdir = os.environ['BOOST_INCLUDEDIR'] + else: + raise DependencyException('BOOST_ROOT or BOOST_INCLUDEDIR is needed while cross-compiling') if mesonlib.is_windows(): self.boost_root = self.detect_win_root() self.incdir = self.boost_root else: - self.incdir = '/usr/include' + if 'BOOST_INCLUDEDIR' in os.environ: + self.incdir = os.environ['BOOST_INCLUDEDIR'] + else: + self.incdir = '/usr/include' else: self.incdir = os.path.join(self.boost_root, 'include') self.boost_inc_subdir = os.path.join(self.incdir, 'boost') @@ -727,7 +764,9 @@ class BoostDependency(Dependency): libsuffix = 'so' globber = 'libboost_*.{}'.format(libsuffix) - if self.boost_root is None: + if 'BOOST_LIBRARYDIR' in os.environ: + libdirs = [os.environ['BOOST_LIBRARYDIR']] + elif self.boost_root is None: libdirs = mesonlib.get_library_dirs() else: libdirs = [os.path.join(self.boost_root, 'lib')] @@ -758,6 +797,8 @@ class BoostDependency(Dependency): args = [] if self.boost_root: args.append('-L' + os.path.join(self.boost_root, 'lib')) + elif 'BOOST_LIBRARYDIR' in os.environ: + args.append('-L' + os.environ['BOOST_LIBRARYDIR']) for module in self.requested_modules: module = BoostDependency.name2lib.get(module, module) libname = 'boost_' + module @@ -795,7 +836,7 @@ class BoostDependency(Dependency): class GTestDependency(Dependency): def __init__(self, environment, kwargs): - Dependency.__init__(self, 'gtest') + Dependency.__init__(self, 'gtest', kwargs) self.main = kwargs.get('main', False) self.name = 'gtest' self.libname = 'libgtest.so' @@ -872,7 +913,7 @@ class GTestDependency(Dependency): class GMockDependency(Dependency): def __init__(self, environment, kwargs): - Dependency.__init__(self, 'gmock') + Dependency.__init__(self, 'gmock', kwargs) # GMock may be a library or just source. # Work with both. self.name = 'gmock' @@ -927,7 +968,7 @@ class GMockDependency(Dependency): class QtBaseDependency(Dependency): def __init__(self, name, env, kwargs): - Dependency.__init__(self, name) + Dependency.__init__(self, name, kwargs) self.name = name self.qtname = name.capitalize() self.qtver = name[-1] @@ -954,26 +995,32 @@ class QtBaseDependency(Dependency): type_text = 'cross' if env.is_cross_build() else 'native' found_msg = '{} {} {{}} dependency (modules: {}) found:' \ ''.format(self.qtname, type_text, ', '.join(mods)) - from_text = '`pkg-config`' + from_text = 'pkg-config' + + # Keep track of the detection methods used, for logging purposes. + methods = [] # Prefer pkg-config, then fallback to `qmake -query` - self._pkgconfig_detect(mods, env, kwargs) - if not self.is_found: + if DependencyMethods.PKGCONFIG in self.methods: + self._pkgconfig_detect(mods, env, kwargs) + methods.append('pkgconfig') + if not self.is_found and DependencyMethods.QMAKE in self.methods: from_text = self._qmake_detect(mods, env, kwargs) - if not self.is_found: - # Reset compile args and link args - self.cargs = [] - self.largs = [] - from_text = '(checked pkg-config, qmake-{}, and qmake)' \ - ''.format(self.name) - self.version = 'none' - if self.required: - err_msg = '{} {} dependency not found {}' \ - ''.format(self.qtname, type_text, from_text) - raise DependencyException(err_msg) - if not self.silent: - mlog.log(found_msg.format(from_text), mlog.red('NO')) - return - from_text = '`{}`'.format(from_text) + methods.append('qmake-' + self.name) + methods.append('qmake') + if not self.is_found: + # Reset compile args and link args + self.cargs = [] + self.largs = [] + from_text = '(checked {})'.format(mlog.format_list(methods)) + self.version = 'none' + if self.required: + err_msg = '{} {} dependency not found {}' \ + ''.format(self.qtname, type_text, from_text) + raise DependencyException(err_msg) + if not self.silent: + mlog.log(found_msg.format(from_text), mlog.red('NO')) + return + from_text = '`{}`'.format(from_text) if not self.silent: mlog.log(found_msg.format(from_text), mlog.green('YES')) @@ -1043,6 +1090,7 @@ class QtBaseDependency(Dependency): return self.version = re.search(self.qtver + '(\.\d+)+', stdo).group(0) # Query library path, header path, and binary path + mlog.log("Found qmake:", mlog.bold(self.qmake.get_name()), '(%s)' % self.version) stdo = Popen_safe(self.qmake.get_command() + ['-query'])[1] qvars = {} for line in stdo.split('\n'): @@ -1082,7 +1130,7 @@ class QtBaseDependency(Dependency): libdir = qvars['QT_INSTALL_LIBS'] for m in modules: fname = 'Qt' + m - fwdep = ExtraFrameworkDependency(fname, kwargs.get('required', True), libdir) + fwdep = ExtraFrameworkDependency(fname, kwargs.get('required', True), libdir, kwargs) self.cargs.append('-F' + libdir) if fwdep.found(): self.is_found = True @@ -1103,6 +1151,9 @@ class QtBaseDependency(Dependency): def get_link_args(self): return self.largs + def get_methods(self): + return [DependencyMethods.PKGCONFIG, DependencyMethods.QMAKE] + def found(self): return self.is_found @@ -1140,7 +1191,7 @@ class Qt4Dependency(QtBaseDependency): class GnuStepDependency(Dependency): def __init__(self, environment, kwargs): - Dependency.__init__(self, 'gnustep') + Dependency.__init__(self, 'gnustep', kwargs) self.required = kwargs.get('required', True) self.modules = kwargs.get('modules', []) self.detect() @@ -1238,7 +1289,7 @@ why. As a hack filter out everything that is not a flag.""" class AppleFrameworks(Dependency): def __init__(self, environment, kwargs): - Dependency.__init__(self, 'appleframeworks') + Dependency.__init__(self, 'appleframeworks', kwargs) modules = kwargs.get('modules', []) if isinstance(modules, str): modules = [modules] @@ -1261,31 +1312,33 @@ class AppleFrameworks(Dependency): class GLDependency(Dependency): def __init__(self, environment, kwargs): - Dependency.__init__(self, 'gl') + Dependency.__init__(self, 'gl', kwargs) self.is_found = False self.cargs = [] self.linkargs = [] - try: - pcdep = PkgConfigDependency('gl', environment, kwargs) - if pcdep.found(): - self.type_name = 'pkgconfig' + if DependencyMethods.PKGCONFIG in self.methods: + try: + pcdep = PkgConfigDependency('gl', environment, kwargs) + if pcdep.found(): + self.type_name = 'pkgconfig' + self.is_found = True + self.cargs = pcdep.get_compile_args() + self.linkargs = pcdep.get_link_args() + self.version = pcdep.get_version() + return + except Exception: + pass + if DependencyMethods.SYSTEM in self.methods: + if mesonlib.is_osx(): self.is_found = True - self.cargs = pcdep.get_compile_args() - self.linkargs = pcdep.get_link_args() - self.version = pcdep.get_version() + self.linkargs = ['-framework', 'OpenGL'] + self.version = '1' # FIXME + return + if mesonlib.is_windows(): + self.is_found = True + self.linkargs = ['-lopengl32'] + self.version = '1' # FIXME: unfixable? return - except Exception: - pass - if mesonlib.is_osx(): - self.is_found = True - self.linkargs = ['-framework', 'OpenGL'] - self.version = '1' # FIXME - return - if mesonlib.is_windows(): - self.is_found = True - self.linkargs = ['-lopengl32'] - self.version = '1' # FIXME: unfixable? - return def get_link_args(self): return self.linkargs @@ -1293,48 +1346,57 @@ class GLDependency(Dependency): def get_version(self): return self.version + def get_methods(self): + if mesonlib.is_osx() or mesonlib.is_windows(): + return [DependencyMethods.PKGCONFIG, DependencyMethods.SYSTEM] + else: + return [DependencyMethods.PKGCONFIG] + # There are three different ways of depending on SDL2: # sdl2-config, pkg-config and OSX framework class SDL2Dependency(Dependency): def __init__(self, environment, kwargs): - Dependency.__init__(self, 'sdl2') + Dependency.__init__(self, 'sdl2', kwargs) self.is_found = False self.cargs = [] self.linkargs = [] - try: - pcdep = PkgConfigDependency('sdl2', environment, kwargs) - if pcdep.found(): - self.type_name = 'pkgconfig' - self.is_found = True - self.cargs = pcdep.get_compile_args() - self.linkargs = pcdep.get_link_args() - self.version = pcdep.get_version() - return - except Exception as e: - mlog.debug('SDL 2 not found via pkgconfig. Trying next, error was:', str(e)) - pass - sdlconf = shutil.which('sdl2-config') - if sdlconf: - stdo = Popen_safe(['sdl2-config', '--cflags'])[1] - self.cargs = stdo.strip().split() - stdo = Popen_safe(['sdl2-config', '--libs'])[1] - self.linkargs = stdo.strip().split() - stdo = Popen_safe(['sdl2-config', '--version'])[1] - self.version = stdo.strip() - self.is_found = True - mlog.log('Dependency', mlog.bold('sdl2'), 'found:', mlog.green('YES'), - self.version, '(%s)' % sdlconf) - return - mlog.debug('Could not find sdl2-config binary, trying next.') - if mesonlib.is_osx(): - fwdep = ExtraFrameworkDependency('sdl2', kwargs.get('required', True)) - if fwdep.found(): + if DependencyMethods.PKGCONFIG in self.methods: + try: + pcdep = PkgConfigDependency('sdl2', environment, kwargs) + if pcdep.found(): + self.type_name = 'pkgconfig' + self.is_found = True + self.cargs = pcdep.get_compile_args() + self.linkargs = pcdep.get_link_args() + self.version = pcdep.get_version() + return + except Exception as e: + mlog.debug('SDL 2 not found via pkgconfig. Trying next, error was:', str(e)) + pass + if DependencyMethods.SDLCONFIG in self.methods: + sdlconf = shutil.which('sdl2-config') + if sdlconf: + stdo = Popen_safe(['sdl2-config', '--cflags'])[1] + self.cargs = stdo.strip().split() + stdo = Popen_safe(['sdl2-config', '--libs'])[1] + self.linkargs = stdo.strip().split() + stdo = Popen_safe(['sdl2-config', '--version'])[1] + self.version = stdo.strip() self.is_found = True - self.cargs = fwdep.get_compile_args() - self.linkargs = fwdep.get_link_args() - self.version = '2' # FIXME + mlog.log('Dependency', mlog.bold('sdl2'), 'found:', mlog.green('YES'), + self.version, '(%s)' % sdlconf) return - mlog.log('Dependency', mlog.bold('sdl2'), 'found:', mlog.red('NO')) + mlog.debug('Could not find sdl2-config binary, trying next.') + if DependencyMethods.EXTRAFRAMEWORK in self.methods: + if mesonlib.is_osx(): + fwdep = ExtraFrameworkDependency('sdl2', kwargs.get('required', True), None, kwargs) + if fwdep.found(): + self.is_found = True + self.cargs = fwdep.get_compile_args() + self.linkargs = fwdep.get_link_args() + self.version = '2' # FIXME + return + mlog.log('Dependency', mlog.bold('sdl2'), 'found:', mlog.red('NO')) def get_compile_args(self): return self.cargs @@ -1348,9 +1410,15 @@ class SDL2Dependency(Dependency): def get_version(self): return self.version + def get_methods(self): + if mesonlib.is_osx(): + return [DependencyMethods.PKGCONFIG, DependencyMethods.SDLCONFIG, DependencyMethods.EXTRAFRAMEWORK] + else: + return [DependencyMethods.PKGCONFIG, DependencyMethods.SDLCONFIG] + class ExtraFrameworkDependency(Dependency): - def __init__(self, name, required, path=None): - Dependency.__init__(self, 'extraframeworks') + def __init__(self, name, required, path, kwargs): + Dependency.__init__(self, 'extraframeworks', kwargs) self.name = None self.detect(name, path) if self.found(): @@ -1394,7 +1462,7 @@ class ExtraFrameworkDependency(Dependency): class ThreadDependency(Dependency): def __init__(self, environment, kwargs): - super().__init__('threads') + super().__init__('threads', {}) self.name = 'threads' self.is_found = True mlog.log('Dependency', mlog.bold(self.name), 'found:', mlog.green('YES')) @@ -1407,28 +1475,29 @@ class ThreadDependency(Dependency): class Python3Dependency(Dependency): def __init__(self, environment, kwargs): - super().__init__('python3') + super().__init__('python3', kwargs) self.name = 'python3' self.is_found = False # We can only be sure that it is Python 3 at this point self.version = '3' - try: - pkgdep = PkgConfigDependency('python3', environment, kwargs) - if pkgdep.found(): - self.cargs = pkgdep.cargs - self.libs = pkgdep.libs - self.version = pkgdep.get_version() - self.is_found = True - return - except Exception: - pass + if DependencyMethods.PKGCONFIG in self.methods: + try: + pkgdep = PkgConfigDependency('python3', environment, kwargs) + if pkgdep.found(): + self.cargs = pkgdep.cargs + self.libs = pkgdep.libs + self.version = pkgdep.get_version() + self.is_found = True + return + except Exception: + pass if not self.is_found: - if mesonlib.is_windows(): + if mesonlib.is_windows() and DependencyMethods.SYSCONFIG in self.methods: self._find_libpy3_windows(environment) - elif mesonlib.is_osx(): + elif mesonlib.is_osx() and DependencyMethods.EXTRAFRAMEWORK in self.methods: # In OSX the Python 3 framework does not have a version # number in its name. - fw = ExtraFrameworkDependency('python', False) + fw = ExtraFrameworkDependency('python', False, None, kwargs) if fw.found(): self.cargs = fw.get_compile_args() self.libs = fw.get_link_args() @@ -1480,9 +1549,25 @@ class Python3Dependency(Dependency): def get_link_args(self): return self.libs + def get_methods(self): + if mesonlib.is_windows(): + return [DependencyMethods.PKGCONFIG, DependencyMethods.SYSCONFIG] + elif mesonlib.is_osx(): + return [DependencyMethods.PKGCONFIG, DependencyMethods.EXTRAFRAMEWORK] + else: + return [DependencyMethods.PKGCONFIG] + def get_version(self): return self.version +class ValgrindDependency(PkgConfigDependency): + + def __init__(self, environment, kwargs): + PkgConfigDependency.__init__(self, 'valgrind', environment, kwargs) + + def get_link_args(self): + return [] + def get_dep_identifier(name, kwargs): elements = [name] modlist = kwargs.get('modules', []) @@ -1504,6 +1589,8 @@ def find_external_dependency(name, environment, kwargs): required = kwargs.get('required', True) if not isinstance(required, bool): raise DependencyException('Keyword "required" must be a boolean.') + if not isinstance(kwargs.get('method', ''), str): + raise DependencyException('Keyword "method" must be a string.') lname = name.lower() if lname in packages: dep = packages[lname](environment, kwargs) @@ -1519,7 +1606,7 @@ def find_external_dependency(name, environment, kwargs): except Exception as e: pkg_exc = e if mesonlib.is_osx(): - fwdep = ExtraFrameworkDependency(name, required) + fwdep = ExtraFrameworkDependency(name, required, None, kwargs) if required and not fwdep.found(): m = 'Dependency {!r} not found, tried Extra Frameworks ' \ 'and Pkg-Config:\n\n' + str(pkg_exc) @@ -1544,4 +1631,5 @@ packages = {'boost': BoostDependency, 'gl': GLDependency, 'threads': ThreadDependency, 'python3': Python3Dependency, + 'valgrind': ValgrindDependency, } diff --git a/mesonbuild/environment.py b/mesonbuild/environment.py index 92040c4..93a41e8 100644 --- a/mesonbuild/environment.py +++ b/mesonbuild/environment.py @@ -102,7 +102,7 @@ def detect_windows_arch(compilers): platform = os.environ.get('Platform', 'x86').lower() if platform == 'x86': return platform - if compiler.id == 'gcc' and compiler.has_define('__i386__'): + if compiler.id == 'gcc' and compiler.has_builtin_define('__i386__'): return 'x86' return os_arch @@ -129,10 +129,10 @@ def detect_cpu_family(compilers): # to know is to check the compiler defines. for c in compilers.values(): try: - if c.has_define('__i386__'): + if c.has_builtin_define('__i386__'): return 'x86' except mesonlib.MesonException: - # Ignore compilers that do not support has_define. + # Ignore compilers that do not support has_builtin_define. pass return 'x86_64' # Add fixes here as bugs are reported. @@ -149,7 +149,7 @@ def detect_cpu(compilers): # Same check as above for cpu_family for c in compilers.values(): try: - if c.has_define('__i386__'): + if c.has_builtin_define('__i386__'): return 'i686' # All 64 bit cpus have at least this level of x86 support. except mesonlib.MesonException: pass @@ -158,7 +158,10 @@ def detect_cpu(compilers): return trial def detect_system(): - return platform.system().lower() + system = platform.system().lower() + if system.startswith('cygwin'): + return 'cygwin' + return system def for_windows(is_cross, env): @@ -173,6 +176,20 @@ def for_windows(is_cross, env): return env.cross_info.config['host_machine']['system'] == 'windows' return False + +def for_cygwin(is_cross, env): + """ + Host machine is cygwin? + + Note: 'host' is the machine on which compiled binaries will run + """ + if not is_cross: + return mesonlib.is_cygwin() + elif env.cross_info.has_host(): + return env.cross_info.config['host_machine']['system'] == 'cygwin' + return False + + def for_darwin(is_cross, env): """ Host machine is Darwin (iOS/OS X)? @@ -257,6 +274,11 @@ class Environment: self.exe_suffix = 'exe' self.object_suffix = 'obj' self.win_libdir_layout = True + elif (not cross and mesonlib.is_cygwin()) \ + or (cross and self.cross_info.has_host() and self.cross_info.config['host_machine']['system'] == 'cygwin'): + self.exe_suffix = 'exe' + self.object_suffix = 'o' + self.win_libdir_layout = True else: self.exe_suffix = '' self.object_suffix = 'o' @@ -368,7 +390,8 @@ class Environment: return GCC_OSX elif '__MINGW32__' in defines or '__MINGW64__' in defines: return GCC_MINGW - # We ignore Cygwin for now, and treat it as a standard GCC + elif '__CYGWIN__' in defines: + return GCC_CYGWIN return GCC_STANDARD def _get_compilers(self, lang, evar, want_cross): @@ -718,6 +741,10 @@ class Environment: "Install dir for the import library (library used for linking)" return self.get_libdir() + def get_shared_module_dir(self): + "Install dir for shared modules that are loaded at runtime" + return self.get_libdir() + def get_shared_lib_dir(self): "Install dir for the shared library" if self.win_libdir_layout: @@ -770,7 +797,7 @@ def get_args_from_envvars(compiler): compiler_is_linker = (compiler.get_exelist() == compiler.get_linker_exelist()) if lang not in ('c', 'cpp', 'objc', 'objcpp', 'fortran', 'd'): - return [], [] + return [], [], [] # Compile flags cflags_mapping = {'c': 'CFLAGS', @@ -781,12 +808,12 @@ def get_args_from_envvars(compiler): 'd': 'DFLAGS'} compile_flags = os.environ.get(cflags_mapping[lang], '') log_var(cflags_mapping[lang], compile_flags) - compile_flags = compile_flags.split() + compile_flags = shlex.split(compile_flags) # Link flags (same for all languages) link_flags = os.environ.get('LDFLAGS', '') log_var('LDFLAGS', link_flags) - link_flags = link_flags.split() + link_flags = shlex.split(link_flags) if compiler_is_linker: # When the compiler is used as a wrapper around the linker (such as # with GCC and Clang), the compile flags can be needed while linking @@ -794,14 +821,15 @@ def get_args_from_envvars(compiler): # this when the linker is stand-alone such as with MSVC C/C++, etc. link_flags = compile_flags + link_flags - # Pre-processof rlags (not for fortran) + # Pre-processor flags (not for fortran or D) preproc_flags = '' if lang in ('c', 'cpp', 'objc', 'objcpp'): preproc_flags = os.environ.get('CPPFLAGS', '') log_var('CPPFLAGS', preproc_flags) - compile_flags += preproc_flags.split() + preproc_flags = shlex.split(preproc_flags) + compile_flags += preproc_flags - return compile_flags, link_flags + return preproc_flags, compile_flags, link_flags class CrossBuildInfo: def __init__(self, filename): diff --git a/mesonbuild/interpreter.py b/mesonbuild/interpreter.py index 0c6d980..18f91ea 100644 --- a/mesonbuild/interpreter.py +++ b/mesonbuild/interpreter.py @@ -22,7 +22,7 @@ from . import optinterpreter from . import compilers from .wrap import wrap, WrapMode from . import mesonlib -from .mesonlib import FileMode, Popen_safe +from .mesonlib import FileMode, Popen_safe, get_meson_script from .dependencies import InternalDependency, Dependency from .interpreterbase import InterpreterBase from .interpreterbase import check_stringlist, noPosargs, noKwargs, stringArgs @@ -72,20 +72,21 @@ class TryRunResultHolder(InterpreterObject): class RunProcess(InterpreterObject): - def __init__(self, command_array, source_dir, build_dir, subdir, in_builddir=False): + def __init__(self, command_array, source_dir, build_dir, subdir, mesonintrospect, in_builddir=False): super().__init__() - pc, self.stdout, self.stderr = self.run_command(command_array, source_dir, build_dir, subdir, in_builddir) + pc, self.stdout, self.stderr = self.run_command(command_array, source_dir, build_dir, subdir, mesonintrospect, in_builddir) self.returncode = pc.returncode self.methods.update({'returncode': self.returncode_method, 'stdout': self.stdout_method, 'stderr': self.stderr_method, }) - def run_command(self, command_array, source_dir, build_dir, subdir, in_builddir): + def run_command(self, command_array, source_dir, build_dir, subdir, mesonintrospect, in_builddir): cmd_name = command_array[0] env = {'MESON_SOURCE_ROOT': source_dir, 'MESON_BUILD_ROOT': build_dir, - 'MESON_SUBDIR': subdir} + 'MESON_SUBDIR': subdir, + 'MESONINTROSPECT': mesonintrospect} if in_builddir: cwd = os.path.join(build_dir, subdir) else: @@ -634,6 +635,7 @@ class CompilerHolder(InterpreterObject): 'get_id': self.get_id_method, 'compute_int': self.compute_int_method, 'sizeof': self.sizeof_method, + 'get_define': self.get_define_method, 'has_header': self.has_header_method, 'has_header_symbol': self.has_header_symbol_method, 'run': self.run_method, @@ -865,6 +867,20 @@ class CompilerHolder(InterpreterObject): mlog.log('Checking for size of "%s": %d' % (element, esize)) return esize + def get_define_method(self, args, kwargs): + if len(args) != 1: + raise InterpreterException('get_define() takes exactly one argument.') + check_stringlist(args) + element = args[0] + prefix = kwargs.get('prefix', '') + if not isinstance(prefix, str): + raise InterpreterException('Prefix argument of get_define() must be a string.') + extra_args = self.determine_args(kwargs) + deps = self.determine_dependencies(kwargs) + value = self.compiler.get_define(element, prefix, self.environment, extra_args, deps) + mlog.log('Checking for value of define "%s": %s' % (element, value)) + return value + def compiles_method(self, args, kwargs): if len(args) != 1: raise InterpreterException('compiles method takes exactly one argument.') @@ -1093,7 +1109,7 @@ class MesonMain(InterpreterObject): found = self._found_source_scripts[key] else: found = dependencies.ExternalProgram(name, search_dir=search_dir) - if found: + if found.found(): self._found_source_scripts[key] = found else: raise InterpreterException('Script {!r} not found'.format(name)) @@ -1224,7 +1240,8 @@ class Interpreter(InterpreterBase): self.builtin.update({'meson': MesonMain(build, self)}) self.generators = [] self.visited_subdirs = {} - self.args_frozen = False + self.project_args_frozen = False + self.global_args_frozen = False # implies self.project_args_frozen self.subprojects = {} self.subproject_stack = [] self.default_project_options = default_project_options[:] # Passed from the outside, only used in subprojects. @@ -1474,8 +1491,8 @@ class Interpreter(InterpreterBase): in_builddir = kwargs.get('in_builddir', False) if not isinstance(in_builddir, bool): raise InterpreterException('in_builddir must be boolean.') - return RunProcess(args, self.environment.source_dir, self.environment.build_dir, - self.subdir, in_builddir) + return RunProcess(args, self.environment.source_dir, self.environment.build_dir, self.subdir, + get_meson_script(self.environment, 'mesonintrospect'), in_builddir) @stringArgs def func_gettext(self, nodes, args, kwargs): @@ -1507,7 +1524,7 @@ class Interpreter(InterpreterBase): raise InterpreterException(msg.format(os.path.join(self.subproject_dir, dirname), e)) subdir = os.path.join(self.subproject_dir, resolved) os.makedirs(os.path.join(self.build.environment.get_build_dir(), subdir), exist_ok=True) - self.args_frozen = True + self.global_args_frozen = True mlog.log('\nExecuting subproject ', mlog.bold(dirname), '.\n', sep='') subi = Interpreter(self.build, self.backend, dirname, subdir, self.subproject_dir, mesonlib.stringlistify(kwargs.get('default_options', []))) @@ -1615,25 +1632,29 @@ class Interpreter(InterpreterBase): def func_project(self, node, args, kwargs): if len(args) < 1: raise InvalidArguments('Not enough arguments to project(). Needs at least the project name.') + proj_name = args[0] + proj_langs = args[1:] + if ':' in proj_name: + raise InvalidArguments("Project name {!r} must not contain ':'".format(proj_name)) default_options = kwargs.get('default_options', []) if self.environment.first_invocation and (len(default_options) > 0 or len(self.default_project_options) > 0): self.parse_default_options(default_options) if not self.is_subproject(): - self.build.project_name = args[0] + self.build.project_name = proj_name if os.path.exists(self.option_file): oi = optinterpreter.OptionInterpreter(self.subproject, self.build.environment.cmd_line_options.projectoptions, ) oi.process(self.option_file) self.build.environment.merge_options(oi.options) - self.active_projectname = args[0] + self.active_projectname = proj_name self.project_version = kwargs.get('version', 'undefined') if self.build.project_version is None: self.build.project_version = self.project_version proj_license = mesonlib.stringlistify(kwargs.get('license', 'unknown')) - self.build.dep_manifest[args[0]] = {'version': self.project_version, - 'license': proj_license} + self.build.dep_manifest[proj_name] = {'version': self.project_version, + 'license': proj_license} if self.subproject in self.build.projects: raise InvalidCode('Second call to project().') if not self.is_subproject() and 'subproject_dir' in kwargs: @@ -1644,9 +1665,9 @@ class Interpreter(InterpreterBase): pv = kwargs['meson_version'] if not mesonlib.version_compare(cv, pv): raise InterpreterException('Meson version is %s but project requires %s.' % (cv, pv)) - self.build.projects[self.subproject] = args[0] - mlog.log('Project name: ', mlog.bold(args[0]), sep='') - self.add_languages(args[1:], True) + self.build.projects[self.subproject] = proj_name + mlog.log('Project name: ', mlog.bold(proj_name), sep='') + self.add_languages(proj_langs, True) langs = self.coredata.compilers.keys() if 'vala' in langs: if 'c' not in langs: @@ -1772,9 +1793,10 @@ class Interpreter(InterpreterBase): raise mlog.log('Native %s compiler: ' % lang, mlog.bold(' '.join(comp.get_exelist())), ' (%s %s)' % (comp.id, comp.version), sep='') if not comp.get_language() in self.coredata.external_args: - (ext_compile_args, ext_link_args) = environment.get_args_from_envvars(comp) - self.coredata.external_args[comp.get_language()] = ext_compile_args - self.coredata.external_link_args[comp.get_language()] = ext_link_args + (preproc_args, compile_args, link_args) = environment.get_args_from_envvars(comp) + self.coredata.external_preprocess_args[comp.get_language()] = preproc_args + self.coredata.external_args[comp.get_language()] = compile_args + self.coredata.external_link_args[comp.get_language()] = link_args self.build.add_compiler(comp) if need_cross_compiler: mlog.log('Cross %s compiler: ' % lang, mlog.bold(' '.join(cross_comp.get_exelist())), ' (%s %s)' % (cross_comp.id, cross_comp.version), sep='') @@ -2221,7 +2243,7 @@ class Interpreter(InterpreterBase): if 'install_mode' not in kwargs: return None install_mode = [] - mode = mesonlib.stringintlistify(kwargs.get('install_mode', [])) + mode = mesonlib.typeslistify(kwargs.get('install_mode', []), (str, int)) for m in mode: # We skip any arguments that are set to `false` if m is False: @@ -2290,7 +2312,11 @@ class Interpreter(InterpreterBase): 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) + if isinstance(inputfile, str): + inputfile = os.path.join(self.subdir, inputfile) + else: + inputfile = inputfile.relative_name() + ifile_abs = os.path.join(self.environment.source_dir, inputfile) elif 'command' in kwargs and '@INPUT@' in kwargs['command']: raise InterpreterException('@INPUT@ used as command argument, but no input file specified.') # Validate output @@ -2309,7 +2335,7 @@ class Interpreter(InterpreterBase): if inputfile is not None: # Normalize the path of the conffile to avoid duplicates # This is especially important to convert '/' to '\' on Windows - conffile = os.path.normpath(os.path.join(self.subdir, inputfile)) + conffile = os.path.normpath(inputfile) 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) @@ -2415,83 +2441,51 @@ different subdirectory. @stringArgs def func_add_global_arguments(self, node, args, kwargs): - if self.subproject != '': - msg = 'Global arguments can not be set in subprojects because ' \ - 'there is no way to make that reliable.\nPlease only call ' \ - 'this if is_subproject() returns false. Alternatively, ' \ - 'define a variable that\ncontains your language-specific ' \ - 'arguments and add it to the appropriate *_args kwarg ' \ - 'in each target.' - raise InvalidCode(msg) - if self.args_frozen: - msg = 'Tried to set global arguments after a build target has ' \ - 'been declared.\nThis is not permitted. Please declare all ' \ - 'global arguments before your targets.' - raise InvalidCode(msg) - if 'language' not in kwargs: - raise InvalidCode('Missing language definition in add_global_arguments') - lang = kwargs['language'].lower() - if lang in self.build.global_args: - self.build.global_args[lang] += args - else: - self.build.global_args[lang] = args + self.add_global_arguments(node, self.build.global_args, args, kwargs) @stringArgs def func_add_global_link_arguments(self, node, args, kwargs): + self.add_global_arguments(node, self.build.global_link_args, args, kwargs) + + @stringArgs + def func_add_project_arguments(self, node, args, kwargs): + self.add_project_arguments(node, self.build.projects_args, args, kwargs) + + @stringArgs + def func_add_project_link_arguments(self, node, args, kwargs): + self.add_project_arguments(node, self.build.projects_link_args, args, kwargs) + + def add_global_arguments(self, node, argsdict, args, kwargs): if self.subproject != '': - msg = 'Global link arguments can not be set in subprojects because ' \ + msg = 'Function \'{}\' cannot be used in subprojects because ' \ 'there is no way to make that reliable.\nPlease only call ' \ 'this if is_subproject() returns false. Alternatively, ' \ 'define a variable that\ncontains your language-specific ' \ 'arguments and add it to the appropriate *_args kwarg ' \ - 'in each target.' + 'in each target.'.format(node.func_name) raise InvalidCode(msg) - if self.args_frozen: - msg = 'Tried to set global link arguments after a build target has ' \ - 'been declared.\nThis is not permitted. Please declare all ' \ - 'global arguments before your targets.' + frozen = self.project_args_frozen or self.global_args_frozen + self.add_arguments(node, argsdict, frozen, args, kwargs) + + def add_project_arguments(self, node, argsdict, args, kwargs): + if self.subproject not in argsdict: + argsdict[self.subproject] = {} + self.add_arguments(node, argsdict[self.subproject], + self.project_args_frozen, args, kwargs) + + def add_arguments(self, node, argsdict, args_frozen, args, kwargs): + if args_frozen: + msg = 'Tried to use \'{}\' after a build target has been declared.\n' \ + 'This is not permitted. Please declare all ' \ + 'arguments before your targets.'.format(node.func_name) raise InvalidCode(msg) - if 'language' not in kwargs: - raise InvalidCode('Missing language definition in add_global_link_arguments') - lang = kwargs['language'].lower() - if lang in self.build.global_link_args: - self.build.global_link_args[lang] += args - else: - self.build.global_link_args[lang] = args - @stringArgs - def func_add_project_link_arguments(self, node, args, kwargs): - if self.args_frozen: - msg = 'Tried to set project link arguments after a build target has ' \ - 'been declared.\nThis is not permitted. Please declare all ' \ - 'project link arguments before your targets.' - raise InvalidCode(msg) if 'language' not in kwargs: - raise InvalidCode('Missing language definition in add_project_link_arguments') - lang = kwargs['language'].lower() - if self.subproject not in self.build.projects_link_args: - self.build.projects_link_args[self.subproject] = {} + raise InvalidCode('Missing language definition in {}'.format(node.func_name)) - args = self.build.projects_link_args[self.subproject].get(lang, []) + args - self.build.projects_link_args[self.subproject][lang] = args - - @stringArgs - def func_add_project_arguments(self, node, args, kwargs): - if self.args_frozen: - msg = 'Tried to set project arguments after a build target has ' \ - 'been declared.\nThis is not permitted. Please declare all ' \ - 'project arguments before your targets.' - raise InvalidCode(msg) - - if 'language' not in kwargs: - raise InvalidCode('Missing language definition in add_project_arguments') - - if self.subproject not in self.build.projects_args: - self.build.projects_args[self.subproject] = {} - - lang = kwargs['language'].lower() - args = self.build.projects_args[self.subproject].get(lang, []) + args - self.build.projects_args[self.subproject][lang] = args + for lang in mesonlib.stringlistify(kwargs['language']): + lang = lang.lower() + argsdict[lang] = argsdict.get(lang, []) + args def func_environment(self, node, args, kwargs): return EnvironmentVariablesHolder() @@ -2576,7 +2570,7 @@ different subdirectory. self.add_cross_stdlib_info(target) l = targetholder(target, self) self.add_target(name, l.held_object) - self.args_frozen = True + self.project_args_frozen = True return l def get_used_languages(self, target): diff --git a/mesonbuild/mesonlib.py b/mesonbuild/mesonlib.py index f0bf9ee..ae2f88c 100644 --- a/mesonbuild/mesonlib.py +++ b/mesonbuild/mesonlib.py @@ -122,16 +122,18 @@ class File: self.is_built = is_built self.subdir = subdir self.fname = fname + assert(isinstance(self.subdir, str)) + assert(isinstance(self.fname, str)) def __str__(self): - return os.path.join(self.subdir, self.fname) + return self.relative_name() def __repr__(self): ret = '<File: {0}' if not self.is_built: ret += ' (not built)' ret += '>' - return ret.format(os.path.join(self.subdir, self.fname)) + return ret.format(self.relative_name()) @staticmethod def from_source_file(source_root, subdir, fname): @@ -149,15 +151,15 @@ class File: def rel_to_builddir(self, build_to_src): if self.is_built: - return os.path.join(self.subdir, self.fname) + return self.relative_name() else: return os.path.join(build_to_src, self.subdir, self.fname) def absolute_path(self, srcdir, builddir): + absdir = srcdir if self.is_built: - return os.path.join(builddir, self.subdir, self.fname) - else: - return os.path.join(srcdir, self.subdir, self.fname) + absdir = builddir + return os.path.join(absdir, self.relative_name()) def endswith(self, ending): return self.fname.endswith(ending) @@ -174,6 +176,15 @@ class File: def relative_name(self): return os.path.join(self.subdir, self.fname) +def get_meson_script(env, script): + ''' + Given the path of `meson.py`/`meson`, get the path of a meson script such + as `mesonintrospect` or `mesontest`. + ''' + meson_py = env.get_build_command() + (base, ext) = os.path.splitext(meson_py) + return os.path.join(os.path.dirname(base), script + ext) + def get_compiler_for_source(compilers, src): for comp in compilers: if comp.can_compile(src): @@ -211,6 +222,10 @@ def is_windows(): platname = platform.system().lower() return platname == 'windows' or 'mingw' in platname +def is_cygwin(): + platname = platform.system().lower() + return platname.startswith('cygwin') + def is_debianlike(): return os.path.isfile('/etc/debian_version') @@ -453,25 +468,22 @@ def replace_if_different(dst, dst_tmp): else: os.unlink(dst_tmp) -def stringintlistify(item): - if isinstance(item, (str, int)): +def typeslistify(item, types): + ''' + Ensure that type(@item) is one of @types or a + list of items all of which are of type @types + ''' + if isinstance(item, types): item = [item] if not isinstance(item, list): - raise MesonException('Item must be a list, a string, or an int') + raise MesonException('Item must be a list or one of {!r}'.format(types)) for i in item: - if not isinstance(i, (str, int, type(None))): - raise MesonException('List item must be a string or an int') + if i is not None and not isinstance(i, types): + raise MesonException('List item must be one of {!r}'.format(types)) return item def stringlistify(item): - if isinstance(item, str): - item = [item] - if not isinstance(item, list): - raise MesonException('Item is not a list') - for i in item: - if not isinstance(i, str): - raise MesonException('List item not a string.') - return item + return typeslistify(item, str) def expand_arguments(args): expended_args = [] @@ -493,6 +505,7 @@ def expand_arguments(args): def Popen_safe(args, write=None, stderr=subprocess.PIPE, **kwargs): p = subprocess.Popen(args, universal_newlines=True, + close_fds=False, stdout=subprocess.PIPE, stderr=stderr, **kwargs) o, e = p.communicate(write) diff --git a/mesonbuild/mlog.py b/mesonbuild/mlog.py index bad756a..82ee6ba 100644 --- a/mesonbuild/mlog.py +++ b/mesonbuild/mlog.py @@ -99,3 +99,16 @@ def log(*args, **kwargs): def warning(*args, **kwargs): log(yellow('WARNING:'), *args, **kwargs) + +# Format a list for logging purposes as a string. It separates +# all but the last item with commas, and the last with 'and'. +def format_list(list): + l = len(list) + if l > 2: + return ' and '.join([', '.join(list[:-1]), list[-1]]) + elif l == 2: + return ' and '.join(list) + elif l == 1: + return list[0] + else: + return '' diff --git a/mesonbuild/modules/gnome.py b/mesonbuild/modules/gnome.py index 481a250..6921472 100644 --- a/mesonbuild/modules/gnome.py +++ b/mesonbuild/modules/gnome.py @@ -766,6 +766,8 @@ class GnomeModule(ExtensionModule): cmd += ['--interface-prefix', kwargs.pop('interface_prefix')] if 'namespace' in kwargs: cmd += ['--c-namespace', kwargs.pop('namespace')] + if kwargs.get('object_manager', False): + cmd += ['--c-generate-object-manager'] # https://git.gnome.org/browse/glib/commit/?id=ee09bb704fe9ccb24d92dd86696a0e6bb8f0dc1a if mesonlib.version_compare(self._get_native_glib_version(state), '>= 2.51.3'): @@ -999,7 +1001,7 @@ class GnomeModule(ExtensionModule): target.get_subdir()) outdir = os.path.join(state.environment.get_build_dir(), target.get_subdir()) - outfile = target.output[0][:-5] # Strip .vapi + outfile = target.get_outputs()[0][:-5] # Strip .vapi ret.append('--vapidir=' + outdir) ret.append('--girdir=' + outdir) ret.append('--pkg=' + outfile) @@ -1066,7 +1068,7 @@ class GnomeModule(ExtensionModule): link_with += self._get_vapi_link_with(i.held_object) subdir = os.path.join(state.environment.get_build_dir(), i.held_object.get_subdir()) - gir_file = os.path.join(subdir, i.held_object.output[0]) + gir_file = os.path.join(subdir, i.held_object.get_outputs()[0]) cmd.append(gir_file) else: raise MesonException('Input must be a str or GirTarget') diff --git a/mesonbuild/modules/pkgconfig.py b/mesonbuild/modules/pkgconfig.py index e46c239..e79371f 100644 --- a/mesonbuild/modules/pkgconfig.py +++ b/mesonbuild/modules/pkgconfig.py @@ -76,8 +76,9 @@ class PkgConfigModule(ExtensionModule): if isinstance(l, str): yield l else: - if l.custom_install_dir: - yield '-L${prefix}/%s ' % l.custom_install_dir + install_dir = l.get_custom_install_dir()[0] + if install_dir: + yield '-L${prefix}/%s ' % install_dir else: yield '-L${libdir}' lname = self._get_lname(l, msg, pcfile) diff --git a/mesonbuild/modules/python3.py b/mesonbuild/modules/python3.py index 53e28c4..9f01043 100644 --- a/mesonbuild/modules/python3.py +++ b/mesonbuild/modules/python3.py @@ -13,11 +13,13 @@ # limitations under the License. import sys +import sysconfig from .. import mesonlib, dependencies from . import ExtensionModule from mesonbuild.modules import ModuleReturnValue + class Python3Module(ExtensionModule): def __init__(self): super().__init__() @@ -45,5 +47,25 @@ class Python3Module(ExtensionModule): py3 = dependencies.ExternalProgram('python3', sys.executable, silent=True) return ModuleReturnValue(py3, [py3]) + def language_version(self, state, args, kwargs): + if args or kwargs: + raise mesonlib.MesonException('language_version() takes no arguments.') + return ModuleReturnValue(sysconfig.get_python_version(), []) + + def sysconfig_path(self, state, args, kwargs): + if len(args) != 1: + raise mesonlib.MesonException('sysconfig_path() requires passing the name of path to get.') + if kwargs: + raise mesonlib.MesonException('sysconfig_path() does not accept keywords.') + path_name = args[0] + valid_names = sysconfig.get_path_names() + if path_name not in valid_names: + raise mesonlib.MesonException('{} is not a valid path name {}.'.format(path_name, valid_names)) + + # Get a relative path without a prefix, e.g. lib/python3.6/site-packages + path = sysconfig.get_path(path_name, vars={'base': ''})[1:] + return ModuleReturnValue(path, []) + + def initialize(): return Python3Module() diff --git a/mesonbuild/modules/qt4.py b/mesonbuild/modules/qt4.py index 7146739..0386291 100644 --- a/mesonbuild/modules/qt4.py +++ b/mesonbuild/modules/qt4.py @@ -24,14 +24,14 @@ from . import ModuleReturnValue class Qt4Module(ExtensionModule): tools_detected = False - def _detect_tools(self, env): + def _detect_tools(self, env, method): if self.tools_detected: return mlog.log('Detecting Qt4 tools') # FIXME: We currently require Qt4 to exist while importing the module. # We should make it gracefully degrade and not create any targets if # the import is marked as 'optional' (not implemented yet) - kwargs = {'required': 'true', 'modules': 'Core', 'silent': 'true'} + kwargs = {'required': 'true', 'modules': 'Core', 'silent': 'true', 'method': method} qt4 = Qt4Dependency(env, kwargs) # Get all tools and then make sure that they are the right version self.moc, self.uic, self.rcc = qt4.compilers_detect() @@ -113,7 +113,8 @@ class Qt4Module(ExtensionModule): if not isinstance(sources, list): sources = [sources] sources += args[1:] - self._detect_tools(state.environment) + method = kwargs.get('method', 'auto') + self._detect_tools(state.environment, method) err_msg = "{0} sources specified and couldn't find {1}, " \ "please check your qt4 installation" if len(moc_headers) + len(moc_sources) > 0 and not self.moc.found(): diff --git a/mesonbuild/modules/qt5.py b/mesonbuild/modules/qt5.py index 2a87a80..6497694 100644 --- a/mesonbuild/modules/qt5.py +++ b/mesonbuild/modules/qt5.py @@ -24,14 +24,14 @@ from . import ModuleReturnValue class Qt5Module(ExtensionModule): tools_detected = False - def _detect_tools(self, env): + def _detect_tools(self, env, method): if self.tools_detected: return mlog.log('Detecting Qt5 tools') # FIXME: We currently require Qt5 to exist while importing the module. # We should make it gracefully degrade and not create any targets if # the import is marked as 'optional' (not implemented yet) - kwargs = {'required': 'true', 'modules': 'Core', 'silent': 'true'} + kwargs = {'required': 'true', 'modules': 'Core', 'silent': 'true', 'method': method} qt5 = Qt5Dependency(env, kwargs) # Get all tools and then make sure that they are the right version self.moc, self.uic, self.rcc = qt5.compilers_detect() @@ -119,7 +119,8 @@ class Qt5Module(ExtensionModule): if not isinstance(sources, list): sources = [sources] sources += args[1:] - self._detect_tools(state.environment) + method = kwargs.get('method', 'auto') + self._detect_tools(state.environment, method) err_msg = "{0} sources specified and couldn't find {1}, " \ "please check your qt5 installation" if len(moc_headers) + len(moc_sources) > 0 and not self.moc.found(): diff --git a/mesonbuild/modules/windows.py b/mesonbuild/modules/windows.py index c233d90..3fb0107 100644 --- a/mesonbuild/modules/windows.py +++ b/mesonbuild/modules/windows.py @@ -51,9 +51,17 @@ class WindowsModule(ExtensionModule): for arg in extra_args: if ' ' in arg: mlog.warning(m.format(arg)) - # Pick-up env var WINDRES if set. This is often used for specifying - # an arch-specific windres. - rescomp_name = os.environ.get('WINDRES', 'windres') + rescomp_name = None + # FIXME: Does not handle `native: true` executables, see + # https://github.com/mesonbuild/meson/issues/1531 + if state.environment.is_cross_build(): + # If cross compiling see if windres has been specified in the + # cross file before trying to find it another way. + rescomp_name = state.environment.cross_info.config['binaries'].get('windres') + if rescomp_name is None: + # Pick-up env var WINDRES if set. This is often used for + # specifying an arch-specific windres. + rescomp_name = os.environ.get('WINDRES', 'windres') rescomp = dependencies.ExternalProgram(rescomp_name, silent=True) res_args = extra_args + ['@INPUT@', '@OUTPUT@'] suffix = 'o' diff --git a/mesonbuild/mparser.py b/mesonbuild/mparser.py index 7a91539..a9f25b1 100644 --- a/mesonbuild/mparser.py +++ b/mesonbuild/mparser.py @@ -407,6 +407,7 @@ class Parser: def __init__(self, code, subdir): self.lexer = Lexer(code) self.stream = self.lexer.lex(subdir) + self.current = Token('eof', '', 0, 0, 0, (0, 0), None) self.getsym() self.in_ternary = False diff --git a/mesonbuild/optinterpreter.py b/mesonbuild/optinterpreter.py index 10b8fab..f9e7f26 100644 --- a/mesonbuild/optinterpreter.py +++ b/mesonbuild/optinterpreter.py @@ -75,15 +75,16 @@ class OptionInterpreter: self.cmd_line_options = {} for o in command_line_options: if self.subproject != '': # Strip the beginning. + # Ignore options that aren't for this subproject if not o.startswith(self.sbprefix): continue - else: - if ':' in o: - continue try: (key, value) = o.split('=', 1) except ValueError: raise OptionException('Option {!r} must have a value separated by equals sign.'.format(o)) + # Ignore subproject options if not fetching subproject options + if self.subproject == '' and ':' in key: + continue self.cmd_line_options[key] = value def process(self, option_file): diff --git a/mesonbuild/scripts/commandrunner.py b/mesonbuild/scripts/commandrunner.py index cf2770d..87e3b8b 100644 --- a/mesonbuild/scripts/commandrunner.py +++ b/mesonbuild/scripts/commandrunner.py @@ -17,11 +17,11 @@ what to run, sets up the environment and executes the command.""" import sys, os, subprocess, shutil -def run_command(source_dir, build_dir, subdir, command, arguments): +def run_command(source_dir, build_dir, subdir, mesonintrospect, command, arguments): env = {'MESON_SOURCE_ROOT': source_dir, 'MESON_BUILD_ROOT': build_dir, 'MESON_SUBDIR': subdir, - } + 'MESONINTROSPECT': mesonintrospect} cwd = os.path.join(source_dir, subdir) child_env = os.environ.copy() child_env.update(env) @@ -47,9 +47,10 @@ def run(args): src_dir = args[0] build_dir = args[1] subdir = args[2] - command = args[3] - arguments = args[4:] - pc = run_command(src_dir, build_dir, subdir, command, arguments) + mesonintrospect = args[3] + command = args[4] + arguments = args[5:] + pc = run_command(src_dir, build_dir, subdir, mesonintrospect, command, arguments) pc.wait() return pc.returncode diff --git a/mesonbuild/scripts/gtkdochelper.py b/mesonbuild/scripts/gtkdochelper.py index 53ed07f..434225e 100644 --- a/mesonbuild/scripts/gtkdochelper.py +++ b/mesonbuild/scripts/gtkdochelper.py @@ -107,7 +107,7 @@ def build_gtkdoc(source_root, build_root, doc_subdir, src_subdirs, gtkdoc_run_check(scan_cmd, abs_out) if gobject_typesfile: - scanobjs_cmd = ['gtkdoc-scangobj'] + scanobjs_args + [gobject_typesfile, + scanobjs_cmd = ['gtkdoc-scangobj'] + scanobjs_args + ['--types=' + gobject_typesfile, '--module=' + module, '--cflags=' + cflags, '--ldflags=' + ldflags] diff --git a/mesonbuild/scripts/meson_exe.py b/mesonbuild/scripts/meson_exe.py index 5c5c317..643e1af 100644 --- a/mesonbuild/scripts/meson_exe.py +++ b/mesonbuild/scripts/meson_exe.py @@ -29,8 +29,12 @@ def is_windows(): platname = platform.system().lower() return platname == 'windows' or 'mingw' in platname +def is_cygwin(): + platname = platform.system().lower() + return 'cygwin' in platname + def run_with_mono(fname): - if fname.endswith('.exe') and not is_windows(): + if fname.endswith('.exe') and not (is_windows() or is_cygwin()): return True return False diff --git a/mesonbuild/scripts/meson_install.py b/mesonbuild/scripts/meson_install.py index 8fb9e04..2205f1a 100644 --- a/mesonbuild/scripts/meson_install.py +++ b/mesonbuild/scripts/meson_install.py @@ -12,7 +12,7 @@ # See the License for the specific language governing permissions and # limitations under the License. -import sys, pickle, os, shutil, subprocess, gzip, platform +import sys, pickle, os, shutil, subprocess, gzip, platform, errno from glob import glob from . import depfixer from . import destdir_join @@ -34,6 +34,15 @@ def set_mode(path, mode): except PermissionError as e: msg = '{!r}: Unable to set owner {!r} and group {!r}: {}, ignoring...' print(msg.format(path, mode.owner, mode.group, e.strerror)) + except LookupError as e: + msg = '{!r}: Non-existent owner {!r} or group {!r}: ignoring...' + print(msg.format(path, mode.owner, mode.group)) + except OSError as e: + if e.errno == errno.EINVAL: + msg = '{!r}: Non-existent numeric owner {!r} or group {!r}: ignoring...' + print(msg.format(path, mode.owner, mode.group)) + else: + raise # Must set permissions *after* setting owner/group otherwise the # setuid/setgid bits will get wiped by chmod # NOTE: On Windows you can set read/write perms; the rest are ignored @@ -174,7 +183,7 @@ def run_install_script(d): 'MESON_BUILD_ROOT': d.build_dir, 'MESON_INSTALL_PREFIX': d.prefix, 'MESON_INSTALL_DESTDIR_PREFIX': d.fullprefix, - } + 'MESONINTROSPECT': d.mesonintrospect} child_env = os.environ.copy() child_env.update(env) @@ -193,7 +202,7 @@ def run_install_script(d): def is_elf_platform(): platname = platform.system().lower() - if platname == 'darwin' or platname == 'windows': + if platname == 'darwin' or platname == 'windows' or platname == 'cygwin': return False return True diff --git a/mesontest.py b/mesontest.py index a1708e3..b59d1ed 100755 --- a/mesontest.py +++ b/mesontest.py @@ -21,6 +21,8 @@ import subprocess, sys, os, argparse import pickle from mesonbuild import build from mesonbuild import environment +from mesonbuild.dependencies import ExternalProgram +from mesonbuild import mlog import time, datetime, multiprocessing, json import concurrent.futures as conc @@ -36,6 +38,10 @@ def is_windows(): platname = platform.system().lower() return platname == 'windows' or 'mingw' in platname +def is_cygwin(): + platname = platform.system().lower() + return 'cygwin' in platname + def determine_worker_count(): varname = 'MESON_TESTTHREADS' if varname in os.environ: @@ -150,7 +156,7 @@ def write_json_log(jsonlogfile, test_name, result): jsonlogfile.write(json.dumps(jresult) + '\n') def run_with_mono(fname): - if fname.endswith('.exe') and not is_windows(): + if fname.endswith('.exe') and not (is_windows() or is_cygwin()): return True return False @@ -202,7 +208,7 @@ class TestHarness: child_env.update(test.env) if len(test.extra_paths) > 0: - child_env['PATH'] += ';'.join([''] + test.extra_paths) + child_env['PATH'] += os.pathsep.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 @@ -278,7 +284,16 @@ class TestHarness: result_str = '%s %s %s%s%s%5.2f s' % \ (num, name, padding1, result.res, padding2, result.duration) if not self.options.quiet or result.res != 'OK': - print(result_str) + if result.res != 'OK' and mlog.colorize_console: + if result.res == 'FAIL' or result.res == 'TIMEOUT': + decorator = mlog.red + elif result.res == 'SKIP': + decorator = mlog.yellow + else: + sys.exit('Unreachable code was ... well ... reached.') + print(decorator(result_str).get_text(True)) + else: + print(result_str) result_str += "\n\n" + result.get_log() if (result.returncode != GNU_SKIP_RETURNCODE) \ and (result.returncode != 0) != result.should_fail: @@ -570,12 +585,21 @@ def run(args): print('Can not be both quiet and verbose at the same time.') return 1 + check_bin = None if options.gdb: options.verbose = True if options.wrapper: print('Must not specify both a wrapper and gdb at the same time.') return 1 + check_bin = 'gdb' + + if options.wrapper: + check_bin = options.wrapper[0] + if check_bin is not None: + exe = ExternalProgram(check_bin, silent=True) + if not exe.found(): + sys.exit("Could not find requested program: %s" % check_bin) options.wd = os.path.abspath(options.wd) if not options.no_rebuild: diff --git a/run_project_tests.py b/run_project_tests.py index 28de638..bcb375d 100755 --- a/run_project_tests.py +++ b/run_project_tests.py @@ -26,13 +26,15 @@ from mesonbuild import mesonlib from mesonbuild import mlog from mesonbuild import mesonmain from mesonbuild.mesonlib import stringlistify, Popen_safe +from mesonbuild.coredata import backendlist import argparse import xml.etree.ElementTree as ET import time import multiprocessing import concurrent.futures as conc +import re -from mesonbuild.coredata import backendlist +from run_tests import get_backend_commands, get_backend_args_for_dir, Backend class BuildStep(Enum): @@ -41,6 +43,7 @@ class BuildStep(Enum): test = 3 install = 4 clean = 5 + validate = 6 class TestResult: @@ -152,50 +155,36 @@ def stop_handler(signal, frame): signal.signal(signal.SIGINT, stop_handler) signal.signal(signal.SIGTERM, stop_handler) -# unity_flags = ['--unity'] -unity_flags = [] +# Needed when running cross tests because we don't generate prebuilt files +compiler = None -backend_flags = None -compile_commands = None -test_commands = None -install_commands = [] -clean_commands = [] - -def setup_commands(backend): - global backend_flags, compile_commands, test_commands, install_commands, clean_commands +def setup_commands(optbackend): + global do_debug, backend, backend_flags + global compile_commands, clean_commands, test_commands, install_commands, uninstall_commands + backend = optbackend msbuild_exe = shutil.which('msbuild') - if (backend and backend.startswith('vs')) or (backend is None and msbuild_exe is not None): - if backend is None: - backend = 'vs2010' + # Auto-detect backend if unspecified + if backend is None: + if msbuild_exe is not None: + backend = 'vs' # Meson will auto-detect VS version to use + elif mesonlib.is_osx(): + backend = 'xcode' + else: + backend = 'ninja' + # Set backend arguments for Meson + if backend.startswith('vs'): backend_flags = ['--backend=' + backend] - compile_commands = ['msbuild'] - test_commands = ['msbuild', 'RUN_TESTS.vcxproj'] - elif backend == 'xcode' or (backend is None and mesonlib.is_osx()): + backend = Backend.vs + elif backend == 'xcode': backend_flags = ['--backend=xcode'] - compile_commands = ['xcodebuild'] - test_commands = ['xcodebuild', '-target', 'RUN_TESTS'] + backend = Backend.xcode + elif backend == 'ninja': + backend_flags = ['--backend=ninja'] + backend = Backend.ninja else: - backend_flags = [] - # We need at least 1.6 because of -w dupbuild=err - ninja_command = environment.detect_ninja(version='1.6') - if ninja_command is None: - raise RuntimeError('Could not find Ninja v1.6 or newer') - if do_debug: - compile_commands = [ninja_command, '-v'] - else: - compile_commands = [ninja_command] - compile_commands += ['-w', 'dupbuild=err'] - test_commands = [ninja_command, 'test', 'benchmark'] - install_commands = [ninja_command, 'install'] - clean_commands = [ninja_command, 'clean'] - -def get_compile_commands_for_dir(compile_commands, test_build_dir): - if 'msbuild' in compile_commands[0]: - sln_name = glob(os.path.join(test_build_dir, '*.sln'))[0] - comp = compile_commands + [os.path.split(sln_name)[-1]] - else: - comp = compile_commands - return comp + raise RuntimeError('Unknown backend: {!r}'.format(backend)) + compile_commands, clean_commands, test_commands, install_commands, \ + uninstall_commands = get_backend_commands(backend, do_debug) def get_relative_files_list_from_dir(fromdir): paths = [] @@ -208,15 +197,21 @@ def get_relative_files_list_from_dir(fromdir): paths.append(path) return paths -def platform_fix_exe_name(fname): - if not fname.endswith('?exe'): - return fname - fname = fname[:-4] - if mesonlib.is_windows(): - return fname + '.exe' +def platform_fix_name(fname): + if '?lib' in fname: + if mesonlib.is_cygwin(): + fname = re.sub(r'\?lib(.*)\.dll$', r'cyg\1.dll', fname) + else: + fname = re.sub(r'\?lib', 'lib', fname) + + if fname.endswith('?exe'): + fname = fname[:-4] + if mesonlib.is_windows() or mesonlib.is_cygwin(): + return fname + '.exe' + return fname -def validate_install(srcdir, installdir): +def validate_install(srcdir, installdir, compiler): # List of installed files info_file = os.path.join(srcdir, 'installed_files.txt') # If this exists, the test does not install any other files @@ -230,13 +225,16 @@ def validate_install(srcdir, installdir): elif os.path.exists(info_file): with open(info_file) as f: for line in f: - expected[platform_fix_exe_name(line.strip())] = False + expected[platform_fix_name(line.strip())] = False # Check if expected files were found for fname in expected: if os.path.exists(os.path.join(installdir, fname)): expected[fname] = True for (fname, found) in expected.items(): if not found: + # Ignore missing PDB files if we aren't using cl + if fname.endswith('.pdb') and compiler != 'cl': + continue ret_msg += 'Expected file {0} missing.\n'.format(fname) # Check if there are any unexpected files found = get_relative_files_list_from_dir(installdir) @@ -307,18 +305,18 @@ def parse_test_args(testdir): pass return args -def run_test(skipped, testdir, extra_args, flags, compile_commands, should_fail): +def run_test(skipped, testdir, extra_args, compiler, backend, flags, commands, should_fail): if skipped: return None with AutoDeletedDir(tempfile.mkdtemp(prefix='b ', dir='.')) as build_dir: with AutoDeletedDir(tempfile.mkdtemp(prefix='i ', dir=os.getcwd())) as install_dir: try: - return _run_test(testdir, build_dir, install_dir, extra_args, flags, compile_commands, should_fail) + return _run_test(testdir, build_dir, install_dir, extra_args, compiler, backend, flags, commands, should_fail) finally: mlog.shutdown() # Close the log file because otherwise Windows wets itself. -def _run_test(testdir, test_build_dir, install_dir, extra_args, flags, compile_commands, should_fail): - global install_commands, clean_commands +def _run_test(testdir, test_build_dir, install_dir, extra_args, compiler, backend, flags, commands, should_fail): + compile_commands, clean_commands, install_commands, uninstall_commands = commands test_args = parse_test_args(testdir) gen_start = time.time() # Configure in-process @@ -339,9 +337,9 @@ def _run_test(testdir, test_build_dir, install_dir, extra_args, flags, compile_c if returncode != 0: return TestResult('Generating the build system failed.', BuildStep.configure, stdo, stde, mesonlog, gen_time) # Build with subprocess - comp = get_compile_commands_for_dir(compile_commands, test_build_dir) + dir_args = get_backend_args_for_dir(backend, test_build_dir) build_start = time.time() - pc, o, e = Popen_safe(comp, cwd=test_build_dir) + pc, o, e = Popen_safe(compile_commands + dir_args, cwd=test_build_dir) build_time = time.time() - build_start stdo += o stde += e @@ -368,25 +366,26 @@ def _run_test(testdir, test_build_dir, install_dir, extra_args, flags, compile_c return TestResult('Test that should have failed to run unit tests succeeded', BuildStep.test, stdo, stde, mesonlog, gen_time) if returncode != 0: return TestResult('Running unit tests failed.', BuildStep.test, stdo, stde, mesonlog, gen_time, build_time, test_time) - if len(install_commands) == 0: - return TestResult('', BuildStep.install, '', '', mesonlog, gen_time, build_time, test_time) - env = os.environ.copy() - env['DESTDIR'] = install_dir - # Install with subprocess - pi, o, e = Popen_safe(install_commands, cwd=test_build_dir, env=env) - stdo += o - stde += e - if pi.returncode != 0: - return TestResult('Running install failed.', BuildStep.install, stdo, stde, mesonlog, gen_time, build_time, test_time) - if len(clean_commands) != 0: + # Do installation, if the backend supports it + if len(install_commands) != 0: env = os.environ.copy() - # Clean with subprocess - pi, o, e = Popen_safe(clean_commands, cwd=test_build_dir, env=env) + env['DESTDIR'] = install_dir + # Install with subprocess + pi, o, e = Popen_safe(install_commands, cwd=test_build_dir, env=env) stdo += o stde += e if pi.returncode != 0: - return TestResult('Running clean failed.', BuildStep.clean, stdo, stde, mesonlog, gen_time, build_time, test_time) - return TestResult(validate_install(testdir, install_dir), BuildStep.clean, stdo, stde, mesonlog, gen_time, build_time, test_time) + return TestResult('Running install failed.', BuildStep.install, stdo, stde, mesonlog, gen_time, build_time, test_time) + # Clean with subprocess + env = os.environ.copy() + pi, o, e = Popen_safe(clean_commands + dir_args, cwd=test_build_dir, env=env) + stdo += o + stde += e + if pi.returncode != 0: + return TestResult('Running clean failed.', BuildStep.clean, stdo, stde, mesonlog, gen_time, build_time, test_time) + if len(install_commands) == 0: + return TestResult('', BuildStep.install, '', '', mesonlog, gen_time, build_time, test_time) + return TestResult(validate_install(testdir, install_dir, compiler), BuildStep.validate, stdo, stde, mesonlog, gen_time, build_time, test_time) def gather_tests(testdir): tests = [t.replace('\\', '/').split('/', 2)[2] for t in glob(os.path.join(testdir, '*'))] @@ -411,23 +410,6 @@ def have_java(): return True return False -def using_backend(backends): - if isinstance(backends, str): - backends = (backends,) - for backend in backends: - if backend == 'ninja': - if not backend_flags: - return True - elif backend == 'xcode': - if backend_flags == '--backend=xcode': - return True - elif backend == 'vs': - if backend_flags.startswith('--backend=vs'): - return True - else: - raise AssertionError('Unknown backend type: ' + backend) - return False - def detect_tests_to_run(): all_tests = [] all_tests.append(('common', gather_tests('test cases/common'), False)) @@ -437,18 +419,18 @@ def detect_tests_to_run(): all_tests.append(('prebuilt', gather_tests('test cases/prebuilt'), False)) all_tests.append(('platform-osx', gather_tests('test cases/osx'), False if mesonlib.is_osx() else True)) - all_tests.append(('platform-windows', gather_tests('test cases/windows'), False if mesonlib.is_windows() else True)) + all_tests.append(('platform-windows', gather_tests('test cases/windows'), False if mesonlib.is_windows() or mesonlib.is_cygwin() else True)) all_tests.append(('platform-linux', gather_tests('test cases/linuxlike'), False if not (mesonlib.is_osx() or mesonlib.is_windows()) else True)) - all_tests.append(('framework', gather_tests('test cases/frameworks'), False if not mesonlib.is_osx() and not mesonlib.is_windows() else True)) - all_tests.append(('java', gather_tests('test cases/java'), False if using_backend('ninja') and not mesonlib.is_osx() and have_java() else True)) - all_tests.append(('C#', gather_tests('test cases/csharp'), False if using_backend('ninja') and shutil.which('mcs') else True)) - all_tests.append(('vala', gather_tests('test cases/vala'), False if using_backend('ninja') and shutil.which('valac') else True)) - all_tests.append(('rust', gather_tests('test cases/rust'), False if using_backend('ninja') and shutil.which('rustc') else True)) - all_tests.append(('d', gather_tests('test cases/d'), False if using_backend('ninja') and have_d_compiler() else True)) - all_tests.append(('objective c', gather_tests('test cases/objc'), False if using_backend(('ninja', 'xcode')) and not mesonlib.is_windows() else True)) - all_tests.append(('fortran', gather_tests('test cases/fortran'), False if using_backend('ninja') and shutil.which('gfortran') else True)) - all_tests.append(('swift', gather_tests('test cases/swift'), False if using_backend(('ninja', 'xcode')) and shutil.which('swiftc') else True)) - all_tests.append(('python3', gather_tests('test cases/python3'), False if using_backend('ninja') and shutil.which('python3') else True)) + all_tests.append(('framework', gather_tests('test cases/frameworks'), False if not mesonlib.is_osx() and not mesonlib.is_windows() and not mesonlib.is_cygwin() else True)) + all_tests.append(('java', gather_tests('test cases/java'), False if backend is Backend.ninja and not mesonlib.is_osx() and have_java() else True)) + all_tests.append(('C#', gather_tests('test cases/csharp'), False if backend is Backend.ninja and shutil.which('mcs') else True)) + all_tests.append(('vala', gather_tests('test cases/vala'), False if backend is Backend.ninja and shutil.which('valac') else True)) + all_tests.append(('rust', gather_tests('test cases/rust'), False if backend is Backend.ninja and shutil.which('rustc') else True)) + all_tests.append(('d', gather_tests('test cases/d'), False if backend is Backend.ninja and have_d_compiler() else True)) + all_tests.append(('objective c', gather_tests('test cases/objc'), False if backend in (Backend.ninja, Backend.xcode) and not mesonlib.is_windows() else True)) + all_tests.append(('fortran', gather_tests('test cases/fortran'), False if backend is Backend.ninja and shutil.which('gfortran') else True)) + all_tests.append(('swift', gather_tests('test cases/swift'), False if backend in (Backend.ninja, Backend.xcode) and shutil.which('swiftc') else True)) + all_tests.append(('python3', gather_tests('test cases/python3'), False if backend is Backend.ninja and shutil.which('python3') else True)) return all_tests def run_tests(all_tests, log_name_base, extra_args): @@ -463,6 +445,7 @@ def run_tests(all_tests, log_name_base, extra_args): passing_tests = 0 failing_tests = 0 skipped_tests = 0 + commands = (compile_commands, clean_commands, install_commands, uninstall_commands) try: # This fails in some CI environments for unknown reasons. @@ -492,7 +475,7 @@ def run_tests(all_tests, log_name_base, extra_args): should_fail = False if name.startswith('failing'): should_fail = name.split('failing-')[1] - result = executor.submit(run_test, skipped, t, extra_args, unity_flags + backend_flags, compile_commands, should_fail) + result = executor.submit(run_test, skipped, t, extra_args, compiler, backend, backend_flags, commands, should_fail) futures.append((testname, t, result)) for (testname, t, result) in futures: sys.stdout.flush() @@ -607,7 +590,7 @@ def generate_prebuilt(): return objectfile, stlibfile def check_meson_commands_work(): - global meson_command, compile_commands, test_commands, install_commands + global backend, meson_command, compile_commands, test_commands, install_commands testdir = 'test cases/common/1 trivial' with AutoDeletedDir(tempfile.mkdtemp(prefix='b ', dir='.')) as build_dir: print('Checking that configuring works...') @@ -616,8 +599,8 @@ def check_meson_commands_work(): if pc.returncode != 0: raise RuntimeError('Failed to configure {!r}:\n{}\n{}'.format(testdir, e, o)) print('Checking that building works...') - compile_cmd = get_compile_commands_for_dir(compile_commands, build_dir) - pc, o, e = Popen_safe(compile_cmd, cwd=build_dir) + dir_args = get_backend_args_for_dir(backend, build_dir) + pc, o, e = Popen_safe(compile_commands + dir_args, cwd=build_dir) if pc.returncode != 0: raise RuntimeError('Failed to build {!r}:\n{}\n{}'.format(testdir, e, o)) print('Checking that testing works...') @@ -639,21 +622,6 @@ if __name__ == '__main__': options = parser.parse_args() setup_commands(options.backend) - # Appveyor sets the `platform` environment variable which completely messes - # up building with the vs2010 and vs2015 backends. - # - # Specifically, MSBuild reads the `platform` environment variable to set - # the configured value for the platform (Win32/x64/arm), which breaks x86 - # builds. - # - # Appveyor setting this also breaks our 'native build arch' detection for - # Windows in environment.py:detect_windows_arch() by overwriting the value - # of `platform` set by vcvarsall.bat. - # - # While building for x86, `platform` should be unset. - if 'APPVEYOR' in os.environ and os.environ['arch'] == 'x86': - os.environ.pop('platform') - script_dir = os.path.split(__file__)[0] if script_dir != '': os.chdir(script_dir) diff --git a/run_tests.py b/run_tests.py index 02aa701..d0a67e8 100755 --- a/run_tests.py +++ b/run_tests.py @@ -20,28 +20,138 @@ import shutil import subprocess import platform from mesonbuild import mesonlib +from mesonbuild.environment import detect_ninja +from enum import Enum +from glob import glob -def using_vs_backend(): - for arg in sys.argv[1:]: - if arg.startswith('--backend=vs'): - return True - return False +Backend = Enum('Backend', 'ninja vs xcode') + +if mesonlib.is_windows() or mesonlib.is_cygwin(): + exe_suffix = '.exe' +else: + exe_suffix = '' + +def get_backend_args_for_dir(backend, builddir): + ''' + Visual Studio backend needs to be given the solution to build + ''' + if backend is Backend.vs: + sln_name = glob(os.path.join(builddir, '*.sln'))[0] + return [os.path.split(sln_name)[-1]] + return [] + +def find_vcxproj_with_target(builddir, target): + import re, fnmatch + t, ext = os.path.splitext(target) + if ext: + p = '<TargetName>{}</TargetName>\s*<TargetExt>\{}</TargetExt>'.format(t, ext) + else: + p = '<TargetName>{}</TargetName>'.format(t) + for root, dirs, files in os.walk(builddir): + for f in fnmatch.filter(files, '*.vcxproj'): + f = os.path.join(builddir, f) + with open(f, 'r', encoding='utf-8') as o: + if re.search(p, o.read(), flags=re.MULTILINE): + return f + raise RuntimeError('No vcxproj matching {!r} in {!r}'.format(p, builddir)) + +def get_builddir_target_args(backend, builddir, target): + dir_args = [] + if not target: + dir_args = get_backend_args_for_dir(backend, builddir) + if target is None: + return dir_args + if backend is Backend.vs: + vcxproj = find_vcxproj_with_target(builddir, target) + target_args = [vcxproj] + elif backend is Backend.xcode: + target_args = ['-target', target] + elif backend is Backend.ninja: + target_args = [target] + else: + raise AssertionError('Unknown backend: {!r}'.format(backend)) + return target_args + dir_args + +def get_backend_commands(backend, debug=False): + install_cmd = [] + uninstall_cmd = [] + if backend is Backend.vs: + cmd = ['msbuild'] + clean_cmd = cmd + ['/target:Clean'] + test_cmd = cmd + ['RUN_TESTS.vcxproj'] + elif backend is Backend.xcode: + cmd = ['xcodebuild'] + clean_cmd = cmd + ['-alltargets', 'clean'] + test_cmd = cmd + ['-target', 'RUN_TESTS'] + elif backend is Backend.ninja: + # We need at least 1.6 because of -w dupbuild=err + cmd = [detect_ninja('1.6'), '-w', 'dupbuild=err'] + if cmd[0] is None: + raise RuntimeError('Could not find Ninja v1.6 or newer') + if debug: + cmd += ['-v'] + clean_cmd = cmd + ['clean'] + test_cmd = cmd + ['test', 'benchmark'] + install_cmd = cmd + ['install'] + uninstall_cmd = cmd + ['uninstall'] + else: + raise AssertionError('Unknown backend: {!r}'.format(backend)) + return cmd, clean_cmd, test_cmd, install_cmd, uninstall_cmd + +def get_fake_options(prefix): + import argparse + opts = argparse.Namespace() + opts.cross_file = None + opts.wrap_mode = None + opts.prefix = prefix + return opts + +class FakeEnvironment(object): + def __init__(self): + self.cross_info = None + + def is_cross_build(self): + return False if __name__ == '__main__': returncode = 0 + # Iterate over list in reverse order to find the last --backend arg + backend = Backend.ninja + for arg in reversed(sys.argv[1:]): + if arg.startswith('--backend'): + if arg.startswith('--backend=vs'): + backend = Backend.vs + elif arg == '--backend=xcode': + backend = Backend.xcode + break # Running on a developer machine? Be nice! if not mesonlib.is_windows() and 'TRAVIS' not in os.environ: os.nice(20) + # Appveyor sets the `platform` environment variable which completely messes + # up building with the vs2010 and vs2015 backends. + # + # Specifically, MSBuild reads the `platform` environment variable to set + # the configured value for the platform (Win32/x64/arm), which breaks x86 + # builds. + # + # Appveyor setting this also breaks our 'native build arch' detection for + # Windows in environment.py:detect_windows_arch() by overwriting the value + # of `platform` set by vcvarsall.bat. + # + # While building for x86, `platform` should be unset. + if 'APPVEYOR' in os.environ and os.environ['arch'] == 'x86': + os.environ.pop('platform') + # Run tests print('Running unittests.\n') units = ['InternalTests', 'AllPlatformTests'] if mesonlib.is_linux(): units += ['LinuxlikeTests'] elif mesonlib.is_windows(): units += ['WindowsTests'] - # Unit tests always use the Ninja backend, so just skip them if we're - # testing the VS backend - if not using_vs_backend(): - returncode += subprocess.call([sys.executable, 'run_unittests.py', '-v'] + units) + # Can't pass arguments to unit tests, so set the backend to use in the environment + env = os.environ.copy() + env['MESON_UNIT_TEST_BACKEND'] = backend.name + returncode += subprocess.call([sys.executable, 'run_unittests.py', '-v'] + units, env=env) # 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 be73839..7bdd57b 100755 --- a/run_unittests.py +++ b/run_unittests.py @@ -24,14 +24,13 @@ 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.mesonlib import is_windows, is_osx, is_cygwin +from mesonbuild.environment import Environment from mesonbuild.dependencies import PkgConfigDependency, ExternalProgram -if is_windows(): - exe_suffix = '.exe' -else: - exe_suffix = '' +from run_tests import exe_suffix, get_fake_options, FakeEnvironment +from run_tests import get_builddir_target_args, get_backend_commands, Backend + def get_soname(fname): # HACK, fix to not use shell. @@ -44,21 +43,6 @@ def get_soname(fname): return m.group(1) raise RuntimeError('Could not determine soname:\n\n' + raw_out) -def get_fake_options(prefix): - import argparse - opts = argparse.Namespace() - opts.cross_file = None - opts.wrap_mode = None - opts.prefix = prefix - return opts - -class FakeEnvironment(object): - def __init__(self): - self.cross_info = None - - def is_cross_build(self): - return False - class InternalTests(unittest.TestCase): def test_version_number(self): @@ -346,16 +330,37 @@ class BasePlatformTests(unittest.TestCase): self.prefix = '/usr' self.libdir = os.path.join(self.prefix, 'lib') self.installdir = os.path.join(self.builddir, 'install') - self.meson_command = [sys.executable, os.path.join(src_root, 'meson.py')] + # Get the backend + # FIXME: Extract this from argv? + self.backend = getattr(Backend, os.environ.get('MESON_UNIT_TEST_BACKEND', 'ninja')) + self.meson_command = [sys.executable, os.path.join(src_root, 'meson.py'), + '--backend=' + self.backend.name] self.mconf_command = [sys.executable, os.path.join(src_root, 'mesonconf.py')] self.mintro_command = [sys.executable, os.path.join(src_root, 'mesonintrospect.py')] self.mtest_command = [sys.executable, os.path.join(src_root, 'mesontest.py'), '-C', self.builddir] - self.ninja_command = [detect_ninja(), '-C', self.builddir] + # Backend-specific build commands + self.build_command, self.clean_command, self.test_command, self.install_command, \ + self.uninstall_command = get_backend_commands(self.backend) + # Test directories self.common_test_dir = os.path.join(src_root, 'test cases/common') self.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') + # Misc stuff self.orig_env = os.environ.copy() + if self.backend is Backend.ninja: + self.no_rebuild_stdout = 'ninja: no work to do.' + else: + # VS doesn't have a stable output when no changes are done + # XCode backend is untested with unit tests, help welcome! + self.no_rebuild_stdout = 'UNKNOWN BACKEND {!r}'.format(self.backend.name) + + def ensure_backend_detects_changes(self): + # This is needed to increase the difference between build.ninja's + # timestamp and the timestamp of whatever you changed due to a Ninja + # bug: https://github.com/ninja-build/ninja/issues/371 + if self.backend is Backend.ninja: + time.sleep(1) def _print_meson_log(self): log = os.path.join(self.logdir, 'meson-log.txt') @@ -370,14 +375,14 @@ class BasePlatformTests(unittest.TestCase): os.environ = self.orig_env super().tearDown() - def _run(self, command): + def _run(self, command, workdir=None): ''' Run a command while printing the stdout and stderr to stdout, and also return a copy of it ''' p = subprocess.Popen(command, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, env=os.environ.copy(), - universal_newlines=True) + universal_newlines=True, cwd=workdir) output = p.communicate()[0] print(output) if p.returncode != 0: @@ -398,47 +403,59 @@ class BasePlatformTests(unittest.TestCase): raise self.privatedir = os.path.join(self.builddir, 'meson-private') - def build(self, extra_args=None): + def build(self, target=None, extra_args=None): if extra_args is None: extra_args = [] - self._run(self.ninja_command + extra_args) + # Add arguments for building the target (if specified), + # and using the build dir (if required, with VS) + args = get_builddir_target_args(self.backend, self.builddir, target) + return self._run(self.build_command + args + extra_args, workdir=self.builddir) + + def clean(self): + dir_args = get_builddir_target_args(self.backend, self.builddir, None) + self._run(self.clean_command + dir_args, workdir=self.builddir) def run_tests(self): - self._run(self.ninja_command + ['test']) + self._run(self.test_command, workdir=self.builddir) def install(self): + if self.backend is not Backend.ninja: + raise unittest.SkipTest('{!r} backend can\'t install files'.format(self.backend.name)) os.environ['DESTDIR'] = self.installdir - self._run(self.ninja_command + ['install']) + self._run(self.install_command, workdir=self.builddir) def uninstall(self): - self._run(self.ninja_command + ['uninstall']) + self._run(self.uninstall_command, workdir=self.builddir) def run_target(self, target): ''' Run a Ninja target while printing the stdout and stderr to stdout, and also return a copy of it ''' - return self._run(self.ninja_command + [target]) + return self.build(target=target) 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.ensure_backend_detects_changes() self._run(self.mconf_command + [arg, self.builddir]) def wipe(self): shutil.rmtree(self.builddir) + def utime(self, f): + self.ensure_backend_detects_changes() + os.utime(f) + def get_compdb(self): + if self.backend is not Backend.ninja: + raise unittest.SkipTest('Compiler db not available with {} backend'.format(self.backend.name)) with open(os.path.join(self.builddir, 'compile_commands.json')) as ifile: contents = json.load(ifile) # If Ninja is using .rsp files, generate them, read their contents, and # replace it as the command for all compile commands in the parsed json. if len(contents) > 0 and contents[0]['command'].endswith('.rsp'): # Pretend to build so that the rsp files are generated - self.build(['-d', 'keeprsp', '-n']) + self.build(extra_args=['-d', 'keeprsp', '-n']) for each in contents: # Extract the actual command from the rsp file compiler, rsp = each['command'].split(' @') @@ -482,6 +499,40 @@ class BasePlatformTests(unittest.TestCase): path_basename = PurePath(path).parts[-1] self.assertEqual(PurePath(path_basename), PurePath(basename), msg) + def assertBuildIsNoop(self): + ret = self.build() + if self.backend is Backend.ninja: + self.assertEqual(ret.split('\n')[-2], self.no_rebuild_stdout) + elif self.backend is Backend.vs: + # Ensure that some target said that no rebuild was done + self.assertIn('CustomBuild:\n All outputs are up-to-date.', ret) + self.assertIn('ClCompile:\n All outputs are up-to-date.', ret) + self.assertIn('Link:\n All outputs are up-to-date.', ret) + # Ensure that no targets were built + clre = re.compile('ClCompile:\n [^\n]*cl', flags=re.IGNORECASE) + linkre = re.compile('Link:\n [^\n]*link', flags=re.IGNORECASE) + self.assertNotRegex(ret, clre) + self.assertNotRegex(ret, linkre) + elif self.backend is Backend.xcode: + raise unittest.SkipTest('Please help us fix this test on the xcode backend') + else: + raise RuntimeError('Invalid backend: {!r}'.format(self.backend.name)) + + def assertRebuiltTarget(self, target): + ret = self.build() + if self.backend is Backend.ninja: + self.assertIn('Linking target {}'.format(target), ret) + elif self.backend is Backend.vs: + # Ensure that this target was rebuilt + clre = re.compile('ClCompile:\n [^\n]*cl[^\n]*' + target, flags=re.IGNORECASE) + linkre = re.compile('Link:\n [^\n]*link[^\n]*' + target, flags=re.IGNORECASE) + self.assertRegex(ret, clre) + self.assertRegex(ret, linkre) + elif self.backend is Backend.xcode: + raise unittest.SkipTest('Please help us fix this test on the xcode backend') + else: + raise RuntimeError('Invalid backend: {!r}'.format(self.backend.name)) + class AllPlatformTests(BasePlatformTests): ''' @@ -613,6 +664,8 @@ class AllPlatformTests(BasePlatformTests): Tests that the Meson introspection API exposes install filenames correctly https://github.com/mesonbuild/meson/issues/829 ''' + if self.backend is not Backend.ninja: + raise unittest.SkipTest('{!r} backend can\'t install files'.format(self.backend.name)) testdir = os.path.join(self.common_test_dir, '8 install') self.init(testdir) intro = self.introspect('--targets') @@ -722,7 +775,7 @@ class AllPlatformTests(BasePlatformTests): 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.build(target=('fooprog' + exe_suffix)) self.assertTrue(os.path.exists(exe)) def test_internal_include_order(self): @@ -833,6 +886,8 @@ class AllPlatformTests(BasePlatformTests): self.assertEqual(cc.gcc_type, mesonbuild.compilers.GCC_OSX) elif is_windows(): self.assertEqual(cc.gcc_type, mesonbuild.compilers.GCC_MINGW) + elif is_cygwin(): + self.assertEqual(cc.gcc_type, mesonbuild.compilers.GCC_CYGWIN) else: self.assertEqual(cc.gcc_type, mesonbuild.compilers.GCC_STANDARD) if isinstance(cc, clang): @@ -947,6 +1002,61 @@ class AllPlatformTests(BasePlatformTests): m = re.search('build c-asm.*: c_LINKER', contents) self.assertIsNotNone(m, msg=contents) + def test_preprocessor_checks_CPPFLAGS(self): + ''' + Test that preprocessor compiler checks read CPPFLAGS but not CFLAGS + ''' + testdir = os.path.join(self.common_test_dir, '140 get define') + define = 'MESON_TEST_DEFINE_VALUE' + # NOTE: this list can't have \n, ' or " + # \n is never substituted by the GNU pre-processor via a -D define + # ' and " confuse shlex.split() even when they are escaped + # % and # confuse the MSVC preprocessor + value = 'spaces and fun!@$^&*()-=_+{}[]:;<>?,./~`' + os.environ['CPPFLAGS'] = '-D{}="{}"'.format(define, value) + os.environ['CFLAGS'] = '-DMESON_FAIL_VALUE=cflags-read'.format(define) + self.init(testdir, ['-D{}={}'.format(define, value)]) + + def test_custom_target_exe_data_deterministic(self): + testdir = os.path.join(self.common_test_dir, '117 custom target capture') + self.init(testdir) + meson_exe_dat1 = glob(os.path.join(self.privatedir, 'meson_exe*.dat')) + self.wipe() + self.init(testdir) + meson_exe_dat2 = glob(os.path.join(self.privatedir, 'meson_exe*.dat')) + self.assertListEqual(meson_exe_dat1, meson_exe_dat2) + + def test_source_changes_cause_rebuild(self): + ''' + Test that changes to sources and headers cause rebuilds, but not + changes to unused files (as determined by the dependency file) in the + input files list. + ''' + testdir = os.path.join(self.common_test_dir, '22 header in file list') + self.init(testdir) + self.build() + # Immediately rebuilding should not do anything + self.assertBuildIsNoop() + # Changing mtime of header.h should rebuild everything + self.utime(os.path.join(testdir, 'header.h')) + self.assertRebuiltTarget('prog') + + def test_custom_target_changes_cause_rebuild(self): + ''' + Test that in a custom target, changes to the input files, the + ExternalProgram, and any File objects on the command-line cause + a rebuild. + ''' + testdir = os.path.join(self.common_test_dir, '64 custom header generator') + self.init(testdir) + self.build() + # Immediately rebuilding should not do anything + self.assertBuildIsNoop() + # Changing mtime of these should rebuild everything + for f in ('input.def', 'makeheader.py', 'somefile.txt'): + self.utime(os.path.join(testdir, f)) + self.assertRebuiltTarget('prog') + class WindowsTests(BasePlatformTests): ''' @@ -1104,7 +1214,7 @@ class LinuxlikeTests(BasePlatformTests): if qt4 != 0 or qt5 != 0: raise unittest.SkipTest('Qt not found with pkg-config') testdir = os.path.join(self.framework_test_dir, '4 qt') - self.init(testdir) + self.init(testdir, ['-Dmethod=pkg-config']) # Confirm that the dependency was found with qmake msg = 'Qt4 native `pkg-config` dependency (modules: Core, Gui) found: YES\n' msg2 = 'Qt5 native `pkg-config` dependency (modules: Core, Gui) found: YES\n' @@ -1127,10 +1237,8 @@ class LinuxlikeTests(BasePlatformTests): if 'Qt version 5' not in output and 'qt5' not in output: raise unittest.SkipTest('Qmake found, but it is not for Qt 5.') # Disable pkg-config codepath and force searching with qmake/qmake-qt5 - os.environ['PKG_CONFIG_LIBDIR'] = self.builddir - os.environ['PKG_CONFIG_PATH'] = self.builddir testdir = os.path.join(self.framework_test_dir, '4 qt') - self.init(testdir) + self.init(testdir, ['-Dmethod=qmake']) # Confirm that the dependency was found with qmake msg = 'Qt5 native `qmake-qt5` dependency (modules: Core) found: YES\n' msg2 = 'Qt5 native `qmake` dependency (modules: Core) found: YES\n' @@ -1214,15 +1322,6 @@ class LinuxlikeTests(BasePlatformTests): Oargs = [arg for arg in cmd if arg.startswith('-O')] self.assertEqual(Oargs, [Oflag, '-O0']) - def test_custom_target_exe_data_deterministic(self): - testdir = os.path.join(self.common_test_dir, '117 custom target capture') - self.init(testdir) - meson_exe_dat1 = glob(os.path.join(self.privatedir, 'meson_exe*.dat')) - self.wipe() - self.init(testdir) - meson_exe_dat2 = glob(os.path.join(self.privatedir, 'meson_exe*.dat')) - self.assertListEqual(meson_exe_dat1, meson_exe_dat2) - def _test_stds_impl(self, testdir, compiler, p): lang_std = p + '_std' # Check that all the listed -std=xxx options for this compiler work @@ -1328,6 +1427,27 @@ class LinuxlikeTests(BasePlatformTests): # The chown failed nonfatally if we're not root self.assertEqual(0, statf.st_uid) + def test_cpp_std_override(self): + testdir = os.path.join(self.unit_test_dir, '6 std override') + self.init(testdir) + compdb = self.get_compdb() + for i in compdb: + if 'prog03' in i['file']: + c03_comp = i['command'] + if 'prog11' in i['file']: + c11_comp = i['command'] + if 'progp' in i['file']: + plain_comp = i['command'] + self.assertNotEqual(len(plain_comp), 0) + self.assertIn('-std=c++03', c03_comp) + self.assertNotIn('-std=c++11', c03_comp) + self.assertIn('-std=c++11', c11_comp) + self.assertNotIn('-std=c++03', c11_comp) + self.assertNotIn('-std=c++03', plain_comp) + self.assertNotIn('-std=c++11', plain_comp) + # Now werror + self.assertIn('-Werror', plain_comp) + self.assertNotIn('-Werror', c03_comp) class RewriterTests(unittest.TestCase): diff --git a/test cases/common/123 subproject project arguments/exe.c b/test cases/common/123 subproject project arguments/exe.c index b04344a..d6440f0 100644 --- a/test cases/common/123 subproject project arguments/exe.c +++ b/test cases/common/123 subproject project arguments/exe.c @@ -18,6 +18,10 @@ #error #endif +#ifndef PROJECT_OPTION_C_CPP +#error +#endif + int main(int argc, char **argv) { return 0; } diff --git a/test cases/common/123 subproject project arguments/exe.cpp b/test cases/common/123 subproject project arguments/exe.cpp index 7ffe098..8471c6f 100644 --- a/test cases/common/123 subproject project arguments/exe.cpp +++ b/test cases/common/123 subproject project arguments/exe.cpp @@ -18,6 +18,10 @@ #error #endif +#ifndef PROJECT_OPTION_C_CPP +#error +#endif + int main(int argc, char **argv) { return 0; } diff --git a/test cases/common/123 subproject project arguments/meson.build b/test cases/common/123 subproject project arguments/meson.build index aee803c..90d4c05 100644 --- a/test cases/common/123 subproject project arguments/meson.build +++ b/test cases/common/123 subproject project arguments/meson.build @@ -4,10 +4,13 @@ project('project options tester', 'c', 'cpp', add_global_arguments('-DGLOBAL_ARGUMENT', language: 'c') add_project_arguments('-DPROJECT_OPTION', language: 'c') -add_project_arguments('-DPROJECT_OPTION_1', language: 'c') add_project_arguments('-DPROJECT_OPTION_CPP', language: 'cpp') +add_project_arguments('-DPROJECT_OPTION_C_CPP', language: ['c', 'cpp']) sub = subproject('subexe', version : '1.0.0') + +add_project_arguments('-DPROJECT_OPTION_1', language: 'c') + e = executable('exe', 'exe.c') e = executable('execpp', 'exe.cpp') test('exetest', e) diff --git a/test cases/common/123 subproject project arguments/subprojects/subexe/subexe.c b/test cases/common/123 subproject project arguments/subprojects/subexe/subexe.c index 6ebd752..f748afc 100644 --- a/test cases/common/123 subproject project arguments/subprojects/subexe/subexe.c +++ b/test cases/common/123 subproject project arguments/subprojects/subexe/subexe.c @@ -6,6 +6,10 @@ #error #endif +#ifdef PROJECT_OPTION_C_CPP +#error +#endif + #ifndef GLOBAL_ARGUMENT #error #endif diff --git a/test cases/common/125 shared module/meson.build b/test cases/common/125 shared module/meson.build index 7c15bcc..d96d8fc 100644 --- a/test cases/common/125 shared module/meson.build +++ b/test cases/common/125 shared module/meson.build @@ -5,7 +5,8 @@ l = shared_library('runtime', 'runtime.c') # Do NOT link the module with the runtime library. This # is a common approach for plugins that are only used # with dlopen. Any symbols are resolved dynamically -# at runtime +# at runtime. This requires extra help on Windows, so +# should be avoided unless really neccessary. m = shared_module('mymodule', 'module.c') e = executable('prog', 'prog.c', link_with : l, dependencies : dl) test('import test', e, args : m) diff --git a/test cases/common/125 shared module/module.c b/test cases/common/125 shared module/module.c index 56078c5..181b760 100644 --- a/test cases/common/125 shared module/module.c +++ b/test cases/common/125 shared module/module.c @@ -9,14 +9,24 @@ #endif #endif -#ifdef _WIN32 +#if defined(_WIN32) || defined(__CYGWIN__) #include <stdio.h> -#include <windows.h> -#include <tlhelp32.h> typedef int (*fptr) (void); +#ifdef __CYGWIN__ + +#include <dlfcn.h> + +fptr find_any_f (const char *name) { + return (fptr) dlsym(RTLD_DEFAULT, name); +} +#else /* _WIN32 */ + +#include <windows.h> +#include <tlhelp32.h> + /* Unlike Linux and OS X, when a library is loaded, all the symbols aren't * loaded into a single namespace. You must fetch the symbol by iterating over * all loaded modules. Code for finding the function from any of the loaded @@ -45,6 +55,7 @@ fptr find_any_f (const char *name) { CloseHandle (snapshot); return f; } +#endif int DLL_PUBLIC func() { fptr f; diff --git a/test cases/common/126 llvm ir and assembly/square-x86_64.S b/test cases/common/126 llvm ir and assembly/square-x86_64.S index 4adc31e..1452f47 100644 --- a/test cases/common/126 llvm ir and assembly/square-x86_64.S +++ b/test cases/common/126 llvm ir and assembly/square-x86_64.S @@ -19,12 +19,12 @@ END .text .globl SYMBOL_NAME(square_unsigned) -# ifdef _WIN32 /* MinGW */ +# if defined(_WIN32) || defined(__CYGWIN__) /* msabi */ SYMBOL_NAME(square_unsigned): imull %ecx, %ecx movl %ecx, %eax retq -# else /* Linux and OS X */ +# else /* sysvabi */ SYMBOL_NAME(square_unsigned): imull %edi, %edi movl %edi, %eax diff --git a/test cases/common/135 generated assembly/main.c b/test cases/common/135 generated assembly/main.c index 97fe723..b669cba 100644 --- a/test cases/common/135 generated assembly/main.c +++ b/test cases/common/135 generated assembly/main.c @@ -1,5 +1,8 @@ #include <stdio.h> +#if defined(_WIN32) || defined(__CYGWIN__) + __declspec(dllimport) +#endif unsigned square_unsigned (unsigned a); int diff --git a/test cases/common/135 generated assembly/square-x86_64.S.in b/test cases/common/135 generated assembly/square-x86_64.S.in index b6d7fb0..0834f16 100644 --- a/test cases/common/135 generated assembly/square-x86_64.S.in +++ b/test cases/common/135 generated assembly/square-x86_64.S.in @@ -23,12 +23,12 @@ END .type square_unsigned,@function # endif -# ifdef _WIN32 /* MinGW */ +# if defined(_WIN32) || defined(__CYGWIN__) /* msabi */ SYMBOL_NAME(square_unsigned): imull %ecx, %ecx movl %ecx, %eax retq -# else /* Linux and OS X */ +# else /* sysvabi */ SYMBOL_NAME(square_unsigned): imull %edi, %edi movl %edi, %eax diff --git a/test cases/common/139 custom target multiple outputs/generator.py b/test cases/common/139 custom target multiple outputs/generator.py new file mode 100755 index 0000000..39dbd11 --- /dev/null +++ b/test cases/common/139 custom target multiple outputs/generator.py @@ -0,0 +1,14 @@ +#!/usr/bin/env python3 + +import sys, os + +if len(sys.argv) != 3: + print(sys.argv[0], '<namespace>', '<output dir>') + +name = sys.argv[1] +odir = sys.argv[2] + +with open(os.path.join(odir, name + '.h'), 'w') as f: + f.write('int func();\n') +with open(os.path.join(odir, name + '.sh'), 'w') as f: + f.write('#!/bin/bash') diff --git a/test cases/common/139 custom target multiple outputs/installed_files.txt b/test cases/common/139 custom target multiple outputs/installed_files.txt new file mode 100644 index 0000000..21e1249 --- /dev/null +++ b/test cases/common/139 custom target multiple outputs/installed_files.txt @@ -0,0 +1,6 @@ +usr/include/diff.h +usr/include/first.h +usr/bin/diff.sh +usr/bin/second.sh +opt/same.h +opt/same.sh diff --git a/test cases/common/139 custom target multiple outputs/meson.build b/test cases/common/139 custom target multiple outputs/meson.build new file mode 100644 index 0000000..6412864 --- /dev/null +++ b/test cases/common/139 custom target multiple outputs/meson.build @@ -0,0 +1,28 @@ +project('multiple outputs install', 'c') + +gen = find_program('generator.py') + +custom_target('different-install-dirs', + output : ['diff.h', 'diff.sh'], + command : [gen, 'diff', '@OUTDIR@'], + install : true, + install_dir : [join_paths(get_option('prefix'), get_option('includedir')), + join_paths(get_option('prefix'), get_option('bindir'))]) + +custom_target('same-install-dir', + output : ['same.h', 'same.sh'], + command : [gen, 'same', '@OUTDIR@'], + install : true, + install_dir : '/opt') + +custom_target('only-install-first', + output : ['first.h', 'first.sh'], + command : [gen, 'first', '@OUTDIR@'], + install : true, + install_dir : [join_paths(get_option('prefix'), get_option('includedir')), false]) + +custom_target('only-install-second', + output : ['second.h', 'second.sh'], + command : [gen, 'second', '@OUTDIR@'], + install : true, + install_dir : [false, join_paths(get_option('prefix'), get_option('bindir'))]) diff --git a/test cases/common/139 override options/four.c b/test cases/common/139 override options/four.c new file mode 100644 index 0000000..54f8491 --- /dev/null +++ b/test cases/common/139 override options/four.c @@ -0,0 +1,9 @@ +int func(); + +static int duplicate_func() { + return -4; +} + +int main(int argc, char **argv) { + return duplicate_func() + func(); +} diff --git a/test cases/common/139 override options/meson.build b/test cases/common/139 override options/meson.build new file mode 100644 index 0000000..0db0513 --- /dev/null +++ b/test cases/common/139 override options/meson.build @@ -0,0 +1,8 @@ +project('option override', 'c', + default_options : 'unity=true') + +executable('mustunity', 'one.c', 'two.c') +executable('notunity', 'three.c', 'four.c', + override_options : ['unity=false']) + + diff --git a/test cases/common/139 override options/one.c b/test cases/common/139 override options/one.c new file mode 100644 index 0000000..14fe9d6 --- /dev/null +++ b/test cases/common/139 override options/one.c @@ -0,0 +1,3 @@ +static int hidden_func() { + return 0; +} diff --git a/test cases/common/139 override options/three.c b/test cases/common/139 override options/three.c new file mode 100644 index 0000000..305a575 --- /dev/null +++ b/test cases/common/139 override options/three.c @@ -0,0 +1,7 @@ +static int duplicate_func() { + return 4; +} + +int func() { + return duplicate_func(); +} diff --git a/test cases/common/139 override options/two.c b/test cases/common/139 override options/two.c new file mode 100644 index 0000000..04b1d3f --- /dev/null +++ b/test cases/common/139 override options/two.c @@ -0,0 +1,6 @@ +/* + * Requires a Unity build. Otherwise hidden_func is not specified. + */ +int main(int argc, char **argv) { + return hidden_func(); +} diff --git a/test cases/common/140 get define/meson.build b/test cases/common/140 get define/meson.build new file mode 100644 index 0000000..5ce4b36 --- /dev/null +++ b/test cases/common/140 get define/meson.build @@ -0,0 +1,31 @@ +project('get define', 'c', 'cpp') + +host_system = host_machine.system() + +foreach lang : ['c', 'cpp'] + cc = meson.get_compiler(lang) + if host_system == 'linux' + d = cc.get_define('__linux__') + assert(d == '1', '__linux__ value is @0@ instead of 1'.format(d)) + elif host_system == 'darwin' + d = cc.get_define('__APPLE__') + assert(d == '1', '__APPLE__ value is @0@ instead of 1'.format(d)) + elif host_system == 'windows' + d = cc.get_define('_WIN32') + assert(d == '1', '_WIN32 value is @0@ instead of 1'.format(d)) + elif host_system == 'cygwin' + d = cc.get_define('__CYGWIN__') + assert(d == '1', '__CYGWIN__ value is @0@ instead of 1'.format(d)) + else + error('Please report a bug and help us improve support for this platform') + endif + + # Check that an undefined value is empty. + have = cc.get_define('MESON_FAIL_VALUE') + assert(have == '', 'MESON_FAIL_VALUE value is "@0@" instead of ""'.format(have)) + + # This is used in the test_preprocessor_checks_CPPFLAGS() unit test. + have = cc.get_define('MESON_TEST_DEFINE_VALUE') + expect = get_option('MESON_TEST_DEFINE_VALUE') + assert(have == expect, 'MESON_TEST_DEFINE_VALUE value is "@0@" instead of "@1@"'.format(have, expect)) +endforeach diff --git a/test cases/common/140 get define/meson_options.txt b/test cases/common/140 get define/meson_options.txt new file mode 100644 index 0000000..a88cecd --- /dev/null +++ b/test cases/common/140 get define/meson_options.txt @@ -0,0 +1 @@ +option('MESON_TEST_DEFINE_VALUE', type : 'string', default : '') diff --git a/test cases/common/141 c cpp and asm/meson.build b/test cases/common/141 c cpp and asm/meson.build index 9c90434..2c3610e 100644 --- a/test cases/common/141 c cpp and asm/meson.build +++ b/test cases/common/141 c cpp and asm/meson.build @@ -1,6 +1,7 @@ project('c cpp and asm', 'c', 'cpp') cpu = host_machine.cpu_family() +cc = meson.get_compiler('c') supported_cpus = ['arm', 'x86', 'x86_64'] @@ -12,6 +13,10 @@ if meson.get_compiler('c').get_id() == 'msvc' error('MESON_SKIP_TEST MSVC can\'t compile assembly') endif +if cc.symbols_have_underscore_prefix() + add_project_arguments('-DMESON_TEST__UNDERSCORE_SYMBOL', language: 'c') +endif + test('test-c-asm', executable('c-asm', ['main.c', 'retval-' + cpu + '.S'])) test('test-cpp-asm', executable('cpp-asm', ['main.cpp', 'retval-' + cpu + '.S'])) test('test-c-cpp-asm', executable('c-cpp-asm', ['somelib.c', 'main.cpp', 'retval-' + cpu + '.S'])) diff --git a/test cases/common/141 c cpp and asm/symbol-underscore.h b/test cases/common/141 c cpp and asm/symbol-underscore.h index 508cf50..d0f3ef9 100644 --- a/test cases/common/141 c cpp and asm/symbol-underscore.h +++ b/test cases/common/141 c cpp and asm/symbol-underscore.h @@ -1,4 +1,4 @@ -#if defined(__WIN32__) || defined(__APPLE__) +#if defined(MESON_TEST__UNDERSCORE_SYMBOL) # define SYMBOL_NAME(name) _##name #else # define SYMBOL_NAME(name) name diff --git a/test cases/common/141 mesonintrospect from scripts/check_env.py b/test cases/common/141 mesonintrospect from scripts/check_env.py new file mode 100644 index 0000000..dc8ad63 --- /dev/null +++ b/test cases/common/141 mesonintrospect from scripts/check_env.py @@ -0,0 +1,20 @@ +#!/usr/bin/env python3 + +import os +import sys + +do_print = False + +if len(sys.argv) > 1: + do_print = bool(sys.argv[1]) + +if 'MESONINTROSPECT' not in os.environ: + raise RuntimeError('MESONINTROSPECT not found') + +mesonintrospect = os.environ['MESONINTROSPECT'] + +if not os.path.isfile(mesonintrospect): + raise RuntimeError('{!r} does not exist'.format(mesonintrospect)) + +if do_print: + print(mesonintrospect, end='') diff --git a/test cases/common/141 mesonintrospect from scripts/meson.build b/test cases/common/141 mesonintrospect from scripts/meson.build new file mode 100644 index 0000000..f78710b --- /dev/null +++ b/test cases/common/141 mesonintrospect from scripts/meson.build @@ -0,0 +1,14 @@ +project('mesonintrospect from scripts', 'c') + +python = import('python3').find_python() + +ret = run_command(python, ['check_env.py', '1']) +if ret.returncode() == 0 + find_program(ret.stdout()) +else + message(ret.stdout()) + message(ret.stderr()) +endif + +meson.add_postconf_script('check_env.py') +meson.add_install_script('check_env.py') diff --git a/test cases/common/139 compute int/config.h.in b/test cases/common/142 compute int/config.h.in index ad8d077..ad8d077 100644 --- a/test cases/common/139 compute int/config.h.in +++ b/test cases/common/142 compute int/config.h.in diff --git a/test cases/common/139 compute int/foobar.h b/test cases/common/142 compute int/foobar.h index fd3cb5e..fd3cb5e 100644 --- a/test cases/common/139 compute int/foobar.h +++ b/test cases/common/142 compute int/foobar.h diff --git a/test cases/common/139 compute int/meson.build b/test cases/common/142 compute int/meson.build index 43553fe..43553fe 100644 --- a/test cases/common/139 compute int/meson.build +++ b/test cases/common/142 compute int/meson.build diff --git a/test cases/common/139 compute int/prog.c.in b/test cases/common/142 compute int/prog.c.in index 3ff1463..3ff1463 100644 --- a/test cases/common/139 compute int/prog.c.in +++ b/test cases/common/142 compute int/prog.c.in diff --git a/test cases/common/139 custom target object output/meson.build b/test cases/common/143 custom target object output/meson.build index ede165b..ede165b 100644 --- a/test cases/common/139 custom target object output/meson.build +++ b/test cases/common/143 custom target object output/meson.build diff --git a/test cases/common/139 custom target object output/obj_generator.py b/test cases/common/143 custom target object output/obj_generator.py index a33872a..a33872a 100644 --- a/test cases/common/139 custom target object output/obj_generator.py +++ b/test cases/common/143 custom target object output/obj_generator.py diff --git a/test cases/common/139 custom target object output/objdir/meson.build b/test cases/common/143 custom target object output/objdir/meson.build index 0d7f6c2..0d7f6c2 100644 --- a/test cases/common/139 custom target object output/objdir/meson.build +++ b/test cases/common/143 custom target object output/objdir/meson.build diff --git a/test cases/common/139 custom target object output/objdir/source.c b/test cases/common/143 custom target object output/objdir/source.c index 7779b33..7779b33 100644 --- a/test cases/common/139 custom target object output/objdir/source.c +++ b/test cases/common/143 custom target object output/objdir/source.c diff --git a/test cases/common/139 custom target object output/progdir/meson.build b/test cases/common/143 custom target object output/progdir/meson.build index 4216c24..4216c24 100644 --- a/test cases/common/139 custom target object output/progdir/meson.build +++ b/test cases/common/143 custom target object output/progdir/meson.build diff --git a/test cases/common/139 custom target object output/progdir/prog.c b/test cases/common/143 custom target object output/progdir/prog.c index c1ece33..c1ece33 100644 --- a/test cases/common/139 custom target object output/progdir/prog.c +++ b/test cases/common/143 custom target object output/progdir/prog.c diff --git a/test cases/common/144 empty build file/meson.build b/test cases/common/144 empty build file/meson.build new file mode 100644 index 0000000..73d0397 --- /dev/null +++ b/test cases/common/144 empty build file/meson.build @@ -0,0 +1,2 @@ +project('subdir with empty meson.build test', 'c') +subdir('subdir') diff --git a/test cases/common/144 empty build file/subdir/meson.build b/test cases/common/144 empty build file/subdir/meson.build new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/test cases/common/144 empty build file/subdir/meson.build diff --git a/test cases/common/16 configure file/meson.build b/test cases/common/16 configure file/meson.build index 8271ca3..8ffc28c 100644 --- a/test cases/common/16 configure file/meson.build +++ b/test cases/common/16 configure file/meson.build @@ -22,6 +22,11 @@ e = executable('inctest', 'prog.c', cfile) test('inctest', e) +# Test if we can also pass files() as input +configure_file(input : files('config.h.in'), + output : 'config2.h', + configuration : conf) + # Now generate a header file with an external script. genprog = import('python3').find_python() scriptfile = '@0@/generator.py'.format(meson.current_source_dir()) diff --git a/test cases/common/22 header in file list/prog.c b/test cases/common/22 header in file list/prog.c index 0314ff1..fbedab8 100644 --- a/test cases/common/22 header in file list/prog.c +++ b/test cases/common/22 header in file list/prog.c @@ -1 +1,3 @@ +#include "header.h" + int main(int argc, char **argv) { return 0; } diff --git a/test cases/common/23 global arg/meson.build b/test cases/common/23 global arg/meson.build index aec5c2d..d7fd428 100644 --- a/test cases/common/23 global arg/meson.build +++ b/test cases/common/23 global arg/meson.build @@ -3,6 +3,8 @@ project('global arg test', 'cpp', 'c') add_global_arguments('-DMYTHING', language : 'c') add_global_arguments('-DMYCPPTHING', language : 'cpp') +add_global_arguments('-DMYCANDCPPTHING', language: ['c', 'cpp']) + exe1 = executable('prog', 'prog.c') exe2 = executable('prog2', 'prog.cc') diff --git a/test cases/common/23 global arg/prog.c b/test cases/common/23 global arg/prog.c index df91777..ace5a0a 100644 --- a/test cases/common/23 global arg/prog.c +++ b/test cases/common/23 global arg/prog.c @@ -6,6 +6,10 @@ #error "Wrong global argument set" #endif +#ifndef MYCANDCPPTHING +#error "Global argument not set" +#endif + int main(int argc, char **argv) { return 0; } diff --git a/test cases/common/23 global arg/prog.cc b/test cases/common/23 global arg/prog.cc index 342fdd0..0ffd85e 100644 --- a/test cases/common/23 global arg/prog.cc +++ b/test cases/common/23 global arg/prog.cc @@ -6,6 +6,10 @@ #error "Global argument not set" #endif +#ifndef MYCANDCPPTHING +#error "Global argument not set" +#endif + int main(int argc, char **argv) { return 0; } diff --git a/test cases/common/37 has header/meson.build b/test cases/common/37 has header/meson.build index 4299ce5..b53849c 100644 --- a/test cases/common/37 has header/meson.build +++ b/test cases/common/37 has header/meson.build @@ -11,19 +11,19 @@ configure_file(input : non_existant_header, # Test that the fallback to __has_include also works on all compilers if host_system != 'darwin' - args = [[], ['-U__has_include']] + fallbacks = ['', '\n#undef __has_include'] else # On Darwin's clang you can't redefine builtin macros so the above doesn't work - args = [[]] + fallbacks = [''] endif -foreach arg : args +foreach fallback : fallbacks foreach comp : [meson.get_compiler('c'), meson.get_compiler('cpp')] - assert(comp.has_header('stdio.h', args : arg), 'Stdio missing.') + assert(comp.has_header('stdio.h', prefix : fallback), 'Stdio missing.') # stdio.h doesn't actually need stdlib.h, but just test that setting the # prefix does not result in an error. - assert(comp.has_header('stdio.h', prefix : '#include <stdlib.h>', args : arg), + assert(comp.has_header('stdio.h', prefix : '#include <stdlib.h>' + fallback), 'Stdio missing.') # XInput.h should not require type definitions from windows.h, but it does @@ -32,9 +32,9 @@ foreach arg : args # We only do this check on MSVC because MinGW often defines its own wrappers # that pre-include windows.h if comp.get_id() == 'msvc' - assert(comp.has_header('XInput.h', prefix : '#include <windows.h>', args : arg), + assert(comp.has_header('XInput.h', prefix : '#include <windows.h>' + fallback), 'XInput.h should not be missing on Windows') - assert(comp.has_header('XInput.h', prefix : '#define _X86_', args : arg), + assert(comp.has_header('XInput.h', prefix : '#define _X86_' + fallback), 'XInput.h should not need windows.h') endif @@ -42,13 +42,13 @@ foreach arg : args # https://gcc.gnu.org/bugzilla/show_bug.cgi?id=80005 # https://github.com/mesonbuild/meson/issues/1458 if host_system == 'linux' - assert(comp.has_header('linux/if.h', args : arg), + assert(comp.has_header('linux/if.h', prefix : fallback), 'Could not find <linux/if.h>') endif # This header exists in the source and the builddir, but we still must not # find it since we are looking in the system directories. - assert(not comp.has_header(non_existant_header, args : arg), + assert(not comp.has_header(non_existant_header, prefix : fallback), 'Found non-existant header.') endforeach endforeach diff --git a/test cases/common/64 custom header generator/meson.build b/test cases/common/64 custom header generator/meson.build index b422401..bcc9a53 100644 --- a/test cases/common/64 custom header generator/meson.build +++ b/test cases/common/64 custom header generator/meson.build @@ -5,7 +5,7 @@ gen = find_program('makeheader.py') generated_h = custom_target('makeheader.py', output : 'myheader.lh', # Suffix not .h to ensure this works with custom suffixes, too. input : 'input.def', -command : [gen, '@INPUT0@', '@OUTPUT0@']) +command : [gen, '@INPUT0@', '@OUTPUT0@', files('somefile.txt')]) prog = executable('prog', 'prog.c', generated_h) test('gentest', prog) diff --git a/test cases/common/64 custom header generator/somefile.txt b/test cases/common/64 custom header generator/somefile.txt new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/test cases/common/64 custom header generator/somefile.txt diff --git a/test cases/failing/14 invalid option name/meson_options.txt b/test cases/failing/14 invalid option name/meson_options.txt index c656402..aab6ae8 100644 --- a/test cases/failing/14 invalid option name/meson_options.txt +++ b/test cases/failing/14 invalid option name/meson_options.txt @@ -1 +1 @@ -option('invalid/name', type : 'boolean', value : false)
\ No newline at end of file +option('invalid:name', type : 'boolean', value : false) diff --git a/test cases/failing/19 target clash/meson.build b/test cases/failing/19 target clash/meson.build index 070631b..fbc757c 100644 --- a/test cases/failing/19 target clash/meson.build +++ b/test cases/failing/19 target clash/meson.build @@ -7,7 +7,7 @@ project('clash', 'c') # This test might fail to work on different backends or when # output location is redirected. -if host_machine.system() == 'windows' +if host_machine.system() == 'windows' or host_machine.system() == 'cygwin' error('This is expected.') endif diff --git a/test cases/failing/43 custom target outputs not matching install_dirs/generator.py b/test cases/failing/43 custom target outputs not matching install_dirs/generator.py new file mode 100755 index 0000000..4ac6179 --- /dev/null +++ b/test cases/failing/43 custom target outputs not matching install_dirs/generator.py @@ -0,0 +1,16 @@ +#!/usr/bin/env python3 + +import sys, os + +if len(sys.argv) != 3: + print(sys.argv[0], '<namespace>', '<output dir>') + +name = sys.argv[1] +odir = sys.argv[2] + +with open(os.path.join(odir, name + '.h'), 'w') as f: + f.write('int func();\n') +with open(os.path.join(odir, name + '.c'), 'w') as f: + f.write('int main(int argc, char *argv[]) { return 0; }') +with open(os.path.join(odir, name + '.sh'), 'w') as f: + f.write('#!/bin/bash') diff --git a/test cases/failing/43 custom target outputs not matching install_dirs/installed_files.txt b/test cases/failing/43 custom target outputs not matching install_dirs/installed_files.txt new file mode 100644 index 0000000..21e1249 --- /dev/null +++ b/test cases/failing/43 custom target outputs not matching install_dirs/installed_files.txt @@ -0,0 +1,6 @@ +usr/include/diff.h +usr/include/first.h +usr/bin/diff.sh +usr/bin/second.sh +opt/same.h +opt/same.sh diff --git a/test cases/failing/43 custom target outputs not matching install_dirs/meson.build b/test cases/failing/43 custom target outputs not matching install_dirs/meson.build new file mode 100644 index 0000000..45bd7b3 --- /dev/null +++ b/test cases/failing/43 custom target outputs not matching install_dirs/meson.build @@ -0,0 +1,13 @@ +project('outputs not matching install_dirs', 'c') + +gen = find_program('generator.py') + +if meson.backend() != 'ninja' + error('Failing manually, test is only for the ninja backend') +endif + +custom_target('too-few-install-dirs', + output : ['toofew.h', 'toofew.c', 'toofew.sh'], + command : [gen, 'toofew', '@OUTDIR@'], + install : true, + install_dir : [join_paths(get_option('prefix'), get_option('includedir')), false]) diff --git a/test cases/failing/43 project name colon/meson.build b/test cases/failing/43 project name colon/meson.build new file mode 100644 index 0000000..53e947e --- /dev/null +++ b/test cases/failing/43 project name colon/meson.build @@ -0,0 +1 @@ +project('name with :') diff --git a/test cases/frameworks/4 qt/meson.build b/test cases/frameworks/4 qt/meson.build index fec5959..468b9c9 100644 --- a/test cases/frameworks/4 qt/meson.build +++ b/test cases/frameworks/4 qt/meson.build @@ -9,17 +9,17 @@ foreach qt : ['qt4', 'qt5'] qt_modules += qt5_modules endif # Test that invalid modules are indeed not found - fakeqtdep = dependency(qt, modules : ['DefinitelyNotFound'], required : false) + fakeqtdep = dependency(qt, modules : ['DefinitelyNotFound'], required : false, method : get_option('method')) if fakeqtdep.found() error('Invalid qt dep incorrectly found!') endif # Test that partially-invalid modules are indeed not found - fakeqtdep = dependency(qt, modules : ['Core', 'DefinitelyNotFound'], required : false) + fakeqtdep = dependency(qt, modules : ['Core', 'DefinitelyNotFound'], required : false, method : get_option('method')) if fakeqtdep.found() error('Invalid qt dep incorrectly found!') endif # If qt4 modules are found, test that. qt5 is required. - qtdep = dependency(qt, modules : qt_modules, required : qt == 'qt5') + qtdep = dependency(qt, modules : qt_modules, required : qt == 'qt5', method : get_option('method')) if qtdep.found() qtmodule = import(qt) @@ -30,10 +30,11 @@ foreach qt : ['qt4', 'qt5'] moc_headers : ['mainWindow.h'], # These need to be fed through the moc tool before use. ui_files : 'mainWindow.ui', # XML files that need to be compiled with the uic tol. qresources : ['stuff.qrc', 'stuff2.qrc'], # Resource file for rcc compiler. + method : get_option('method') ) # Test that setting a unique name with a positional argument works - qtmodule.preprocess(qt + 'teststuff', qresources : ['stuff.qrc']) + qtmodule.preprocess(qt + 'teststuff', qresources : ['stuff.qrc'], method : get_option('method')) qexe = executable(qt + 'app', sources : ['main.cpp', 'mainWindow.cpp', # Sources that don't need preprocessing. @@ -43,7 +44,7 @@ foreach qt : ['qt4', 'qt5'] # We need a console test application because some test environments # do not have an X server. - qtcore = dependency(qt, modules : 'Core') + qtcore = dependency(qt, modules : 'Core', method : get_option('method')) qtcoreapp = executable(qt + 'core', 'q5core.cpp', dependencies : qtcore) @@ -55,7 +56,8 @@ foreach qt : ['qt4', 'qt5'] # files from sources. manpreprocessed = qtmodule.preprocess( moc_sources : 'manualinclude.cpp', - moc_headers : 'manualinclude.h') + moc_headers : 'manualinclude.h', + method : get_option('method')) qtmaninclude = executable(qt + 'maninclude', sources : ['manualinclude.cpp', manpreprocessed], diff --git a/test cases/frameworks/4 qt/meson_options.txt b/test cases/frameworks/4 qt/meson_options.txt new file mode 100644 index 0000000..bc1069e --- /dev/null +++ b/test cases/frameworks/4 qt/meson_options.txt @@ -0,0 +1 @@ +option('method', type : 'string', value : 'auto', description : 'The method to use to find Qt') diff --git a/test cases/linuxlike/10 large file support/meson.build b/test cases/linuxlike/10 large file support/meson.build index aa4eccf..6683345 100644 --- a/test cases/linuxlike/10 large file support/meson.build +++ b/test cases/linuxlike/10 large file support/meson.build @@ -1,5 +1,9 @@ project('trivial test', 'c') +if host_machine.system() == 'cygwin' + error('MESON_SKIP_TEST _FILE_OFFSET_BITS not yet supported on Cygwin.') +endif + cc = meson.get_compiler('c') size = cc.sizeof('off_t') diff --git a/test cases/linuxlike/11 runpath rpath ldlibrarypath/lib.c b/test cases/linuxlike/11 runpath rpath ldlibrarypath/lib.c new file mode 100644 index 0000000..2784e01 --- /dev/null +++ b/test cases/linuxlike/11 runpath rpath ldlibrarypath/lib.c @@ -0,0 +1,3 @@ +int some_symbol (void) { + return RET_VALUE; +} diff --git a/test cases/linuxlike/11 runpath rpath ldlibrarypath/lib1/meson.build b/test cases/linuxlike/11 runpath rpath ldlibrarypath/lib1/meson.build new file mode 100644 index 0000000..cbc75bd --- /dev/null +++ b/test cases/linuxlike/11 runpath rpath ldlibrarypath/lib1/meson.build @@ -0,0 +1,2 @@ +lib1 = shared_library('testeh', libsrc, + c_args : '-DRET_VALUE=1') diff --git a/test cases/linuxlike/11 runpath rpath ldlibrarypath/lib2/meson.build b/test cases/linuxlike/11 runpath rpath ldlibrarypath/lib2/meson.build new file mode 100644 index 0000000..f2c6dcc --- /dev/null +++ b/test cases/linuxlike/11 runpath rpath ldlibrarypath/lib2/meson.build @@ -0,0 +1,3 @@ +lib2 = shared_library('esteh', libsrc, + name_prefix : 'libt', + c_args : '-DRET_VALUE=2') diff --git a/test cases/linuxlike/11 runpath rpath ldlibrarypath/main.c b/test cases/linuxlike/11 runpath rpath ldlibrarypath/main.c new file mode 100644 index 0000000..c9eff99 --- /dev/null +++ b/test cases/linuxlike/11 runpath rpath ldlibrarypath/main.c @@ -0,0 +1,11 @@ +#include <stdio.h> + +int some_symbol (void); + +int main (int argc, char *argv[]) { + int ret = some_symbol (); + if (ret == 1) + return 0; + fprintf (stderr, "ret was %i instead of 1\n", ret); + return -1; +} diff --git a/test cases/linuxlike/11 runpath rpath ldlibrarypath/meson.build b/test cases/linuxlike/11 runpath rpath ldlibrarypath/meson.build new file mode 100644 index 0000000..b49da66 --- /dev/null +++ b/test cases/linuxlike/11 runpath rpath ldlibrarypath/meson.build @@ -0,0 +1,14 @@ +project('runpath rpath ldlibrarypath', 'c') + +libsrc = files('lib.c') + +subdir('lib1') +subdir('lib2') + +lib2dir = meson.current_build_dir() + '/lib2' + +e = executable('testexe', 'main.c', + link_with : lib1) + +test('ld-library-path-test', e, + env : ['LD_LIBRARY_PATH=' + lib2dir]) diff --git a/test cases/linuxlike/7 library versions/installed_files.txt b/test cases/linuxlike/7 library versions/installed_files.txt index b997e53..763a907 100644 --- a/test cases/linuxlike/7 library versions/installed_files.txt +++ b/test cases/linuxlike/7 library versions/installed_files.txt @@ -7,3 +7,4 @@ usr/lib/libonlyversion.so.1 usr/lib/libonlyversion.so.1.4.5 usr/lib/libonlysoversion.so usr/lib/libonlysoversion.so.5 +usr/lib/libmodule.so diff --git a/test cases/linuxlike/7 library versions/meson.build b/test cases/linuxlike/7 library versions/meson.build index 48b75ad..d156eb0 100644 --- a/test cases/linuxlike/7 library versions/meson.build +++ b/test cases/linuxlike/7 library versions/meson.build @@ -1,5 +1,9 @@ project('library versions', 'c') +if host_machine.system() == 'cygwin' + error('MESON_SKIP_TEST linuxlike soversions not supported on Cygwin.') +endif + some = shared_library('some', 'lib.c', version : '1.2.3', soversion : '0', @@ -43,3 +47,5 @@ test('manually linked 3', executable('manuallink3', out, test('manually linked 4', executable('manuallink4', out, link_args : ['-L.', '-lonlysoversion', rpath_arg])) + +shared_module('module', 'lib.c', install : true) diff --git a/test cases/linuxlike/8 subproject library install/meson.build b/test cases/linuxlike/8 subproject library install/meson.build index 63e57cf..ff55799 100644 --- a/test cases/linuxlike/8 subproject library install/meson.build +++ b/test cases/linuxlike/8 subproject library install/meson.build @@ -2,5 +2,9 @@ project('subproj lib install', 'c', version : '2.3.4', license : 'mylicense') +if host_machine.system() == 'cygwin' + error('MESON_SKIP_TEST linuxlike soversions not supported on Cygwin.') +endif + # Test that the subproject library gets installed subproject('sublib', version : '1.0.0') diff --git a/test cases/objc/2 nsstring/meson.build b/test cases/objc/2 nsstring/meson.build index ec496a2..a877d74 100644 --- a/test cases/objc/2 nsstring/meson.build +++ b/test cases/objc/2 nsstring/meson.build @@ -2,6 +2,8 @@ project('nsstring', 'objc') if host_machine.system() == 'darwin' dep = dependency('appleframeworks', modules : 'foundation') +elif host_machine.system() == 'cygwin' + error('MESON_SKIP_TEST GNUstep is not packaged for Cygwin.') else dep = dependency('gnustep') if host_machine.system() == 'linux' and meson.get_compiler('objc').get_id() == 'clang' diff --git a/test cases/osx/2 library versions/installed_files.txt b/test cases/osx/2 library versions/installed_files.txt index fc76046..de7b078 100644 --- a/test cases/osx/2 library versions/installed_files.txt +++ b/test cases/osx/2 library versions/installed_files.txt @@ -5,3 +5,4 @@ usr/lib/libonlyversion.dylib usr/lib/libonlyversion.1.dylib usr/lib/libonlysoversion.dylib usr/lib/libonlysoversion.5.dylib +usr/lib/libmodule.dylib diff --git a/test cases/osx/2 library versions/meson.build b/test cases/osx/2 library versions/meson.build index b1962ca..9624998 100644 --- a/test cases/osx/2 library versions/meson.build +++ b/test cases/osx/2 library versions/meson.build @@ -39,3 +39,5 @@ test('manually linked 3', executable('manuallink3', out, test('manually linked 4', executable('manuallink4', out, link_args : ['-L.', '-lonlysoversion'])) + +shared_module('module', 'lib.c', install : true) diff --git a/test cases/python3/1 basic/meson.build b/test cases/python3/1 basic/meson.build index 9d5f874..111b717 100644 --- a/test cases/python3/1 basic/meson.build +++ b/test cases/python3/1 basic/meson.build @@ -3,6 +3,16 @@ project('python sample', 'c') py3_mod = import('python3') py3 = py3_mod.find_python() +py3_version = py3_mod.language_version() +if py3_version.version_compare('< 3.2') + error('Invalid python version!?') +endif + +py3_purelib = py3_mod.sysconfig_path('purelib') +if not py3_purelib.endswith('site-packages') + error('Python3 purelib path seems invalid?') +endif + main = files('prog.py') test('toplevel', py3, args : main) diff --git a/test cases/unit/6 std override/meson.build b/test cases/unit/6 std override/meson.build new file mode 100644 index 0000000..ef2baac --- /dev/null +++ b/test cases/unit/6 std override/meson.build @@ -0,0 +1,10 @@ +project('cpp std override', 'cpp', + default_options : ['cpp_std=c++03', + 'werror=true']) + +executable('plain', 'progp.cpp', + override_options : 'cpp_std=none') +executable('v03', 'prog03.cpp', + override_options : 'werror=false') +executable('v11', 'prog11.cpp', + override_options : 'cpp_std=c++11') diff --git a/test cases/unit/6 std override/prog03.cpp b/test cases/unit/6 std override/prog03.cpp new file mode 100644 index 0000000..d30abc9 --- /dev/null +++ b/test cases/unit/6 std override/prog03.cpp @@ -0,0 +1,6 @@ +#include<iostream> + +int main(int argc, char **argv) { + std::cout << "I am a c++03 test program.\n"; + return 0; +} diff --git a/test cases/unit/6 std override/prog11.cpp b/test cases/unit/6 std override/prog11.cpp new file mode 100644 index 0000000..dde1fc0 --- /dev/null +++ b/test cases/unit/6 std override/prog11.cpp @@ -0,0 +1,6 @@ +#include<iostream> + +int main(int argc, char **argv) { + std::cout << "I am a C++11 test program.\n"; + return 0; +} diff --git a/test cases/unit/6 std override/progp.cpp b/test cases/unit/6 std override/progp.cpp new file mode 100644 index 0000000..b9bd97f --- /dev/null +++ b/test cases/unit/6 std override/progp.cpp @@ -0,0 +1,6 @@ +#include<iostream> + +int main(int argc, char **argv) { + std::cout << "I am a test program of undefined C++ standard.\n"; + return 0; +} diff --git a/test cases/vala/7 shared library/installed_files.txt b/test cases/vala/7 shared library/installed_files.txt new file mode 100644 index 0000000..f70e439 --- /dev/null +++ b/test cases/vala/7 shared library/installed_files.txt @@ -0,0 +1,8 @@ +usr/lib/libinstalled_vala_lib.so +usr/lib/libinstalled_vala_all.so +usr/include/installed_vala_all.h +usr/include/valah/installed_vala_all_nolib.h +usr/include/installed_vala_onlyh.h +usr/share/vala/vapi/installed_vala_all.vapi +usr/share/vala-1.0/vapi/installed_vala_all_nolib.vapi +usr/share/vala/vapi/installed_vala_onlyvapi.vapi diff --git a/test cases/vala/7 shared library/lib/meson.build b/test cases/vala/7 shared library/lib/meson.build index 8eca0d4..78646a8 100644 --- a/test cases/vala/7 shared library/lib/meson.build +++ b/test cases/vala/7 shared library/lib/meson.build @@ -1 +1,27 @@ l = shared_library('valalib', 'mylib.vala', dependencies : valadeps) + +shared_library('installed_vala_lib', 'mylib.vala', + dependencies : valadeps, + install : true) + +shared_library('installed_vala_all', 'mylib.vala', + dependencies : valadeps, + install : true, + install_dir : [true, true, true]) + +shared_library('installed_vala_all_nolib', 'mylib.vala', + dependencies : valadeps, + install : true, + install_dir : [false, + join_paths(get_option('includedir'), 'valah'), + join_paths(get_option('datadir'), 'vala-1.0', 'vapi')]) + +shared_library('installed_vala_onlyh', 'mylib.vala', + dependencies : valadeps, + install : true, + install_dir : [false, get_option('includedir'), false]) + +shared_library('installed_vala_onlyvapi', 'mylib.vala', + dependencies : valadeps, + install : true, + install_dir : [false, false, join_paths(get_option('datadir'), 'vala', 'vapi')]) diff --git a/test cases/vala/9 gir/installed_files.txt b/test cases/vala/9 gir/installed_files.txt new file mode 100644 index 0000000..7a0e055 --- /dev/null +++ b/test cases/vala/9 gir/installed_files.txt @@ -0,0 +1,2 @@ +usr/lib/libfoo.so +usr/share/gir-1.0/Foo-1.0.gir diff --git a/test cases/vala/9 gir/meson.build b/test cases/vala/9 gir/meson.build index c0a8f54..1a09bec 100644 --- a/test cases/vala/9 gir/meson.build +++ b/test cases/vala/9 gir/meson.build @@ -5,6 +5,8 @@ gobject = dependency('gobject-2.0') g_ir_compiler = find_program('g-ir-compiler') foo = shared_library('foo', 'foo.vala', + install : true, + install_dir : [true, false, false, true], vala_gir: 'Foo-1.0.gir', dependencies: [glib, gobject]) diff --git a/test cases/windows/10 vs module defs generated/meson.build b/test cases/windows/10 vs module defs generated/meson.build new file mode 100644 index 0000000..5ce1a20 --- /dev/null +++ b/test cases/windows/10 vs module defs generated/meson.build @@ -0,0 +1,7 @@ +project('generated_dll_module_defs', 'c') + +if meson.get_compiler('c').get_id() == 'msvc' + subdir('subdir') + exe = executable('prog', 'prog.c', link_with : shlib) + test('runtest', exe) +endif diff --git a/test cases/windows/10 vs module defs generated/prog.c b/test cases/windows/10 vs module defs generated/prog.c new file mode 100644 index 0000000..f35f4a0 --- /dev/null +++ b/test cases/windows/10 vs module defs generated/prog.c @@ -0,0 +1,5 @@ +int somedllfunc(); + +int main(int argc, char **argv) { + return somedllfunc() == 42 ? 0 : 1; +} diff --git a/test cases/windows/10 vs module defs generated/subdir/meson.build b/test cases/windows/10 vs module defs generated/subdir/meson.build new file mode 100644 index 0000000..5d390a0 --- /dev/null +++ b/test cases/windows/10 vs module defs generated/subdir/meson.build @@ -0,0 +1,9 @@ +conf = configuration_data() +conf.set('func', 'somedllfunc') +def_file = configure_file( + input: 'somedll.def.in', + output: 'somedll.def', + configuration : conf, +) + +shlib = shared_library('somedll', 'somedll.c', vs_module_defs : def_file) diff --git a/test cases/windows/10 vs module defs generated/subdir/somedll.c b/test cases/windows/10 vs module defs generated/subdir/somedll.c new file mode 100644 index 0000000..df255e3 --- /dev/null +++ b/test cases/windows/10 vs module defs generated/subdir/somedll.c @@ -0,0 +1,5 @@ +#ifdef _MSC_VER +int somedllfunc() { + return 42; +} +#endif diff --git a/test cases/windows/10 vs module defs generated/subdir/somedll.def.in b/test cases/windows/10 vs module defs generated/subdir/somedll.def.in new file mode 100644 index 0000000..c29207c --- /dev/null +++ b/test cases/windows/10 vs module defs generated/subdir/somedll.def.in @@ -0,0 +1,2 @@ +EXPORTS + @func@ diff --git a/test cases/windows/5 resources/meson.build b/test cases/windows/5 resources/meson.build index e92a43c..ddb7d6e 100644 --- a/test cases/windows/5 resources/meson.build +++ b/test cases/windows/5 resources/meson.build @@ -3,7 +3,7 @@ project('winmain', 'c') # MinGW windres has a bug due to which it doesn't parse args with space properly: # https://github.com/mesonbuild/meson/pull/1346 # https://sourceware.org/bugzilla/show_bug.cgi?id=4933 -if meson.get_compiler('c').get_id() == 'gcc' +if meson.get_compiler('c').get_id() == 'gcc' and host_machine.system() == 'windows' # Construct build_to_src and skip this test if it has spaces # because then the -I flag to windres will also have spaces # and we know the test will fail diff --git a/test cases/windows/7 mingw dll versioning/installed_files.txt b/test cases/windows/7 mingw dll versioning/installed_files.txt index ebad9e4..26e14a7 100644 --- a/test cases/windows/7 mingw dll versioning/installed_files.txt +++ b/test cases/windows/7 mingw dll versioning/installed_files.txt @@ -1,8 +1,11 @@ -usr/bin/libsome-0.dll +usr/bin/?libsome-0.dll usr/lib/libsome.dll.a -usr/bin/libnoversion.dll +usr/bin/?libnoversion.dll usr/lib/libnoversion.dll.a -usr/bin/libonlyversion-1.dll +usr/bin/?libonlyversion-1.dll usr/lib/libonlyversion.dll.a -usr/bin/libonlysoversion-5.dll +usr/bin/?libonlysoversion-5.dll usr/lib/libonlysoversion.dll.a +usr/libexec/?libcustomdir.dll +usr/libexec/libcustomdir.dll.a +usr/lib/?libmodule.dll diff --git a/test cases/windows/7 mingw dll versioning/meson.build b/test cases/windows/7 mingw dll versioning/meson.build index d1fe73a..1d6562c 100644 --- a/test cases/windows/7 mingw dll versioning/meson.build +++ b/test cases/windows/7 mingw dll versioning/meson.build @@ -47,3 +47,9 @@ test('manually linked 3', executable('manuallink3', out, test('manually linked 4', executable('manuallink4', out, link_args : ['-L.', '-lonlysoversion'])) + +shared_library('customdir', 'lib.c', + install : true, + install_dir : get_option('libexecdir')) + +shared_module('module', 'lib.c', install : true) diff --git a/test cases/windows/8 msvc dll versioning/installed_files.txt b/test cases/windows/8 msvc dll versioning/installed_files.txt index ae0fa1f..df43343 100644 --- a/test cases/windows/8 msvc dll versioning/installed_files.txt +++ b/test cases/windows/8 msvc dll versioning/installed_files.txt @@ -8,3 +8,6 @@ usr/bin/onlyversion-1.dll usr/lib/onlyversion.lib usr/bin/onlysoversion-5.dll usr/lib/onlysoversion.lib +usr/libexec/customdir.dll +usr/libexec/customdir.lib +usr/lib/module.dll diff --git a/test cases/windows/8 msvc dll versioning/meson.build b/test cases/windows/8 msvc dll versioning/meson.build index b72c5ec..4074747 100644 --- a/test cases/windows/8 msvc dll versioning/meson.build +++ b/test cases/windows/8 msvc dll versioning/meson.build @@ -48,3 +48,9 @@ test('manually linked 3', executable('manuallink3', out, test('manually linked 4', executable('manuallink4', out, link_args : ['-L.', '-lonlysoversion'])) + +shared_library('customdir', 'lib.c', + install : true, + install_dir : get_option('libexecdir')) + +shared_module('module', 'lib.c', install : true) |