aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--.appveyor.yml18
-rw-r--r--authors.txt4
-rw-r--r--mesonbuild/backend/backends.py23
-rw-r--r--mesonbuild/backend/ninjabackend.py140
-rw-r--r--mesonbuild/backend/vs2010backend.py42
-rw-r--r--mesonbuild/build.py91
-rw-r--r--mesonbuild/dependencies.py318
-rw-r--r--mesonbuild/environment.py4
-rw-r--r--mesonbuild/interpreter.py4
-rw-r--r--mesonbuild/mesonlib.py40
-rw-r--r--mesonbuild/mlog.py13
-rw-r--r--mesonbuild/modules/gnome.py16
-rw-r--r--mesonbuild/modules/pkgconfig.py5
-rw-r--r--mesonbuild/modules/qt4.py7
-rw-r--r--mesonbuild/modules/qt5.py7
-rw-r--r--mesonbuild/scripts/gtkdochelper.py2
-rwxr-xr-xmesontest.py22
-rwxr-xr-xrun_project_tests.py172
-rwxr-xr-xrun_tests.py128
-rwxr-xr-xrun_unittests.py180
-rwxr-xr-xtest cases/common/139 custom target multiple outputs/generator.py14
-rw-r--r--test cases/common/139 custom target multiple outputs/installed_files.txt6
-rw-r--r--test cases/common/139 custom target multiple outputs/meson.build28
-rw-r--r--test cases/common/22 header in file list/prog.c2
-rw-r--r--test cases/common/64 custom header generator/meson.build2
-rw-r--r--test cases/common/64 custom header generator/somefile.txt0
-rwxr-xr-xtest cases/failing/43 custom target outputs not matching install_dirs/generator.py16
-rw-r--r--test cases/failing/43 custom target outputs not matching install_dirs/installed_files.txt6
-rw-r--r--test cases/failing/43 custom target outputs not matching install_dirs/meson.build13
-rw-r--r--test cases/frameworks/4 qt/meson.build14
-rw-r--r--test cases/frameworks/4 qt/meson_options.txt1
-rw-r--r--test cases/linuxlike/7 library versions/installed_files.txt1
-rw-r--r--test cases/linuxlike/7 library versions/meson.build2
-rw-r--r--test cases/osx/2 library versions/installed_files.txt1
-rw-r--r--test cases/osx/2 library versions/meson.build2
-rw-r--r--test cases/vala/7 shared library/installed_files.txt8
-rw-r--r--test cases/vala/7 shared library/lib/meson.build26
-rw-r--r--test cases/vala/9 gir/installed_files.txt2
-rw-r--r--test cases/vala/9 gir/meson.build2
-rw-r--r--test cases/windows/10 vs module defs generated/meson.build7
-rw-r--r--test cases/windows/10 vs module defs generated/prog.c5
-rw-r--r--test cases/windows/10 vs module defs generated/subdir/meson.build9
-rw-r--r--test cases/windows/10 vs module defs generated/subdir/somedll.c5
-rw-r--r--test cases/windows/10 vs module defs generated/subdir/somedll.def.in2
-rw-r--r--test cases/windows/7 mingw dll versioning/installed_files.txt3
-rw-r--r--test cases/windows/7 mingw dll versioning/meson.build6
-rw-r--r--test cases/windows/8 msvc dll versioning/installed_files.txt3
-rw-r--r--test cases/windows/8 msvc dll versioning/meson.build6
48 files changed, 1013 insertions, 415 deletions
diff --git a/.appveyor.yml b/.appveyor.yml
index c79e250..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,14 +42,6 @@ environment:
backend: vs2017
APPVEYOR_BUILD_WORKER_IMAGE: Visual Studio 2017
- - arch: x64
- compiler: msys2-mingw
- backend: ninja
-
- - arch: x64
- compiler: cygwin
- backend: ninja
-
platform:
- x64
diff --git a/authors.txt b/authors.txt
index 50c032b..9b2ea72 100644
--- a/authors.txt
+++ b/authors.txt
@@ -77,3 +77,7 @@ Philipp Ittershagen
Dylan Baker
Aaron Plattner
Jon Turney
+Wade Berrier
+Richard Hughes
+Rafael Fontenelle
+Michael Olbrich
diff --git a/mesonbuild/backend/backends.py b/mesonbuild/backend/backends.py
index efc5bff..a77047b 100644
--- a/mesonbuild/backend/backends.py
+++ b/mesonbuild/backend/backends.py
@@ -23,6 +23,7 @@ import subprocess
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
class CleanTrees:
'''
@@ -574,7 +575,7 @@ class Backend:
return newargs
def get_build_by_default_targets(self):
- result = {}
+ result = OrderedDict()
# Get all build and custom targets that must be built by default
for name, t in self.build.get_targets().items():
if t.build_by_default or t.install or t.build_always:
@@ -601,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
@@ -631,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?
@@ -642,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
diff --git a/mesonbuild/backend/ninjabackend.py b/mesonbuild/backend/ninjabackend.py
index 4bfddc2..dc05d34 100644
--- a/mesonbuild/backend/ninjabackend.py
+++ b/mesonbuild/backend/ninjabackend.py
@@ -166,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.')
@@ -468,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')
@@ -476,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
@@ -633,40 +635,88 @@ int dummy;
def generate_target_install(self, d):
for t in self.build.get_targets().values():
- if t.should_install():
+ 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)
- # 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
+ # 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):
@@ -1032,10 +1082,21 @@ 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)
+ 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():
@@ -2299,7 +2360,10 @@ rule FORTRAN_DEP_HACK
cmds = []
for (k, v) in self.environment.coredata.user_options.items():
cmds.append('-D' + k + '=' + (v.value if isinstance(v.value, str) else str(v.value).lower()))
- return cmds
+ # The order of these arguments must be the same between runs of Meson
+ # to ensure reproducible output. The order we pass them shouldn't
+ # affect behaviour in any other way.
+ return sorted(cmds)
# For things like scan-build and other helper tools we might have.
def generate_utils(self, outfile):
diff --git a/mesonbuild/backend/vs2010backend.py b/mesonbuild/backend/vs2010backend.py
index b50b91c..9937521 100644
--- a/mesonbuild/backend/vs2010backend.py
+++ b/mesonbuild/backend/vs2010backend.py
@@ -420,14 +420,16 @@ class Vs2010Backend(backends.Backend):
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)
@@ -437,6 +439,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))
@@ -448,11 +451,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):
@@ -578,6 +580,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'
@@ -1024,19 +1033,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 " 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('"', '"'))
+ self._prettyprint_vcxproj_xml(ET.ElementTree(root), ofname)
def gen_regenproj(self, project_name, ofname):
root = ET.Element('Project', {'DefaultTargets': 'Build',
@@ -1115,8 +1112,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
@@ -1190,8 +1186,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 f379a68..6815b0c 100644
--- a/mesonbuild/build.py
+++ b/mesonbuild/build.py
@@ -19,7 +19,7 @@ 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, for_cygwin
from .compilers import is_object, clike_langs, sort_clike, lang_suffixes
@@ -84,9 +84,9 @@ class Build:
self.project_version = None
self.environment = environment
self.projects = {}
- self.targets = {}
- self.compilers = {}
- self.cross_compilers = {}
+ self.targets = OrderedDict()
+ self.compilers = OrderedDict()
+ self.cross_compilers = OrderedDict()
self.global_args = {}
self.projects_args = {}
self.global_link_args = {}
@@ -320,6 +320,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 = {}
@@ -344,6 +347,9 @@ class BuildTarget(Target):
self.validate_sources()
self.validate_cross_install(environment)
+ def __lt__(self, other):
+ return self.get_id() < other.get_id()
+
def __repr__(self):
repr_str = "<{0} {1}: {2}>"
return repr_str.format(self.__class__.__name__, self.get_id(), self.filename)
@@ -546,7 +552,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)
@@ -602,7 +608,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', [])
@@ -628,10 +634,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')
@@ -702,7 +708,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, [])
@@ -1032,6 +1038,7 @@ class Executable(BuildTarget):
self.filename = self.name
if self.suffix:
self.filename += '.' + self.suffix
+ self.outputs = [self.filename]
def type_suffix(self):
return "@exe"
@@ -1059,6 +1066,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"
@@ -1187,6 +1195,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)
@@ -1212,12 +1221,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)
@@ -1280,6 +1299,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,
@@ -1314,6 +1334,9 @@ class CustomTarget(Target):
mlog.warning('Unknown keyword arguments in target %s: %s' %
(self.name, ', '.join(unknowns)))
+ def __lt__(self, other):
+ return self.get_id() < other.get_id()
+
def __repr__(self):
repr_str = "<{0} {1}: {2}>"
return repr_str.format(self.__class__.__name__, self.get_id(), self.command)
@@ -1338,12 +1361,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)
@@ -1361,13 +1388,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:
@@ -1382,9 +1409,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".')
@@ -1406,12 +1433,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.')
@@ -1444,10 +1473,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
@@ -1474,6 +1503,9 @@ class RunTarget(Target):
self.args = args
self.dependencies = dependencies
+ def __lt__(self, other):
+ return self.get_id() < other.get_id()
+
def __repr__(self):
repr_str = "<{0} {1}: {2}>"
return repr_str.format(self.__class__.__name__, self.get_id(), self.command)
@@ -1506,6 +1538,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/dependencies.py b/mesonbuild/dependencies.py
index 7f22ae6..39bf2b1 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')
@@ -638,13 +675,38 @@ class BoostDependency(Dependency):
def get_compile_args(self):
args = []
+ include_dir = ''
if self.boost_root is not None:
if mesonlib.is_windows():
- args.append('-I' + self.boost_root)
+ include_dir = self.boost_root
else:
- args.append('-I' + os.path.join(self.boost_root, 'include'))
+ include_dir = os.path.join(self.boost_root, 'include')
else:
- args.append('-I' + self.incdir)
+ include_dir = self.incdir
+
+ # Use "-isystem" when including boost headers instead of "-I"
+ # to avoid compiler warnings/failures when "-Werror" is used
+
+ # Careful not to use "-isystem" on default include dirs as it
+ # breaks some of the headers for certain gcc versions
+
+ # For example, doing g++ -isystem /usr/include on a simple
+ # "int main()" source results in the error:
+ # "/usr/include/c++/6.3.1/cstdlib:75:25: fatal error: stdlib.h: No such file or directory"
+
+ # See https://gcc.gnu.org/bugzilla/show_bug.cgi?id=70129
+ # and http://stackoverflow.com/questions/37218953/isystem-on-a-system-include-directory-causes-errors
+ # for more details
+
+ # TODO: The correct solution would probably be to ask the
+ # compiler for it's default include paths (ie: "gcc -xc++ -E
+ # -v -") and avoid including those with -isystem
+
+ # For now, use -isystem for all includes except for some
+ # typical defaults (which don't need to be included at all
+ # since they are in the default include paths)
+ if include_dir != '/usr/include' and include_dir != '/usr/local/include':
+ args.append("".join(self.cpp_compiler.get_include_args(include_dir, True)))
return args
def get_requested(self, kwargs):
@@ -727,7 +789,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 +822,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 +861,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 +938,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 +993,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 +1020,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 +1115,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 +1155,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 +1176,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 +1216,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 +1314,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 +1337,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 +1371,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 +1435,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 +1487,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 +1500,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,6 +1574,14 @@ 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
@@ -1512,6 +1614,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)
@@ -1527,7 +1631,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)
diff --git a/mesonbuild/environment.py b/mesonbuild/environment.py
index 7861612..93a41e8 100644
--- a/mesonbuild/environment.py
+++ b/mesonbuild/environment.py
@@ -741,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:
diff --git a/mesonbuild/interpreter.py b/mesonbuild/interpreter.py
index 8c8000c..18f91ea 100644
--- a/mesonbuild/interpreter.py
+++ b/mesonbuild/interpreter.py
@@ -1109,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))
@@ -2243,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:
diff --git a/mesonbuild/mesonlib.py b/mesonbuild/mesonlib.py
index a2ab484..ae2f88c 100644
--- a/mesonbuild/mesonlib.py
+++ b/mesonbuild/mesonlib.py
@@ -16,6 +16,7 @@
import stat
import platform, subprocess, operator, os, shutil, re
+import collections
from glob import glob
@@ -467,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 = []
@@ -507,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)
@@ -687,3 +686,18 @@ def get_filenames_templates_dict(inputs, outputs):
if values['@OUTDIR@'] == '':
values['@OUTDIR@'] = '.'
return values
+
+class OrderedSet(collections.OrderedDict):
+ '''
+ A 'set' equivalent that preserves the order in which items are added.
+
+ This is a hack implementation that wraps OrderedDict. It may not be the
+ most efficient solution and might need fixing to override more methods.
+ '''
+ def __init__(self, iterable=None):
+ if iterable:
+ self.update(iterable)
+
+ def update(self, iterable):
+ for item in iterable:
+ self[item] = True
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 4b366bf..6921472 100644
--- a/mesonbuild/modules/gnome.py
+++ b/mesonbuild/modules/gnome.py
@@ -21,7 +21,7 @@ import sys
import copy
import subprocess
from . import ModuleReturnValue
-from ..mesonlib import MesonException, Popen_safe
+from ..mesonlib import MesonException, OrderedSet, Popen_safe
from ..dependencies import Dependency, PkgConfigDependency, InternalDependency
from .. import mlog
from .. import mesonlib
@@ -154,7 +154,7 @@ class GnomeModule(ExtensionModule):
# Ensure build directories of generated deps are included
source_dirs += subdirs
- for source_dir in set(source_dirs):
+ for source_dir in OrderedSet(source_dirs):
cmd += ['--sourcedir', source_dir]
if 'c_name' in kwargs:
@@ -299,9 +299,9 @@ class GnomeModule(ExtensionModule):
def _get_dependencies_flags(self, deps, state, depends=None, include_rpath=False,
use_gir_args=False):
- cflags = set()
- ldflags = set()
- gi_includes = set()
+ cflags = OrderedSet()
+ ldflags = OrderedSet()
+ gi_includes = OrderedSet()
if not isinstance(deps, list):
deps = [deps]
@@ -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/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/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/mesontest.py b/mesontest.py
index c4d1178..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
@@ -282,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:
@@ -574,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 dc05524..bcb375d 100755
--- a/run_project_tests.py
+++ b/run_project_tests.py
@@ -26,6 +26,7 @@ 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
@@ -33,7 +34,7 @@ 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):
@@ -42,6 +43,7 @@ class BuildStep(Enum):
test = 3
install = 4
clean = 5
+ validate = 6
class TestResult:
@@ -153,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 = []
-
-backend_flags = None
-compile_commands = None
-test_commands = None
-install_commands = []
-clean_commands = []
+# Needed when running cross tests because we don't generate prebuilt files
+compiler = None
-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']
- 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]]
+ backend = Backend.xcode
+ elif backend == 'ninja':
+ backend_flags = ['--backend=ninja']
+ backend = Backend.ninja
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 = []
@@ -223,7 +211,7 @@ def platform_fix_name(fname):
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
@@ -317,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
@@ -349,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
@@ -378,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, '*'))]
@@ -421,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))
@@ -450,15 +422,15 @@ def detect_tests_to_run():
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() and not mesonlib.is_cygwin() 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(('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):
@@ -473,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.
@@ -502,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()
@@ -617,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...')
@@ -626,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...')
@@ -649,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 1b24d08..7bdd57b 100755
--- a/run_unittests.py
+++ b/run_unittests.py
@@ -25,13 +25,12 @@ import mesonbuild.compilers
import mesonbuild.environment
import mesonbuild.mesonlib
from mesonbuild.mesonlib import is_windows, is_osx, is_cygwin
-from mesonbuild.environment import detect_ninja, Environment
+from mesonbuild.environment import Environment
from mesonbuild.dependencies import PkgConfigDependency, ExternalProgram
-if is_windows() or is_cygwin():
- 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):
@@ -964,6 +1017,46 @@ class AllPlatformTests(BasePlatformTests):
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):
'''
@@ -1121,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'
@@ -1144,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'
@@ -1231,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
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/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/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/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/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/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 451e42e..d156eb0 100644
--- a/test cases/linuxlike/7 library versions/meson.build
+++ b/test cases/linuxlike/7 library versions/meson.build
@@ -47,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/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/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/7 mingw dll versioning/installed_files.txt b/test cases/windows/7 mingw dll versioning/installed_files.txt
index 661005c..26e14a7 100644
--- a/test cases/windows/7 mingw dll versioning/installed_files.txt
+++ b/test cases/windows/7 mingw dll versioning/installed_files.txt
@@ -6,3 +6,6 @@ usr/bin/?libonlyversion-1.dll
usr/lib/libonlyversion.dll.a
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)