diff options
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 &quot; whereas MSVS - # requires quoted but not fixed elements. Enter horrible hack. - with open(ofname, 'r') as of: - txt = of.read() - with open(ofname, 'w') as of: - of.write(txt.replace('&quot;', '"')) + self._prettyprint_vcxproj_xml(ET.ElementTree(root), ofname) def gen_regenproj(self, project_name, ofname): root = ET.Element('Project', {'DefaultTargets': 'Build', @@ -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) |