diff options
46 files changed, 973 insertions, 342 deletions
diff --git a/.appveyor.yml b/.appveyor.yml index 60a6fd3..d150c42 100644 --- a/.appveyor.yml +++ b/.appveyor.yml @@ -19,10 +19,12 @@ environment: - arch: x86 compiler: msvc2015 backend: ninja + BOOST_ROOT: C:\Libraries\Boost_1_59_0 - arch: x86 compiler: msvc2015 backend: vs2015 + BOOST_ROOT: C:\Libraries\Boost_1_59_0 - arch: x64 compiler: cygwin @@ -36,11 +38,13 @@ environment: compiler: msvc2017 backend: ninja APPVEYOR_BUILD_WORKER_IMAGE: Visual Studio 2017 + BOOST_ROOT: C:\Libraries\Boost_1_64_0 - arch: x64 compiler: msvc2017 backend: vs2017 APPVEYOR_BUILD_WORKER_IMAGE: Visual Studio 2017 + BOOST_ROOT: C:\Libraries\Boost_1_64_0 platform: - x64 @@ -59,8 +63,6 @@ init: install: - cmd: set "ORIG_PATH=%PATH%" - # Boost 1.56.0: https://www.appveyor.com/docs/build-environment/#boost - #- cmd: set "BOOST_ROOT=C:\Libraries\boost" # Use a Ninja with QuLogic's patch: https://github.com/ninja-build/ninja/issues/1219 - cmd: set "MESON_FIXED_NINJA=1" - ps: (new-object net.webclient).DownloadFile('http://nirbheek.in/files/binaries/ninja/win32/ninja.exe', 'C:\projects\meson\ninja.exe') @@ -68,6 +70,10 @@ install: # For all other archs (including, say, arm), use the x64 python. - cmd: if %arch%==x86 (set MESON_PYTHON_PATH=C:\python34) else (set MESON_PYTHON_PATH=C:\python34-x64) + # Set paths for BOOST dll files + - cmd: if %compiler%==msvc2015 ( if %arch%==x86 ( set "PATH=%PATH%;C:\Libraries\boost_1_59_0\lib32-msvc-14.0" ) else ( set "PATH=%PATH%;C:\Libraries\boost_1_59_0\lib64-msvc-14.0" ) ) + - cmd: if %compiler%==msvc2017 ( if %arch%==x86 ( set "PATH=%PATH%;C:\Libraries\boost_1_64_0\lib32-msvc-14.1" ) else ( set "PATH=%PATH%;C:\Libraries\boost_1_64_0\lib64-msvc-14.1" ) ) + # Set paths and config for each build type. - cmd: if %compiler%==msvc2010 ( call "C:\Program Files (x86)\Microsoft Visual Studio 10.0\VC\vcvarsall.bat" %arch% ) - cmd: if %compiler%==msvc2015 ( call "C:\Program Files (x86)\Microsoft Visual Studio 14.0\VC\vcvarsall.bat" %arch% ) diff --git a/docs/markdown/Dependencies.md b/docs/markdown/Dependencies.md index 187c4fe..c46334c 100644 --- a/docs/markdown/Dependencies.md +++ b/docs/markdown/Dependencies.md @@ -50,8 +50,16 @@ pkg-config files. Meson has autodetection support for some of these. ## Boost ## Boost is not a single dependency but rather a group of different -libraries. To use Boost with Meson, simply list which Boost modules -you would like to use. +libraries. To use Boost headers-only libraries, simply add Boost as a +dependency. + +```meson +boost_dep = dependency('boost') +exe = executable('myprog', 'file.cc', dependencies : boost_dep) +``` + +To link against boost with Meson, simply list which libraries you would like to +use. ```meson boost_dep = dependency('boost', modules : ['thread', 'utility']) @@ -65,6 +73,9 @@ If your boost headers or libraries are in non-standard locations you can set the BOOST_ROOT, BOOST_INCLUDEDIR, and/or BOOST_LIBRARYDIR environment variables. +You can set the argument `threading` to `single` to use boost libraries that +has been compiled for single-threaded use instead. + ## GTest and GMock ## GTest and GMock come as sources that must be compiled as part of your diff --git a/docs/markdown/Gnome-module.md b/docs/markdown/Gnome-module.md index 99a9c8e..d87e108 100644 --- a/docs/markdown/Gnome-module.md +++ b/docs/markdown/Gnome-module.md @@ -81,6 +81,10 @@ tool so see its documentation for more information. * `includes`: list of gir names to be included, can also be a GirTarget +* `header`: *(Added 0.43.0)* name of main c header to include for the library, e.g. `glib.h` + +* `dependencies`: deps to use during introspection scanning + * `include_directories`: extra include paths to look for gir files * `install`: if true, install the generated files diff --git a/docs/markdown/Qt5-module.md b/docs/markdown/Qt5-module.md index 7082309..a8ad73d 100644 --- a/docs/markdown/Qt5-module.md +++ b/docs/markdown/Qt5-module.md @@ -5,17 +5,19 @@ tools and steps required for Qt. The module has one method. ## preprocess -This method takes four keyword arguments, `moc_headers`, +This method takes five keyword arguments, `moc_headers`, `moc_sources`, `ui_files` and `qresources` which define the files that -require preprocessing with `moc`, `uic` and `rcc`. It returns an +require preprocessing with `moc`, `uic` and `rcc` and 'include_directories' which might be needed by moc. It returns an opaque object that should be passed to a main build target. A simple example would look like this: ```meson qt5 = import('qt5') qt5_dep = dependency('qt5', modules: ['Core', 'Gui']) -moc_files = qt5.preprocess(moc_headers : 'myclass.h') +inc = include_directories('includes') +moc_files = qt5.preprocess(moc_headers : 'myclass.h', include_directories: inc) executable('myprog', 'main.cpp', 'myclass.cpp', moc_files, + include_directories: inc, dependencies : qt5_dep) ``` diff --git a/docs/markdown/Reference-manual.md b/docs/markdown/Reference-manual.md index f37fb34..3bdd3c5 100644 --- a/docs/markdown/Reference-manual.md +++ b/docs/markdown/Reference-manual.md @@ -1614,7 +1614,7 @@ during tests. It should be passed as the `env` keyword argument to tests. It has the following methods. - `append(varname, value)` appends the given value to the old value of - the environment variable, e.g. `env.append'('FOO', 'BAR', separator + the environment variable, e.g. `env.append('FOO', 'BAR', separator : ';')` produces `BOB;BAR` if `FOO` had the value `BOB` and plain `BAR` if the value was not defined. If the separator is not specified explicitly, the default path separator for the host diff --git a/docs/markdown/Release-notes-for-0.43.0/001-boost-platform-improvement.md b/docs/markdown/Release-notes-for-0.43.0/001-boost-platform-improvement.md new file mode 100644 index 0000000..2cd44ce --- /dev/null +++ b/docs/markdown/Release-notes-for-0.43.0/001-boost-platform-improvement.md @@ -0,0 +1,10 @@ +## Portability improvements to Boost Dependency + +The Boost dependency has been improved to better detect the various ways to +install boost on multiple platforms. At the same time the `modules` semantics +for the dependency has been changed. Previously it was allowed to specify +header directories as `modules` but it wasn't required. Now, modules are only +used to specify libraries that require linking. + +This is a breaking change and the fix is to remove all modules that aren't +found. diff --git a/docs/markdown/snippets/wrap-svn.md b/docs/markdown/snippets/wrap-svn.md new file mode 100644 index 0000000..cdf155b --- /dev/null +++ b/docs/markdown/snippets/wrap-svn.md @@ -0,0 +1,4 @@ +# wrap-svn + +The [Wrap dependency system](Wrap-dependency-system-manual.md) now supports [Subversion](https://subversion.apache.org/) (svn). +This support is rudimentary. The repository url has to point to a specific (sub)directory containing the `meson.build` file (typically `trunk/`). However, providing a `revision` is supported. diff --git a/manual tests/10 svn wrap/meson.build b/manual tests/10 svn wrap/meson.build new file mode 100644 index 0000000..23ef1f1 --- /dev/null +++ b/manual tests/10 svn wrap/meson.build @@ -0,0 +1,10 @@ +project('Subversion outchecker', 'c') + +sp = subproject('samplesubproject') + +exe = executable('gitprog', 'prog.c', +include_directories : sp.get_variable('subproj_inc'), +link_with : sp.get_variable('subproj_lib'), +) + +test('maintest', exe) diff --git a/manual tests/10 svn wrap/prog.c b/manual tests/10 svn wrap/prog.c new file mode 100644 index 0000000..df38000 --- /dev/null +++ b/manual tests/10 svn wrap/prog.c @@ -0,0 +1,6 @@ +#include"subproj.h" + +int main(int argc, char **argv) { + subproj_function(); + return 0; +} diff --git a/manual tests/10 svn wrap/subprojects/samplesubproject.wrap b/manual tests/10 svn wrap/subprojects/samplesubproject.wrap new file mode 100644 index 0000000..c8a687e --- /dev/null +++ b/manual tests/10 svn wrap/subprojects/samplesubproject.wrap @@ -0,0 +1,4 @@ +[wrap-svn] +directory=samplesubproject +url=https://svn.code.sf.net/p/mesonsubproject/code/trunk +revision=head diff --git a/mesonbuild/backend/backends.py b/mesonbuild/backend/backends.py index e0e2abc..61f7535 100644 --- a/mesonbuild/backend/backends.py +++ b/mesonbuild/backend/backends.py @@ -155,9 +155,18 @@ class Backend: dirname = 'meson-out' return dirname + def get_target_dir_relative_to(self, t, o): + '''Get a target dir relative to another target's directory''' + target_dir = os.path.join(self.environment.get_build_dir(), self.get_target_dir(t)) + othert_dir = os.path.join(self.environment.get_build_dir(), self.get_target_dir(o)) + return os.path.relpath(target_dir, othert_dir) + def get_target_source_dir(self, target): - dirname = os.path.join(self.build_to_src, self.get_target_dir(target)) - return dirname + # if target dir is empty, avoid extraneous trailing / from os.path.join() + target_dir = self.get_target_dir(target) + if target_dir: + return os.path.join(self.build_to_src, target_dir) + return self.build_to_src def get_target_private_dir(self, target): dirname = os.path.join(self.get_target_dir(target), target.get_basename() + target.type_suffix()) diff --git a/mesonbuild/backend/vs2010backend.py b/mesonbuild/backend/vs2010backend.py index 2d18b0e..e4e9696 100644 --- a/mesonbuild/backend/vs2010backend.py +++ b/mesonbuild/backend/vs2010backend.py @@ -344,6 +344,11 @@ class Vs2010Backend(backends.Backend): def quote_arguments(self, arr): return ['"%s"' % i for i in arr] + def add_project_reference(self, root, include, projid): + ig = ET.SubElement(root, 'ItemGroup') + pref = ET.SubElement(ig, 'ProjectReference', Include=include) + ET.SubElement(pref, 'Project').text = '{%s}' % projid + def create_basic_crap(self, target): project_name = target.name root = ET.Element('Project', {'DefaultTargets': "Build", @@ -537,6 +542,8 @@ class Vs2010Backend(backends.Backend): if lpath in lpaths: lpaths.remove(lpath) lpaths.append(lpath) + elif arg.startswith(('/', '-')): + other.append(arg) # It's ok if we miss libraries with non-standard extensions here. # They will go into the general link arguments. elif arg.endswith('.lib') or arg.endswith('.a'): @@ -907,20 +914,26 @@ class Vs2010Backend(backends.Backend): # *_winlibs that we want to link to are static mingw64 libraries. extra_link_args += compiler.get_option_link_args(self.environment.coredata.compiler_options) (additional_libpaths, additional_links, extra_link_args) = self.split_link_args(extra_link_args.to_native()) - if len(extra_link_args) > 0: - extra_link_args.append('%(AdditionalOptions)') - ET.SubElement(link, "AdditionalOptions").text = ' '.join(extra_link_args) - if len(additional_libpaths) > 0: - additional_libpaths.insert(0, '%(AdditionalLibraryDirectories)') - ET.SubElement(link, 'AdditionalLibraryDirectories').text = ';'.join(additional_libpaths) # Add more libraries to be linked if needed for t in target.get_dependencies(): lobj = self.build.targets[t.get_id()] linkname = os.path.join(down, self.get_target_filename_for_linking(lobj)) if t in target.link_whole_targets: - linkname = compiler.get_link_whole_for(linkname)[0] - additional_links.append(linkname) + # /WHOLEARCHIVE:foo must go into AdditionalOptions + extra_link_args += compiler.get_link_whole_for(linkname) + # To force Visual Studio to build this project even though it + # has no sources, we include a reference to the vcxproj file + # that builds this target. Technically we should add this only + # if the current target has no sources, but it doesn't hurt to + # have 'extra' references. + trelpath = self.get_target_dir_relative_to(t, target) + tvcxproj = os.path.join(trelpath, t.get_id() + '.vcxproj') + tid = self.environment.coredata.target_guids[t.get_id()] + self.add_project_reference(root, tvcxproj, tid) + else: + # Other libraries go into AdditionalDependencies + additional_links.append(linkname) for lib in self.get_custom_target_provided_libraries(target): additional_links.append(self.relpath(lib, self.get_target_dir(target))) additional_objects = [] @@ -929,6 +942,13 @@ class Vs2010Backend(backends.Backend): additional_objects.append(o) for o in custom_objs: additional_objects.append(o) + + if len(extra_link_args) > 0: + extra_link_args.append('%(AdditionalOptions)') + ET.SubElement(link, "AdditionalOptions").text = ' '.join(extra_link_args) + if len(additional_libpaths) > 0: + additional_libpaths.insert(0, '%(AdditionalLibraryDirectories)') + ET.SubElement(link, 'AdditionalLibraryDirectories').text = ';'.join(additional_libpaths) if len(additional_links) > 0: additional_links.append('%(AdditionalDependencies)') ET.SubElement(link, 'AdditionalDependencies').text = ';'.join(additional_links) @@ -1018,9 +1038,8 @@ class Vs2010Backend(backends.Backend): ET.SubElement(root, 'Import', Project='$(VCTargetsPath)\Microsoft.Cpp.targets') # Reference the regen target. - 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 + regen_vcxproj = os.path.join(self.environment.get_build_dir(), 'REGEN.vcxproj') + self.add_project_reference(root, regen_vcxproj, self.environment.coredata.regen_guid) self._prettyprint_vcxproj_xml(ET.ElementTree(root), ofname) def gen_regenproj(self, project_name, ofname): diff --git a/mesonbuild/build.py b/mesonbuild/build.py index 8c02d1d..9837d5a 100644 --- a/mesonbuild/build.py +++ b/mesonbuild/build.py @@ -20,7 +20,7 @@ from . import environment from . import dependencies from . import mlog from .mesonlib import File, MesonException, listify, extract_as_list -from .mesonlib import flatten, typeslistify, stringlistify, classify_unity_sources +from .mesonlib import 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 @@ -357,7 +357,7 @@ class BuildTarget(Target): self.process_compilers() self.process_kwargs(kwargs, environment) self.check_unknown_kwargs(kwargs) - if not self.sources and not self.generated and not self.objects: + if not any([self.sources, self.generated, self.objects, self.link_whole]): raise InvalidArguments('Build target %s has no sources.' % name) self.process_compilers_late() self.validate_sources() @@ -682,7 +682,7 @@ class BuildTarget(Target): if 'd' in self.compilers: self.add_compiler_args('d', self.compilers['d'].get_feature_args(dfeatures)) - self.link_args = flatten(kwargs.get('link_args', [])) + self.link_args = extract_as_list(kwargs, 'link_args') for i in self.link_args: if not isinstance(i, str): raise InvalidArguments('Link_args arguments must be strings.') @@ -856,9 +856,7 @@ You probably should put it in link_with instead.''') return self.external_deps def link(self, target): - for t in flatten(target): - if hasattr(t, 'held_object'): - t = t.held_object + for t in listify(target, unholder=True): if not t.is_linkable_target(): raise InvalidArguments('Link target {!r} is not linkable.'.format(t)) if isinstance(self, SharedLibrary) and isinstance(t, StaticLibrary) and not t.pic: @@ -870,9 +868,7 @@ You probably should put it in link_with instead.''') self.link_targets.append(t) def link_whole(self, target): - for t in flatten(target): - if hasattr(t, 'held_object'): - t = t.held_object + for t in listify(target, unholder=True): if not isinstance(t, StaticLibrary): raise InvalidArguments('{!r} is not a static library.'.format(t)) if isinstance(self, SharedLibrary) and not t.pic: @@ -915,7 +911,7 @@ You probably should put it in link_with instead.''') self.include_dirs += ids def add_compiler_args(self, language, args): - args = flatten(args) + args = listify(args) for a in args: if not isinstance(a, (str, File)): raise InvalidArguments('A non-string passed to compiler args.') @@ -1546,11 +1542,9 @@ class CustomTarget(Target): return deps def flatten_command(self, cmd): - cmd = listify(cmd) + cmd = listify(cmd, unholder=True) final_cmd = [] for c in cmd: - if hasattr(c, 'held_object'): - c = c.held_object if isinstance(c, str): final_cmd.append(c) elif isinstance(c, File): @@ -1573,12 +1567,7 @@ class CustomTarget(Target): def process_kwargs(self, kwargs): super().process_kwargs(kwargs) - sources = flatten(kwargs.get('input', [])) - self.sources = [] - for s in sources: - if hasattr(s, 'held_object'): - s = s.held_object - self.sources.append(s) + self.sources = extract_as_list(kwargs, 'input', unholder=True) if 'output' not in kwargs: raise InvalidArguments('Missing keyword argument "output".') self.outputs = listify(kwargs['output']) diff --git a/mesonbuild/compilers/c.py b/mesonbuild/compilers/c.py index 82b1ef0..3f9ba5c 100644 --- a/mesonbuild/compilers/c.py +++ b/mesonbuild/compilers/c.py @@ -172,6 +172,9 @@ class CCompiler(Compiler): def get_linker_search_args(self, dirname): return ['-L' + dirname] + def get_default_include_dirs(self): + return [] + def gen_import_library_args(self, implibname): """ The name of the outputted import library @@ -1056,3 +1059,34 @@ class VisualStudioCCompiler(CCompiler): # and the can not be called. return None return vs32_instruction_set_args.get(instruction_set, None) + + def get_toolset_version(self): + # See boost/config/compiler/visualc.cpp for up to date mapping + try: + version = int(''.join(self.version.split('.')[0:2])) + except: + return None + if version < 1310: + return '7.0' + elif version < 1400: + return '7.1' # (Visual Studio 2003) + elif version < 1500: + return '8.0' # (Visual Studio 2005) + elif version < 1600: + return '9.0' # (Visual Studio 2008) + elif version < 1700: + return '10.0' # (Visual Studio 2010) + elif version < 1800: + return '11.0' # (Visual Studio 2012) + elif version < 1900: + return '12.0' # (Visual Studio 2013) + elif version < 1910: + return '14.0' # (Visual Studio 2015) + elif version < 1920: + return '14.1' # (Visual Studio 2017) + return None + + def get_default_include_dirs(self): + if 'INCLUDE' not in os.environ: + return [] + return os.environ['INCLUDE'].split(os.pathsep) diff --git a/mesonbuild/compilers/compilers.py b/mesonbuild/compilers/compilers.py index 89208e0..3f088b0 100644 --- a/mesonbuild/compilers/compilers.py +++ b/mesonbuild/compilers/compilers.py @@ -13,6 +13,7 @@ # limitations under the License. import contextlib, os.path, re, tempfile +import subprocess from ..linkers import StaticLinker from .. import coredata @@ -917,6 +918,35 @@ def get_largefile_args(compiler): # those features explicitly. return [] +# TODO: The result from calling compiler should be cached. So that calling this +# function multiple times don't add latency. +def gnulike_default_include_dirs(compiler, lang): + if lang == 'cpp': + lang = 'c++' + p = subprocess.Popen( + compiler + ['-x{}'.format(lang), '-E', '-v', '-'], + stdin=subprocess.DEVNULL, + stderr=subprocess.PIPE, + stdout=subprocess.PIPE + ) + stderr = p.stderr.read().decode('utf-8') + parse_state = 0 + paths = [] + for line in stderr.split('\n'): + if parse_state == 0: + if line == '#include "..." search starts here:': + parse_state = 1 + elif parse_state == 1: + if line == '#include <...> search starts here:': + parse_state = 2 + else: + paths.append(line[1:]) + elif parse_state == 2: + if line == 'End of search list.': + break + else: + paths.append(line[1:]) + return paths class GnuCompiler: # Functionality that is common to all GNU family compilers. @@ -998,6 +1028,9 @@ class GnuCompiler: def get_instruction_set_args(self, instruction_set): return gnulike_instruction_set_args.get(instruction_set, None) + def get_default_include_dirs(self): + return gnulike_default_include_dirs(self.exelist, self.language) + class ClangCompiler: def __init__(self, clang_type): @@ -1082,6 +1115,9 @@ class ClangCompiler: def get_instruction_set_args(self, instruction_set): return gnulike_instruction_set_args.get(instruction_set, None) + def get_default_include_dirs(self): + return gnulike_default_include_dirs(self.exelist, self.language) + # Tested on linux for ICC 14.0.3, 15.0.6, 16.0.4, 17.0.1 class IntelCompiler: @@ -1132,3 +1168,6 @@ class IntelCompiler: # if self.icc_type == ICC_OSX: # return ['-bundle'] return ['-shared'] + + def get_default_include_dirs(self): + return gnulike_default_include_dirs(self.exelist, self.language) diff --git a/mesonbuild/dependencies/base.py b/mesonbuild/dependencies/base.py index 7c7f986..0d9742d 100644 --- a/mesonbuild/dependencies/base.py +++ b/mesonbuild/dependencies/base.py @@ -23,7 +23,7 @@ from enum import Enum from .. import mlog from .. import mesonlib -from ..mesonlib import MesonException, Popen_safe, flatten, version_compare_many, listify +from ..mesonlib import MesonException, Popen_safe, version_compare_many, listify # These must be defined in this file to avoid cyclical references. @@ -586,7 +586,7 @@ class ExtraFrameworkDependency(ExternalDependency): def get_dep_identifier(name, kwargs, want_cross): # Need immutable objects since the identifier will be used as a dict key - version_reqs = flatten(kwargs.get('version', [])) + version_reqs = listify(kwargs.get('version', [])) if isinstance(version_reqs, list): version_reqs = frozenset(version_reqs) identifier = (name, version_reqs, want_cross) @@ -599,7 +599,7 @@ def get_dep_identifier(name, kwargs, want_cross): continue # All keyword arguments are strings, ints, or lists (or lists of lists) if isinstance(value, list): - value = frozenset(flatten(value)) + value = frozenset(listify(value)) identifier += (key, value) return identifier diff --git a/mesonbuild/dependencies/dev.py b/mesonbuild/dependencies/dev.py index d2dd107..387300a 100644 --- a/mesonbuild/dependencies/dev.py +++ b/mesonbuild/dependencies/dev.py @@ -21,7 +21,7 @@ import shutil from .. import mlog from .. import mesonlib -from ..mesonlib import version_compare, Popen_safe +from ..mesonlib import version_compare, Popen_safe, stringlistify, extract_as_list from .base import DependencyException, ExternalDependency, PkgConfigDependency class GTestDependency(ExternalDependency): @@ -185,7 +185,7 @@ class LLVMDependency(ExternalDependency): raise DependencyException('Could not generate modules for LLVM.') self.modules = shlex.split(out) - modules = mesonlib.stringlistify(mesonlib.flatten(kwargs.get('modules', []))) + modules = stringlistify(extract_as_list(kwargs, 'modules')) for mod in sorted(set(modules)): if mod not in self.modules: mlog.log('LLVM module', mod, 'found:', mlog.red('NO')) diff --git a/mesonbuild/dependencies/misc.py b/mesonbuild/dependencies/misc.py index c2b6dbd..a0d5e47 100644 --- a/mesonbuild/dependencies/misc.py +++ b/mesonbuild/dependencies/misc.py @@ -18,7 +18,6 @@ import glob import os import re import shlex -import stat import shutil import sysconfig @@ -30,77 +29,153 @@ from ..environment import detect_cpu_family from .base import DependencyException, DependencyMethods from .base import ExternalDependency, ExternalProgram, ExtraFrameworkDependency, PkgConfigDependency +# On windows 3 directory layouts are supported: +# * The default layout (versioned) installed: +# - $BOOST_ROOT/include/boost-x_x/boost/*.hpp +# - $BOOST_ROOT/lib/*.lib +# * The non-default layout (system) installed: +# - $BOOST_ROOT/include/boost/*.hpp +# - $BOOST_ROOT/lib/*.lib +# * The pre-built binaries from sf.net: +# - $BOOST_ROOT/boost/*.hpp +# - $BOOST_ROOT/lib<arch>-<compiler>/*.lib where arch=32/64 and compiler=msvc-14.1 +# +# Library names supported: +# - libboost_<module>-<compiler>-mt-gd-x_x.lib (static) +# - boost_<module>-<compiler>-mt-gd-x_x.lib|.dll (shared) +# - libboost_<module>.lib (static) +# - boost_<module>.lib|.dll (shared) +# where compiler is vc141 for example. +# +# NOTE: -gb means runtime and build time debugging is on +# -mt means threading=multi +# +# The `modules` argument accept library names. This is because every module that +# has libraries to link against also has multiple options regarding how to +# link. See for example: +# * http://www.boost.org/doc/libs/1_65_1/libs/test/doc/html/boost_test/usage_variants.html +# * http://www.boost.org/doc/libs/1_65_1/doc/html/stacktrace/configuration_and_build.html +# * http://www.boost.org/doc/libs/1_65_1/libs/math/doc/html/math_toolkit/main_tr1.html class BoostDependency(ExternalDependency): - # Some boost libraries have different names for - # their sources and libraries. This dict maps - # between the two. - name2lib = {'test': 'unit_test_framework'} - def __init__(self, environment, kwargs): super().__init__('boost', environment, 'cpp', kwargs) + self.need_static_link = ['boost_exception', 'boost_test_exec_monitor'] + self.is_debug = environment.cmd_line_options.buildtype.startswith('debug') + threading = kwargs.get("threading", "multi") + self.is_multithreading = threading == "multi" + + self.requested_modules = self.get_requested(kwargs) + + self.boost_root = None + self.boost_roots = [] + self.incdir = None self.libdir = None - try: + + if 'BOOST_ROOT' in os.environ: self.boost_root = os.environ['BOOST_ROOT'] + self.boost_roots = [self.boost_root] if not os.path.isabs(self.boost_root): raise DependencyException('BOOST_ROOT must be an absolute path.') - except KeyError: - self.boost_root = None + if 'BOOST_INCLUDEDIR' in os.environ: + self.incdir = os.environ['BOOST_INCLUDEDIR'] + if 'BOOST_LIBRARYDIR' in os.environ: + self.libdir = os.environ['BOOST_LIBRARYDIR'] + + if self.want_cross and self.boost_root is None and self.incdir is None: + raise DependencyException('BOOST_ROOT or BOOST_INCLUDEDIR is needed while cross-compiling') + if self.boost_root is None: - if self.want_cross: - 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 + self.boost_roots = self.detect_win_roots() else: - if 'BOOST_INCLUDEDIR' in os.environ: - self.incdir = os.environ['BOOST_INCLUDEDIR'] - else: - self.incdir = '/usr/include' + self.boost_roots = self.detect_nix_roots() + + if self.boost_root is None and not self.boost_roots: + self.log_fail() + return + + if self.incdir is None: + if mesonlib.is_windows(): + self.incdir = self.detect_win_incdir() + else: + self.incdir = self.detect_nix_incdir() + + if self.incdir is None: + self.log_fail() + return + + mlog.debug('Boost library root dir is', mlog.bold(self.boost_root)) + mlog.debug('Boost include directory is', mlog.bold(self.incdir)) - if 'BOOST_LIBRARYDIR' in os.environ: - self.libdir = os.environ['BOOST_LIBRARYDIR'] - else: - self.incdir = os.path.join(self.boost_root, 'include') - self.boost_inc_subdir = os.path.join(self.incdir, 'boost') - mlog.debug('Boost library root dir is', self.boost_root) - self.src_modules = {} self.lib_modules = {} - self.lib_modules_mt = {} self.detect_version() - self.requested_modules = self.get_requested(kwargs) - module_str = ', '.join(self.requested_modules) if self.is_found: - self.detect_src_modules() self.detect_lib_modules() + mlog.debug('Boost library directory is', mlog.bold(self.libdir)) self.validate_requested() - if self.boost_root is not None: - info = self.version + ', ' + self.boost_root - else: - info = self.version - mlog.log('Dependency Boost (%s) found:' % module_str, mlog.green('YES'), info) + self.log_success() + else: + self.log_fail() + + def log_fail(self): + module_str = ', '.join(self.requested_modules) + mlog.log("Dependency Boost (%s) found:" % module_str, mlog.red('NO')) + + def log_success(self): + module_str = ', '.join(self.requested_modules) + if self.boost_root: + info = self.version + ', ' + self.boost_root else: - mlog.log("Dependency Boost (%s) found:" % module_str, mlog.red('NO')) + info = self.version + mlog.log('Dependency Boost (%s) found:' % module_str, mlog.green('YES'), info) + + def detect_nix_roots(self): + return ['/usr/local', '/usr'] - def detect_win_root(self): - globtext = 'c:\\local\\boost_*' + def detect_win_roots(self): + res = [] + # Where boost documentation says it should be + globtext = 'C:\\Program Files\\boost\\boost_*' files = glob.glob(globtext) - if len(files) > 0: - return files[0] - return 'C:\\' + res.extend(files) + + # Where boost built from source actually installs it + if os.path.isdir('C:\\Boost'): + res.append('C:\\Boost') + + # Where boost prebuilt binaries are + globtext = 'C:\\local\\boost_*' + files = glob.glob(globtext) + res.extend(files) + return res + + def detect_nix_incdir(self): + for root in self.boost_roots: + incdir = os.path.join(root, 'include', 'boost') + if os.path.isdir(incdir): + return os.path.join(root, 'include') + return None + + # FIXME: Should pick a version that matches the requested version + # Returns the folder that contains the boost folder. + def detect_win_incdir(self): + for root in self.boost_roots: + globtext = os.path.join(root, 'include', 'boost-*') + incdirs = glob.glob(globtext) + if len(incdirs) > 0: + return incdirs[0] + incboostdir = os.path.join(root, 'include', 'boost') + if os.path.isdir(incboostdir): + return os.path.join(root, 'include') + incboostdir = os.path.join(root, 'boost') + if os.path.isdir(incboostdir): + return root + return None def get_compile_args(self): args = [] - if self.boost_root is not None: - if mesonlib.is_windows(): - include_dir = self.boost_root - else: - include_dir = os.path.join(self.boost_root, 'include') - else: - include_dir = self.incdir + include_dir = self.incdir # Use "-isystem" when including boost headers instead of "-I" # to avoid compiler warnings/failures when "-Werror" is used @@ -116,18 +191,7 @@ class BoostDependency(ExternalDependency): # 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). These typical - # defaults include the usual directories at the root of the - # filesystem, but also any path that ends with those directory - # names in order to handle cases like cross-compiling where we - # might have a different sysroot. - if not include_dir.endswith(('/usr/include', '/usr/local/include')): + if include_dir and include_dir not in self.compiler.get_default_include_dirs(): args.append("".join(self.compiler.get_include_args(include_dir, True))) return args @@ -136,19 +200,23 @@ class BoostDependency(ExternalDependency): for c in candidates: if not isinstance(c, str): raise DependencyException('Boost module argument is not a string.') + if 'boost_' + c not in BOOST_LIBS: + raise DependencyException('Dependency {} not found. It is not a valid boost library.'.format(c)) return candidates def validate_requested(self): for m in self.requested_modules: - if m not in self.src_modules and m not in self.lib_modules and m + '-mt' not in self.lib_modules_mt: - msg = 'Requested Boost module {!r} not found' + if 'boost_' + m not in self.lib_modules: + msg = 'Requested Boost library {!r} not found' raise DependencyException(msg.format(m)) def detect_version(self): try: - ifile = open(os.path.join(self.boost_inc_subdir, 'version.hpp')) + ifile = open(os.path.join(self.incdir, 'boost', 'version.hpp')) except FileNotFoundError: return + except TypeError: + return with ifile: for line in ifile: if line.startswith("#define") and 'BOOST_LIB_VERSION' in line: @@ -158,12 +226,6 @@ class BoostDependency(ExternalDependency): self.is_found = True return - def detect_src_modules(self): - for entry in os.listdir(self.boost_inc_subdir): - entry = os.path.join(self.boost_inc_subdir, entry) - if stat.S_ISDIR(os.stat(entry).st_mode): - self.src_modules[os.path.split(entry)[-1]] = True - def detect_lib_modules(self): if mesonlib.is_windows(): return self.detect_lib_modules_win() @@ -171,32 +233,79 @@ class BoostDependency(ExternalDependency): def detect_lib_modules_win(self): arch = detect_cpu_family(self.env.coredata.compilers) - # Guess the libdir - if arch == 'x86': - gl = 'lib32*' - elif arch == 'x86_64': - gl = 'lib64*' - else: - # Does anyone do Boost cross-compiling to other archs on Windows? - gl = None - # See if the libdir is valid - if gl: - libdir = glob.glob(os.path.join(self.boost_root, gl)) - else: - libdir = [] - # Can't find libdir, bail - if not libdir: + compiler_ts = self.env.detect_cpp_compiler(self.want_cross).get_toolset_version().split('.') + compiler = 'vc{}{}'.format(compiler_ts[0], compiler_ts[1]) + if not self.libdir: + # The libdirs in the distributed binaries + if arch == 'x86': + gl = 'lib32*' + elif arch == 'x86_64': + gl = 'lib64*' + else: + # Does anyone do Boost cross-compiling to other archs on Windows? + gl = None + if self.boost_root: + roots = [self.boost_root] + else: + roots = self.boost_roots + for root in roots: + # The default libdir when building + libdir = os.path.join(root, 'lib') + if os.path.isdir(libdir): + self.libdir = libdir + break + if gl: + tmp = glob.glob(os.path.join(root, gl)) + if len(tmp) > 0: + # FIXME: Should pick the correct version + self.libdir = tmp[0] + break + + if not self.libdir: return - libdir = libdir[0] - # Don't override what was set in the environment - if self.libdir: - self.libdir = libdir - globber = 'libboost_*-gd-*.lib' if self.static else 'boost_*-gd-*.lib' # FIXME - for entry in glob.glob(os.path.join(libdir, globber)): + + for name in self.need_static_link: + libname = "lib{}".format(name) + '-' + compiler + if self.is_multithreading: + libname = libname + '-mt' + if self.is_debug: + libname = libname + '-gd' + libname = libname + "-{}.lib".format(self.version.replace('.', '_')) + if os.path.isfile(os.path.join(self.libdir, libname)): + modname = libname.split('-', 1)[0][3:] + self.lib_modules[modname] = libname + else: + libname = "lib{}.lib".format(name) + if os.path.isfile(os.path.join(self.libdir, libname)): + self.lib_modules[name[3:]] = libname + + # globber1 applies to a layout=system installation + # globber2 applies to a layout=versioned installation + globber1 = 'libboost_*' if self.static else 'boost_*' + globber2 = globber1 + '-' + compiler + if self.is_multithreading: + globber2 = globber2 + '-mt' + if self.is_debug: + globber2 = globber2 + '-gd' + globber2 = globber2 + '-{}'.format(self.version.replace('.', '_')) + globber2_matches = glob.glob(os.path.join(self.libdir, globber2 + '.lib')) + for entry in globber2_matches: (_, fname) = os.path.split(entry) - base = fname.split('_', 1)[1] - modname = base.split('-', 1)[0] - self.lib_modules_mt[modname] = fname + modname = fname.split('-', 1) + if len(modname) > 1: + modname = modname[0] + else: + modname = modname.split('.', 1)[0] + if self.static: + modname = modname[3:] + self.lib_modules[modname] = fname + if len(globber2_matches) == 0: + for entry in glob.glob(os.path.join(self.libdir, globber1 + '.lib')): + (_, fname) = os.path.split(entry) + modname = fname.split('.', 1)[0] + if self.static: + modname = modname[3:] + self.lib_modules[modname] = fname def detect_lib_modules_nix(self): if self.static: @@ -214,25 +323,32 @@ class BoostDependency(ExternalDependency): else: libdirs = [os.path.join(self.boost_root, 'lib')] for libdir in libdirs: + for name in self.need_static_link: + libname = 'lib{}.a'.format(name) + if os.path.isfile(os.path.join(libdir, libname)): + self.lib_modules[name] = libname for entry in glob.glob(os.path.join(libdir, globber)): lib = os.path.basename(entry) - name = lib.split('.')[0].split('_', 1)[-1] + name = lib.split('.')[0][3:] # I'm not 100% sure what to do here. Some distros # have modules such as thread only as -mt versions. - if entry.endswith('-mt.{}'.format(libsuffix)): - self.lib_modules_mt[name] = True - else: - self.lib_modules[name] = True + # On debian all packages are built threading=multi + # but not suffixed with -mt. + # FIXME: implement detect_lib_modules_{debian, redhat, ...} + if self.is_multithreading and mesonlib.is_debianlike(): + self.lib_modules[name] = lib + elif self.is_multithreading and entry.endswith('-mt.{}'.format(libsuffix)): + self.lib_modules[name] = lib + elif not entry.endswith('-mt.{}'.format(libsuffix)): + self.lib_modules[name] = lib def get_win_link_args(self): args = [] # TODO: should this check self.libdir? - if self.boost_root: + if self.libdir: args.append('-L' + self.libdir) - for module in self.requested_modules: - module = BoostDependency.name2lib.get(module, module) - if module in self.lib_modules_mt: - args.append(self.lib_modules_mt[module]) + for lib in self.requested_modules: + args.append(self.lib_modules['boost_' + lib]) return args def get_link_args(self): @@ -243,33 +359,14 @@ class BoostDependency(ExternalDependency): args.append('-L' + os.path.join(self.boost_root, 'lib')) elif self.libdir: args.append('-L' + self.libdir) - for module in self.requested_modules: - module = BoostDependency.name2lib.get(module, module) - libname = 'boost_' + module + for lib in self.requested_modules: # The compiler's library detector is the most reliable so use that first. - default_detect = self.compiler.find_library(libname, self.env, []) + default_detect = self.compiler.find_library('boost_' + lib, self.env, []) if default_detect is not None: - if module == 'unit_testing_framework': - emon_args = self.compiler.find_library('boost_test_exec_monitor') - else: - emon_args = None args += default_detect - if emon_args is not None: - args += emon_args - elif module in self.lib_modules or module in self.lib_modules_mt: - linkcmd = '-l' + libname - args.append(linkcmd) - # FIXME a hack, but Boost's testing framework has a lot of - # different options and it's hard to determine what to do - # without feedback from actual users. Update this - # as we get more bug reports. - if module == 'unit_testing_framework': - args.append('-lboost_test_exec_monitor') - elif module + '-mt' in self.lib_modules_mt: - linkcmd = '-lboost_' + module + '-mt' + elif lib in self.lib_modules: + linkcmd = '-l' + lib args.append(linkcmd) - if module == 'unit_testing_framework': - args.append('-lboost_test_exec_monitor-mt') return args def get_sources(self): @@ -664,3 +761,78 @@ class CupsDependency(ExternalDependency): return [DependencyMethods.PKGCONFIG, DependencyMethods.CUPSCONFIG, DependencyMethods.EXTRAFRAMEWORK] else: return [DependencyMethods.PKGCONFIG, DependencyMethods.CUPSCONFIG] + +# Generated with boost_names.py +BOOST_LIBS = [ + 'boost_atomic', + 'boost_chrono', + 'boost_chrono', + 'boost_container', + 'boost_context', + 'boost_coroutine', + 'boost_date_time', + 'boost_exception', + 'boost_fiber', + 'boost_filesystem', + 'boost_graph', + 'boost_iostreams', + 'boost_locale', + 'boost_log', + 'boost_log_setup', + 'boost_math_tr1', + 'boost_math_tr1f', + 'boost_math_tr1l', + 'boost_math_c99', + 'boost_math_c99f', + 'boost_math_c99l', + 'boost_math_tr1', + 'boost_math_tr1f', + 'boost_math_tr1l', + 'boost_math_c99', + 'boost_math_c99f', + 'boost_math_c99l', + 'boost_math_tr1', + 'boost_math_tr1f', + 'boost_math_tr1l', + 'boost_math_c99', + 'boost_math_c99f', + 'boost_math_c99l', + 'boost_math_tr1', + 'boost_math_tr1f', + 'boost_math_tr1l', + 'boost_math_c99', + 'boost_math_c99f', + 'boost_math_c99l', + 'boost_math_tr1', + 'boost_math_tr1f', + 'boost_math_tr1l', + 'boost_math_c99', + 'boost_math_c99f', + 'boost_math_c99l', + 'boost_math_tr1', + 'boost_math_tr1f', + 'boost_math_tr1l', + 'boost_math_c99', + 'boost_math_c99f', + 'boost_math_c99l', + 'boost_mpi', + 'boost_random', + 'boost_regex', + 'boost_serialization', + 'boost_wserialization', + 'boost_signals', + 'boost_stacktrace_noop', + 'boost_stacktrace_backtrace', + 'boost_stacktrace_addr2line', + 'boost_stacktrace_basic', + 'boost_stacktrace_windbg', + 'boost_stacktrace_windbg_cached', + 'boost_system', + 'boost_prg_exec_monitor', + 'boost_test_exec_monitor', + 'boost_unit_test_framework', + 'boost_thread', + 'boost_timer', + 'boost_type_erasure', + 'boost_wave' +] diff --git a/mesonbuild/interpreter.py b/mesonbuild/interpreter.py index 8197b5e..b938080 100644 --- a/mesonbuild/interpreter.py +++ b/mesonbuild/interpreter.py @@ -48,6 +48,14 @@ def stringifyUserArguments(args): raise InvalidArguments('Function accepts only strings, integers, lists and lists thereof.') +class ObjectHolder: + def __init__(self, obj): + self.held_object = obj + + def __repr__(self): + return '<Holder: {!r}>'.format(self.held_object) + + class TryRunResultHolder(InterpreterObject): def __init__(self, res): super().__init__() @@ -117,17 +125,18 @@ class RunProcess(InterpreterObject): def stderr_method(self, args, kwargs): return self.stderr -class ConfigureFileHolder(InterpreterObject): +class ConfigureFileHolder(InterpreterObject, ObjectHolder): def __init__(self, subdir, sourcename, targetname, configuration_data): InterpreterObject.__init__(self) - self.held_object = build.ConfigureFile(subdir, sourcename, targetname, configuration_data) + ObjectHolder.__init__(self, build.ConfigureFile(subdir, sourcename, + targetname, configuration_data)) -class EnvironmentVariablesHolder(MutableInterpreterObject): +class EnvironmentVariablesHolder(MutableInterpreterObject, ObjectHolder): def __init__(self): - super().__init__() - self.held_object = build.EnvironmentVariables() + MutableInterpreterObject.__init__(self) + ObjectHolder.__init__(self, build.EnvironmentVariables()) self.methods.update({'set': self.set_method, 'append': self.append_method, 'prepend': self.prepend_method, @@ -158,11 +167,11 @@ class EnvironmentVariablesHolder(MutableInterpreterObject): self.add_var(self.held_object.prepend, args, kwargs) -class ConfigurationDataHolder(MutableInterpreterObject): +class ConfigurationDataHolder(MutableInterpreterObject, ObjectHolder): def __init__(self): - super().__init__() + MutableInterpreterObject.__init__(self) self.used = False # These objects become immutable after use in configure_file. - self.held_object = build.ConfigurationData() + ObjectHolder.__init__(self, build.ConfigurationData()) self.methods.update({'set': self.set_method, 'set10': self.set10_method, 'set_quoted': self.set_quoted_method, @@ -242,10 +251,10 @@ class ConfigurationDataHolder(MutableInterpreterObject): # Interpreter objects can not be pickled so we must have # these wrappers. -class DependencyHolder(InterpreterObject): +class DependencyHolder(InterpreterObject, ObjectHolder): def __init__(self, dep): InterpreterObject.__init__(self) - self.held_object = dep + ObjectHolder.__init__(self, dep) self.methods.update({'found': self.found_method, 'type_name': self.type_name_method, 'version': self.version_method, @@ -272,10 +281,10 @@ class DependencyHolder(InterpreterObject): raise InterpreterException('Variable name must be a string.') return self.held_object.get_pkgconfig_variable(varname) -class InternalDependencyHolder(InterpreterObject): +class InternalDependencyHolder(InterpreterObject, ObjectHolder): def __init__(self, dep): InterpreterObject.__init__(self) - self.held_object = dep + ObjectHolder.__init__(self, dep) self.methods.update({'found': self.found_method, 'version': self.version_method, }) @@ -286,10 +295,10 @@ class InternalDependencyHolder(InterpreterObject): def version_method(self, args, kwargs): return self.held_object.get_version() -class ExternalProgramHolder(InterpreterObject): +class ExternalProgramHolder(InterpreterObject, ObjectHolder): def __init__(self, ep): InterpreterObject.__init__(self) - self.held_object = ep + ObjectHolder.__init__(self, ep) self.methods.update({'found': self.found_method, 'path': self.path_method}) @@ -308,10 +317,10 @@ class ExternalProgramHolder(InterpreterObject): def get_name(self): return self.held_object.get_name() -class ExternalLibraryHolder(InterpreterObject): +class ExternalLibraryHolder(InterpreterObject, ObjectHolder): def __init__(self, el): InterpreterObject.__init__(self) - self.held_object = el + ObjectHolder.__init__(self, el) self.methods.update({'found': self.found_method}) def found(self): @@ -332,11 +341,11 @@ class ExternalLibraryHolder(InterpreterObject): def get_exe_args(self): return self.held_object.get_exe_args() -class GeneratorHolder(InterpreterObject): +class GeneratorHolder(InterpreterObject, ObjectHolder): def __init__(self, interpreter, args, kwargs): - super().__init__() + InterpreterObject.__init__(self) self.interpreter = interpreter - self.held_object = build.Generator(args, kwargs) + ObjectHolder.__init__(self, build.Generator(args, kwargs)) self.methods.update({'process': self.process_method}) def process_method(self, args, kwargs): @@ -345,13 +354,13 @@ class GeneratorHolder(InterpreterObject): return GeneratedListHolder(gl) -class GeneratedListHolder(InterpreterObject): +class GeneratedListHolder(InterpreterObject, ObjectHolder): def __init__(self, arg1, extra_args=[]): - super().__init__() + InterpreterObject.__init__(self) if isinstance(arg1, GeneratorHolder): - self.held_object = build.GeneratedList(arg1.held_object, extra_args) + ObjectHolder.__init__(self, build.GeneratedList(arg1.held_object, extra_args)) else: - self.held_object = arg1 + ObjectHolder.__init__(self, arg1) def __repr__(self): r = '<{}: {!r}>' @@ -360,14 +369,15 @@ class GeneratedListHolder(InterpreterObject): def add_file(self, a): self.held_object.add_file(a) -class BuildMachine(InterpreterObject): +class BuildMachine(InterpreterObject, ObjectHolder): def __init__(self, compilers): self.compilers = compilers InterpreterObject.__init__(self) - self.held_object = environment.MachineInfo(environment.detect_system(), - environment.detect_cpu_family(self.compilers), - environment.detect_cpu(self.compilers), - sys.byteorder) + held_object = environment.MachineInfo(environment.detect_system(), + environment.detect_cpu_family(self.compilers), + environment.detect_cpu(self.compilers), + sys.byteorder) + ObjectHolder.__init__(self, held_object) self.methods.update({'system': self.system_method, 'cpu_family': self.cpu_family_method, 'cpu': self.cpu_method, @@ -388,7 +398,7 @@ class BuildMachine(InterpreterObject): # This class will provide both host_machine and # target_machine -class CrossMachineInfo(InterpreterObject): +class CrossMachineInfo(InterpreterObject, ObjectHolder): def __init__(self, cross_info): InterpreterObject.__init__(self) minimum_cross_info = {'cpu', 'cpu_family', 'endian', 'system'} @@ -397,10 +407,11 @@ class CrossMachineInfo(InterpreterObject): 'Machine info is currently {}\n'.format(cross_info) + 'but is missing {}.'.format(minimum_cross_info - set(cross_info))) self.info = cross_info - self.held_object = environment.MachineInfo(cross_info['system'], - cross_info['cpu_family'], - cross_info['cpu'], - cross_info['endian']) + minfo = environment.MachineInfo(cross_info['system'], + cross_info['cpu_family'], + cross_info['cpu'], + cross_info['endian']) + ObjectHolder.__init__(self, minfo) self.methods.update({'system': self.system_method, 'cpu': self.cpu_method, 'cpu_family': self.cpu_family_method, @@ -419,10 +430,10 @@ class CrossMachineInfo(InterpreterObject): def endian_method(self, args, kwargs): return self.held_object.endian -class IncludeDirsHolder(InterpreterObject): +class IncludeDirsHolder(InterpreterObject, ObjectHolder): def __init__(self, idobj): - super().__init__() - self.held_object = idobj + InterpreterObject.__init__(self) + ObjectHolder.__init__(self, idobj) class Headers(InterpreterObject): @@ -447,10 +458,10 @@ class Headers(InterpreterObject): def get_custom_install_dir(self): return self.custom_install_dir -class DataHolder(InterpreterObject): +class DataHolder(InterpreterObject, ObjectHolder): def __init__(self, data): - super().__init__() - self.held_object = data + InterpreterObject.__init__(self) + ObjectHolder.__init__(self, data) def get_source_subdir(self): return self.held_object.source_subdir @@ -495,20 +506,20 @@ class Man(InterpreterObject): def get_sources(self): return self.sources -class GeneratedObjectsHolder(InterpreterObject): +class GeneratedObjectsHolder(InterpreterObject, ObjectHolder): def __init__(self, held_object): - super().__init__() - self.held_object = held_object + InterpreterObject.__init__(self) + ObjectHolder.__init__(self, held_object) -class TargetHolder(InterpreterObject): - def __init__(self): - super().__init__() +class TargetHolder(InterpreterObject, ObjectHolder): + def __init__(self, target, interp): + InterpreterObject.__init__(self) + ObjectHolder.__init__(self, target) + self.interpreter = interp class BuildTargetHolder(TargetHolder): def __init__(self, target, interp): - super().__init__() - self.held_object = target - self.interpreter = interp + super().__init__(target, interp) self.methods.update({'extract_objects': self.extract_objects_method, 'extract_all_objects': self.extract_all_objects_method, 'get_id': self.get_id_method, @@ -566,16 +577,14 @@ class JarHolder(BuildTargetHolder): def __init__(self, target, interp): super().__init__(target, interp) -class CustomTargetIndexHolder(InterpreterObject): +class CustomTargetIndexHolder(InterpreterObject, ObjectHolder): def __init__(self, object_to_hold): - super().__init__() - self.held_object = object_to_hold + InterpreterObject.__init__(self) + ObjectHolder.__init__(self, object_to_hold) class CustomTargetHolder(TargetHolder): - def __init__(self, object_to_hold, interp): - super().__init__() - self.held_object = object_to_hold - self.interpreter = interp + def __init__(self, target, interp): + super().__init__(target, interp) self.methods.update({'full_path': self.full_path_method, }) @@ -596,10 +605,10 @@ class CustomTargetHolder(TargetHolder): def __delitem__(self, index): raise InterpreterException('Cannot delete a member of a CustomTarget') -class RunTargetHolder(InterpreterObject): +class RunTargetHolder(InterpreterObject, ObjectHolder): def __init__(self, name, command, args, dependencies, subdir): - super().__init__() - self.held_object = build.RunTarget(name, command, args, dependencies, subdir) + InterpreterObject.__init__(self) + ObjectHolder.__init__(self, build.RunTarget(name, command, args, dependencies, subdir)) def __repr__(self): r = '<{} {}: {}>' @@ -625,11 +634,11 @@ class Test(InterpreterObject): def get_name(self): return self.name -class SubprojectHolder(InterpreterObject): +class SubprojectHolder(InterpreterObject, ObjectHolder): def __init__(self, subinterpreter): - super().__init__() - self.held_object = subinterpreter + InterpreterObject.__init__(self) + ObjectHolder.__init__(self, subinterpreter) self.methods.update({'get_variable': self.get_variable_method, }) @@ -1056,11 +1065,11 @@ ModuleState = namedtuple('ModuleState', [ 'man', 'global_args', 'project_args', 'build_machine', 'host_machine', 'target_machine']) -class ModuleHolder(InterpreterObject): +class ModuleHolder(InterpreterObject, ObjectHolder): def __init__(self, modname, module, interpreter): InterpreterObject.__init__(self) + ObjectHolder.__init__(self, module) self.modname = modname - self.held_object = module self.interpreter = interpreter def method_call(self, method_name, args, kwargs): @@ -1551,12 +1560,11 @@ class Interpreter(InterpreterBase): version = kwargs.get('version', self.project_version) if not isinstance(version, str): raise InterpreterException('Version must be a string.') - incs = extract_as_list(kwargs, 'include_directories') - libs = extract_as_list(kwargs, 'link_with') + incs = extract_as_list(kwargs, 'include_directories', unholder=True) + libs = extract_as_list(kwargs, 'link_with', unholder=True) sources = extract_as_list(kwargs, 'sources') - sources = self.source_strings_to_files(self.flatten(sources)) - deps = self.flatten(kwargs.get('dependencies', [])) - deps = listify(deps) + sources = listify(self.source_strings_to_files(sources), unholder=True) + deps = extract_as_list(kwargs, 'dependencies', unholder=True) compile_args = mesonlib.stringlistify(kwargs.get('compile_args', [])) link_args = mesonlib.stringlistify(kwargs.get('link_args', [])) final_deps = [] @@ -1568,13 +1576,8 @@ class Interpreter(InterpreterBase): if not isinstance(d, (dependencies.Dependency, dependencies.ExternalLibrary, dependencies.InternalDependency)): raise InterpreterException('Dependencies must be external deps') final_deps.append(d) - dep = dependencies.InternalDependency(version, - mesonlib.unholder_array(incs), - compile_args, - link_args, - mesonlib.unholder_array(libs), - mesonlib.unholder_array(sources), - final_deps) + dep = dependencies.InternalDependency(version, incs, compile_args, + link_args, libs, sources, final_deps) return DependencyHolder(dep) @noKwargs @@ -1629,7 +1632,7 @@ class Interpreter(InterpreterBase): 'or not executable'.format(cmd)) cmd = prog expanded_args = [] - for a in mesonlib.flatten(cargs): + for a in listify(cargs): if isinstance(a, str): expanded_args.append(a) elif isinstance(a, mesonlib.File): @@ -1700,6 +1703,9 @@ class Interpreter(InterpreterBase): if len(args) != 1: raise InterpreterException('Argument required for get_option.') optname = args[0] + if ':' in optname: + raise InterpreterException('''Having a colon in option name is forbidden, projects are not allowed +to directly access options of other subprojects.''') try: return self.environment.get_coredata().base_options[optname].value except KeyError: @@ -2296,11 +2302,7 @@ class Interpreter(InterpreterBase): raise InterpreterException('Run_target needs at least one positional argument.') cleaned_args = [] - for i in mesonlib.flatten(all_args): - try: - i = i.held_object - except AttributeError: - pass + for i in listify(all_args, unholder=True): if not isinstance(i, (str, build.BuildTarget, build.CustomTarget, dependencies.ExternalProgram, mesonlib.File)): mlog.debug('Wrong type:', str(i)) raise InterpreterException('Invalid argument to run_target.') @@ -2371,11 +2373,10 @@ class Interpreter(InterpreterBase): par = kwargs.get('is_parallel', True) if not isinstance(par, bool): raise InterpreterException('Keyword argument is_parallel must be a boolean.') - cmd_args = extract_as_list(kwargs, 'args') + cmd_args = extract_as_list(kwargs, 'args', unholder=True) for i in cmd_args: - if not isinstance(i, (str, mesonlib.File, TargetHolder)): + if not isinstance(i, (str, mesonlib.File, build.Target)): raise InterpreterException('Command line arguments must be strings, files or targets.') - cmd_args = mesonlib.unholder_array(cmd_args) env = self.unpack_env_kwarg(kwargs) should_fail = kwargs.get('should_fail', False) if not isinstance(should_fail, bool): @@ -2793,7 +2794,8 @@ different subdirectory. elif isinstance(s, str): s = mesonlib.File.from_source_file(self.environment.source_dir, self.subdir, s) else: - raise InterpreterException("Source item is not string or File-type object.") + raise InterpreterException('Source item is {!r} instead of ' + 'string or File-type object'.format(s)) results.append(s) return results @@ -2819,7 +2821,7 @@ different subdirectory. if not args: raise InterpreterException('Target does not have a name.') name = args[0] - sources = args[1:] + sources = listify(args[1:]) if self.environment.is_cross_build(): if kwargs.get('native', False): is_cross = False @@ -2827,19 +2829,14 @@ different subdirectory. is_cross = True else: is_cross = False - try: - kw_src = self.flatten(kwargs['sources']) - kw_src = listify(kw_src) - except KeyError: - kw_src = [] - sources += kw_src + if 'sources' in kwargs: + sources += listify(kwargs['sources']) sources = self.source_strings_to_files(sources) - objs = self.flatten(kwargs.get('objects', [])) - kwargs['dependencies'] = self.flatten(kwargs.get('dependencies', [])) + objs = extract_as_list(kwargs, 'objects') + kwargs['dependencies'] = extract_as_list(kwargs, 'dependencies') if 'extra_files' in kwargs: ef = extract_as_list(kwargs, 'extra_files') kwargs['extra_files'] = self.source_strings_to_files(ef) - objs = listify(objs) self.check_sources_exist(os.path.join(self.source_root, self.subdir), sources) if targetholder is ExecutableHolder: targetclass = build.Executable diff --git a/mesonbuild/mesonlib.py b/mesonbuild/mesonlib.py index 71134a8..5c4c374 100644 --- a/mesonbuild/mesonlib.py +++ b/mesonbuild/mesonlib.py @@ -199,17 +199,6 @@ def classify_unity_sources(compilers, sources): compsrclist[comp].append(src) return compsrclist -def flatten(item): - if not isinstance(item, list): - return [item] - result = [] - for i in item: - if isinstance(i, list): - result += flatten(i) - else: - result.append(i) - return result - def is_osx(): return platform.system().lower() == 'darwin' @@ -474,24 +463,45 @@ def replace_if_different(dst, dst_tmp): else: os.unlink(dst_tmp) - -def listify(*args): +def listify(item, flatten=True, unholder=False): ''' - Returns a list with all args embedded in a list if they are not of type list. + Returns a list with all args embedded in a list if they are not a list. This function preserves order. + @flatten: Convert lists of lists to a flat list + @unholder: Replace each item with the object it holds, if required + + Note: unholding only works recursively when flattening ''' - if len(args) == 1: # Special case with one single arg - return args[0] if type(args[0]) is list else [args[0]] - return [item if type(item) is list else [item] for item in args] + if not isinstance(item, list): + if unholder and hasattr(item, 'held_object'): + item = item.held_object + return [item] + result = [] + for i in item: + if unholder and hasattr(i, 'held_object'): + i = i.held_object + if flatten and isinstance(i, list): + result += listify(i, flatten=True, unholder=unholder) + else: + result.append(i) + return result -def extract_as_list(dict_object, *keys, pop = False): +def extract_as_list(dict_object, *keys, pop=False, **kwargs): ''' Extracts all values from given dict_object and listifies them. ''' + result = [] + fetch = dict_object.get if pop: - return listify(*[dict_object.pop(key, []) for key in keys]) - return listify(*[dict_object.get(key, []) for key in keys]) + fetch = dict_object.pop + # If there's only one key, we don't return a list with one element + if len(keys) == 1: + return listify(fetch(keys[0], []), **kwargs) + # Return a list of values corresponding to *keys + for key in keys: + result.append(listify(fetch(key, []), **kwargs)) + return result def typeslistify(item, types): @@ -750,15 +760,6 @@ def windows_proof_rmtree(f): # Try one last time and throw if it fails. shutil.rmtree(f) -def unholder_array(entries): - result = [] - entries = flatten(entries) - for e in entries: - if hasattr(e, 'held_object'): - e = e.held_object - result.append(e) - return result - class OrderedSet(collections.MutableSet): """A set that preserves the order in which items are added, by first insertion. diff --git a/mesonbuild/modules/gnome.py b/mesonbuild/modules/gnome.py index d1d7013..1f813da 100644 --- a/mesonbuild/modules/gnome.py +++ b/mesonbuild/modules/gnome.py @@ -20,7 +20,7 @@ import os import copy import subprocess from . import ModuleReturnValue -from ..mesonlib import MesonException, OrderedSet, Popen_safe +from ..mesonlib import MesonException, OrderedSet, Popen_safe, extract_as_list from ..dependencies import Dependency, PkgConfigDependency, InternalDependency from .. import mlog from .. import mesonlib @@ -323,11 +323,9 @@ class GnomeModule(ExtensionModule): cflags = OrderedSet() ldflags = OrderedSet() gi_includes = OrderedSet() - deps = mesonlib.listify(deps) + deps = mesonlib.listify(deps, unholder=True) for dep in deps: - if hasattr(dep, 'held_object'): - dep = dep.held_object if isinstance(dep, InternalDependency): cflags.update(get_include_args(dep.include_directories)) for lib in dep.libraries: @@ -378,7 +376,7 @@ class GnomeModule(ExtensionModule): elif isinstance(dep, (build.StaticLibrary, build.SharedLibrary)): cflags.update(get_include_args(dep.get_include_dirs())) else: - mlog.log('dependency %s not handled to build gir files' % dep) + mlog.log('dependency {!r} not handled to build gir files'.format(dep)) continue if gir_has_extra_lib_arg() and use_gir_args: @@ -394,7 +392,7 @@ class GnomeModule(ExtensionModule): @permittedKwargs({'sources', 'nsversion', 'namespace', 'symbol_prefix', 'identifier_prefix', 'export_packages', 'includes', 'dependencies', 'link_with', 'include_directories', 'install', 'install_dir_gir', 'install_dir_typelib', 'extra_args', - 'packages', 'build_by_default'}) + 'packages', 'header', 'build_by_default'}) def generate_gir(self, state, args, kwargs): if len(args) != 1: raise MesonException('Gir takes one argument') @@ -417,7 +415,7 @@ class GnomeModule(ExtensionModule): raise MesonException('gobject-introspection dependency was not found, gir cannot be generated.') ns = kwargs.pop('namespace') nsversion = kwargs.pop('nsversion') - libsources = mesonlib.flatten(kwargs.pop('sources')) + libsources = mesonlib.extract_as_list(kwargs, 'sources', pop=True) girfile = '%s-%s.gir' % (ns, nsversion) srcdir = os.path.join(state.environment.get_source_dir(), state.subdir) builddir = os.path.join(state.environment.get_build_dir(), state.subdir) @@ -429,6 +427,12 @@ class GnomeModule(ExtensionModule): scan_command += ['--no-libtool', '--namespace=' + ns, '--nsversion=' + nsversion, '--warn-all', '--output', '@OUTPUT@'] + header = kwargs.pop('header', None) + if header: + if not isinstance(header, str): + raise MesonException('header must be a string') + scan_command += ['--c-include=' + header] + extra_args = mesonlib.stringlistify(kwargs.pop('extra_args', [])) scan_command += extra_args scan_command += ['-I' + srcdir, @@ -525,9 +529,8 @@ class GnomeModule(ExtensionModule): else: raise MesonException('Gir export packages must be str or list') - deps = mesonlib.extract_as_list(kwargs, 'dependencies', pop = True) deps = (girtarget.get_all_link_deps() + girtarget.get_external_deps() + - deps) + extract_as_list(kwargs, 'dependencies', pop=True, unholder=True)) # Need to recursively add deps on GirTarget sources from our # dependencies and also find the include directories needed for the # typelib generation custom target below. @@ -794,7 +797,8 @@ This will become a hard error in the future.''') def _get_build_args(self, kwargs, state): args = [] - cflags, ldflags, gi_includes = self._get_dependencies_flags(kwargs.get('dependencies', []), state, include_rpath=True) + deps = extract_as_list(kwargs, 'dependencies', unholder=True) + cflags, ldflags, gi_includes = self._get_dependencies_flags(deps, state, include_rpath=True) inc_dirs = mesonlib.extract_as_list(kwargs, 'include_directories') for incd in inc_dirs: if not isinstance(incd.held_object, (str, build.IncludeDirs)): diff --git a/mesonbuild/modules/qt4.py b/mesonbuild/modules/qt4.py index a63aff8..4ab07b9 100644 --- a/mesonbuild/modules/qt4.py +++ b/mesonbuild/modules/qt4.py @@ -21,6 +21,7 @@ from . import ExtensionModule import xml.etree.ElementTree as ET from . import ModuleReturnValue from ..interpreterbase import permittedKwargs +from . import get_include_args class Qt4Module(ExtensionModule): tools_detected = False @@ -97,10 +98,10 @@ class Qt4Module(ExtensionModule): except Exception: return [] - @permittedKwargs({'moc_headers', 'moc_sources', 'ui_files', 'qresources', 'method'}) + @permittedKwargs({'moc_headers', 'moc_sources', 'include_directories', 'ui_files', 'qresources', 'method'}) def preprocess(self, state, args, kwargs): - rcc_files, ui_files, moc_headers, moc_sources, sources \ - = extract_as_list(kwargs, 'qresources', 'ui_files', 'moc_headers', 'moc_sources', 'sources', pop = True) + rcc_files, ui_files, moc_headers, moc_sources, sources, include_directories \ + = extract_as_list(kwargs, 'qresources', 'ui_files', 'moc_headers', 'moc_sources', 'sources', 'include_directories', pop = True) sources += args[1:] method = kwargs.get('method', 'auto') self._detect_tools(state.environment, method) @@ -133,9 +134,10 @@ class Qt4Module(ExtensionModule): ui_gen = build.Generator([self.uic], ui_kwargs) ui_output = ui_gen.process_files('Qt4 ui', ui_files, state) sources.append(ui_output) + inc = get_include_args(include_dirs=include_directories) if len(moc_headers) > 0: moc_kwargs = {'output': 'moc_@BASENAME@.cpp', - 'arguments': ['@INPUT@', '-o', '@OUTPUT@']} + 'arguments': inc + ['@INPUT@', '-o', '@OUTPUT@']} moc_gen = build.Generator([self.moc], moc_kwargs) moc_output = moc_gen.process_files('Qt4 moc header', moc_headers, state) sources.append(moc_output) diff --git a/mesonbuild/modules/qt5.py b/mesonbuild/modules/qt5.py index 08ce662..001caa7 100644 --- a/mesonbuild/modules/qt5.py +++ b/mesonbuild/modules/qt5.py @@ -21,6 +21,7 @@ from . import ExtensionModule import xml.etree.ElementTree as ET from . import ModuleReturnValue from ..interpreterbase import permittedKwargs +from . import get_include_args class Qt5Module(ExtensionModule): tools_detected = False @@ -103,10 +104,10 @@ class Qt5Module(ExtensionModule): except Exception: return [] - @permittedKwargs({'moc_headers', 'moc_sources', 'ui_files', 'qresources', 'method'}) + @permittedKwargs({'moc_headers', 'moc_sources', 'include_directories', 'ui_files', 'qresources', 'method'}) def preprocess(self, state, args, kwargs): - rcc_files, ui_files, moc_headers, moc_sources, sources \ - = extract_as_list(kwargs, 'qresources', 'ui_files', 'moc_headers', 'moc_sources', 'sources', pop = True) + rcc_files, ui_files, moc_headers, moc_sources, sources, include_directories \ + = extract_as_list(kwargs, 'qresources', 'ui_files', 'moc_headers', 'moc_sources', 'sources', 'include_directories', pop = True) sources += args[1:] method = kwargs.get('method', 'auto') self._detect_tools(state.environment, method) @@ -139,9 +140,10 @@ class Qt5Module(ExtensionModule): ui_gen = build.Generator([self.uic], ui_kwargs) ui_output = ui_gen.process_files('Qt5 ui', ui_files, state) sources.append(ui_output) + inc = get_include_args(include_dirs=include_directories) if len(moc_headers) > 0: moc_kwargs = {'output': 'moc_@BASENAME@.cpp', - 'arguments': ['@INPUT@', '-o', '@OUTPUT@']} + 'arguments': inc + ['@INPUT@', '-o', '@OUTPUT@']} moc_gen = build.Generator([self.moc], moc_kwargs) moc_output = moc_gen.process_files('Qt5 moc header', moc_headers, state) sources.append(moc_output) diff --git a/mesonbuild/wrap/wrap.py b/mesonbuild/wrap/wrap.py index 81a2f88..26a3489 100644 --- a/mesonbuild/wrap/wrap.py +++ b/mesonbuild/wrap/wrap.py @@ -19,6 +19,7 @@ import subprocess import sys from pathlib import Path from . import WrapMode +from ..mesonlib import Popen_safe try: import ssl @@ -78,6 +79,8 @@ class PackageDefinition: self.type = 'git' elif first == '[wrap-hg]': self.type = 'hg' + elif first == '[wrap-svn]': + self.type = 'svn' else: raise RuntimeError('Invalid format of package file') for line in ifile: @@ -145,6 +148,8 @@ class Resolver: self.get_git(p) elif p.type == "hg": self.get_hg(p) + elif p.type == "svn": + self.get_svn(p) else: raise AssertionError('Unreachable code.') return p.get('directory') @@ -228,6 +233,27 @@ class Resolver: subprocess.check_call(['hg', 'checkout', revno], cwd=checkoutdir) + def get_svn(self, p): + checkoutdir = os.path.join(self.subdir_root, p.get('directory')) + revno = p.get('revision') + is_there = os.path.isdir(checkoutdir) + if is_there: + p, out = Popen_safe(['svn', 'info', '--show-item', 'revision', checkoutdir]) + current_revno = out + if current_revno == revno: + return + + if revno.lower() == 'head': + # Failure to do pull is not a fatal error, + # because otherwise you can't develop without + # a working net connection. + subprocess.call(['svn', 'update'], cwd=checkoutdir) + else: + subprocess.check_call(['svn', 'update', '-r', revno], cwd=checkoutdir) + else: + subprocess.check_call(['svn', 'checkout', '-r', revno, p.get('url'), + p.get('directory')], cwd=self.subdir_root) + def get_data(self, url): blocksize = 10 * 1024 h = hashlib.sha256() diff --git a/run_tests.py b/run_tests.py index f74ff13..00c97ca 100755 --- a/run_tests.py +++ b/run_tests.py @@ -90,7 +90,7 @@ def get_backend_commands(backend, debug=False): 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'] + cmd = [detect_ninja('1.6'), '-w', 'dupbuild=err', '-d', 'explain'] if cmd[0] is None: raise RuntimeError('Could not find Ninja v1.6 or newer') if debug: diff --git a/run_unittests.py b/run_unittests.py index af1a5f2..fa8f049 100755 --- a/run_unittests.py +++ b/run_unittests.py @@ -31,6 +31,7 @@ import mesonbuild.compilers import mesonbuild.environment import mesonbuild.mesonlib import mesonbuild.coredata +from mesonbuild.interpreter import ObjectHolder from mesonbuild.mesonlib import is_linux, is_windows, is_osx, is_cygwin, windows_proof_rmtree from mesonbuild.environment import Environment from mesonbuild.dependencies import DependencyException @@ -62,7 +63,6 @@ def get_soname(fname): def get_rpath(fname): return get_dynamic_section_entry(fname, r'(?:rpath|runpath)') - class InternalTests(unittest.TestCase): def test_version_number(self): @@ -398,6 +398,49 @@ class InternalTests(unittest.TestCase): self.assertEqual(forced_value, desired_value) + def test_listify(self): + listify = mesonbuild.mesonlib.listify + # Test sanity + self.assertEqual([1], listify(1)) + self.assertEqual([], listify([])) + self.assertEqual([1], listify([1])) + # Test flattening + self.assertEqual([1, 2, 3], listify([1, [2, 3]])) + self.assertEqual([1, 2, 3], listify([1, [2, [3]]])) + self.assertEqual([1, [2, [3]]], listify([1, [2, [3]]], flatten=False)) + # Test flattening and unholdering + holder1 = ObjectHolder(1) + holder3 = ObjectHolder(3) + self.assertEqual([holder1], listify(holder1)) + self.assertEqual([holder1], listify([holder1])) + self.assertEqual([holder1, 2], listify([holder1, 2])) + self.assertEqual([holder1, 2, 3], listify([holder1, 2, [3]])) + self.assertEqual([1], listify(holder1, unholder=True)) + self.assertEqual([1], listify([holder1], unholder=True)) + self.assertEqual([1, 2], listify([holder1, 2], unholder=True)) + self.assertEqual([1, 2, 3], listify([holder1, 2, [holder3]], unholder=True)) + # Unholding doesn't work recursively when not flattening + self.assertEqual([1, [2], [holder3]], listify([holder1, [2], [holder3]], unholder=True, flatten=False)) + + def test_extract_as_list(self): + extract = mesonbuild.mesonlib.extract_as_list + # Test sanity + kwargs = {'sources': [1, 2, 3]} + self.assertEqual([1, 2, 3], extract(kwargs, 'sources')) + self.assertEqual(kwargs, {'sources': [1, 2, 3]}) + self.assertEqual([1, 2, 3], extract(kwargs, 'sources', pop=True)) + self.assertEqual(kwargs, {}) + # Test unholding + holder3 = ObjectHolder(3) + kwargs = {'sources': [1, 2, holder3]} + self.assertEqual([1, 2, 3], extract(kwargs, 'sources', unholder=True)) + self.assertEqual(kwargs, {'sources': [1, 2, holder3]}) + self.assertEqual([1, 2, 3], extract(kwargs, 'sources', unholder=True, pop=True)) + self.assertEqual(kwargs, {}) + # Test listification + kwargs = {'sources': [1, 2, 3], 'pch_sources': [4, 5, 6]} + self.assertEqual([[1, 2, 3], [4, 5, 6]], extract(kwargs, 'sources', 'pch_sources')) + class BasePlatformTests(unittest.TestCase): def setUp(self): diff --git a/test cases/common/145 whole archive/allofme/meson.build b/test cases/common/145 whole archive/allofme/meson.build new file mode 100644 index 0000000..f5c2027 --- /dev/null +++ b/test cases/common/145 whole archive/allofme/meson.build @@ -0,0 +1 @@ +stlib = static_library('allofme', '../libfile.c') diff --git a/test cases/common/145 whole archive/exe/meson.build b/test cases/common/145 whole archive/exe/meson.build new file mode 100644 index 0000000..f47a246 --- /dev/null +++ b/test cases/common/145 whole archive/exe/meson.build @@ -0,0 +1,2 @@ +exe = executable('prog', '../prog.c', + link_with : dylib) diff --git a/test cases/common/145 whole archive/exe2/meson.build b/test cases/common/145 whole archive/exe2/meson.build new file mode 100644 index 0000000..5365f03 --- /dev/null +++ b/test cases/common/145 whole archive/exe2/meson.build @@ -0,0 +1 @@ +exe2 = executable('prog2', '../prog.c', link_with : dylib2) diff --git a/test cases/common/145 whole archive/meson.build b/test cases/common/145 whole archive/meson.build index eadebf8..617ae03 100644 --- a/test cases/common/145 whole archive/meson.build +++ b/test cases/common/145 whole archive/meson.build @@ -1,5 +1,7 @@ project('whole archive', 'c') +add_project_arguments('-I' + meson.source_root(), language : 'c') + cc = meson.get_compiler('c') if cc.get_id() == 'msvc' @@ -8,15 +10,15 @@ if cc.get_id() == 'msvc' endif endif -stlib = static_library('allofme', 'libfile.c') - -# Nothing in dylib.c uses func1, so the linker would throw it -# away and thus linking the exe would fail. -dylib = shared_library('shlib', 'dylib.c', - link_whole : stlib) - -exe = executable('prog', 'prog.c', - link_with : dylib) +subdir('allofme') +subdir('shlib') +subdir('exe') test('prog', exe) +# link_whole only +subdir('stlib') +subdir('wholeshlib') +subdir('exe2') + +test('prog2', exe2) diff --git a/test cases/common/145 whole archive/shlib/meson.build b/test cases/common/145 whole archive/shlib/meson.build new file mode 100644 index 0000000..34a1b78 --- /dev/null +++ b/test cases/common/145 whole archive/shlib/meson.build @@ -0,0 +1,4 @@ +# Nothing in dylib.c uses func1, so the linker would throw it +# away and thus linking the exe would fail. +dylib = shared_library('shlib', '../dylib.c', + link_whole : stlib) diff --git a/test cases/common/145 whole archive/stlib/meson.build b/test cases/common/145 whole archive/stlib/meson.build new file mode 100644 index 0000000..07a434e --- /dev/null +++ b/test cases/common/145 whole archive/stlib/meson.build @@ -0,0 +1 @@ +static = static_library('static', '../dylib.c') diff --git a/test cases/common/145 whole archive/wholeshlib/meson.build b/test cases/common/145 whole archive/wholeshlib/meson.build new file mode 100644 index 0000000..69a1995 --- /dev/null +++ b/test cases/common/145 whole archive/wholeshlib/meson.build @@ -0,0 +1 @@ +dylib2 = shared_library('link_whole', link_whole : [stlib, static]) diff --git a/test cases/failing/61 getoption prefix/meson.build b/test cases/failing/61 getoption prefix/meson.build new file mode 100644 index 0000000..8f85cff --- /dev/null +++ b/test cases/failing/61 getoption prefix/meson.build @@ -0,0 +1,5 @@ +project('getopt prefix') + +subproject('abc') + +get_option('abc:foo') diff --git a/test cases/failing/61 getoption prefix/subprojects/abc/meson.build b/test cases/failing/61 getoption prefix/subprojects/abc/meson.build new file mode 100644 index 0000000..aa9c3df --- /dev/null +++ b/test cases/failing/61 getoption prefix/subprojects/abc/meson.build @@ -0,0 +1 @@ +project('abc', 'c') diff --git a/test cases/failing/61 getoption prefix/subprojects/abc/meson_options.txt b/test cases/failing/61 getoption prefix/subprojects/abc/meson_options.txt new file mode 100644 index 0000000..89e624e --- /dev/null +++ b/test cases/failing/61 getoption prefix/subprojects/abc/meson_options.txt @@ -0,0 +1 @@ +option('foo', type : 'boolean') diff --git a/test cases/frameworks/1 boost/meson.build b/test cases/frameworks/1 boost/meson.build index 6f25f8b..b73f93a 100644 --- a/test cases/frameworks/1 boost/meson.build +++ b/test cases/frameworks/1 boost/meson.build @@ -9,21 +9,18 @@ add_project_arguments(['-DBOOST_LOG_DYN_LINK'], # within one project. The need to be independent of each other. # Use one without a library dependency and one with it. -nolinkdep = dependency('boost', modules: 'utility') linkdep = dependency('boost', modules : ['thread', 'system']) staticdep = dependency('boost', modules : ['thread', 'system'], static : true) -testdep = dependency('boost', modules : 'test') +testdep = dependency('boost', modules : ['unit_test_framework']) nomoddep = dependency('boost') extralibdep = dependency('boost', modules : ['thread', 'system', 'log_setup', 'log']) -nolinkexe = executable('nolinkedexe', 'nolinkexe.cc', dependencies : nolinkdep) linkexe = executable('linkedexe', 'linkexe.cc', dependencies : linkdep) staticexe = executable('staticlinkedexe', 'linkexe.cc', dependencies : staticdep) unitexe = executable('utf', 'unit_test.cpp', dependencies: testdep) nomodexe = executable('nomod', 'nomod.cpp', dependencies : nomoddep) extralibexe = executable('extralibexe', 'extralib.cpp', dependencies : extralibdep) -test('Boost nolinktest', nolinkexe) test('Boost linktest', linkexe) test('Boost statictest', staticexe) test('Boost UTF test', unitexe) diff --git a/test cases/frameworks/1 boost/nolinkexe.cc b/test cases/frameworks/1 boost/nolinkexe.cc deleted file mode 100644 index 7b6c6d9..0000000 --- a/test cases/frameworks/1 boost/nolinkexe.cc +++ /dev/null @@ -1,12 +0,0 @@ -#include<boost/utility.hpp> - -class MyClass : boost::noncopyable { -public: - MyClass() {}; - ~MyClass() {}; -}; - -int main(int argc, char **argv) { - MyClass obj; - return 0; -} diff --git a/test cases/frameworks/4 qt/meson.build b/test cases/frameworks/4 qt/meson.build index d9cab6f..c6f108b 100644 --- a/test cases/frameworks/4 qt/meson.build +++ b/test cases/frameworks/4 qt/meson.build @@ -70,5 +70,15 @@ foreach qt : ['qt4', 'qt5'] dependencies : qtcore) test(qt + 'maninclude', qtmaninclude) + + # building Qt plugins implies to give include path to moc + plugin_includes = include_directories('pluginInterface', 'plugin') + pluginpreprocess = qtmodule.preprocess( + moc_headers : 'plugin/plugin.h', + include_directories : plugin_includes + ) + plugin = library('plugin', 'plugin/plugin.cpp', pluginpreprocess, + include_directories : plugin_includes, + dependencies : qtcore) endif endforeach diff --git a/test cases/frameworks/4 qt/plugin/plugin.cpp b/test cases/frameworks/4 qt/plugin/plugin.cpp new file mode 100644 index 0000000..eeae98d --- /dev/null +++ b/test cases/frameworks/4 qt/plugin/plugin.cpp @@ -0,0 +1,7 @@ +#include "plugin.h" +#include <QFile> + +QString plugin1::getResource() +{ + return "hello world"; +} diff --git a/test cases/frameworks/4 qt/plugin/plugin.h b/test cases/frameworks/4 qt/plugin/plugin.h new file mode 100644 index 0000000..1138f41 --- /dev/null +++ b/test cases/frameworks/4 qt/plugin/plugin.h @@ -0,0 +1,11 @@ +#pragma once +#include <plugin_if.h> + +class plugin1:public QObject,public PluginInterface +{ + Q_OBJECT + Q_INTERFACES(PluginInterface) + Q_PLUGIN_METADATA(IID "demo.PluginInterface" FILE "plugin.json") +public: + QString getResource() override; +}; diff --git a/test cases/frameworks/4 qt/plugin/plugin.json b/test cases/frameworks/4 qt/plugin/plugin.json new file mode 100644 index 0000000..6c6a011 --- /dev/null +++ b/test cases/frameworks/4 qt/plugin/plugin.json @@ -0,0 +1,3 @@ +{ + "name" : "Plugin1" +} diff --git a/test cases/frameworks/4 qt/pluginInterface/plugin_if.h b/test cases/frameworks/4 qt/pluginInterface/plugin_if.h new file mode 100644 index 0000000..97d2800 --- /dev/null +++ b/test cases/frameworks/4 qt/pluginInterface/plugin_if.h @@ -0,0 +1,21 @@ +#ifndef PLUGIN_IF_H +#define PLUGIN_IF_H + +#include <QString> +#include <QtPlugin> + +/** + * @brief Interface for a plugin + */ +class PluginInterface +{ +public: + virtual ~PluginInterface() = default; + + /// Initializes the plugin + virtual QString getResource() = 0; +}; + +Q_DECLARE_INTERFACE(PluginInterface, "demo.PluginInterface") + +#endif diff --git a/test cases/frameworks/7 gnome/gir/dep1/meson.build b/test cases/frameworks/7 gnome/gir/dep1/meson.build index 75dd731..baa0b1d 100644 --- a/test cases/frameworks/7 gnome/gir/dep1/meson.build +++ b/test cases/frameworks/7 gnome/gir/dep1/meson.build @@ -19,6 +19,7 @@ dep1gir = gnome.generate_gir( namespace : 'MesonDep1', symbol_prefix : 'meson', identifier_prefix : 'Meson', + header: 'dep1.h', includes : ['GObject-2.0', 'MesonDep2-1.0'], dependencies : [dep2_dep], install : true diff --git a/test cases/frameworks/7 gnome/gir/meson.build b/test cases/frameworks/7 gnome/gir/meson.build index 3598b66..a91cb97 100644 --- a/test cases/frameworks/7 gnome/gir/meson.build +++ b/test cases/frameworks/7 gnome/gir/meson.build @@ -27,7 +27,7 @@ gnome.generate_gir( identifier_prefix : 'Meson', includes : ['GObject-2.0', 'MesonDep1-1.0'], # dep1_dep pulls in dep2_dep for us - dependencies : [fake_dep, dep1_dep], + dependencies : [[fake_dep, dep1_dep]], install : true, build_by_default : true, # Test that unknown kwargs do not crash the parser. diff --git a/tools/boost_names.py b/tools/boost_names.py new file mode 100755 index 0000000..9ac16bb --- /dev/null +++ b/tools/boost_names.py @@ -0,0 +1,180 @@ +#!/usr/bin/env python3 + +# Copyright 2017 Niklas Claesson + +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at + +# http://www.apache.org/licenses/LICENSE-2.0 + +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +"""This is two implementations for how to get module names from the boost +sources. One relies on json metadata files in the sources, the other relies on +the folder names. + +Run the tool in the boost directory and append the stdout to the misc.py: + +boost/$ path/to/meson/tools/boost_names.py >> path/to/meson/dependencies/misc.py +""" + +import sys +import os +import collections +import pprint +import json +import re + +Module = collections.namedtuple('Module', ['dirname', 'name', 'libnames']) +Module.__repr__ = lambda self: str((self.dirname, self.name, self.libnames)) + +LIBS = 'libs' + +manual_map = { + 'callable_traits': 'Call Traits', + 'crc': 'CRC', + 'dll': 'DLL', + 'gil': 'GIL', + 'graph_parallel': 'GraphParallel', + 'icl': 'ICL', + 'io': 'IO State Savers', + 'msm': 'Meta State Machine', + 'mpi': 'MPI', + 'mpl': 'MPL', + 'multi_array': 'Multi-Array', + 'multi_index': 'Multi-Index', + 'numeric': 'Numeric Conversion', + 'ptr_container': 'Pointer Container', + 'poly_collection': 'PolyCollection', + 'qvm': 'QVM', + 'throw_exception': 'ThrowException', + 'tti': 'TTI', + 'vmd': 'VMD', +} + +extra = [ + Module('utility', 'Compressed Pair', []), + Module('core', 'Enable If', []), + Module('functional', 'Functional/Factory', []), + Module('functional', 'Functional/Forward', []), + Module('functional', 'Functional/Hash', []), + Module('functional', 'Functional/Overloaded Function', []), + Module('utility', 'Identity Type', []), + Module('utility', 'In Place Factory, Typed In Place Factory', []), + Module('numeric', 'Interval', []), + Module('math', 'Math Common Factor', []), + Module('math', 'Math Octonion', []), + Module('math', 'Math Quaternion', []), + Module('math', 'Math/Special Functions', []), + Module('math', 'Math/Statistical Distributions', []), + Module('bind', 'Member Function', []), + Module('algorithm', 'Min-Max', []), + Module('numeric', 'Odeint', []), + Module('utility', 'Operators', []), + Module('core', 'Ref', []), + Module('utility', 'Result Of', []), + Module('algorithm', 'String Algo', []), + Module('core', 'Swap', []), + Module('', 'Tribool', []), + Module('numeric', 'uBLAS', []), + Module('utility', 'Value Initialized', []), +] + +# Cannot find the following modules in the documentation of boost +not_modules = ['beast', 'logic', 'mp11', 'winapi'] + +def eprint(message): + print(message, file=sys.stderr) + +def get_library_names(jamfile): + libs = [] + with open(jamfile) as jamfh: + jam = jamfh.read() + res = re.finditer(r'^lib[\s]+([A-Za-z0-9_]+)([^;]*);', jam, re.MULTILINE | re.DOTALL) + for matches in res: + if ':' in matches.group(2): + libs.append(matches.group(1)) + return libs + +def exists(modules, module): + return len([x for x in modules if x.dirname == module.dirname]) != 0 + +def get_modules(init=extra): + modules = init + for directory in os.listdir(LIBS): + if not os.path.isdir(os.path.join(LIBS, directory)): + continue + if directory in not_modules: + continue + jamfile = os.path.join(LIBS, directory, 'build', 'Jamfile.v2') + if os.path.isfile(jamfile): + libs = get_library_names(jamfile) + else: + libs = [] + if directory in manual_map.keys(): + modname = manual_map[directory] + else: + modname = directory.replace('_', ' ').title() + modules.append(Module(directory, modname, libs)) + return modules + +def get_modules_2(): + modules = [] + for (root, dirs, files) in os.walk(LIBS): + for f in files: + if f == "libraries.json": + projectdir = os.path.dirname(root) + + jamfile = os.path.join(projectdir, 'build', 'Jamfile.v2') + if os.path.isfile(jamfile): + libs = get_library_names(jamfile) + else: + libs = [] + + # Get metadata for module + jsonfile = os.path.join(root, f) + with open(jsonfile) as jsonfh: + boost_modules = json.loads(jsonfh.read()) + if(isinstance(boost_modules, dict)): + boost_modules = [boost_modules] + for boost_module in boost_modules: + modules.append(Module(boost_module['key'], boost_module['name'], libs)) + + # Some subprojects do not have meta directory with json file. Find those + jsonless_modules = [x for x in get_modules([]) if not exists(modules, x)] + for module in jsonless_modules: + eprint("WARNING: {} does not have meta/libraries.json. Will guess pretty name '{}'".format(module.dirname, module.name)) + modules.extend(jsonless_modules) + + return modules + +def main(args): + if not os.path.isdir(LIBS): + eprint("ERROR: script must be run in boost source directory") + + # It will pick jsonless algorithm if 1 is given as argument + impl = 0 + if len(args) > 1: + if args[1] == '1': + impl = 1 + + if impl == 1: + modules = get_modules() + else: + modules = get_modules_2() + + sorted_modules = sorted(modules, key=lambda module: module.name.lower()) + sorted_modules = [x[2] for x in sorted_modules if x[2]] + sorted_modules = sum(sorted_modules, []) + sorted_modules = [x for x in sorted_modules if x.startswith('boost')] + + pp = pprint.PrettyPrinter() + pp.pprint(sorted_modules) + +if __name__ == '__main__': + main(sys.argv) |