diff options
60 files changed, 994 insertions, 594 deletions
diff --git a/.appveyor.yml b/.appveyor.yml index 1e20a37..6551445 100644 --- a/.appveyor.yml +++ b/.appveyor.yml @@ -55,6 +55,8 @@ skip_commits: 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 the x86 python only when building for x86 for the cpython tests. # For all other archs (including, say, arm), use the x64 python. - ps: (new-object net.webclient).DownloadFile('https://www.dropbox.com/s/bbzvepq85hv47x1/ninja.exe?dl=1', 'C:\projects\meson\ninja.exe') diff --git a/docs/markdown/Gnome-module.md b/docs/markdown/Gnome-module.md index 61b88ad..e709fd5 100644 --- a/docs/markdown/Gnome-module.md +++ b/docs/markdown/Gnome-module.md @@ -127,9 +127,11 @@ This also creates two targets for translations `help-$project-update-po` and `he * `sources`: list of pages * `media`: list of media such as images -* `symlink_media`: if media should be symlinked (requires newish yelp) defaults to `false` +* `symlink_media`: if media should be symlinked not copied (defaults to `true` since 0.42.0) * `languages`: list of languages for translations +Note that very old versions of yelp may not support symlinked media; At least 3.10 should work. + *Added 0.36.0* ### gnome.gtkdoc() diff --git a/docs/markdown/Release-notes-for-0.41.0.md b/docs/markdown/Release-notes-for-0.41.0.md index 6e46828..1ddde95 100644 --- a/docs/markdown/Release-notes-for-0.41.0.md +++ b/docs/markdown/Release-notes-for-0.41.0.md @@ -1,21 +1,17 @@ --- title: Release 0.41 -short-description: Release notes for 0.41 (preliminary) +short-description: Release notes for 0.41 ... -**Preliminary, 0.41.0 has not been released yet.** - # New features -Add features here as code is merged to master. - ## Dependency Handler for LLVM Native support for linking against LLVM using the `dependency` function. ## vcs_tag keyword fallback is is now optional -The `fallback` keyword in `vcs_tag` is now optional. If not given, its value +The `fallback` keyword in `vcs_tag()` is now optional. If not given, its value defaults to the return value of `meson.project_version()`. ## Better quoting of special characters in ninja command invocations @@ -60,7 +56,7 @@ Targets for building rust now take a `rust_args` keyword. Code coverage can be generated for tests by passing the `--cov` argument to the `run_tests.py` test runner. Note, since multiple processes are used, -coverage must be combined before producing a report (`coverage3 combine`.) +coverage must be combined before producing a report (`coverage3 combine`). ## Reproducible builds diff --git a/docs/markdown/Release-notes-for-0.42.0.md b/docs/markdown/Release-notes-for-0.42.0.md new file mode 100644 index 0000000..dcb86c3 --- /dev/null +++ b/docs/markdown/Release-notes-for-0.42.0.md @@ -0,0 +1,13 @@ +--- +title: Release 0.42 +short-description: Release notes for 0.42 (preliminary) +... + +**Preliminary, 0.42.0 has not been released yet.** + +# New features + +## Distribution tarballs from Mercurial repositories + +Creating distribution tarballs can now be made out of projects based on +Mercurial. As before, this remains possible only with the Ninja backend. diff --git a/docs/sitemap.txt b/docs/sitemap.txt index 7246749..9831b93 100644 --- a/docs/sitemap.txt +++ b/docs/sitemap.txt @@ -58,6 +58,7 @@ index.md Shipping-prebuilt-binaries-as-wraps.md fallback-wraptool.md Release-notes.md + Release-notes-for-0.42.0.md Release-notes-for-0.41.0.md Release-notes-for-0.40.0.md Release-notes-for-0.39.0.md diff --git a/man/meson.1 b/man/meson.1 index 185e097..1cd60b6 100644 --- a/man/meson.1 +++ b/man/meson.1 @@ -1,4 +1,4 @@ -.TH MESON "1" "April 2017" "meson 0.40.1" "User Commands" +.TH MESON "1" "June 2017" "meson 0.41.1" "User Commands" .SH NAME meson - a high productivity build system .SH DESCRIPTION diff --git a/man/mesonconf.1 b/man/mesonconf.1 index 388d46b..6bb9d39 100644 --- a/man/mesonconf.1 +++ b/man/mesonconf.1 @@ -1,4 +1,4 @@ -.TH MESONCONF "1" "April 2017" "mesonconf 0.40.1" "User Commands" +.TH MESONCONF "1" "June 2017" "mesonconf 0.41.1" "User Commands" .SH NAME mesonconf - a tool to configure Meson builds .SH DESCRIPTION diff --git a/man/mesonintrospect.1 b/man/mesonintrospect.1 index 4c64d6e..1918b4f 100644 --- a/man/mesonintrospect.1 +++ b/man/mesonintrospect.1 @@ -1,4 +1,4 @@ -.TH MESONCONF "1" "April 2017" "mesonintrospect 0.40.1" "User Commands" +.TH MESONCONF "1" "June 2017" "mesonintrospect 0.41.1" "User Commands" .SH NAME mesonintrospect - a tool to extract information about a Meson build .SH DESCRIPTION diff --git a/man/mesontest.1 b/man/mesontest.1 index 5731a9e..cf21fe7 100644 --- a/man/mesontest.1 +++ b/man/mesontest.1 @@ -1,4 +1,4 @@ -.TH MESON "1" "April 2017" "meson 0.40.1" "User Commands" +.TH MESON "1" "June 2017" "meson 0.41.1" "User Commands" .SH NAME mesontest - test tool for the Meson build system .SH DESCRIPTION diff --git a/man/wraptool.1 b/man/wraptool.1 index 84cb03c..73c2568 100644 --- a/man/wraptool.1 +++ b/man/wraptool.1 @@ -1,4 +1,4 @@ -.TH WRAPTOOL "1" "April 2017" "meson 0.40.1" "User Commands" +.TH WRAPTOOL "1" "June 2017" "meson 0.41.1" "User Commands" .SH NAME wraptool - source dependency downloader .SH DESCRIPTION diff --git a/mesonbuild/backend/backends.py b/mesonbuild/backend/backends.py index 3044ce6..1dd128b 100644 --- a/mesonbuild/backend/backends.py +++ b/mesonbuild/backend/backends.py @@ -294,6 +294,15 @@ class Backend: raise MesonException(m.format(target.name)) return l + def determine_rpath_dirs(self, target): + link_deps = target.get_all_link_deps() + result = [] + for ld in link_deps: + prospective = self.get_target_dir(ld) + if prospective not in result: + result.append(prospective) + return result + def object_filename_from_source(self, target, source, is_unity): if isinstance(source, mesonlib.File): source = source.fname @@ -429,7 +438,7 @@ class Backend: break commands += ['--pkg', dep.name] elif isinstance(dep, dependencies.ExternalLibrary): - commands += dep.get_lang_args('vala') + commands += dep.get_link_args('vala') else: commands += dep.get_compile_args() # Qt needs -fPIC for executables diff --git a/mesonbuild/backend/ninjabackend.py b/mesonbuild/backend/ninjabackend.py index 3638621..761d508 100644 --- a/mesonbuild/backend/ninjabackend.py +++ b/mesonbuild/backend/ninjabackend.py @@ -510,12 +510,12 @@ int dummy; cmd_type = 'meson_exe.py custom' else: cmd_type = 'custom' - if target.depfile is not None: rel_dfile = os.path.join(self.get_target_dir(target), target.depfile) abs_pdir = os.path.join(self.environment.get_build_dir(), self.get_target_dir(target)) os.makedirs(abs_pdir, exist_ok=True) elem.add_item('DEPFILE', rel_dfile) + cmd = self.replace_paths(target, cmd) elem.add_item('COMMAND', cmd) elem.add_item('description', desc.format(target.name, cmd_type)) elem.write(outfile) @@ -564,7 +564,9 @@ int dummy; else: cmd.append(target.command) cmd += arg_strings + elem.add_dep(deps) + cmd = self.replace_paths(target, cmd) elem.add_item('COMMAND', cmd) elem.add_item('description', 'Running external command %s.' % target.name) elem.add_item('pool', 'console') @@ -1098,7 +1100,13 @@ int dummy; args += ['--library=' + target.name] # Outputted header hname = os.path.join(self.get_target_dir(target), target.vala_header) - args += ['-H', hname, '--use-header'] + args += ['-H', hname] + if self.is_unity(target): + # Without this the declarations will get duplicated in the .c + # files and cause a build failure when all of them are + # #include-d in one .c file. + # https://github.com/mesonbuild/meson/issues/1969 + args += ['--use-header'] valac_outputs.append(hname) # Outputted vapi file vapiname = os.path.join(self.get_target_dir(target), target.vala_vapi) @@ -1683,6 +1691,16 @@ rule FORTRAN_DEP_HACK continue self.generate_genlist_for_target(genlist, target, outfile) + def replace_paths(self, target, args): + source_target_dir = self.get_target_source_dir(target) + relout = self.get_target_private_dir(target) + args = [x.replace("@SOURCE_DIR@", self.build_to_src).replace("@BUILD_DIR@", relout) + for x in args] + args = [x.replace("@CURRENT_SOURCE_DIR@", source_target_dir) for x in args] + args = [x.replace("@SOURCE_ROOT@", self.build_to_src).replace("@BUILD_ROOT@", '.') + for x in args] + return args + def generate_genlist_for_target(self, genlist, target, outfile): generator = genlist.get_generator() exe = generator.get_exe() @@ -1716,11 +1734,7 @@ rule FORTRAN_DEP_HACK if sole_output == '': outfilelist = outfilelist[len(generator.outputs):] relout = self.get_target_private_dir(target) - args = [x.replace("@SOURCE_DIR@", self.build_to_src).replace("@BUILD_DIR@", relout) - for x in args] - args = [x.replace("@CURRENT_SOURCE_DIR@", source_target_dir) for x in args] - args = [x.replace("@SOURCE_ROOT@", self.build_to_src).replace("@BUILD_ROOT@", '.') - for x in args] + args = self.replace_paths(target, args) cmdlist = exe_arr + self.replace_extra_args(args, genlist) elem = NinjaBuildElement(self.all_outputs, outfiles, rulename, infilename) if generator.depfile is not None: @@ -2328,11 +2342,13 @@ rule FORTRAN_DEP_HACK commands += target.link_args # External deps must be last because target link libraries may depend on them. for dep in target.get_external_deps(): - commands += dep.get_link_args() + # Extend without reordering or de-dup to preserve `-L -l` sets + # https://github.com/mesonbuild/meson/issues/1718 + commands.extend_direct(dep.get_link_args()) for d in target.get_dependencies(): if isinstance(d, build.StaticLibrary): for dep in d.get_external_deps(): - commands += dep.get_link_args() + commands.extend_direct(dep.get_link_args()) # Add link args for c_* or cpp_* build options. Currently this only # adds c_winlibs and cpp_winlibs when building for Windows. This needs # to be after all internal and external libraries so that unresolved @@ -2368,15 +2384,6 @@ rule FORTRAN_DEP_HACK elem.add_item('LINK_ARGS', commands) return elem - def determine_rpath_dirs(self, target): - link_deps = target.get_all_link_deps() - result = [] - for ld in link_deps: - prospective = self.get_target_dir(ld) - if prospective not in result: - result.append(prospective) - return result - def get_dependency_filename(self, t): if isinstance(t, build.SharedLibrary): return os.path.join(self.get_target_private_dir(t), self.get_target_filename(t) + '.symbols') diff --git a/mesonbuild/backend/vs2010backend.py b/mesonbuild/backend/vs2010backend.py index fafde8f..57b0437 100644 --- a/mesonbuild/backend/vs2010backend.py +++ b/mesonbuild/backend/vs2010backend.py @@ -29,26 +29,24 @@ from ..environment import Environment def autodetect_vs_version(build): vs_version = os.getenv('VisualStudioVersion', None) - if vs_version: - if vs_version == '14.0': - from mesonbuild.backend.vs2015backend import Vs2015Backend - return Vs2015Backend(build) - if vs_version == '15.0': - from mesonbuild.backend.vs2017backend import Vs2017Backend - return Vs2017Backend(build) - raise MesonException('Could not detect Visual Studio (unknown Visual Studio version: "{}")!\n' - 'Please specify the exact backend to use.'.format(vs_version)) - vs_install_dir = os.getenv('VSINSTALLDIR', None) - if not vs_install_dir: - raise MesonException('Could not detect Visual Studio (neither VisualStudioVersion nor VSINSTALLDIR set in ' - 'environment)!\nPlease specify the exact backend to use.') - + if not vs_version and not vs_install_dir: + raise MesonException('Could not detect Visual Studio: VisualStudioVersion and VSINSTALLDIR are unset!\n' + 'Are we inside a Visual Studio build environment? ' + 'You can also try specifying the exact backend to use.') + # VisualStudioVersion is set since Visual Studio 12.0, but sometimes + # vcvarsall.bat doesn't set it, so also use VSINSTALLDIR + if vs_version == '14.0' or 'Visual Studio 14' in vs_install_dir: + from mesonbuild.backend.vs2015backend import Vs2015Backend + return Vs2015Backend(build) + if vs_version == '15.0' or 'Visual Studio 17' in vs_install_dir or \ + 'Visual Studio\\2017' in vs_install_dir: + from mesonbuild.backend.vs2017backend import Vs2017Backend + return Vs2017Backend(build) if 'Visual Studio 10.0' in vs_install_dir: return Vs2010Backend(build) - - raise MesonException('Could not detect Visual Studio (unknown VSINSTALLDIR: "{}")!\n' - 'Please specify the exact backend to use.'.format(vs_install_dir)) + raise MesonException('Could not detect Visual Studio using VisualStudioVersion: {!r} or VSINSTALLDIR: {!r}!\n' + 'Please specify the exact backend to use.'.format(vs_version, vs_install_dir)) def split_o_flags_args(args): """ @@ -910,11 +908,13 @@ class Vs2010Backend(backends.Backend): extra_link_args += target.link_args # External deps must be last because target link libraries may depend on them. for dep in target.get_external_deps(): - extra_link_args += dep.get_link_args() + # Extend without reordering or de-dup to preserve `-L -l` sets + # https://github.com/mesonbuild/meson/issues/1718 + extra_link_args.extend_direct(dep.get_link_args()) for d in target.get_dependencies(): if isinstance(d, build.StaticLibrary): for dep in d.get_external_deps(): - extra_link_args += dep.get_link_args() + extra_link_args.extend_direct(dep.get_link_args()) # Add link args for c_* or cpp_* build options. Currently this only # adds c_winlibs and cpp_winlibs when building for Windows. This needs # to be after all internal and external libraries so that unresolved diff --git a/mesonbuild/backend/vs2017backend.py b/mesonbuild/backend/vs2017backend.py index 35d56f3..fe1d7c7 100644 --- a/mesonbuild/backend/vs2017backend.py +++ b/mesonbuild/backend/vs2017backend.py @@ -24,4 +24,6 @@ class Vs2017Backend(Vs2010Backend): self.platform_toolset = 'v141' self.vs_version = '2017' # WindowsSDKVersion should be set by command prompt. - self.windows_target_platform_version = os.getenv('WindowsSDKVersion', None).rstrip('\\') + sdk_version = os.environ.get('WindowsSDKVersion', None) + if sdk_version: + self.windows_target_platform_version = sdk_version.rstrip('\\') diff --git a/mesonbuild/build.py b/mesonbuild/build.py index 7ce6fa6..c73ba3a 100644 --- a/mesonbuild/build.py +++ b/mesonbuild/build.py @@ -807,7 +807,7 @@ class BuildTarget(Target): self.external_deps.append(extpart) # Deps of deps. self.add_deps(dep.ext_deps) - elif isinstance(dep, dependencies.Dependency): + elif isinstance(dep, dependencies.ExternalDependency): self.external_deps.append(dep) self.process_sourcelist(dep.get_sources()) elif isinstance(dep, BuildTarget): diff --git a/mesonbuild/compilers.py b/mesonbuild/compilers.py index 833071e..979a5ac 100644 --- a/mesonbuild/compilers.py +++ b/mesonbuild/compilers.py @@ -430,6 +430,17 @@ class CompilerArgs(list): to recursively search for symbols in the libraries. This is not needed with other linkers. ''' + + # A standalone argument must never be deduplicated because it is + # defined by what comes _after_ it. Thus dedupping this: + # -D FOO -D BAR + # would yield either + # -D FOO BAR + # or + # FOO -D BAR + # both of which are invalid. + if arg in cls.dedup2_prefixes: + return 0 if arg in cls.dedup2_args or \ arg.startswith(cls.dedup2_prefixes) or \ arg.endswith(cls.dedup2_suffixes): @@ -465,6 +476,19 @@ class CompilerArgs(list): self.insert(i + 1, '-Wl,--end-group') return self.compiler.unix_args_to_native(self) + def append_direct(self, arg): + ''' + Append the specified argument without any reordering or de-dup + ''' + super().append(arg) + + def extend_direct(self, iterable): + ''' + Extend using the elements in the specified iterable without any + reordering or de-dup + ''' + super().extend(iterable) + def __add__(self, args): new = CompilerArgs(self, self.compiler) new += args @@ -731,35 +755,35 @@ class Compiler: raise EnvironmentException('Language %s does not support linking whole archives.' % self.language) def build_unix_rpath_args(self, build_dir, from_dir, rpath_paths, install_rpath): - if not rpath_paths and not install_rpath: - return [] - # The rpaths we write must be relative, because otherwise - # they have different length depending on the build - # directory. This breaks reproducible builds. - rel_rpaths = [] - for p in rpath_paths: - if p == from_dir: - relative = '' # relpath errors out in this case - else: - relative = os.path.relpath(p, from_dir) - rel_rpaths.append(relative) - paths = ':'.join([os.path.join('$ORIGIN', p) for p in rel_rpaths]) - if len(paths) < len(install_rpath): - padding = 'X' * (len(install_rpath) - len(paths)) - if not paths: - paths = padding - else: - paths = paths + ':' + padding - args = ['-Wl,-rpath,' + paths] - if get_compiler_is_linuxlike(self): - # Rpaths to use while linking must be absolute. These are not - # written to the binary. Needed only with GNU ld: - # https://sourceware.org/bugzilla/show_bug.cgi?id=16936 - # Not needed on Windows or other platforms that don't use RPATH - # https://github.com/mesonbuild/meson/issues/1897 - lpaths = ':'.join([os.path.join(build_dir, p) for p in rpath_paths]) - args += ['-Wl,-rpath-link,' + lpaths] - return args + if not rpath_paths and not install_rpath: + return [] + # The rpaths we write must be relative, because otherwise + # they have different length depending on the build + # directory. This breaks reproducible builds. + rel_rpaths = [] + for p in rpath_paths: + if p == from_dir: + relative = '' # relpath errors out in this case + else: + relative = os.path.relpath(p, from_dir) + rel_rpaths.append(relative) + paths = ':'.join([os.path.join('$ORIGIN', p) for p in rel_rpaths]) + if len(paths) < len(install_rpath): + padding = 'X' * (len(install_rpath) - len(paths)) + if not paths: + paths = padding + else: + paths = paths + ':' + padding + args = ['-Wl,-rpath,' + paths] + if get_compiler_is_linuxlike(self): + # Rpaths to use while linking must be absolute. These are not + # written to the binary. Needed only with GNU ld: + # https://sourceware.org/bugzilla/show_bug.cgi?id=16936 + # Not needed on Windows or other platforms that don't use RPATH + # https://github.com/mesonbuild/meson/issues/1897 + lpaths = ':'.join([os.path.join(build_dir, p) for p in rpath_paths]) + args += ['-Wl,-rpath-link,' + lpaths] + return args class CCompiler(Compiler): def __init__(self, exelist, version, is_cross, exe_wrapper=None): @@ -1029,7 +1053,7 @@ class CCompiler(Compiler): def _links_wrapper(self, code, env, extra_args, dependencies): "Shares common code between self.links and self.run" args = self._get_compiler_check_args(env, extra_args, dependencies, mode='link') - return self.compile(code, args.to_native()) + return self.compile(code, args) def links(self, code, env, extra_args=None, dependencies=None): with self._links_wrapper(code, env, extra_args, dependencies) as p: @@ -1773,7 +1797,7 @@ class ValaCompiler(Compiler): for d in extra_dirs: vapi = os.path.join(d, libname + '.vapi') if os.path.isfile(vapi): - return vapi + return [vapi] mlog.debug('Searched {!r} and {!r} wasn\'t found'.format(extra_dirs, libname)) return None diff --git a/mesonbuild/coredata.py b/mesonbuild/coredata.py index 7bc20f8..80ad94f 100644 --- a/mesonbuild/coredata.py +++ b/mesonbuild/coredata.py @@ -19,7 +19,7 @@ from .mesonlib import MesonException, commonpath from .mesonlib import default_libdir, default_libexecdir, default_prefix import ast -version = '0.41.0.dev1' +version = '0.42.0.dev1' backendlist = ['ninja', 'vs', 'vs2010', 'vs2015', 'vs2017', 'xcode'] class UserOption: diff --git a/mesonbuild/dependencies/__init__.py b/mesonbuild/dependencies/__init__.py index ec11152..3d41a2b 100644 --- a/mesonbuild/dependencies/__init__.py +++ b/mesonbuild/dependencies/__init__.py @@ -13,8 +13,9 @@ # limitations under the License. from .base import ( # noqa: F401 - Dependency, DependencyException, DependencyMethods, ExternalProgram, ExternalLibrary, ExtraFrameworkDependency, - InternalDependency, PkgConfigDependency, find_external_dependency, get_dep_identifier, packages) + Dependency, DependencyException, DependencyMethods, ExternalProgram, + ExternalDependency, ExternalLibrary, ExtraFrameworkDependency, InternalDependency, + PkgConfigDependency, find_external_dependency, get_dep_identifier, packages) from .dev import GMockDependency, GTestDependency, LLVMDependency, ValgrindDependency from .misc import BoostDependency, Python3Dependency, ThreadDependency from .platform import AppleFrameworks diff --git a/mesonbuild/dependencies/base.py b/mesonbuild/dependencies/base.py index 139ff39..fdb5ab8 100644 --- a/mesonbuild/dependencies/base.py +++ b/mesonbuild/dependencies/base.py @@ -52,10 +52,17 @@ class DependencyMethods(Enum): class Dependency: def __init__(self, type_name, kwargs): self.name = "null" - self.language = None + self.version = 'none' + self.language = None # None means C-like self.is_found = False self.type_name = type_name - method = DependencyMethods(kwargs.get('method', 'auto')) + self.compile_args = [] + self.link_args = [] + self.sources = [] + method = kwargs.get('method', 'auto') + if method not in [e.value for e in DependencyMethods]: + raise DependencyException('method {!r} is invalid'.format(method)) + method = DependencyMethods(method) # 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. @@ -64,7 +71,7 @@ class Dependency: elif method in self.get_methods(): self.methods = [method] else: - raise MesonException( + raise DependencyException( 'Unsupported detection method: {}, allowed methods are {}'.format( method.value, mlog.format_list(map(lambda x: x.value, [DependencyMethods.AUTO] + self.get_methods())))) @@ -74,10 +81,10 @@ class Dependency: return s.format(self.__class__.__name__, self.name, self.is_found) def get_compile_args(self): - return [] + return self.compile_args def get_link_args(self): - return [] + return self.link_args def found(self): return self.is_found @@ -85,7 +92,7 @@ class Dependency: def get_sources(self): """Source files that need to be added to the target. As an example, gtest-all.cc when using GTest.""" - return [] + return self.sources def get_methods(self): return [DependencyMethods.AUTO] @@ -93,6 +100,9 @@ class Dependency: def get_name(self): return self.name + def get_version(self): + return self.version + def get_exe_args(self, compiler): return [] @@ -100,7 +110,7 @@ class Dependency: return False def get_pkgconfig_variable(self, variable_name): - raise MesonException('Tried to get a pkg-config variable from a non-pkgconfig dependency.') + raise NotImplementedError('{!r} is not a pkgconfig dependency'.format(self.name)) class InternalDependency(Dependency): @@ -115,41 +125,52 @@ class InternalDependency(Dependency): self.sources = sources self.ext_deps = ext_deps - def get_compile_args(self): - return self.compile_args - def get_link_args(self): - return self.link_args +class ExternalDependency(Dependency): + def __init__(self, type_name, environment, language, kwargs): + super().__init__(type_name, kwargs) + self.env = environment + self.name = type_name # default + self.is_found = False + self.language = language + if language and language not in self.env.coredata.compilers: + m = self.name.capitalize() + ' requires a {} compiler' + raise DependencyException(m.format(language.capitalize())) + self.version_reqs = kwargs.get('version', None) + self.required = kwargs.get('required', True) + self.silent = kwargs.get('silent', False) + self.static = kwargs.get('static', False) + if not isinstance(self.static, bool): + raise DependencyException('Static keyword must be boolean') + # Is this dependency for cross-compilation? + if 'native' in kwargs and self.env.is_cross_build(): + self.want_cross = not kwargs['native'] + else: + self.want_cross = self.env.is_cross_build() + # Set the compiler that will be used by this dependency + # This is only used for configuration checks + if self.want_cross: + compilers = self.env.coredata.cross_compilers + else: + compilers = self.env.coredata.compilers + self.compiler = compilers.get(self.language or 'c', None) - def get_version(self): - return self.version + def get_compiler(self): + return self.compiler -class PkgConfigDependency(Dependency): +class PkgConfigDependency(ExternalDependency): # The class's copy of the pkg-config path. Avoids having to search for it # multiple times in the same Meson invocation. class_pkgbin = None def __init__(self, name, environment, kwargs): - Dependency.__init__(self, 'pkgconfig', kwargs) + super().__init__('pkgconfig', environment, None, kwargs) + self.name = name self.is_libtool = False - self.version_reqs = kwargs.get('version', None) - self.required = kwargs.get('required', True) - self.static = kwargs.get('static', False) - self.silent = kwargs.get('silent', False) - if not isinstance(self.static, bool): - raise DependencyException('Static keyword must be boolean') # Store a copy of the pkg-config path on the object itself so it is # stored in the pickled coredata and recovered. self.pkgbin = None - self.cargs = [] - self.libs = [] - if 'native' in kwargs and environment.is_cross_build(): - self.want_cross = not kwargs['native'] - else: - self.want_cross = environment.is_cross_build() - self.name = name - self.modversion = 'none' # When finding dependencies for cross-compiling, we don't care about # the 'native' pkg-config @@ -175,7 +196,6 @@ class PkgConfigDependency(Dependency): else: self.pkgbin = PkgConfigDependency.class_pkgbin - self.is_found = False if not self.pkgbin: if self.required: raise DependencyException('Pkg-config not found.') @@ -187,7 +207,7 @@ class PkgConfigDependency(Dependency): mlog.debug('Determining dependency {!r} with pkg-config executable ' '{!r}'.format(name, self.pkgbin)) - ret, self.modversion = self._call_pkgbin(['--modversion', name]) + ret, self.version = self._call_pkgbin(['--modversion', name]) if ret != 0: if self.required: raise DependencyException('{} dependency {!r} not found' @@ -202,10 +222,10 @@ class PkgConfigDependency(Dependency): if isinstance(self.version_reqs, str): self.version_reqs = [self.version_reqs] (self.is_found, not_found, found) = \ - version_compare_many(self.modversion, self.version_reqs) + version_compare_many(self.version, self.version_reqs) if not self.is_found: found_msg += [mlog.red('NO'), - 'found {!r} but need:'.format(self.modversion), + 'found {!r} but need:'.format(self.version), ', '.join(["'{}'".format(e) for e in not_found])] if found: found_msg += ['; matched:', @@ -214,9 +234,9 @@ class PkgConfigDependency(Dependency): mlog.log(*found_msg) if self.required: m = 'Invalid version of dependency, need {!r} {!r} found {!r}.' - raise DependencyException(m.format(name, not_found, self.modversion)) + raise DependencyException(m.format(name, not_found, self.version)) return - found_msg += [mlog.green('YES'), self.modversion] + found_msg += [mlog.green('YES'), self.version] # Fetch cargs to be used while using this dependency self._set_cargs() # Fetch the libraries and library paths needed for using this @@ -240,7 +260,7 @@ class PkgConfigDependency(Dependency): if ret != 0: raise DependencyException('Could not generate cargs for %s:\n\n%s' % (self.name, out)) - self.cargs = out.split() + self.compile_args = out.split() def _set_libs(self): libcmd = [self.name, '--libs'] @@ -250,7 +270,7 @@ class PkgConfigDependency(Dependency): if ret != 0: raise DependencyException('Could not generate libs for %s:\n\n%s' % (self.name, out)) - self.libs = [] + self.link_args = [] for lib in out.split(): if lib.endswith(".la"): shared_libname = self.extract_libtool_shlib(lib) @@ -264,7 +284,7 @@ class PkgConfigDependency(Dependency): 'library path' % lib) lib = shared_lib self.is_libtool = True - self.libs.append(lib) + self.link_args.append(lib) def get_pkgconfig_variable(self, variable_name): ret, out = self._call_pkgbin(['--variable=' + variable_name, self.name]) @@ -278,18 +298,6 @@ class PkgConfigDependency(Dependency): mlog.debug('Got pkgconfig variable %s : %s' % (variable_name, variable)) return variable - def get_modversion(self): - return self.modversion - - def get_version(self): - return self.modversion - - def get_compile_args(self): - return self.cargs - - def get_link_args(self): - return self.libs - def get_methods(self): return [DependencyMethods.PKGCONFIG] @@ -319,9 +327,6 @@ class PkgConfigDependency(Dependency): mlog.log('Found Pkg-config:', mlog.red('NO')) return pkgbin - def found(self): - return self.is_found - def extract_field(self, la_file, fieldname): with open(la_file) as f: for line in f: @@ -500,52 +505,40 @@ class ExternalProgram: return self.name -class ExternalLibrary(Dependency): - # TODO: Add `language` support to all Dependency objects so that languages - # can be exposed for dependencies that support that (i.e., not pkg-config) - def __init__(self, name, link_args, language, silent=False): - super().__init__('external', {}) +class ExternalLibrary(ExternalDependency): + def __init__(self, name, link_args, environment, language, silent=False): + super().__init__('external', environment, language, {}) self.name = name self.language = language self.is_found = False - self.link_args = [] - self.lang_args = [] if link_args: self.is_found = True - if not isinstance(link_args, list): - link_args = [link_args] - self.lang_args = {language: link_args} - # We special-case Vala for now till the Dependency object gets - # proper support for exposing the language it was written in. - # Without this, vala-specific link args will end up in the C link - # args list if you link to a Vala library. - # This hack use to be in CompilerHolder.find_library(). - if language != 'vala': - self.link_args = link_args + self.link_args = link_args if not silent: if self.is_found: mlog.log('Library', mlog.bold(name), 'found:', mlog.green('YES')) else: mlog.log('Library', mlog.bold(name), 'found:', mlog.red('NO')) - def found(self): - return self.is_found - - def get_name(self): - return self.name - - def get_link_args(self): + def get_link_args(self, language=None): + ''' + External libraries detected using a compiler must only be used with + compatible code. For instance, Vala libraries (.vapi files) cannot be + used with C code, and not all Rust library types can be linked with + C-like code. Note that C++ libraries *can* be linked with C code with + a C++ linker (and vice-versa). + ''' + # Using a vala library in a non-vala target, or a non-vala library in a vala target + # XXX: This should be extended to other non-C linkers such as Rust + if (self.language == 'vala' and language != 'vala') or \ + (language == 'vala' and self.language != 'vala'): + return [] return self.link_args - def get_lang_args(self, lang): - if lang in self.lang_args: - return self.lang_args[lang] - return [] - -class ExtraFrameworkDependency(Dependency): - def __init__(self, name, required, path, kwargs): - Dependency.__init__(self, 'extraframeworks', kwargs) +class ExtraFrameworkDependency(ExternalDependency): + def __init__(self, name, required, path, env, lang, kwargs): + super().__init__('extraframeworks', env, lang, kwargs) self.name = None self.required = required self.detect(name, path) @@ -570,6 +563,7 @@ class ExtraFrameworkDependency(Dependency): continue self.path = p self.name = d + self.is_found = True return if not self.found() and self.required: raise DependencyException('Framework dependency %s not found.' % (name, )) @@ -584,9 +578,6 @@ class ExtraFrameworkDependency(Dependency): return ['-F' + self.path, '-framework', self.name.split('.')[0]] return [] - def found(self): - return self.name is not None - def get_version(self): return 'unknown' @@ -611,7 +602,7 @@ def get_dep_identifier(name, kwargs, want_cross): return identifier -def find_external_dependency(name, environment, kwargs): +def find_external_dependency(name, env, kwargs): required = kwargs.get('required', True) if not isinstance(required, bool): raise DependencyException('Keyword "required" must be a boolean.') @@ -619,20 +610,20 @@ def find_external_dependency(name, environment, kwargs): raise DependencyException('Keyword "method" must be a string.') lname = name.lower() if lname in packages: - dep = packages[lname](environment, kwargs) + dep = packages[lname](env, kwargs) if required and not dep.found(): raise DependencyException('Dependency "%s" not found' % name) return dep pkg_exc = None pkgdep = None try: - pkgdep = PkgConfigDependency(name, environment, kwargs) + pkgdep = PkgConfigDependency(name, env, kwargs) if pkgdep.found(): return pkgdep except Exception as e: pkg_exc = e if mesonlib.is_osx(): - fwdep = ExtraFrameworkDependency(name, required, None, kwargs) + fwdep = ExtraFrameworkDependency(name, False, None, env, None, kwargs) if required and not fwdep.found(): m = 'Dependency {!r} not found, tried Extra Frameworks ' \ 'and Pkg-Config:\n\n' + str(pkg_exc) @@ -642,16 +633,3 @@ def find_external_dependency(name, environment, kwargs): raise pkg_exc mlog.log('Dependency', mlog.bold(name), 'found:', mlog.red('NO')) return pkgdep - -def dependency_get_compiler(language, environment, kwargs): - if 'native' in kwargs and environment.is_cross_build(): - want_cross = not kwargs['native'] - else: - want_cross = environment.is_cross_build() - - if want_cross: - compilers = environment.coredata.cross_compilers - else: - compilers = environment.coredata.compilers - - return compilers.get(language, None) diff --git a/mesonbuild/dependencies/dev.py b/mesonbuild/dependencies/dev.py index 76d6691..f991d3c 100644 --- a/mesonbuild/dependencies/dev.py +++ b/mesonbuild/dependencies/dev.py @@ -22,29 +22,20 @@ import shutil from .. import mlog from .. import mesonlib from ..mesonlib import version_compare, Popen_safe -from .base import Dependency, DependencyException, PkgConfigDependency, dependency_get_compiler +from .base import DependencyException, ExternalDependency, PkgConfigDependency -class GTestDependency(Dependency): +class GTestDependency(ExternalDependency): def __init__(self, environment, kwargs): - Dependency.__init__(self, 'gtest', kwargs) - self.env = environment + super().__init__('gtest', environment, 'cpp', kwargs) self.main = kwargs.get('main', False) - self.name = 'gtest' - self.include_dir = '/usr/include' self.src_dirs = ['/usr/src/gtest/src', '/usr/src/googletest/googletest/src'] - - self.cpp_compiler = dependency_get_compiler('cpp', environment, kwargs) - if self.cpp_compiler is None: - raise DependencyException('Tried to use gtest but a C++ compiler is not defined.') self.detect() - def found(self): - return self.is_found - def detect(self): - gtest_detect = self.cpp_compiler.find_library("gtest", self.env, []) - gtest_main_detect = self.cpp_compiler.find_library("gtest_main", self.env, []) - if gtest_detect and gtest_main_detect: + self.version = '1.something_maybe' + gtest_detect = self.compiler.find_library("gtest", self.env, []) + gtest_main_detect = self.compiler.find_library("gtest_main", self.env, []) + if gtest_detect and (not self.main or gtest_main_detect): self.is_found = True self.compile_args = [] self.link_args = gtest_detect @@ -64,7 +55,6 @@ class GTestDependency(Dependency): else: mlog.log('Dependency GTest found:', mlog.red('NO')) self.is_found = False - return self.is_found def detect_srcdir(self): for s in self.src_dirs: @@ -78,37 +68,17 @@ class GTestDependency(Dependency): return True return False - def get_compile_args(self): - arr = [] - if self.include_dir != '/usr/include': - arr.append('-I' + self.include_dir) - if hasattr(self, 'src_include_dir'): - arr.append('-I' + self.src_include_dir) - return arr - - def get_link_args(self): - return self.link_args - - def get_version(self): - return '1.something_maybe' - - def get_sources(self): - return self.sources - def need_threads(self): return True -class GMockDependency(Dependency): +class GMockDependency(ExternalDependency): def __init__(self, environment, kwargs): - Dependency.__init__(self, 'gmock', kwargs) + super().__init__('gmock', environment, 'cpp', kwargs) + self.version = '1.something_maybe' # GMock may be a library or just source. # Work with both. - self.name = 'gmock' - cpp_compiler = dependency_get_compiler('cpp', environment, kwargs) - if cpp_compiler is None: - raise DependencyException('Tried to use gmock but a C++ compiler is not defined.') - gmock_detect = cpp_compiler.find_library("gmock", environment, []) + gmock_detect = self.compiler.find_library("gmock", self.env, []) if gmock_detect: self.is_found = True self.compile_args = [] @@ -133,29 +103,12 @@ class GMockDependency(Dependency): self.sources = [all_src] mlog.log('Dependency GMock found:', mlog.green('YES'), '(building self)') return - mlog.log('Dependency GMock found:', mlog.red('NO')) self.is_found = False - def get_version(self): - return '1.something_maybe' - - def get_compile_args(self): - return self.compile_args - - def get_sources(self): - return self.sources - - def get_link_args(self): - return self.link_args - - def found(self): - return self.is_found - - -class LLVMDependency(Dependency): - """LLVM dependency. +class LLVMDependency(ExternalDependency): + """ LLVM uses a special tool, llvm-config, which has arguments for getting c args, cxx args, and ldargs as well as version. """ @@ -182,15 +135,11 @@ class LLVMDependency(Dependency): __cpp_blacklist = {'-DNDEBUG'} def __init__(self, environment, kwargs): - super().__init__('llvm-config', kwargs) # It's necessary for LLVM <= 3.8 to use the C++ linker. For 3.9 and 4.0 # the C linker works fine if only using the C API. - self.language = 'cpp' - self.cargs = [] - self.libs = [] + super().__init__('llvm-config', environment, 'cpp', kwargs) self.modules = [] - - required = kwargs.get('required', True) + # FIXME: Support multiple version requirements ala PkgConfigDependency req_version = kwargs.get('version', None) if self.llvmconfig is None: self.check_llvmconfig(req_version) @@ -201,14 +150,14 @@ class LLVMDependency(Dependency): else: mlog.log("No llvm-config found; can't detect dependency") mlog.log('Dependency LLVM found:', mlog.red('NO')) - if required: + if self.required: raise DependencyException('Dependency LLVM not found') return p, out, err = Popen_safe([self.llvmconfig, '--version']) if p.returncode != 0: mlog.debug('stdout: {}\nstderr: {}'.format(out, err)) - if required: + if self.required: raise DependencyException('Dependency LLVM not found') return else: @@ -220,12 +169,13 @@ class LLVMDependency(Dependency): [self.llvmconfig, '--libs', '--ldflags', '--system-libs'])[:2] if p.returncode != 0: raise DependencyException('Could not generate libs for LLVM.') - self.libs = shlex.split(out) + self.link_args = shlex.split(out) p, out = Popen_safe([self.llvmconfig, '--cppflags'])[:2] if p.returncode != 0: raise DependencyException('Could not generate includedir for LLVM.') - self.cargs = list(mesonlib.OrderedSet(shlex.split(out)).difference(self.__cpp_blacklist)) + cargs = mesonlib.OrderedSet(shlex.split(out)) + self.compile_args = list(cargs.difference(self.__cpp_blacklist)) p, out = Popen_safe([self.llvmconfig, '--components'])[:2] if p.returncode != 0: @@ -237,21 +187,12 @@ class LLVMDependency(Dependency): if mod not in self.modules: mlog.log('LLVM module', mod, 'found:', mlog.red('NO')) self.is_found = False - if required: + if self.required: raise DependencyException( 'Could not find required LLVM Component: {}'.format(mod)) else: mlog.log('LLVM module', mod, 'found:', mlog.green('YES')) - def get_version(self): - return self.version - - def get_compile_args(self): - return self.cargs - - def get_link_args(self): - return self.libs - @classmethod def check_llvmconfig(cls, version_req): """Try to find the highest version of llvm-config.""" @@ -261,6 +202,8 @@ class LLVMDependency(Dependency): out = out.strip() if p.returncode != 0: continue + # FIXME: As soon as some llvm-config is found, version checks + # in further dependnecy() calls will be ignored if version_req: if version_compare(out, version_req, strict=True): if cls.__best_found and version_compare(out, '<={}'.format(cls.__best_found), strict=True): @@ -288,8 +231,12 @@ class LLVMDependency(Dependency): class ValgrindDependency(PkgConfigDependency): - def __init__(self, environment, kwargs): - PkgConfigDependency.__init__(self, 'valgrind', environment, kwargs) + ''' + Consumers of Valgrind usually only need the compile args and do not want to + link to its (static) libraries. + ''' + def __init__(self, env, kwargs): + super().__init__('valgrind', env, kwargs) def get_link_args(self): return [] diff --git a/mesonbuild/dependencies/misc.py b/mesonbuild/dependencies/misc.py index 6a76ba6..c24acf0 100644 --- a/mesonbuild/dependencies/misc.py +++ b/mesonbuild/dependencies/misc.py @@ -23,25 +23,19 @@ from .. import mlog from .. import mesonlib from ..environment import detect_cpu_family -from .base import Dependency, DependencyException, DependencyMethods, ExtraFrameworkDependency, PkgConfigDependency +from .base import DependencyException, DependencyMethods +from .base import ExternalDependency, ExtraFrameworkDependency, PkgConfigDependency -class BoostDependency(Dependency): +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): - Dependency.__init__(self, 'boost', kwargs) - self.name = 'boost' - self.environment = environment + super().__init__('boost', environment, 'cpp', kwargs) self.libdir = '' - self.static = kwargs.get('static', False) - if 'native' in kwargs and environment.is_cross_build(): - self.want_cross = not kwargs['native'] - else: - self.want_cross = environment.is_cross_build() try: self.boost_root = os.environ['BOOST_ROOT'] if not os.path.isabs(self.boost_root): @@ -72,7 +66,7 @@ class BoostDependency(Dependency): self.detect_version() self.requested_modules = self.get_requested(kwargs) module_str = ', '.join(self.requested_modules) - if self.version is not None: + if self.is_found: self.detect_src_modules() self.detect_lib_modules() self.validate_requested() @@ -83,9 +77,6 @@ class BoostDependency(Dependency): mlog.log('Dependency Boost (%s) found:' % module_str, mlog.green('YES'), info) else: mlog.log("Dependency Boost (%s) found:" % module_str, mlog.red('NO')) - if 'cpp' not in self.environment.coredata.compilers: - raise DependencyException('Tried to use Boost but a C++ compiler is not defined.') - self.cpp_compiler = self.environment.coredata.compilers['cpp'] def detect_win_root(self): globtext = 'c:\\local\\boost_*' @@ -130,13 +121,13 @@ class BoostDependency(Dependency): # 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')): - args.append("".join(self.cpp_compiler.get_include_args(include_dir, True))) + args.append("".join(self.compiler.get_include_args(include_dir, True))) return args def get_requested(self, kwargs): candidates = kwargs.get('modules', []) - if isinstance(candidates, str): - return [candidates] + if not isinstance(candidates, list): + candidates = [candidates] for c in candidates: if not isinstance(c, str): raise DependencyException('Boost module argument is not a string.') @@ -145,19 +136,13 @@ class BoostDependency(Dependency): def validate_requested(self): for m in self.requested_modules: if m not in self.src_modules: - raise DependencyException('Requested Boost module "%s" not found.' % m) - - def found(self): - return self.version is not None - - def get_version(self): - return self.version + msg = 'Requested Boost module {!r} not found' + raise DependencyException(msg.format(m)) def detect_version(self): try: ifile = open(os.path.join(self.boost_inc_subdir, 'version.hpp')) except FileNotFoundError: - self.version = None return with ifile: for line in ifile: @@ -165,8 +150,8 @@ class BoostDependency(Dependency): ver = line.split()[-1] ver = ver[1:-1] self.version = ver.replace('_', '.') + self.is_found = True return - self.version = None def detect_src_modules(self): for entry in os.listdir(self.boost_inc_subdir): @@ -180,7 +165,7 @@ class BoostDependency(Dependency): return self.detect_lib_modules_nix() def detect_lib_modules_win(self): - arch = detect_cpu_family(self.environment.coredata.compilers) + arch = detect_cpu_family(self.env.coredata.compilers) # Guess the libdir if arch == 'x86': gl = 'lib32*' @@ -254,10 +239,10 @@ class BoostDependency(Dependency): module = BoostDependency.name2lib.get(module, module) libname = 'boost_' + module # The compiler's library detector is the most reliable so use that first. - default_detect = self.cpp_compiler.find_library(libname, self.environment, []) + default_detect = self.compiler.find_library(libname, self.env, []) if default_detect is not None: if module == 'unit_testing_framework': - emon_args = self.cpp_compiler.find_library('boost_test_exec_monitor') + emon_args = self.compiler.find_library('boost_test_exec_monitor') else: emon_args = None args += default_detect @@ -286,9 +271,9 @@ class BoostDependency(Dependency): return 'thread' in self.requested_modules -class ThreadDependency(Dependency): +class ThreadDependency(ExternalDependency): def __init__(self, environment, kwargs): - super().__init__('threads', {}) + super().__init__('threads', environment, None, {}) self.name = 'threads' self.is_found = True mlog.log('Dependency', mlog.bold(self.name), 'found:', mlog.green('YES')) @@ -300,19 +285,18 @@ class ThreadDependency(Dependency): return 'unknown' -class Python3Dependency(Dependency): +class Python3Dependency(ExternalDependency): def __init__(self, environment, kwargs): - super().__init__('python3', kwargs) + super().__init__('python3', environment, None, kwargs) self.name = 'python3' - self.is_found = False # We can only be sure that it is Python 3 at this point self.version = '3' if DependencyMethods.PKGCONFIG in self.methods: try: pkgdep = PkgConfigDependency('python3', environment, kwargs) if pkgdep.found(): - self.cargs = pkgdep.cargs - self.libs = pkgdep.libs + self.compile_args = pkgdep.get_compile_args() + self.link_args = pkgdep.get_link_args() self.version = pkgdep.get_version() self.is_found = True return @@ -324,10 +308,11 @@ class Python3Dependency(Dependency): 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, None, kwargs) + fw = ExtraFrameworkDependency('python', False, None, self.env, + self.language, kwargs) if fw.found(): - self.cargs = fw.get_compile_args() - self.libs = fw.get_link_args() + self.compile_args = fw.get_compile_args() + self.link_args = fw.get_link_args() self.is_found = True if self.is_found: mlog.log('Dependency', mlog.bold(self.name), 'found:', mlog.green('YES')) @@ -359,23 +344,17 @@ class Python3Dependency(Dependency): return inc = sysconfig.get_path('include') platinc = sysconfig.get_path('platinclude') - self.cargs = ['-I' + inc] + self.compile_args = ['-I' + inc] if inc != platinc: - self.cargs.append('-I' + platinc) + self.compile_args.append('-I' + platinc) # Nothing exposes this directly that I coulf find basedir = sysconfig.get_config_var('base') vernum = sysconfig.get_config_var('py_version_nodot') - self.libs = ['-L{}/libs'.format(basedir), - '-lpython{}'.format(vernum)] + self.link_args = ['-L{}/libs'.format(basedir), + '-lpython{}'.format(vernum)] self.version = sysconfig.get_config_var('py_version_short') self.is_found = True - def get_compile_args(self): - return self.cargs - - def get_link_args(self): - return self.libs - def get_methods(self): if mesonlib.is_windows(): return [DependencyMethods.PKGCONFIG, DependencyMethods.SYSCONFIG] @@ -383,6 +362,3 @@ class Python3Dependency(Dependency): return [DependencyMethods.PKGCONFIG, DependencyMethods.EXTRAFRAMEWORK] else: return [DependencyMethods.PKGCONFIG] - - def get_version(self): - return self.version diff --git a/mesonbuild/dependencies/platform.py b/mesonbuild/dependencies/platform.py index cd46412..95ab727 100644 --- a/mesonbuild/dependencies/platform.py +++ b/mesonbuild/dependencies/platform.py @@ -17,25 +17,21 @@ from .. import mesonlib -from .base import Dependency, DependencyException +from .base import ExternalDependency, DependencyException -class AppleFrameworks(Dependency): - def __init__(self, environment, kwargs): - Dependency.__init__(self, 'appleframeworks', kwargs) +class AppleFrameworks(ExternalDependency): + def __init__(self, env, kwargs): + super().__init__('appleframeworks', env, None, kwargs) modules = kwargs.get('modules', []) if isinstance(modules, str): modules = [modules] if not modules: raise DependencyException("AppleFrameworks dependency requires at least one module.") self.frameworks = modules - - def get_link_args(self): - args = [] + # FIXME: Use self.compiler to check if the frameworks are available for f in self.frameworks: - args.append('-framework') - args.append(f) - return args + self.link_args += ['-framework', f] def found(self): return mesonlib.is_osx() diff --git a/mesonbuild/dependencies/ui.py b/mesonbuild/dependencies/ui.py index 3174176..7cc8bb4 100644 --- a/mesonbuild/dependencies/ui.py +++ b/mesonbuild/dependencies/ui.py @@ -26,24 +26,22 @@ from .. import mesonlib from ..mesonlib import MesonException, Popen_safe, version_compare from ..environment import for_windows -from .base import (Dependency, DependencyException, DependencyMethods, - ExternalProgram, ExtraFrameworkDependency, PkgConfigDependency) +from .base import DependencyException, DependencyMethods +from .base import ExternalDependency, ExternalProgram +from .base import ExtraFrameworkDependency, PkgConfigDependency -class GLDependency(Dependency): +class GLDependency(ExternalDependency): def __init__(self, environment, kwargs): - Dependency.__init__(self, 'gl', kwargs) - self.is_found = False - self.cargs = [] - self.linkargs = [] + super().__init__('gl', environment, None, kwargs) 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.compile_args = pcdep.get_compile_args() + self.link_args = pcdep.get_link_args() self.version = pcdep.get_version() return except Exception: @@ -51,21 +49,19 @@ class GLDependency(Dependency): if DependencyMethods.SYSTEM in self.methods: if mesonlib.is_osx(): self.is_found = True - self.linkargs = ['-framework', 'OpenGL'] - self.version = '1' # FIXME + # FIXME: Use AppleFrameworks dependency + self.link_args = ['-framework', 'OpenGL'] + # FIXME: Detect version using self.compiler + self.version = '1' return if mesonlib.is_windows(): self.is_found = True - self.linkargs = ['-lopengl32'] - self.version = '1' # FIXME: unfixable? + # FIXME: Use self.compiler.find_library() + self.link_args = ['-lopengl32'] + # FIXME: Detect version using self.compiler + self.version = '1' return - def get_link_args(self): - return self.linkargs - - def get_version(self): - return self.version - def get_methods(self): if mesonlib.is_osx() or mesonlib.is_windows(): return [DependencyMethods.PKGCONFIG, DependencyMethods.SYSTEM] @@ -73,10 +69,9 @@ class GLDependency(Dependency): return [DependencyMethods.PKGCONFIG] -class GnuStepDependency(Dependency): +class GnuStepDependency(ExternalDependency): def __init__(self, environment, kwargs): - Dependency.__init__(self, 'gnustep', kwargs) - self.required = kwargs.get('required', True) + super().__init__('gnustep', environment, 'objc', kwargs) self.modules = kwargs.get('modules', []) self.detect() @@ -85,11 +80,9 @@ class GnuStepDependency(Dependency): try: gp = Popen_safe([self.confprog, '--help'])[0] except (FileNotFoundError, PermissionError): - self.args = None mlog.log('Dependency GnuStep found:', mlog.red('NO'), '(no gnustep-config)') return if gp.returncode != 0: - self.args = None mlog.log('Dependency GnuStep found:', mlog.red('NO')) return if 'gui' in self.modules: @@ -100,12 +93,13 @@ class GnuStepDependency(Dependency): if fp.returncode != 0: raise DependencyException('Error getting objc-args: %s %s' % (flagtxt, flagerr)) args = flagtxt.split() - self.args = self.filter_arsg(args) + self.compile_args = self.filter_args(args) fp, libtxt, liberr = Popen_safe([self.confprog, arg]) if fp.returncode != 0: raise DependencyException('Error getting objc-lib args: %s %s' % (libtxt, liberr)) - self.libs = self.weird_filter(libtxt.split()) + self.link_args = self.weird_filter(libtxt.split()) self.version = self.detect_version() + self.is_found = True mlog.log('Dependency', mlog.bold('GnuStep'), 'found:', mlog.green('YES'), self.version) @@ -115,7 +109,7 @@ is sometimes mixed among the subprocess output. I have no idea why. As a hack filter out everything that is not a flag.""" return [e for e in elems if e.startswith('-')] - def filter_arsg(self, args): + def filter_args(self, args): """gnustep-config returns a bunch of garbage args such as -O2 and so on. Drop everything that is not needed.""" result = [] @@ -157,25 +151,10 @@ why. As a hack filter out everything that is not a flag.""" ''.format(self.confprog, var)) return o.strip() - def found(self): - return self.args is not None - - def get_version(self): - return self.version - - def get_compile_args(self): - if self.args is None: - return [] - return self.args - - def get_link_args(self): - return self.libs - -class QtBaseDependency(Dependency): +class QtBaseDependency(ExternalDependency): def __init__(self, name, env, kwargs): - Dependency.__init__(self, name, kwargs) - self.name = name + super().__init__(name, env, 'cpp', kwargs) self.qtname = name.capitalize() self.qtver = name[-1] if self.qtver == "4": @@ -184,16 +163,7 @@ class QtBaseDependency(Dependency): self.qtpkgname = self.qtname self.root = '/usr' self.bindir = None - self.silent = kwargs.get('silent', False) - # We store the value of required here instead of passing it on to - # PkgConfigDependency etc because we want to try the qmake-based - # fallback as well. - self.required = kwargs.pop('required', True) - kwargs['required'] = False mods = kwargs.get('modules', []) - self.cargs = [] - self.largs = [] - self.is_found = False if isinstance(mods, str): mods = [mods] if not mods: @@ -207,16 +177,16 @@ class QtBaseDependency(Dependency): methods = [] # Prefer pkg-config, then fallback to `qmake -query` if DependencyMethods.PKGCONFIG in self.methods: - self._pkgconfig_detect(mods, env, kwargs) + self._pkgconfig_detect(mods, kwargs) methods.append('pkgconfig') if not self.is_found and DependencyMethods.QMAKE in self.methods: - from_text = self._qmake_detect(mods, env, kwargs) + from_text = self._qmake_detect(mods, kwargs) methods.append('qmake-' + self.name) methods.append('qmake') if not self.is_found: # Reset compile args and link args - self.cargs = [] - self.largs = [] + self.compile_args = [] + self.link_args = [] from_text = '(checked {})'.format(mlog.format_list(methods)) self.version = 'none' if self.required: @@ -244,24 +214,27 @@ class QtBaseDependency(Dependency): rcc = ExternalProgram('rcc-' + self.name, silent=True) return moc, uic, rcc - def _pkgconfig_detect(self, mods, env, kwargs): + def _pkgconfig_detect(self, mods, kwargs): + # We set the value of required to False so that we can try the + # qmake-based fallback if pkg-config fails. + kwargs['required'] = False modules = OrderedDict() for module in mods: - modules[module] = PkgConfigDependency(self.qtpkgname + module, env, kwargs) - self.is_found = True + modules[module] = PkgConfigDependency(self.qtpkgname + module, self.env, kwargs) for m in modules.values(): if not m.found(): self.is_found = False return - self.cargs += m.get_compile_args() - self.largs += m.get_link_args() - self.version = m.modversion + self.compile_args += m.get_compile_args() + self.link_args += m.get_link_args() + self.is_found = True + self.version = m.version # Try to detect moc, uic, rcc if 'Core' in modules: core = modules['Core'] else: corekwargs = {'required': 'false', 'silent': 'true'} - core = PkgConfigDependency(self.qtpkgname + 'Core', env, corekwargs) + core = PkgConfigDependency(self.qtpkgname + 'Core', self.env, corekwargs) # Used by self.compilers_detect() self.bindir = self.get_pkgconfig_host_bins(core) if not self.bindir: @@ -270,16 +243,16 @@ class QtBaseDependency(Dependency): if prefix: self.bindir = os.path.join(prefix, 'bin') - def _find_qmake(self, qmake, env): + def _find_qmake(self, qmake): # Even when cross-compiling, if we don't get a cross-info qmake, we # fallback to using the qmake in PATH because that's what we used to do - if env.is_cross_build(): - qmake = env.cross_info.config['binaries'].get('qmake', qmake) + if self.env.is_cross_build(): + qmake = self.env.cross_info.config['binaries'].get('qmake', qmake) return ExternalProgram(qmake, silent=True) - def _qmake_detect(self, mods, env, kwargs): + def _qmake_detect(self, mods, kwargs): for qmake in ('qmake-' + self.name, 'qmake'): - self.qmake = self._find_qmake(qmake, env) + self.qmake = self._find_qmake(qmake) if not self.qmake.found(): continue # Check that the qmake is for qt5 @@ -293,6 +266,7 @@ class QtBaseDependency(Dependency): break else: # Didn't find qmake :( + self.is_found = False return self.version = re.search(self.qtver + '(\.\d+)+', stdo).group(0) # Query library path, header path, and binary path @@ -308,15 +282,15 @@ class QtBaseDependency(Dependency): if mesonlib.is_osx(): return self._framework_detect(qvars, mods, kwargs) incdir = qvars['QT_INSTALL_HEADERS'] - self.cargs.append('-I' + incdir) + self.compile_args.append('-I' + incdir) libdir = qvars['QT_INSTALL_LIBS'] # Used by self.compilers_detect() self.bindir = self.get_qmake_host_bins(qvars) self.is_found = True for module in mods: mincdir = os.path.join(incdir, 'Qt' + module) - self.cargs.append('-I' + mincdir) - if for_windows(env.is_cross_build(), env): + self.compile_args.append('-I' + mincdir) + if for_windows(self.env.is_cross_build(), self.env): libfile = os.path.join(libdir, self.qtpkgname + module + '.lib') if not os.path.isfile(libfile): # MinGW can link directly to .dll @@ -329,19 +303,20 @@ class QtBaseDependency(Dependency): if not os.path.isfile(libfile): self.is_found = False break - self.largs.append(libfile) + self.link_args.append(libfile) return qmake def _framework_detect(self, qvars, modules, kwargs): libdir = qvars['QT_INSTALL_LIBS'] for m in modules: fname = 'Qt' + m - fwdep = ExtraFrameworkDependency(fname, kwargs.get('required', True), libdir, kwargs) - self.cargs.append('-F' + libdir) + fwdep = ExtraFrameworkDependency(fname, False, libdir, self.env, + self.language, kwargs) + self.compile_args.append('-F' + libdir) if fwdep.found(): self.is_found = True - self.cargs += fwdep.get_compile_args() - self.largs += fwdep.get_link_args() + self.compile_args += fwdep.get_compile_args() + self.link_args += fwdep.get_link_args() # Used by self.compilers_detect() self.bindir = self.get_qmake_host_bins(qvars) @@ -353,24 +328,9 @@ class QtBaseDependency(Dependency): else: return qvars['QT_INSTALL_BINS'] - def get_version(self): - return self.version - - def get_compile_args(self): - return self.cargs - - def get_sources(self): - return [] - - def get_link_args(self): - return self.largs - def get_methods(self): return [DependencyMethods.PKGCONFIG, DependencyMethods.QMAKE] - def found(self): - return self.is_found - def get_exe_args(self, compiler): # Originally this was -fPIE but nowadays the default # for upstream and distros seems to be -reduce-relocations @@ -408,20 +368,18 @@ class Qt5Dependency(QtBaseDependency): # There are three different ways of depending on SDL2: # sdl2-config, pkg-config and OSX framework -class SDL2Dependency(Dependency): +class SDL2Dependency(ExternalDependency): def __init__(self, environment, kwargs): - Dependency.__init__(self, 'sdl2', kwargs) - self.is_found = False - self.cargs = [] - self.linkargs = [] + super().__init__('sdl2', environment, None, kwargs) if DependencyMethods.PKGCONFIG in self.methods: try: + kwargs['required'] = False 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.compile_args = pcdep.get_compile_args() + self.link_args = pcdep.get_link_args() self.version = pcdep.get_version() return except Exception as e: @@ -431,9 +389,9 @@ class SDL2Dependency(Dependency): sdlconf = shutil.which('sdl2-config') if sdlconf: stdo = Popen_safe(['sdl2-config', '--cflags'])[1] - self.cargs = stdo.strip().split() + self.compile_args = stdo.strip().split() stdo = Popen_safe(['sdl2-config', '--libs'])[1] - self.linkargs = stdo.strip().split() + self.link_args = stdo.strip().split() stdo = Popen_safe(['sdl2-config', '--version'])[1] self.version = stdo.strip() self.is_found = True @@ -443,27 +401,16 @@ class SDL2Dependency(Dependency): 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) + fwdep = ExtraFrameworkDependency('sdl2', False, None, self.env, + self.language, kwargs) if fwdep.found(): self.is_found = True - self.cargs = fwdep.get_compile_args() - self.linkargs = fwdep.get_link_args() + self.compile_args = fwdep.get_compile_args() + self.link_args = 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 - - def get_link_args(self): - return self.linkargs - - def found(self): - return self.is_found - - def get_version(self): - return self.version - def get_methods(self): if mesonlib.is_osx(): return [DependencyMethods.PKGCONFIG, DependencyMethods.SDLCONFIG, DependencyMethods.EXTRAFRAMEWORK] @@ -471,18 +418,17 @@ class SDL2Dependency(Dependency): return [DependencyMethods.PKGCONFIG, DependencyMethods.SDLCONFIG] -class WxDependency(Dependency): +class WxDependency(ExternalDependency): wx_found = None def __init__(self, environment, kwargs): - Dependency.__init__(self, 'wx', kwargs) - self.is_found = False - # FIXME: use version instead of modversion - self.modversion = 'none' + super().__init__('wx', environment, None, kwargs) + self.version = 'none' if WxDependency.wx_found is None: self.check_wxconfig() + else: + self.wxc = WxDependency.wx_found if not WxDependency.wx_found: - # FIXME: this message could be printed after Dependncy found mlog.log("Neither wx-config-3.0 nor wx-config found; can't detect dependency") return @@ -490,15 +436,14 @@ class WxDependency(Dependency): p, out = Popen_safe([self.wxc, '--version'])[0:2] if p.returncode != 0: mlog.log('Dependency wxwidgets found:', mlog.red('NO')) - self.cargs = [] - self.libs = [] else: - self.modversion = out.strip() + self.version = out.strip() + # FIXME: Support multiple version reqs like PkgConfigDependency version_req = kwargs.get('version', None) if version_req is not None: - if not version_compare(self.modversion, version_req, strict=True): + if not version_compare(self.version, version_req, strict=True): mlog.log('Wxwidgets version %s does not fullfill requirement %s' % - (self.modversion, version_req)) + (self.version, version_req)) return mlog.log('Dependency wxwidgets found:', mlog.green('YES')) self.is_found = True @@ -509,38 +454,26 @@ class WxDependency(Dependency): # FIXME: this error should only be raised if required is true if p.returncode != 0: raise DependencyException('Could not generate cargs for wxwidgets.') - self.cargs = out.split() + self.compile_args = out.split() # FIXME: this error should only be raised if required is true p, out = Popen_safe([self.wxc, '--libs'] + self.requested_modules)[0:2] if p.returncode != 0: raise DependencyException('Could not generate libs for wxwidgets.') - self.libs = out.split() + self.link_args = out.split() def get_requested(self, kwargs): modules = 'modules' if modules not in kwargs: return [] candidates = kwargs[modules] - if isinstance(candidates, str): - return [candidates] + if not isinstance(candidates, list): + candidates = [candidates] for c in candidates: if not isinstance(c, str): - raise DependencyException('wxwidgets module argument is not a string.') + raise DependencyException('wxwidgets module argument is not a string') return candidates - def get_modversion(self): - return self.modversion - - def get_version(self): - return self.modversion - - def get_compile_args(self): - return self.cargs - - def get_link_args(self): - return self.libs - def check_wxconfig(self): for wxc in ['wx-config-3.0', 'wx-config']: try: @@ -549,12 +482,9 @@ class WxDependency(Dependency): mlog.log('Found wx-config:', mlog.bold(shutil.which(wxc)), '(%s)' % out.strip()) self.wxc = wxc - WxDependency.wx_found = True + WxDependency.wx_found = wxc return except (FileNotFoundError, PermissionError): pass WxDependency.wxconfig_found = False mlog.log('Found wx-config:', mlog.red('NO')) - - def found(self): - return self.is_found diff --git a/mesonbuild/interpreter.py b/mesonbuild/interpreter.py index 621047c..7f279c1 100644 --- a/mesonbuild/interpreter.py +++ b/mesonbuild/interpreter.py @@ -973,7 +973,8 @@ class CompilerHolder(InterpreterObject): if required and not linkargs: l = self.compiler.language.capitalize() raise InterpreterException('{} library {!r} not found'.format(l, libname)) - lib = dependencies.ExternalLibrary(libname, linkargs, self.compiler.language) + lib = dependencies.ExternalLibrary(libname, linkargs, self.environment, + self.compiler.language) return ExternalLibraryHolder(lib) def has_argument_method(self, args, kwargs): @@ -1010,10 +1011,9 @@ class CompilerHolder(InterpreterObject): return [] ModuleState = namedtuple('ModuleState', [ - 'build_to_src', 'subdir', 'environment', 'project_name', - 'project_version', 'compilers', 'targets', 'data', 'headers', - 'man', 'global_args', 'project_args', 'build_machine', - 'host_machine', 'target_machine']) + 'build_to_src', 'subdir', 'environment', 'project_name', 'project_version', + 'backend', 'compilers', 'targets', 'data', 'headers', 'man', 'global_args', + 'project_args', 'build_machine', 'host_machine', 'target_machine']) class ModuleHolder(InterpreterObject): def __init__(self, modname, module, interpreter): @@ -1039,6 +1039,9 @@ class ModuleHolder(InterpreterObject): environment=self.interpreter.environment, project_name=self.interpreter.build.project_name, project_version=self.interpreter.build.dep_manifest[self.interpreter.active_projectname], + # The backend object is under-used right now, but we will need it: + # https://github.com/mesonbuild/meson/issues/1419 + backend=self.interpreter.backend, compilers=self.interpreter.build.compilers, targets=self.interpreter.build.targets, data=self.interpreter.build.data, diff --git a/mesonbuild/mesonmain.py b/mesonbuild/mesonmain.py index dacc478..603be18 100644 --- a/mesonbuild/mesonmain.py +++ b/mesonbuild/mesonmain.py @@ -312,7 +312,11 @@ def run(mainfile, args): else: mlog.log(mlog.red('\nMeson encountered an error:')) mlog.log(e) + if os.environ.get('MESON_FORCE_BACKTRACE'): + raise else: + if os.environ.get('MESON_FORCE_BACKTRACE'): + raise traceback.print_exc() return 1 return 0 diff --git a/mesonbuild/mintro.py b/mesonbuild/mintro.py index 525a41d..5e672bb 100644 --- a/mesonbuild/mintro.py +++ b/mesonbuild/mintro.py @@ -158,7 +158,7 @@ def list_buildsystem_files(coredata, builddata): def list_deps(coredata): result = [] - for d in coredata.deps: + for d in coredata.deps.values(): if d.found(): args = {'compile_args': d.get_compile_args(), 'link_args': d.get_link_args()} diff --git a/mesonbuild/modules/gnome.py b/mesonbuild/modules/gnome.py index 6644ba7..fe85aa0 100644 --- a/mesonbuild/modules/gnome.py +++ b/mesonbuild/modules/gnome.py @@ -68,7 +68,7 @@ class GnomeModule(ExtensionModule): if native_glib_version is None: glib_dep = PkgConfigDependency('glib-2.0', state.environment, {'native': True}) - native_glib_version = glib_dep.get_modversion() + native_glib_version = glib_dep.get_version() return native_glib_version def __print_gresources_warning(self, state): @@ -288,15 +288,25 @@ class GnomeModule(ExtensionModule): def _get_link_args(self, state, lib, depends=None, include_rpath=False, use_gir_args=False): + # Construct link args if gir_has_extra_lib_arg() and use_gir_args: - link_command = ['--extra-library=%s' % lib.name] + link_command = ['--extra-library=' + lib.name] else: - link_command = ['-l%s' % lib.name] + link_command = ['-l' + lib.name] if isinstance(lib, build.SharedLibrary): - libdir = os.path.join(state.environment.get_build_dir(), lib.subdir) - link_command += ['-L%s' % libdir] + libdir = os.path.join(state.environment.get_build_dir(), state.backend.get_target_dir(lib)) + link_command.append('-L' + libdir) + # Needed for the following binutils bug: + # https://github.com/mesonbuild/meson/issues/1911 + # However, g-ir-scanner does not understand -Wl,-rpath + # so we need to use -L instead + for d in state.backend.determine_rpath_dirs(lib): + d = os.path.join(state.environment.get_build_dir(), d) + link_command.append('-L' + d) + if include_rpath: + link_command.append('-Wl,-rpath,' + d) if include_rpath: - link_command += ['-Wl,-rpath %s' % libdir] + link_command.append('-Wl,-rpath,' + libdir) if depends: depends.append(lib) return link_command @@ -359,7 +369,8 @@ class GnomeModule(ExtensionModule): gi_includes.update([girdir]) elif isinstance(dep, (build.StaticLibrary, build.SharedLibrary)): for incd in dep.get_include_dirs(): - cflags.update(incd.get_incdirs()) + for idir in incd.get_incdirs(): + cflags.update(["-I%s" % idir]) else: mlog.log('dependency %s not handled to build gir files' % dep) continue @@ -435,19 +446,21 @@ class GnomeModule(ExtensionModule): 'Gir includes must be str, GirTarget, or list of them') cflags = [] - if state.global_args.get('c'): - cflags += state.global_args['c'] - if state.project_args.get('c'): - cflags += state.project_args['c'] - if 'c' in state.compilers: - compiler = state.compilers['c'] + for lang, compiler in girtarget.compilers.items(): + # XXX: Can you use g-i with any other language? + if lang in ('c', 'cpp', 'objc', 'objcpp', 'd'): + break + else: + lang = None + compiler = None + if lang and compiler: + if state.global_args.get(lang): + cflags += state.global_args[lang] + if state.project_args.get(lang): + cflags += state.project_args[lang] sanitize = compiler.get_options().get('b_sanitize') if sanitize: cflags += compilers.sanitizer_compile_args(sanitize) - if cflags: - scan_command += ['--cflags-begin'] - scan_command += cflags - scan_command += ['--cflags-end'] if kwargs.get('symbol_prefix'): sym_prefix = kwargs.pop('symbol_prefix') if not isinstance(sym_prefix, str): @@ -510,9 +523,12 @@ class GnomeModule(ExtensionModule): # ldflags will be misinterpreted by gir scanner (showing # spurious dependencies) but building GStreamer fails if they # are not used here. - cflags, ldflags, gi_includes = self._get_dependencies_flags(deps, state, depends, - use_gir_args=True) - scan_command += list(cflags) + dep_cflags, ldflags, gi_includes = self._get_dependencies_flags(deps, state, depends, + use_gir_args=True) + cflags += list(dep_cflags) + scan_command += ['--cflags-begin'] + scan_command += cflags + scan_command += ['--cflags-end'] # need to put our output directory first as we need to use the # generated libraries instead of any possibly installed system/prefix # ones. @@ -536,6 +552,13 @@ class GnomeModule(ExtensionModule): scan_command += ['--program', girtarget] elif isinstance(girtarget, build.SharedLibrary): libname = girtarget.get_basename() + # Needed for the following binutils bug: + # https://github.com/mesonbuild/meson/issues/1911 + # However, g-ir-scanner does not understand -Wl,-rpath + # so we need to use -L instead + for d in state.backend.determine_rpath_dirs(girtarget): + d = os.path.join(state.environment.get_build_dir(), d) + scan_command.append('-L' + d) scan_command += ['--library', libname] scankwargs = {'output': girfile, 'input': libsources, @@ -598,7 +621,7 @@ class GnomeModule(ExtensionModule): langs = mesonlib.stringlistify(kwargs.pop('languages', [])) media = mesonlib.stringlistify(kwargs.pop('media', [])) - symlinks = kwargs.pop('symlink_media', False) + symlinks = kwargs.pop('symlink_media', True) if not isinstance(symlinks, bool): raise MesonException('symlink_media must be a boolean') @@ -679,6 +702,8 @@ class GnomeModule(ExtensionModule): for inc_dir in src_dir.get_incdirs(): header_dirs.append(os.path.join(state.environment.get_source_dir(), src_dir.get_curdir(), inc_dir)) + header_dirs.append(os.path.join(state.environment.get_build_dir(), + src_dir.get_curdir(), inc_dir)) else: header_dirs.append(src_dir) diff --git a/mesonbuild/scripts/dist.py b/mesonbuild/scripts/dist.py index 325a882..cb3bbe2 100644 --- a/mesonbuild/scripts/dist.py +++ b/mesonbuild/scripts/dist.py @@ -13,6 +13,7 @@ # limitations under the License. +import lzma import os import shutil import subprocess @@ -29,10 +30,11 @@ def create_hash(fname): m = hashlib.sha256() m.update(open(fname, 'rb').read()) with open(hashname, 'w') as f: - f.write('%s %s\n' % (m.hexdigest(), os.path.split(fname)[-1])) + f.write('%s %s\n' % (m.hexdigest(), os.path.basename(fname))) + def create_zip(zipfilename, packaging_dir): - prefix = os.path.split(packaging_dir)[0] + prefix = os.path.dirname(packaging_dir) removelen = len(prefix) + 1 with zipfile.ZipFile(zipfilename, 'w', @@ -70,7 +72,8 @@ def process_submodules(dirname): continue del_gitfiles(os.path.join(dirname, v)) -def create_dist(dist_name, src_root, bld_root, dist_sub): + +def create_dist_git(dist_name, src_root, bld_root, dist_sub): distdir = os.path.join(dist_sub, dist_name) if os.path.exists(distdir): shutil.rmtree(distdir) @@ -81,13 +84,29 @@ def create_dist(dist_name, src_root, bld_root, dist_sub): xzname = distdir + '.tar.xz' # Should use shutil but it got xz support only in 3.5. with tarfile.open(xzname, 'w:xz') as tf: - tf.add(distdir, os.path.split(distdir)[1]) + tf.add(distdir, dist_name) # Create only .tar.xz for now. # zipname = distdir + '.zip' # create_zip(zipname, distdir) shutil.rmtree(distdir) return (xzname, ) + +def create_dist_hg(dist_name, src_root, bld_root, dist_sub): + os.makedirs(dist_sub, exist_ok=True) + + tarname = os.path.join(dist_sub, dist_name + '.tar') + xzname = tarname + '.xz' + subprocess.check_call(['hg', 'archive', '-R', src_root, '-S', '-t', 'tar', tarname]) + with lzma.open(xzname, 'wb') as xf, open(tarname, 'rb') as tf: + shutil.copyfileobj(tf, xf) + os.unlink(tarname) + # Create only .tar.xz for now. + # zipname = os.path.join(dist_sub, dist_name + '.zip') + # subprocess.check_call(['hg', 'archive', '-R', src_root, '-S', '-t', 'zip', zipname]) + return (xzname, ) + + def check_dist(packagename, meson_command): print('Testing distribution package %s.' % packagename) unpackdir = tempfile.mkdtemp() @@ -132,10 +151,13 @@ def run(args): dist_name = build.project_name + '-' + build.project_version - if not os.path.isdir(os.path.join(src_root, '.git')): - print('Dist currently only works with Git repos.') + if os.path.isdir(os.path.join(src_root, '.git')): + names = create_dist_git(dist_name, src_root, bld_root, dist_sub) + elif os.path.isdir(os.path.join(src_root, '.hg')): + names = create_dist_hg(dist_name, src_root, bld_root, dist_sub) + else: + print('Dist currently only works with Git or Mercurial repos.') return 1 - names = create_dist(dist_name, src_root, bld_root, dist_sub) if names is None: return 1 error_count = 0 diff --git a/mesonbuild/scripts/yelphelper.py b/mesonbuild/scripts/yelphelper.py index 47dfb71..978a870 100644 --- a/mesonbuild/scripts/yelphelper.py +++ b/mesonbuild/scripts/yelphelper.py @@ -74,13 +74,24 @@ def install_help(srcdir, blddir, sources, media, langs, install_dir, destdir, pr if not os.path.exists(infile): if lang == 'C': mlog.warning('Media file "%s" did not exist in C directory' % m) + continue elif symlinks: srcfile = os.path.join(c_install_dir, m) mlog.log('Symlinking %s to %s.' % (outfile, srcfile)) if '/' in m or '\\' in m: os.makedirs(os.path.dirname(outfile), exist_ok=True) - os.symlink(srcfile, outfile) - continue + try: + try: + os.symlink(srcfile, outfile) + except FileExistsError: + os.remove(outfile) + os.symlink(srcfile, outfile) + continue + except (NotImplementedError, OSError): + mlog.warning('Symlinking not supported, falling back to copying') + else: + # Lang doesn't have media file so copy it over 'C' one + infile = os.path.join(srcdir, 'C', m) mlog.log('Installing %s to %s' % (infile, outfile)) if '/' in m or '\\' in m: os.makedirs(os.path.dirname(outfile), exist_ok=True) diff --git a/run_project_tests.py b/run_project_tests.py index 822286b..76216a4 100755 --- a/run_project_tests.py +++ b/run_project_tests.py @@ -34,7 +34,7 @@ import time import multiprocessing import concurrent.futures as conc import re -from run_unittests import get_fake_options +from run_unittests import get_fake_options, run_configure_inprocess from run_tests import get_backend_commands, get_backend_args_for_dir, Backend from run_tests import ensure_backend_detects_changes @@ -214,7 +214,8 @@ def validate_install(srcdir, installdir, compiler): expected[platform_fix_name(line.strip())] = False # Check if expected files were found for fname in expected: - if os.path.exists(os.path.join(installdir, fname)): + file_path = os.path.join(installdir, fname) + if os.path.exists(file_path) or os.path.islink(file_path): expected[fname] = True for (fname, found) in expected.items(): if not found: @@ -249,17 +250,22 @@ def log_text_file(logfile, testdir, stdo, stde): executor.shutdown() raise StopException() -def run_configure_inprocess(commandlist): - old_stdout = sys.stdout - sys.stdout = mystdout = StringIO() - old_stderr = sys.stderr - sys.stderr = mystderr = StringIO() - try: - returncode = mesonmain.run(commandlist[0], commandlist[1:]) - finally: - sys.stdout = old_stdout - sys.stderr = old_stderr - return returncode, mystdout.getvalue(), mystderr.getvalue() + +def bold(text): + return mlog.bold(text).get_text(mlog.colorize_console) + + +def green(text): + return mlog.green(text).get_text(mlog.colorize_console) + + +def red(text): + return mlog.red(text).get_text(mlog.colorize_console) + + +def yellow(text): + return mlog.yellow(text).get_text(mlog.colorize_console) + def run_test_inprocess(testdir): old_stdout = sys.stdout @@ -383,7 +389,7 @@ def _run_test(testdir, test_build_dir, install_dir, extra_args, compiler, backen 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, '*'))] + tests = [t.replace('\\', '/').split('/', 2)[2] for t in glob(testdir + '/*')] testlist = [(int(t.split()[0]), t) for t in tests] testlist.sort() tests = [os.path.join(testdir, t[1]) for t in testlist] @@ -437,7 +443,6 @@ def detect_tests_to_run(): ('platform-windows', 'windows', not mesonlib.is_windows() and not mesonlib.is_cygwin()), ('platform-linux', 'linuxlike', mesonlib.is_osx() or mesonlib.is_windows()), - ('framework', 'frameworks', mesonlib.is_osx() or mesonlib.is_windows() or mesonlib.is_cygwin()), ('java', 'java', backend is not Backend.ninja or mesonlib.is_osx() or not have_java()), ('C#', 'csharp', backend is not Backend.ninja or not shutil.which('mcs')), ('vala', 'vala', backend is not Backend.ninja or not shutil.which('valac')), @@ -446,9 +451,17 @@ def detect_tests_to_run(): ('objective c', 'objc', backend not in (Backend.ninja, Backend.xcode) or mesonlib.is_windows() or not have_objc_compiler()), ('fortran', 'fortran', backend is not Backend.ninja or not shutil.which('gfortran')), ('swift', 'swift', backend not in (Backend.ninja, Backend.xcode) or not shutil.which('swiftc')), - ('python3', 'python3', backend is not Backend.ninja or not shutil.which('python3')), + ('python3', 'python3', backend is not Backend.ninja), ] - return [(name, gather_tests('test cases/' + subdir), skip) for name, subdir, skip in all_tests] + gathered_tests = [(name, gather_tests('test cases/' + subdir), skip) for name, subdir, skip in all_tests] + if mesonlib.is_windows(): + # TODO: Set BOOST_ROOT in .appveyor.yml + gathered_tests += [('framework', ['test cases/frameworks/1 boost'], 'BOOST_ROOT' not in os.environ)] + elif mesonlib.is_osx() or mesonlib.is_cygwin(): + gathered_tests += [('framework', gather_tests('test cases/frameworks'), True)] + else: + gathered_tests += [('framework', gather_tests('test cases/frameworks'), False)] + return gathered_tests def run_tests(all_tests, log_name_base, extra_args): global stop, executor, futures @@ -479,10 +492,12 @@ def run_tests(all_tests, log_name_base, extra_args): for name, test_cases, skipped in all_tests: current_suite = ET.SubElement(junit_root, 'testsuite', {'name': name, 'tests': str(len(test_cases))}) + print() if skipped: - print('\nNot running %s tests.\n' % name) + print(bold('Not running %s tests.' % name)) else: - print('\nRunning %s tests.\n' % name) + print(bold('Running %s tests.' % name)) + print() futures = [] for t in test_cases: # Jenkins screws us over by automatically sorting test cases by name @@ -498,7 +513,7 @@ def run_tests(all_tests, log_name_base, extra_args): sys.stdout.flush() result = result.result() if result is None or 'MESON_SKIP_TEST' in result.stdo: - print('Skipping:', t) + print(yellow('Skipping:'), t) current_test = ET.SubElement(current_suite, 'testcase', {'name': testname, 'classname': name}) ET.SubElement(current_test, 'skipped', {}) @@ -506,7 +521,7 @@ def run_tests(all_tests, log_name_base, extra_args): else: without_install = "" if len(install_commands) > 0 else " (without install)" if result.msg != '': - print('Failed test{} during {}: {!r}'.format(without_install, result.step.name, t)) + print(red('Failed test{} during {}: {!r}'.format(without_install, result.step.name, t))) print('Reason:', result.msg) failing_tests += 1 if result.step == BuildStep.configure and result.mlog != no_meson_log_msg: @@ -652,9 +667,9 @@ if __name__ == '__main__': pass for f in pbfiles: os.unlink(f) - print('\nTotal passed tests:', passing_tests) - print('Total failed tests:', failing_tests) - print('Total skipped tests:', skipped_tests) + print('\nTotal passed tests:', green(str(passing_tests))) + print('Total failed tests:', red(str(failing_tests))) + print('Total skipped tests:', yellow(str(skipped_tests))) if failing_tests > 0: print('\nMesonlogs of failing tests\n') for l in failing_logs: diff --git a/run_tests.py b/run_tests.py index 00c2595..040f958 100755 --- a/run_tests.py +++ b/run_tests.py @@ -22,7 +22,10 @@ import subprocess import tempfile import platform from mesonbuild import mesonlib +from mesonbuild import mesonmain +from mesonbuild import mlog from mesonbuild.environment import detect_ninja +from io import StringIO from enum import Enum from glob import glob @@ -118,9 +121,23 @@ def get_fake_options(prefix): def should_run_linux_cross_tests(): return shutil.which('arm-linux-gnueabihf-gcc-6') and not platform.machine().startswith('arm') +def run_configure_inprocess(commandlist): + old_stdout = sys.stdout + sys.stdout = mystdout = StringIO() + old_stderr = sys.stderr + sys.stderr = mystderr = StringIO() + try: + returncode = mesonmain.run(commandlist[0], commandlist[1:]) + finally: + sys.stdout = old_stdout + sys.stderr = old_stderr + return returncode, mystdout.getvalue(), mystderr.getvalue() + class FakeEnvironment(object): def __init__(self): self.cross_info = None + self.coredata = lambda: None + self.coredata.compilers = {} def is_cross_build(self): return False @@ -161,8 +178,9 @@ if __name__ == '__main__': if 'APPVEYOR' in os.environ and os.environ['arch'] == 'x86': os.environ.pop('platform') # Run tests - print('Running unittests.\n') - units = ['InternalTests', 'AllPlatformTests'] + print(mlog.bold('Running unittests.').get_text(mlog.colorize_console)) + print() + units = ['InternalTests', 'AllPlatformTests', 'FailureTests'] if mesonlib.is_linux(): units += ['LinuxlikeTests'] if should_run_linux_cross_tests(): @@ -184,7 +202,8 @@ if __name__ == '__main__': returncode += subprocess.call([sys.executable, 'run_unittests.py', '-v'] + units, env=env) # Ubuntu packages do not have a binary without -6 suffix. if should_run_linux_cross_tests(): - print('Running cross compilation tests.\n') + print(mlog.bold('Running cross compilation tests.').get_text(mlog.colorize_console)) + print() returncode += subprocess.call([sys.executable, 'run_cross_test.py', 'cross/ubuntu-armhf.txt'], env=env) returncode += subprocess.call([sys.executable, 'run_project_tests.py'] + sys.argv[1:], env=env) sys.exit(returncode) diff --git a/run_unittests.py b/run_unittests.py index 8a9ac0a..63462d8 100755 --- a/run_unittests.py +++ b/run_unittests.py @@ -25,16 +25,19 @@ import unittest from configparser import ConfigParser from glob import glob from pathlib import PurePath + +import mesonbuild.mlog import mesonbuild.compilers import mesonbuild.environment import mesonbuild.mesonlib -from mesonbuild.mesonlib import is_windows, is_osx, is_cygwin +from mesonbuild.mesonlib import is_windows, is_osx, is_cygwin, windows_proof_rmtree from mesonbuild.environment import Environment +from mesonbuild.dependencies import DependencyException from mesonbuild.dependencies import PkgConfigDependency, ExternalProgram from run_tests import exe_suffix, get_fake_options, FakeEnvironment from run_tests import get_builddir_target_args, get_backend_commands, Backend -from run_tests import ensure_backend_detects_changes +from run_tests import ensure_backend_detects_changes, run_configure_inprocess def get_dynamic_section_entry(fname, entry): @@ -173,6 +176,16 @@ class InternalTests(unittest.TestCase): l += ['-lbar'] self.assertEqual(l, ['-Lbardir', '-Lfoodir', '-lfoo', '-lbar']) + ## Test that 'direct' append and extend works + l = cargsfunc(c, ['-Lfoodir', '-lfoo']) + self.assertEqual(l, ['-Lfoodir', '-lfoo']) + # Direct-adding a library and a libpath appends both correctly + l.extend_direct(['-Lbardir', '-lbar']) + self.assertEqual(l, ['-Lfoodir', '-lfoo', '-Lbardir', '-lbar']) + # Direct-adding the same library again still adds it + l.append_direct('-lbar') + self.assertEqual(l, ['-Lfoodir', '-lfoo', '-Lbardir', '-lbar', '-lbar']) + def test_commonpath(self): from os.path import sep commonpath = mesonbuild.mesonlib.commonpath @@ -401,8 +414,8 @@ class BasePlatformTests(unittest.TestCase): # 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.meson_args = [os.path.join(src_root, 'meson.py'), '--backend=' + self.backend.name] + self.meson_command = [sys.executable] + self.meson_args 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] @@ -432,7 +445,7 @@ class BasePlatformTests(unittest.TestCase): print(f.read()) def tearDown(self): - shutil.rmtree(self.builddir) + windows_proof_rmtree(self.builddir) os.environ = self.orig_env super().tearDown() @@ -452,7 +465,7 @@ class BasePlatformTests(unittest.TestCase): raise subprocess.CalledProcessError(p.returncode, command) return output - def init(self, srcdir, extra_args=None, default_args=True): + def init(self, srcdir, extra_args=None, default_args=True, inprocess=False): self.assertTrue(os.path.exists(srcdir)) if extra_args is None: extra_args = [] @@ -462,14 +475,27 @@ class BasePlatformTests(unittest.TestCase): if default_args: args += ['--prefix', self.prefix, '--libdir', self.libdir] - try: - self._run(self.meson_command + args + extra_args) - except unittest.SkipTest: - raise unittest.SkipTest('Project requested skipping: ' + srcdir) - except: - self._print_meson_log() - raise self.privatedir = os.path.join(self.builddir, 'meson-private') + if inprocess: + try: + out = run_configure_inprocess(self.meson_args + args + extra_args)[1] + except: + self._print_meson_log() + raise + finally: + # Close log file to satisfy Windows file locking + mesonbuild.mlog.shutdown() + mesonbuild.mlog.log_dir = None + mesonbuild.mlog.log_file = None + else: + try: + out = self._run(self.meson_command + args + extra_args) + except unittest.SkipTest: + raise unittest.SkipTest('Project requested skipping: ' + srcdir) + except: + self._print_meson_log() + raise + return out def build(self, target=None, extra_args=None): if extra_args is None: @@ -508,7 +534,7 @@ class BasePlatformTests(unittest.TestCase): self._run(self.mconf_command + [arg, self.builddir]) def wipe(self): - shutil.rmtree(self.builddir) + windows_proof_rmtree(self.builddir) def utime(self, f): ensure_backend_detects_changes(self.backend) @@ -1135,18 +1161,50 @@ class AllPlatformTests(BasePlatformTests): self.build() self.run_tests() - def test_dist(self): + def test_dist_git(self): if not shutil.which('git'): raise unittest.SkipTest('Git not found') + + def git_init(project_dir): + subprocess.check_call(['git', 'init'], cwd=project_dir) + subprocess.check_call(['git', 'config', + 'user.name', 'Author Person'], cwd=project_dir) + subprocess.check_call(['git', 'config', + 'user.email', 'teh_coderz@example.com'], cwd=project_dir) + subprocess.check_call(['git', 'add', 'meson.build', 'distexe.c'], cwd=project_dir) + subprocess.check_call(['git', 'commit', '-a', '-m', 'I am a project'], cwd=project_dir) + try: - self.dist_impl() + self.dist_impl(git_init) except PermissionError: # When run under Windows CI, something (virus scanner?) # holds on to the git files so cleaning up the dir # fails sometimes. pass - def dist_impl(self): + def test_dist_hg(self): + if not shutil.which('hg'): + raise unittest.SkipTest('Mercurial not found') + if self.backend is not Backend.ninja: + raise unittest.SkipTest('Dist is only supported with Ninja') + + def hg_init(project_dir): + subprocess.check_call(['hg', 'init'], cwd=project_dir) + with open(os.path.join(project_dir, '.hg', 'hgrc'), 'w') as f: + print('[ui]', file=f) + print('username=Author Person <teh_coderz@example.com>', file=f) + subprocess.check_call(['hg', 'add', 'meson.build', 'distexe.c'], cwd=project_dir) + subprocess.check_call(['hg', 'commit', '-m', 'I am a project'], cwd=project_dir) + + try: + self.dist_impl(hg_init) + except PermissionError: + # When run under Windows CI, something (virus scanner?) + # holds on to the hg files so cleaning up the dir + # fails sometimes. + pass + + def dist_impl(self, vcs_init): # Create this on the fly because having rogue .git directories inside # the source tree leads to all kinds of trouble. with tempfile.TemporaryDirectory() as project_dir: @@ -1163,13 +1221,7 @@ int main(int argc, char **argv) { return 0; } ''') - subprocess.check_call(['git', 'init'], cwd=project_dir) - subprocess.check_call(['git', 'config', - 'user.name', 'Author Person'], cwd=project_dir) - subprocess.check_call(['git', 'config', - 'user.email', 'teh_coderz@example.com'], cwd=project_dir) - subprocess.check_call(['git', 'add', 'meson.build', 'distexe.c'], cwd=project_dir) - subprocess.check_call(['git', 'commit', '-a', '-m', 'I am a project'], cwd=project_dir) + vcs_init(project_dir) self.init(project_dir) self.build('dist') distfile = os.path.join(self.distdir, 'disttest-1.4.3.tar.xz') @@ -1195,6 +1247,133 @@ int main(int argc, char **argv) { for path in rpath.split(':'): self.assertTrue(path.startswith('$ORIGIN'), msg=(each, path)) + def test_dash_d_dedup(self): + testdir = os.path.join(self.unit_test_dir, '10 d dedup') + self.init(testdir) + cmd = self.get_compdb()[0]['command'] + self.assertTrue('-D FOO -D BAR' in cmd or + '"-D" "FOO" "-D" "BAR"' in cmd or + '/D FOO /D BAR' in cmd or + '"/D" "FOO" "/D" "BAR"' in cmd) + + +class FailureTests(BasePlatformTests): + ''' + Tests that test failure conditions. Build files here should be dynamically + generated and static tests should go into `test cases/failing*`. + This is useful because there can be many ways in which a particular + function can fail, and creating failing tests for all of them is tedious + and slows down testing. + ''' + dnf = "[Dd]ependency.*not found" + + def setUp(self): + super().setUp() + self.srcdir = os.path.realpath(tempfile.mkdtemp()) + self.mbuild = os.path.join(self.srcdir, 'meson.build') + + def tearDown(self): + super().tearDown() + windows_proof_rmtree(self.srcdir) + + def assertMesonRaises(self, contents, match, extra_args=None, langs=None): + ''' + Assert that running meson configure on the specified @contents raises + a error message matching regex @match. + ''' + if langs is None: + langs = [] + with open(self.mbuild, 'w') as f: + f.write("project('failure test', 'c', 'cpp')\n") + for lang in langs: + f.write("add_languages('{}', required : false)\n".format(lang)) + f.write(contents) + # Force tracebacks so we can detect them properly + os.environ['MESON_FORCE_BACKTRACE'] = '1' + with self.assertRaisesRegex(DependencyException, match, msg=contents): + # Must run in-process or we'll get a generic CalledProcessError + self.init(self.srcdir, extra_args=extra_args, inprocess=True) + + def assertMesonOutputs(self, contents, match, extra_args=None, langs=None): + ''' + Assert that running meson configure on the specified @contents outputs + something that matches regex @match. + ''' + if langs is None: + langs = [] + with open(self.mbuild, 'w') as f: + f.write("project('output test', 'c', 'cpp')\n") + for lang in langs: + f.write("add_languages('{}', required : false)\n".format(lang)) + f.write(contents) + # Run in-process for speed and consistency with assertMesonRaises + out = self.init(self.srcdir, extra_args=extra_args, inprocess=True) + self.assertRegex(out, match) + + def test_dependency(self): + if not shutil.which('pkg-config'): + raise unittest.SkipTest('pkg-config not found') + a = (("dependency('zlib', method : 'fail')", "'fail' is invalid"), + ("dependency('zlib', static : '1')", "[Ss]tatic.*boolean"), + ("dependency('zlib', version : 1)", "[Vv]ersion.*string or list"), + ("dependency('zlib', required : 1)", "[Rr]equired.*boolean"), + ("dependency('zlib', method : 1)", "[Mm]ethod.*string"), + ("dependency('zlibfail')", self.dnf),) + for contents, match in a: + self.assertMesonRaises(contents, match) + + def test_apple_frameworks_dependency(self): + if not is_osx(): + raise unittest.SkipTest('only run on macOS') + self.assertMesonRaises("dependency('appleframeworks')", + "requires at least one module") + + def test_sdl2_notfound_dependency(self): + # Want to test failure, so skip if available + if shutil.which('sdl2-config'): + raise unittest.SkipTest('sdl2-config found') + self.assertMesonRaises("dependency('sdl2', method : 'sdlconfig')", self.dnf) + self.assertMesonRaises("dependency('sdl2', method : 'pkg-config')", self.dnf) + + def test_gnustep_notfound_dependency(self): + # Want to test failure, so skip if available + if shutil.which('gnustep-config'): + raise unittest.SkipTest('gnustep-config found') + self.assertMesonRaises("dependency('gnustep')", + "(requires a Objc compiler|{})".format(self.dnf), + langs = ['objc']) + + def test_wx_notfound_dependency(self): + # Want to test failure, so skip if available + if shutil.which('wx-config-3.0') or shutil.which('wx-config'): + raise unittest.SkipTest('wx-config or wx-config-3.0 found') + self.assertMesonRaises("dependency('wxwidgets')", self.dnf) + self.assertMesonOutputs("dependency('wxwidgets', required : false)", + "nor wx-config found") + + def test_wx_dependency(self): + if not shutil.which('wx-config-3.0') and not shutil.which('wx-config'): + raise unittest.SkipTest('Neither wx-config nor wx-config-3.0 found') + self.assertMesonRaises("dependency('wxwidgets', modules : 1)", + "module argument is not a string") + + def test_llvm_dependency(self): + self.assertMesonRaises("dependency('llvm', modules : 'fail')", + "(required.*fail|{})".format(self.dnf)) + + def test_boost_notfound_dependency(self): + # Can be run even if Boost is found or not + self.assertMesonRaises("dependency('boost', modules : 1)", + "module.*not a string") + self.assertMesonRaises("dependency('boost', modules : 'fail')", + "(fail.*not found|{})".format(self.dnf)) + + def test_boost_BOOST_ROOT_dependency(self): + # Test BOOST_ROOT; can be run even if Boost is found or not + os.environ['BOOST_ROOT'] = 'relative/path' + self.assertMesonRaises("dependency('boost')", + "(BOOST_ROOT.*absolute|{})".format(self.dnf)) + class WindowsTests(BasePlatformTests): ''' @@ -1611,6 +1790,31 @@ class LinuxlikeTests(BasePlatformTests): env['LD_LIBRARY_PATH'] = installed_libdir self.assertEqual(subprocess.call(installed_exe, env=env), 0) + def test_order_of_l_arguments(self): + testdir = os.path.join(self.unit_test_dir, '9 -L -l order') + os.environ['PKG_CONFIG_PATH'] = testdir + self.init(testdir) + # NOTE: .pc file has -Lfoo -lfoo -Lbar -lbar but pkg-config reorders + # the flags before returning them to -Lfoo -Lbar -lfoo -lbar + # but pkgconf seems to not do that. Sigh. Support both. + expected_order = [('-L/me/first', '-lfoo1'), + ('-L/me/second', '-lfoo2'), + ('-L/me/first', '-L/me/second'), + ('-lfoo1', '-lfoo2'), + ('-L/me/second', '-L/me/third'), + ('-L/me/third', '-L/me/fourth',), + ('-L/me/third', '-lfoo3'), + ('-L/me/fourth', '-lfoo4'), + ('-lfoo3', '-lfoo4'), + ] + with open(os.path.join(self.builddir, 'build.ninja')) as ifile: + for line in ifile: + if expected_order[0][0] in line: + for first, second in expected_order: + self.assertLess(line.index(first), line.index(second)) + return + raise RuntimeError('Linker entries not found in the Ninja file.') + class LinuxArmCrossCompileTests(BasePlatformTests): ''' Tests that verify cross-compilation to Linux/ARM @@ -1644,7 +1848,7 @@ class RewriterTests(unittest.TestCase): self.test_dir = os.path.join(src_root, 'test cases/rewrite') def tearDown(self): - shutil.rmtree(self.tmpdir) + windows_proof_rmtree(self.tmpdir) def read_contents(self, fname): with open(os.path.join(self.workdir, fname)) as f: diff --git a/test cases/common/127 cpp and asm/retval-arm.S b/test cases/common/127 cpp and asm/retval-arm.S index 8b37197..a892362 100644 --- a/test cases/common/127 cpp and asm/retval-arm.S +++ b/test cases/common/127 cpp and asm/retval-arm.S @@ -2,7 +2,10 @@ .text .globl SYMBOL_NAME(get_retval) +# ifdef __linux__ +.type get_retval, %function +#endif SYMBOL_NAME(get_retval): - mov r0, #0 - mov pc, lr + mov r0, #0 + mov pc, lr diff --git a/test cases/common/127 cpp and asm/retval-x86.S b/test cases/common/127 cpp and asm/retval-x86.S index 06bd75c..f9e8190 100644 --- a/test cases/common/127 cpp and asm/retval-x86.S +++ b/test cases/common/127 cpp and asm/retval-x86.S @@ -2,7 +2,10 @@ .text .globl SYMBOL_NAME(get_retval) +# ifdef __linux__ +.type get_retval, %function +#endif SYMBOL_NAME(get_retval): - xorl %eax, %eax - retl + xorl %eax, %eax + retl diff --git a/test cases/common/127 cpp and asm/retval-x86_64.S b/test cases/common/127 cpp and asm/retval-x86_64.S index 638921e..1a5f3eb 100644 --- a/test cases/common/127 cpp and asm/retval-x86_64.S +++ b/test cases/common/127 cpp and asm/retval-x86_64.S @@ -2,7 +2,10 @@ .text .globl SYMBOL_NAME(get_retval) +# ifdef __linux__ +.type get_retval, %function +#endif SYMBOL_NAME(get_retval): - xorl %eax, %eax - retq + xorl %eax, %eax + retq diff --git a/test cases/common/141 c cpp and asm/retval-arm.S b/test cases/common/141 c cpp and asm/retval-arm.S index 8b37197..a892362 100644 --- a/test cases/common/141 c cpp and asm/retval-arm.S +++ b/test cases/common/141 c cpp and asm/retval-arm.S @@ -2,7 +2,10 @@ .text .globl SYMBOL_NAME(get_retval) +# ifdef __linux__ +.type get_retval, %function +#endif SYMBOL_NAME(get_retval): - mov r0, #0 - mov pc, lr + mov r0, #0 + mov pc, lr diff --git a/test cases/common/141 c cpp and asm/retval-x86.S b/test cases/common/141 c cpp and asm/retval-x86.S index 06bd75c..3cb0237 100644 --- a/test cases/common/141 c cpp and asm/retval-x86.S +++ b/test cases/common/141 c cpp and asm/retval-x86.S @@ -2,7 +2,11 @@ .text .globl SYMBOL_NAME(get_retval) +/* Only supported on Linux with GAS */ +# ifdef __linux__ +.type get_retval, %function +#endif SYMBOL_NAME(get_retval): - xorl %eax, %eax - retl + xorl %eax, %eax + retl diff --git a/test cases/common/141 c cpp and asm/retval-x86_64.S b/test cases/common/141 c cpp and asm/retval-x86_64.S index 638921e..1a5f3eb 100644 --- a/test cases/common/141 c cpp and asm/retval-x86_64.S +++ b/test cases/common/141 c cpp and asm/retval-x86_64.S @@ -2,7 +2,10 @@ .text .globl SYMBOL_NAME(get_retval) +# ifdef __linux__ +.type get_retval, %function +#endif SYMBOL_NAME(get_retval): - xorl %eax, %eax - retq + xorl %eax, %eax + retq diff --git a/test cases/frameworks/10 gtk-doc/doc/foobar-docs.sgml b/test cases/frameworks/10 gtk-doc/doc/foobar-docs.sgml index d23b22f..028b808 100644 --- a/test cases/frameworks/10 gtk-doc/doc/foobar-docs.sgml +++ b/test cases/frameworks/10 gtk-doc/doc/foobar-docs.sgml @@ -34,6 +34,7 @@ </para> </partintro> <xi:include href="xml/foo.xml"/> + <xi:include href="xml/foo-version.xml"/> </reference> </book> diff --git a/test cases/frameworks/10 gtk-doc/include/foo-version.h.in b/test cases/frameworks/10 gtk-doc/include/foo-version.h.in new file mode 100644 index 0000000..30751cd --- /dev/null +++ b/test cases/frameworks/10 gtk-doc/include/foo-version.h.in @@ -0,0 +1,29 @@ +#pragma once + +/** + * SECTION:version + * @section_id: foo-version + * @short_description: <filename>foo-version.h</filename> + * @title: Foo Versioning + */ + +/** + * FOO_MAJOR_VERSION: + * + * The major version of foo. + */ +#define FOO_MAJOR_VERSION (@FOO_MAJOR_VERSION@) + +/** + * FOO_MINOR_VERSION: + * + * The minor version of foo. + */ +#define FOO_MINOR_VERSION (@FOO_MINOR_VERSION@) + +/** + * FOO_MICRO_VERSION: + * + * The micro version of foo. + */ +#define FOO_MICRO_VERSION (@FOO_MICRO_VERSION@) diff --git a/test cases/frameworks/10 gtk-doc/include/meson.build b/test cases/frameworks/10 gtk-doc/include/meson.build new file mode 100644 index 0000000..4c85b80 --- /dev/null +++ b/test cases/frameworks/10 gtk-doc/include/meson.build @@ -0,0 +1,10 @@ +cdata = configuration_data() +parts = meson.project_version().split('.') +cdata.set('FOO_MAJOR_VERSION', parts[0]) +cdata.set('FOO_MINOR_VERSION', parts[1]) +cdata.set('FOO_MICRO_VERSION', parts[2]) +configure_file(input : 'foo-version.h.in', + output : 'foo-version.h', + configuration : cdata, + install : true, + install_dir : get_option('includedir')) diff --git a/test cases/frameworks/10 gtk-doc/meson.build b/test cases/frameworks/10 gtk-doc/meson.build index 95eeefa..4cfcca1 100644 --- a/test cases/frameworks/10 gtk-doc/meson.build +++ b/test cases/frameworks/10 gtk-doc/meson.build @@ -1,4 +1,4 @@ -project('gtkdoctest', 'c') +project('gtkdoctest', 'c', version : '1.0.0') gnome = import('gnome') @@ -6,6 +6,8 @@ assert(gnome.gtkdoc_html_dir('foobar') == 'share/gtkdoc/html/foobar', 'Gtkdoc in inc = include_directories('include') +subdir('include') + # We have to disable this test until this bug fix has landed to # distros https://bugzilla.gnome.org/show_bug.cgi?id=753145 error('MESON_SKIP_TEST can not enable gtk-doc test until upstream fixes have landed.') diff --git a/test cases/frameworks/13 yelp/help/meson.build b/test cases/frameworks/13 yelp/help/meson.build index 7c6f01d..85bc980 100644 --- a/test cases/frameworks/13 yelp/help/meson.build +++ b/test cases/frameworks/13 yelp/help/meson.build @@ -3,5 +3,13 @@ gnome = import('gnome') gnome.yelp('meson', sources: 'index.page', media: 'media/test.txt', + symlink_media: false, + languages: ['de', 'es'], +) + +gnome.yelp('meson-symlink', + sources: 'index.page', + media: 'media/test.txt', + symlink_media: true, languages: ['de', 'es'], ) diff --git a/test cases/frameworks/13 yelp/installed_files.txt b/test cases/frameworks/13 yelp/installed_files.txt index 59d2158..9fc097d 100644 --- a/test cases/frameworks/13 yelp/installed_files.txt +++ b/test cases/frameworks/13 yelp/installed_files.txt @@ -3,3 +3,10 @@ usr/share/help/C/meson/media/test.txt usr/share/help/es/meson/index.page usr/share/help/es/meson/media/test.txt usr/share/help/de/meson/index.page +usr/share/help/de/meson/media/test.txt +usr/share/help/C/meson-symlink/index.page +usr/share/help/C/meson-symlink/media/test.txt +usr/share/help/es/meson-symlink/media/test.txt +usr/share/help/es/meson-symlink/index.page +usr/share/help/de/meson-symlink/index.page +usr/share/help/de/meson-symlink/media/test.txt diff --git a/test cases/frameworks/15 llvm/meson.build b/test cases/frameworks/15 llvm/meson.build index 582ff37..af7f8c6 100644 --- a/test cases/frameworks/15 llvm/meson.build +++ b/test cases/frameworks/15 llvm/meson.build @@ -7,4 +7,11 @@ llvm_dep = dependency( required : true, ) +d = dependency('llvm', modules : 'not-found', required : false) +assert(d.found() == false, 'not-found llvm module found') + +# XXX: Version checks are broken, see FIXME in LLVMDependency +#d = dependency('llvm', version : '<0.1', required : false) +#assert(d.found() == false, 'ancient llvm module found') + executable('sum', 'sum.c', dependencies : llvm_dep) diff --git a/test cases/frameworks/16 sdl2/meson.build b/test cases/frameworks/16 sdl2/meson.build new file mode 100644 index 0000000..c79bd46 --- /dev/null +++ b/test cases/frameworks/16 sdl2/meson.build @@ -0,0 +1,10 @@ +project('sdl2 test', 'c') + +sdl2_dep = dependency('sdl2', version : '>=2.0.0') + +e = executable('sdl2prog', 'sdl2prog.c', dependencies : sdl2_dep) + +test('sdl2test', e) + +# Ensure that we can find it with sdl2-config too +configdep = dependency('sdl2', method : 'sdlconfig') diff --git a/test cases/frameworks/16 sdl2/sdl2prog.c b/test cases/frameworks/16 sdl2/sdl2prog.c new file mode 100644 index 0000000..b67aab4 --- /dev/null +++ b/test cases/frameworks/16 sdl2/sdl2prog.c @@ -0,0 +1,33 @@ +/* vim: set sts=4 sw=4 et : */ + +#include <stdio.h> +#include <SDL_version.h> + +int main(int argc, char *argv[]) { + SDL_version compiled; + SDL_version linked; + + SDL_VERSION(&compiled); + SDL_GetVersion(&linked); + + if (compiled.major != linked.major) { + fprintf(stderr, "Compiled major '%u' != linked major '%u'", + compiled.major, linked.major); + return -1; + } + + if (compiled.minor != linked.minor) { + fprintf(stderr, "Compiled minor '%u' != linked minor '%u'", + compiled.minor, linked.minor); + return -2; + } +#if 0 + /* Disabled because sometimes this is 'micro' and sometimes 'patch' */ + if (compiled.micro != linked.micro) { + fprintf(stderr, "Compiled micro '%u' != linked micro '%u'", + compiled.micro, linked.micro); + return -3; + } +#endif + return 0; +} diff --git a/test cases/frameworks/4 qt/meson.build b/test cases/frameworks/4 qt/meson.build index 468b9c9..d9cab6f 100644 --- a/test cases/frameworks/4 qt/meson.build +++ b/test cases/frameworks/4 qt/meson.build @@ -8,16 +8,22 @@ foreach qt : ['qt4', 'qt5'] if qt == 'qt5' qt_modules += qt5_modules endif + # Test that invalid modules are indeed not found 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, method : get_option('method')) if fakeqtdep.found() error('Invalid qt dep incorrectly found!') endif + + # Ensure that the "no-Core-module-specified" code branch is hit + nocoredep = dependency(qt, modules : ['Gui'], required : qt == 'qt5', method : get_option('method')) + # If qt4 modules are found, test that. qt5 is required. qtdep = dependency(qt, modules : qt_modules, required : qt == 'qt5', method : get_option('method')) if qtdep.found() diff --git a/test cases/frameworks/9 wxwidgets/meson.build b/test cases/frameworks/9 wxwidgets/meson.build index c1fa367..da3aa26 100644 --- a/test cases/frameworks/9 wxwidgets/meson.build +++ b/test cases/frameworks/9 wxwidgets/meson.build @@ -1,5 +1,6 @@ project('wxwidgets test', 'cpp') +wxd = dependency('wxwidgets', version : '>=5', required : false) wxd = dependency('wxwidgets', version : '>=3.0.0', required : false) if wxd.found() diff --git a/test cases/linuxlike/5 dependency versions/meson.build b/test cases/linuxlike/5 dependency versions/meson.build index 5c2c262..d4140b7 100644 --- a/test cases/linuxlike/5 dependency versions/meson.build +++ b/test cases/linuxlike/5 dependency versions/meson.build @@ -90,9 +90,15 @@ if meson.is_cross_build() assert(native_prefix != cross_prefix, 'native prefix == cross_prefix == ' + native_prefix) endif -foreach d : ['sdl2', 'gnustep', 'wx', 'gl', 'python3', 'boost', 'gtest', 'gmock'] - dep = dependency(d, required : false) - if dep.found() - dep.version() +objc_found = add_languages('objc', required : false) + +foreach d : ['sdl2', 'gnustep', 'wxwidgets', 'gl', 'python3', 'boost', 'gtest', 'gmock', 'valgrind'] + if d == 'gnustep' and not objc_found + message('Skipping gnustep because no ObjC compiler found') + else + dep = dependency(d, required : false) + if dep.found() + dep.version() + endif endif endforeach diff --git a/test cases/osx/4 framework/meson.build b/test cases/osx/4 framework/meson.build index 8d93bf9..460b480 100644 --- a/test cases/osx/4 framework/meson.build +++ b/test cases/osx/4 framework/meson.build @@ -10,8 +10,13 @@ project('xcode framework test', 'c', default_options : ['libdir=libtest']) -dep_libs = [dependency('appleframeworks', modules : ['OpenGL'], required : true)] -dep_main = [dependency('appleframeworks', modules : ['Foundation'], required : true)] +dep_libs = dependency('appleframeworks', modules : ['OpenGL'], required : false) +if not dep_libs.found() + error('OpenGL framework not found') +endif +assert(dep_libs.type_name() == 'appleframeworks', 'type_name is wrong') + +dep_main = dependency('appleframeworks', modules : ['Foundation']) stlib = static_library('stat', 'stat.c', install : true, dependencies: dep_libs) exe = executable('prog', 'prog.c', install : true, dependencies: dep_main) diff --git a/test cases/unit/10 d dedup/meson.build b/test cases/unit/10 d dedup/meson.build new file mode 100644 index 0000000..08f3a6c --- /dev/null +++ b/test cases/unit/10 d dedup/meson.build @@ -0,0 +1,6 @@ +project('d dedup', 'c') + +add_project_arguments('-D', 'FOO', '-D', 'BAR', language : 'c') + +executable('prog', 'prog.c') + diff --git a/test cases/unit/10 d dedup/prog.c b/test cases/unit/10 d dedup/prog.c new file mode 100644 index 0000000..505f122 --- /dev/null +++ b/test cases/unit/10 d dedup/prog.c @@ -0,0 +1,14 @@ +#include<stdio.h> + +#ifndef FOO +#error FOO is not defined. +#endif + +#ifndef BAR +#error BAR is not defined. +#endif + +int main(int argc, char **argv) { + printf("All is well.\n"); + return 0; +} diff --git a/test cases/unit/9 -L -l order/first.pc b/test cases/unit/9 -L -l order/first.pc new file mode 100644 index 0000000..3b811b2 --- /dev/null +++ b/test cases/unit/9 -L -l order/first.pc @@ -0,0 +1,13 @@ +prefix=/usr +exec_prefix=${prefix} +libdir=${prefix}/lib/x86_64-linux-gnu +sharedlibdir=${libdir} +includedir=${prefix}/include + +Name: jonne +Description: jonne library +Version: 1.0.0 + +Requires: +Libs: -L/me/first -lfoo1 -L/me/second -lfoo2 +Cflags: -I${includedir} diff --git a/test cases/unit/9 -L -l order/meson.build b/test cases/unit/9 -L -l order/meson.build new file mode 100644 index 0000000..cfcf033 --- /dev/null +++ b/test cases/unit/9 -L -l order/meson.build @@ -0,0 +1,6 @@ +project('jonne', 'c') + +firstdep = dependency('first') +seconddep = dependency('second') + +executable('lprog', 'prog.c', dependencies : [firstdep, seconddep]) diff --git a/test cases/unit/9 -L -l order/prog.c b/test cases/unit/9 -L -l order/prog.c new file mode 100644 index 0000000..3a16ac3 --- /dev/null +++ b/test cases/unit/9 -L -l order/prog.c @@ -0,0 +1,5 @@ +#include<stdio.h> + +int main(int argc, char **argv) { + return 0; +} diff --git a/test cases/unit/9 -L -l order/second.pc b/test cases/unit/9 -L -l order/second.pc new file mode 100644 index 0000000..196824b --- /dev/null +++ b/test cases/unit/9 -L -l order/second.pc @@ -0,0 +1,13 @@ +prefix=/usr +exec_prefix=${prefix} +libdir=${prefix}/lib/x86_64-linux-gnu +sharedlibdir=${libdir} +includedir=${prefix}/include + +Name: jonne2 +Description: jonne2 library +Version: 1.0.0 + +Requires: +Libs: -L/me/third -lfoo3 -L/me/fourth -lfoo4 +Cflags: -I${includedir} diff --git a/test cases/vala/16 mixed dependence/meson.build b/test cases/vala/16 mixed dependence/meson.build index e6967be..b44b47b 100644 --- a/test cases/vala/16 mixed dependence/meson.build +++ b/test cases/vala/16 mixed dependence/meson.build @@ -1,6 +1,10 @@ project('mixed dependence', 'vala', 'c') -deps = [dependency('glib-2.0'), dependency('gobject-2.0')] +cc = meson.get_compiler('c') + +deps = [dependency('glib-2.0'), dependency('gobject-2.0'), + # Should be ignored, see https://github.com/mesonbuild/meson/issues/1939 + cc.find_library('z')] mixer = static_library('mixer', 'mixer.vala', 'mixer-glue.c', dependencies : deps) diff --git a/test cases/vala/7 shared library/lib/meson.build b/test cases/vala/7 shared library/lib/meson.build index 78646a8..edeeb96 100644 --- a/test cases/vala/7 shared library/lib/meson.build +++ b/test cases/vala/7 shared library/lib/meson.build @@ -1,4 +1,12 @@ -l = shared_library('valalib', 'mylib.vala', dependencies : valadeps) +args = [] +# https://github.com/mesonbuild/meson/issues/1969 +if get_option('unity') == 'on' + vala_args = ['-H', 'mylib.h'] +endif + +l = shared_library('valalib', 'mylib.vala', + vala_args : args, + dependencies : valadeps) shared_library('installed_vala_lib', 'mylib.vala', dependencies : valadeps, diff --git a/test cases/windows/1 basic/prog.c b/test cases/windows/1 basic/prog.c index 8703980..58162a4 100644 --- a/test cases/windows/1 basic/prog.c +++ b/test cases/windows/1 basic/prog.c @@ -1,4 +1,4 @@ -#include <Windows.h> +#include <windows.h> int main(int argc, char **argv) { return 0; |