diff options
33 files changed, 1151 insertions, 86 deletions
diff --git a/ciimage/Dockerfile b/ciimage/Dockerfile index 72788c3..8573367 100644 --- a/ciimage/Dockerfile +++ b/ciimage/Dockerfile @@ -13,6 +13,7 @@ RUN apt-get -y update && apt-get -y upgrade \ && apt-get -y install gtk-sharp2 gtk-sharp2-gapi libglib2.0-cil-dev \ && apt-get -y install libwmf-dev \ && apt-get -y install qt4-linguist-tools qttools5-dev-tools \ +&& apt-get -y install python-dev \ && python3 -m pip install hotdoc codecov ENV LANG='C.UTF-8' diff --git a/docs/markdown/Python-module.md b/docs/markdown/Python-module.md new file mode 100644 index 0000000..cad74c9 --- /dev/null +++ b/docs/markdown/Python-module.md @@ -0,0 +1,195 @@ +--- +short-description: Generic python module +authors: + - name: Mathieu Duponchelle + email: mathieu@centricular.com + years: [2018] + has-copyright: false +... + +# Python module + +This module provides support for finding and building extensions against +python installations, be they python 2 or 3. + +*Added 0.46.0* + +## Functions + +### `find_installation()` + +``` meson +pymod.find_installation(name_or_path, ...) +``` + +Find a python installation matching `name_or_path`. + +That argument is optional, if not provided then the returned python +installation will be the one used to run meson. + +If provided, it can be: + +- A simple name, eg `python-2.7`, meson will look for an external program + named that way, using [find_program] + +- A path, eg `/usr/local/bin/python3.4m` + +- One of `python2` or `python3`: in either case, the module will try some + alternative names: `py -2` or `py -3` on Windows, and `python` everywhere. + In the latter case, it will check whether the version provided by the + sysconfig module matches the required major version + +Keyword arguments are the following: + +- `required`: by default, `required` is set to `true` and Meson will + abort if no python installation can be found. If `required` is set to `false`, + Meson will continue even if no python installation was found. You can + then use the `.found()` method on the returned object to check + whether it was found or not. + +**Returns**: a [python installation][`python_installation` object] + +## `python_installation` object + +The `python_installation` object is an [external program], with several +added methods. + +### Methods + +#### `extension_module()` + +``` meson +shared_module py_installation.extension_module(module_name, list_of_sources, ...) +``` + +Create a `shared_module` target that is named according to the naming +conventions of the target platform. + +All positional and keyword arguments are the same as for [shared_module], +excluding `name_suffix` and `name_prefix`, and with the addition of the following: + +- `subdir`: By default, meson will install the extension module in + the relevant top-level location for the python installation, eg + `/usr/lib/site-packages`. When subdir is passed to this method, + it will be appended to that location. This keyword argument is + mutually exclusive with `install_dir` + +`extension_module` does not add any dependencies to the library so user may +need to add `dependencies : py_installation.dependency()`, see [][`dependency()`]. + +**Returns**: a [buildtarget object] + +#### `dependency()` + +``` meson +python_dependency py_installation.dependency(...) +``` + +This method accepts the same arguments as the standard [dependency] function. + +**Returns**: a [python dependency][`python_dependency` object] + +#### `install_sources()` + +``` meson +void py_installation.install_sources(list_of_files, ...) +``` + +Install actual python sources (`.py`). + +All positional and keyword arguments are the same as for [install_data], +with the addition of the following: + +- `pure`: On some platforms, architecture independent files are expected + to be placed in a separate directory. However, if the python sources + should be installed alongside an extension module built with this + module, this keyword argument can be used to override that behaviour. + Defaults to `true` + +- `subdir`: See documentation for the argument of the same name to + [][`extension_module()`] + +#### `get_install_dir()` + +``` meson +string py_installation.get_install_dir(...) +``` + +Retrieve the directory [][`install_sources()`] will install to. + +It can be useful in cases where `install_sources` cannot be used directly, +for example when using [configure_file]. + +This function accepts no arguments, its keyword arguments are the same +as [][`install_sources()`]. + +**Returns**: A string + +#### `language_version()` + +``` meson +string py_installation.language_version() +``` + +Get the major.minor python version, eg `2.7`. + +The version is obtained through the `sysconfig` module. + +This function expects no arguments or keyword arguments. + +**Returns**: A string + +#### `get_path()` + +``` meson +string py_installation.get_path(path_name) +``` + +Get a path as defined by the `sysconfig` module. + +For example: + +``` meson +purelib = py_installation.get_path('purelib') +``` + +This function accepts a single argument, `path_name`, which is expected to +be a non-empty string. + +**Returns**: A string + +#### `get_variable()` + +``` meson +string py_installation.get_variable(variable_name) +``` + +Get a variable as defined by the `sysconfig` module. + +For example: + +``` meson +py_bindir = py_installation.get_variable('BINDIR') +``` + +This function accepts a single argument, `variable_name`, which is expected to +be a non-empty string. + +**Returns**: A string + +## `python_dependency` object + +This [dependency object] subclass will try various methods to obtain the +compiler and linker arguments, starting with pkg-config then potentially +using information obtained from python's `sysconfig` module. + +It exposes the same methods as its parent class. + +[find_program]: Reference-manual.md#find_program +[shared_module]: Reference-manual.md#shared_module +[external program]: Reference-manual.md#external-program-object +[dependency]: Reference-manual.md#dependency +[install_data]: Reference-manual.md#install-data +[configure_file]: Reference-manual.md#configure-file +[dependency object]: Reference-manual.md#dependency-object +[buildtarget object]: Reference-manual.md#build-target-object diff --git a/docs/markdown/Reference-manual.md b/docs/markdown/Reference-manual.md index fad6a3c..42abe75 100644 --- a/docs/markdown/Reference-manual.md +++ b/docs/markdown/Reference-manual.md @@ -1508,7 +1508,11 @@ the following methods: - `first_supported_argument(list_of_strings)`, given a list of strings, returns the first argument that passes the `has_argument` - test above or an empty array if none pass. + test or an empty array if none pass. + +- `first_supported_link_argument(list_of_strings)` *(added 0.46.0)*, given a + list of strings, returns the first argument that passes the + `has_link_argument` test or an empty array if none pass. - `get_define(definename)` returns the given preprocessor symbol's value as a string or empty string if it is not defined. @@ -1520,11 +1524,19 @@ the following methods: an array containing only the arguments supported by the compiler, as if `has_argument` were called on them individually. +- `get_supported_link_arguments(list_of_string)` *(added 0.46.0)* returns + an array containing only the arguments supported by the linker, + as if `has_link_argument` were called on them individually. + - `has_argument(argument_name)` returns true if the compiler accepts the specified command line argument, that is, can compile code - without erroring out or printing a warning about an unknown flag, - you can specify external dependencies to use with `dependencies` - keyword argument. + without erroring out or printing a warning about an unknown flag. + +- `has_link_argument(argument_name)` *(added 0.46.0)* returns true if the linker + accepts the specified command line argument, that is, can compile and link + code without erroring out or printing a warning about an unknown flag. Link + arguments will be passed to the compiler, so should usually have the `-Wl,` + prefix. On VisualStudio a `/link` argument will be prepended. - `has_function(funcname)` returns true if the given function is provided by the standard library or a library passed in with the @@ -1559,6 +1571,10 @@ the following methods: `has_argument` but takes multiple arguments and uses them all in a single compiler invocation, available since 0.37.0. +- `has_multi_link_arguments(arg1, arg2, arg3, ...)` *(added 0.46.0)* is the same + as `has_link_argument` but takes multiple arguments and uses them all in a + single compiler invocation. + - `has_type(typename)` returns true if the specified token is a type, you can specify external dependencies to use with `dependencies` keyword argument. diff --git a/docs/markdown/snippets/has-link-argument.md b/docs/markdown/snippets/has-link-argument.md new file mode 100644 index 0000000..7beda63 --- /dev/null +++ b/docs/markdown/snippets/has-link-argument.md @@ -0,0 +1,9 @@ +## has_link_argument() and friends + +A new set of methods has been added on compiler objects to test if the linker +supports given arguments. + +- `has_link_argument()` +- `has_multi_link_arguments()` +- `get_supported_link_arguments()` +- `first_supported_link_argument()` diff --git a/docs/markdown/snippets/openmp-dependency.md b/docs/markdown/snippets/openmp-dependency.md new file mode 100644 index 0000000..ad70011 --- /dev/null +++ b/docs/markdown/snippets/openmp-dependency.md @@ -0,0 +1,6 @@ +## Addition of OpenMP dependency + +An OpenMP dependency (`openmp`) has been added that encapsulates the various +flags used by compilers to enable OpenMP and checks for the existence of the +`omp.h` header. The `language` keyword may be passed to force the use of a +specific compiler for the checks. diff --git a/docs/markdown/snippets/python-module.md b/docs/markdown/snippets/python-module.md new file mode 100644 index 0000000..c2e8138 --- /dev/null +++ b/docs/markdown/snippets/python-module.md @@ -0,0 +1,6 @@ +## Generic python module + +This is a revamped and generic (python 2 and 3) version of the python3 +module. With this new interface, projects can now fully specify the version +of python they want to build against / install sources to, and can do so +against multiple major or minor versions in parallel. diff --git a/docs/sitemap.txt b/docs/sitemap.txt index 844b600..b439b69 100644 --- a/docs/sitemap.txt +++ b/docs/sitemap.txt @@ -32,6 +32,7 @@ index.md i18n-module.md Icestorm-module.md Pkgconfig-module.md + Python-module.md Python-3-module.md Qt4-module.md Qt5-module.md diff --git a/docs/theme/extra/templates/navbar_links.html b/docs/theme/extra/templates/navbar_links.html index 8f3da63..2edce24 100644 --- a/docs/theme/extra/templates/navbar_links.html +++ b/docs/theme/extra/templates/navbar_links.html @@ -8,6 +8,7 @@ @for tup in (("Gnome-module.html","GNOME"), \ ("i18n-module.html","i18n"), \ ("Pkgconfig-module.html","Pkgconfig"), \ + ("Python-module.html","Python"), \ ("Python-3-module.html","Python 3"), \ ("Qt4-module.html","Qt4"), \ ("Qt5-module.html","Qt5"), \ diff --git a/manual tests/2 multiwrap/meson.build b/manual tests/2 multiwrap/meson.build index 741a899..a4c42f4 100644 --- a/manual tests/2 multiwrap/meson.build +++ b/manual tests/2 multiwrap/meson.build @@ -6,7 +6,7 @@ project('multiwrap', 'c', cc = meson.get_compiler('c') luadep = dependency('lua', fallback : ['lua', 'lua_dep']) -pngdep = dependency('libpng', fallback : ['libpng', 'pngdep']) +pngdep = dependency('libpng', fallback : ['libpng', 'png_dep']) executable('prog', 'prog.c', dependencies : [pngdep, luadep]) diff --git a/mesonbuild/backend/backends.py b/mesonbuild/backend/backends.py index 916f680..694700e 100644 --- a/mesonbuild/backend/backends.py +++ b/mesonbuild/backend/backends.py @@ -540,6 +540,8 @@ class Backend: # pkg-config puts the thread flags itself via `Cflags:` if dep.need_threads(): commands += compiler.thread_flags(self.environment) + elif dep.need_openmp(): + commands += compiler.openmp_flags() # Fortran requires extra include directives. if compiler.language == 'fortran': for lt in target.link_targets: diff --git a/mesonbuild/backend/ninjabackend.py b/mesonbuild/backend/ninjabackend.py index 466a677..bc3a8ef 100644 --- a/mesonbuild/backend/ninjabackend.py +++ b/mesonbuild/backend/ninjabackend.py @@ -2551,6 +2551,8 @@ rule FORTRAN_DEP_HACK for d in target.external_deps: if d.need_threads(): commands += linker.thread_link_flags(self.environment) + elif d.need_openmp(): + commands += linker.openmp_flags() # Only non-static built targets need link args and link dependencies if not isinstance(target, build.StaticLibrary): commands += target.link_args diff --git a/mesonbuild/backend/vs2010backend.py b/mesonbuild/backend/vs2010backend.py index 3171451..22383dc 100644 --- a/mesonbuild/backend/vs2010backend.py +++ b/mesonbuild/backend/vs2010backend.py @@ -824,7 +824,10 @@ class Vs2010Backend(backends.Backend): for d in reversed(target.get_external_deps()): # Cflags required by external deps might have UNIX-specific flags, # so filter them out if needed - d_compile_args = compiler.unix_args_to_native(d.get_compile_args()) + if isinstance(d, dependencies.OpenMPDependency): + d_compile_args = compiler.openmp_flags() + else: + d_compile_args = compiler.unix_args_to_native(d.get_compile_args()) for arg in d_compile_args: if arg.startswith(('-D', '/D')): define = arg[2:] @@ -915,11 +918,17 @@ class Vs2010Backend(backends.Backend): for dep in target.get_external_deps(): # 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()) + if isinstance(dep, dependencies.OpenMPDependency): + extra_link_args.extend_direct(compiler.openmp_flags()) + else: + 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.extend_direct(dep.get_link_args()) + if isinstance(dep, dependencies.OpenMPDependency): + extra_link_args.extend_direct(compiler.openmp_flags()) + else: + 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/compilers/c.py b/mesonbuild/compilers/c.py index a6bd0af..1230e3f 100644 --- a/mesonbuild/compilers/c.py +++ b/mesonbuild/compilers/c.py @@ -296,6 +296,8 @@ class CCompiler(Compiler): args += d.get_compile_args() if d.need_threads(): args += self.thread_flags(env) + elif d.need_openmp(): + args += self.openmp_flags() if mode == 'link': # Add link flags needed to find dependencies args += d.get_link_args() @@ -321,25 +323,21 @@ class CCompiler(Compiler): args += extra_args return args - def compiles(self, code, env, extra_args=None, dependencies=None, mode='compile', want_output=False): - args = self._get_compiler_check_args(env, extra_args, dependencies, mode) - # We only want to compile; not link - with self.compile(code, args.to_native(), mode) as p: + def compiles(self, code, env, extra_args=None, dependencies=None, mode='compile'): + with self._build_wrapper(code, env, extra_args, dependencies, mode) as p: return p.returncode == 0 - def _links_wrapper(self, code, env, extra_args, dependencies, want_output=False): - "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, want_output=want_output) + def _build_wrapper(self, code, env, extra_args, dependencies=None, mode='compile', want_output=False): + args = self._get_compiler_check_args(env, extra_args, dependencies, mode) + return self.compile(code, args.to_native(), mode, want_output=want_output) def links(self, code, env, extra_args=None, dependencies=None): - with self._links_wrapper(code, env, extra_args, dependencies) as p: - return p.returncode == 0 + return self.compiles(code, env, extra_args, dependencies, mode='link') def run(self, code, env, extra_args=None, dependencies=None): if self.is_cross and self.exe_wrapper is None: raise CrossNoRunException('Can not run test applications in this cross environment.') - with self._links_wrapper(code, env, extra_args, dependencies, True) as p: + with self._build_wrapper(code, env, extra_args, dependencies, mode='link', want_output=True) as p: if p.returncode != 0: mlog.debug('Could not compile test file %s: %d\n' % ( p.input_name, @@ -839,6 +837,12 @@ class CCompiler(Compiler): return [] return ['-pthread'] + def linker_to_compiler_args(self, args): + return args + + def has_arguments(self, args, env, code, mode): + return self.compiles(code, env, extra_args=args, mode=mode) + def has_multi_arguments(self, args, env): for arg in args[:]: # some compilers, e.g. GCC, don't warn for unsupported warning-disable @@ -847,13 +851,21 @@ class CCompiler(Compiler): if arg.startswith('-Wno-'): args.append('-W' + arg[5:]) if arg.startswith('-Wl,'): - mlog.warning('''{} looks like a linker argument, but has_argument -and other similar methods only support checking compiler arguments. -Using them to check linker arguments are never supported, and results -are likely to be wrong regardless of the compiler you are using. -'''.format(arg)) - return self.compiles('int i;\n', env, extra_args=args) + mlog.warning('{} looks like a linker argument, ' + 'but has_argument and other similar methods only ' + 'support checking compiler arguments. Using them ' + 'to check linker arguments are never supported, ' + 'and results are likely to be wrong regardless of ' + 'the compiler you are using. has_link_argument or ' + 'other similar method can be used instead.' + .format(arg)) + code = 'int i;\n' + return self.has_arguments(args, env, code, mode='compile') + def has_multi_link_arguments(self, args, env): + args = self.linker_to_compiler_args(args) + code = 'int main(int argc, char **argv) { return 0; }' + return self.has_arguments(args, env, code, mode='link') class ClangCCompiler(ClangCompiler, CCompiler): def __init__(self, exelist, version, clang_type, is_cross, exe_wrapper=None, **kwargs): @@ -979,8 +991,8 @@ class IntelCCompiler(IntelCompiler, CCompiler): def get_std_shared_lib_link_args(self): return ['-shared'] - def has_multi_arguments(self, args, env): - return super().has_multi_arguments(args + ['-diag-error', '10006'], env) + def has_arguments(self, args, env, code, mode): + return super().has_arguments(args + ['-diag-error', '10006'], env, code, mode) class VisualStudioCCompiler(CCompiler): @@ -1064,6 +1076,9 @@ class VisualStudioCCompiler(CCompiler): def get_linker_search_args(self, dirname): return ['/LIBPATH:' + dirname] + def linker_to_compiler_args(self, args): + return ['/link'] + args + def get_gui_app_args(self): return ['/SUBSYSTEM:WINDOWS'] @@ -1091,6 +1106,9 @@ class VisualStudioCCompiler(CCompiler): def build_rpath_args(self, build_dir, from_dir, rpath_paths, build_rpath, install_rpath): return [] + def openmp_flags(self): + return ['/openmp'] + # FIXME, no idea what these should be. def thread_flags(self, env): return [] @@ -1144,24 +1162,12 @@ class VisualStudioCCompiler(CCompiler): # Visual Studio is special. It ignores some arguments it does not # understand and you can't tell it to error out on those. # http://stackoverflow.com/questions/15259720/how-can-i-make-the-microsoft-c-compiler-treat-unknown-flags-as-errors-rather-t - def has_multi_arguments(self, args, env): - warning_text = '9002' - code = 'int i;\n' - (fd, srcname) = tempfile.mkstemp(suffix='.' + self.default_suffix) - os.close(fd) - with open(srcname, 'w') as ofile: - ofile.write(code) - # Read c_args/cpp_args/etc from the cross-info file (if needed) - extra_args = self.get_cross_extra_flags(env, link=False) - extra_args += self.get_compile_only_args() - commands = self.exelist + args + extra_args + [srcname] - mlog.debug('Running VS compile:') - mlog.debug('Command line: ', ' '.join(commands)) - mlog.debug('Code:\n', code) - p, stdo, stde = Popen_safe(commands, cwd=os.path.dirname(srcname)) - if p.returncode != 0: - return False - return not(warning_text in stde or warning_text in stdo) + def has_arguments(self, args, env, code, mode): + warning_text = '4044' if mode == 'link' else '9002' + with self._build_wrapper(code, env, extra_args=args, mode=mode) as p: + if p.returncode != 0: + return False + return not(warning_text in p.stde or warning_text in p.stdo) def get_compile_debugfile_args(self, rel_obj, pch=False): pdbarr = rel_obj.split('.')[:-1] diff --git a/mesonbuild/compilers/compilers.py b/mesonbuild/compilers/compilers.py index 99e9164..a2c6668 100644 --- a/mesonbuild/compilers/compilers.py +++ b/mesonbuild/compilers/compilers.py @@ -745,20 +745,15 @@ class Compiler: def get_library_dirs(self): return [] - def has_argument(self, arg, env): - return self.has_multi_arguments([arg], env) - def has_multi_arguments(self, args, env): raise EnvironmentException( 'Language {} does not support has_multi_arguments.'.format( self.get_display_language())) - def get_supported_arguments(self, args, env): - supported_args = [] - for arg in args: - if self.has_argument(arg, env): - supported_args.append(arg) - return supported_args + def has_multi_link_arguments(self, args, env): + raise EnvironmentException( + 'Language {} does not support has_multi_link_arguments.'.format( + self.get_display_language())) def get_cross_extra_flags(self, environment, link): extra_flags = [] @@ -815,7 +810,6 @@ class Compiler: # Construct the compiler command-line commands = CompilerArgs(self) commands.append(srcname) - commands += extra_args commands += self.get_always_args() if mode == 'compile': commands += self.get_compile_only_args() @@ -825,6 +819,10 @@ class Compiler: else: output = self._get_compile_output(tmpdirname, mode) commands += self.get_output_args(output) + # extra_args must be last because it could contain '/link' to + # pass args to VisualStudio's linker. In that case everything + # in the command line after '/link' is given to the linker. + commands += extra_args # Generate full command-line with the exelist commands = self.get_exelist() + commands.to_native() mlog.debug('Running compile:') @@ -937,6 +935,9 @@ class Compiler: def thread_flags(self, env): return [] + def openmp_flags(self): + raise EnvironmentException('Language %s does not support OpenMP flags.' % self.get_display_language()) + GCC_STANDARD = 0 GCC_OSX = 1 @@ -1152,6 +1153,9 @@ class GnuCompiler: def get_default_include_dirs(self): return gnulike_default_include_dirs(self.exelist, self.language) + def openmp_flags(self): + return ['-fopenmp'] + class ElbrusCompiler(GnuCompiler): # Elbrus compiler is nearly like GCC, but does not support @@ -1270,6 +1274,15 @@ class ClangCompiler: def get_default_include_dirs(self): return gnulike_default_include_dirs(self.exelist, self.language) + def openmp_flags(self): + if version_compare(self.version, '>=3.8.0'): + return ['-fopenmp'] + elif version_compare(self.version, '>=3.7.0'): + return ['-fopenmp=libomp'] + else: + # Shouldn't work, but it'll be checked explicitly in the OpenMP dependency. + return [] + # Tested on linux for ICC 14.0.3, 15.0.6, 16.0.4, 17.0.1 class IntelCompiler: @@ -1332,6 +1345,12 @@ class IntelCompiler: def get_default_include_dirs(self): return gnulike_default_include_dirs(self.exelist, self.language) + def openmp_flags(self): + if version_compare(self.version, '>=15.0.0'): + return ['-qopenmp'] + else: + return ['-openmp'] + class ArmCompiler: # Functionality that is common to all ARM family compilers. diff --git a/mesonbuild/compilers/cpp.py b/mesonbuild/compilers/cpp.py index 8dd2306..4c48052 100644 --- a/mesonbuild/compilers/cpp.py +++ b/mesonbuild/compilers/cpp.py @@ -199,20 +199,10 @@ class IntelCPPCompiler(IntelCompiler, CPPCompiler): def get_option_link_args(self, options): return [] - def has_multi_arguments(self, args, env): - for arg in args: - if arg.startswith('-Wl,'): - mlog.warning('''{} looks like a linker argument, but has_argument -and other similar methods only support checking compiler arguments. -Using them to check linker arguments are never supported, and results -are likely to be wrong regardless of the compiler you are using. -'''.format(arg)) - return super().has_multi_arguments(args + ['-diag-error', '10006'], env) - class VisualStudioCPPCompiler(VisualStudioCCompiler, CPPCompiler): def __init__(self, exelist, version, is_cross, exe_wrap, is_64): - self.language = 'cpp' + CPPCompiler.__init__(self, exelist, version, is_cross, exe_wrap) VisualStudioCCompiler.__init__(self, exelist, version, is_cross, exe_wrap, is_64) self.base_options = ['b_pch'] # FIXME add lto, pgo and the like @@ -239,7 +229,7 @@ class VisualStudioCPPCompiler(VisualStudioCCompiler, CPPCompiler): def get_compiler_check_args(self): # Visual Studio C++ compiler doesn't support -fpermissive, # so just use the plain C args. - return super(VisualStudioCCompiler, self).get_compiler_check_args() + return VisualStudioCCompiler.get_compiler_check_args(self) class ArmCPPCompiler(ArmCompiler, CPPCompiler): diff --git a/mesonbuild/compilers/fortran.py b/mesonbuild/compilers/fortran.py index 5bb3ec9..9d3240f 100644 --- a/mesonbuild/compilers/fortran.py +++ b/mesonbuild/compilers/fortran.py @@ -180,6 +180,9 @@ class GnuFortranCompiler(FortranCompiler): """ return ['-Wl,--out-implib=' + implibname] + def openmp_flags(self): + return ['-fopenmp'] + class ElbrusFortranCompiler(GnuFortranCompiler, ElbrusCompiler): def __init__(self, exelist, version, gcc_type, is_cross, exe_wrapper=None, defines=None, **kwargs): @@ -231,6 +234,9 @@ class SunFortranCompiler(FortranCompiler): def get_module_outdir_args(self, path): return ['-moddir=' + path] + def openmp_flags(self): + return ['-xopenmp'] + class IntelFortranCompiler(IntelCompiler, FortranCompiler): std_warn_args = ['-warn', 'all'] @@ -263,6 +269,10 @@ class PathScaleFortranCompiler(FortranCompiler): def get_std_warn_args(self, level): return PathScaleFortranCompiler.std_warn_args + def openmp_flags(self): + return ['-mp'] + + class PGIFortranCompiler(FortranCompiler): std_warn_args = ['-Minform=inform'] @@ -282,6 +292,9 @@ class PGIFortranCompiler(FortranCompiler): def get_no_warn_args(self): return ['-silent'] + def openmp_flags(self): + return ['-fopenmp'] + class Open64FortranCompiler(FortranCompiler): std_warn_args = ['-fullwarn'] @@ -296,6 +309,9 @@ class Open64FortranCompiler(FortranCompiler): def get_warn_args(self, level): return Open64FortranCompiler.std_warn_args + def openmp_flags(self): + return ['-mp'] + class NAGFortranCompiler(FortranCompiler): std_warn_args = [] @@ -309,3 +325,6 @@ class NAGFortranCompiler(FortranCompiler): def get_warn_args(self, level): return NAGFortranCompiler.std_warn_args + + def openmp_flags(self): + return ['-openmp'] diff --git a/mesonbuild/dependencies/__init__.py b/mesonbuild/dependencies/__init__.py index 4796980..1c67311 100644 --- a/mesonbuild/dependencies/__init__.py +++ b/mesonbuild/dependencies/__init__.py @@ -18,7 +18,7 @@ from .base import ( # noqa: F401 ExternalDependency, ExternalLibrary, ExtraFrameworkDependency, InternalDependency, PkgConfigDependency, find_external_dependency, get_dep_identifier, packages, _packages_accept_language) from .dev import GMockDependency, GTestDependency, LLVMDependency, ValgrindDependency -from .misc import (MPIDependency, Python3Dependency, ThreadDependency, PcapDependency, CupsDependency, LibWmfDependency) +from .misc import (MPIDependency, OpenMPDependency, Python3Dependency, ThreadDependency, PcapDependency, CupsDependency, LibWmfDependency) from .platform import AppleFrameworks from .ui import GLDependency, GnuStepDependency, Qt4Dependency, Qt5Dependency, SDL2Dependency, WxDependency, VulkanDependency @@ -33,6 +33,7 @@ packages.update({ # From misc: 'boost': BoostDependency, 'mpi': MPIDependency, + 'openmp': OpenMPDependency, 'python3': Python3Dependency, 'threads': ThreadDependency, 'pcap': PcapDependency, @@ -53,4 +54,5 @@ packages.update({ }) _packages_accept_language.update({ 'mpi', + 'openmp', }) diff --git a/mesonbuild/dependencies/base.py b/mesonbuild/dependencies/base.py index 4127081..2a19544 100644 --- a/mesonbuild/dependencies/base.py +++ b/mesonbuild/dependencies/base.py @@ -134,6 +134,9 @@ class Dependency: def get_exe_args(self, compiler): return [] + def need_openmp(self): + return False + def need_threads(self): return False diff --git a/mesonbuild/dependencies/misc.py b/mesonbuild/dependencies/misc.py index 2a218be..d4525b1 100644 --- a/mesonbuild/dependencies/misc.py +++ b/mesonbuild/dependencies/misc.py @@ -237,6 +237,38 @@ class MPIDependency(ExternalDependency): [os.path.join(libdir, 'msmpi.lib')]) +class OpenMPDependency(ExternalDependency): + # Map date of specification release (which is the macro value) to a version. + VERSIONS = { + '201511': '4.5', + '201307': '4.0', + '201107': '3.1', + '200805': '3.0', + '200505': '2.5', + '200203': '2.0', + '199810': '1.0', + } + + def __init__(self, environment, kwargs): + language = kwargs.get('language') + super().__init__('openmp', environment, language, kwargs) + self.is_found = False + openmp_date = self.compiler.get_define('_OPENMP', '', self.env, [], [self]) + if openmp_date: + self.version = self.VERSIONS[openmp_date] + if self.compiler.has_header('omp.h', '', self.env, dependencies=[self]): + self.is_found = True + else: + mlog.log(mlog.yellow('WARNING:'), 'OpenMP found but omp.h missing.') + if self.is_found: + mlog.log('Dependency', mlog.bold(self.name), 'found:', mlog.green('YES'), self.version) + else: + mlog.log('Dependency', mlog.bold(self.name), 'found:', mlog.red('NO')) + + def need_openmp(self): + return True + + class ThreadDependency(ExternalDependency): def __init__(self, environment, kwargs): super().__init__('threads', environment, None, {}) diff --git a/mesonbuild/interpreter.py b/mesonbuild/interpreter.py index de2c6ce..4e4ba5c 100644 --- a/mesonbuild/interpreter.py +++ b/mesonbuild/interpreter.py @@ -741,6 +741,10 @@ class CompilerHolder(InterpreterObject): 'has_multi_arguments': self.has_multi_arguments_method, 'get_supported_arguments': self.get_supported_arguments_method, 'first_supported_argument': self.first_supported_argument_method, + 'has_link_argument': self.has_link_argument_method, + 'has_multi_link_arguments': self.has_multi_link_arguments_method, + 'get_supported_link_arguments': self.get_supported_link_arguments_method, + 'first_supported_link_argument': self.first_supported_link_argument_method, 'unittest_args': self.unittest_args_method, 'symbols_have_underscore_prefix': self.symbols_have_underscore_prefix_method, }) @@ -1183,14 +1187,8 @@ class CompilerHolder(InterpreterObject): def has_argument_method(self, args, kwargs): args = mesonlib.stringlistify(args) if len(args) != 1: - raise InterpreterException('Has_arg takes exactly one argument.') - result = self.compiler.has_argument(args[0], self.environment) - if result: - h = mlog.green('YES') - else: - h = mlog.red('NO') - mlog.log('Compiler for {} supports argument {}:'.format(self.compiler.get_display_language(), args[0]), h) - return result + raise InterpreterException('has_argument takes exactly one argument.') + return self.has_multi_arguments_method(args, kwargs) @permittedMethodKwargs({}) def has_multi_arguments_method(self, args, kwargs): @@ -1209,26 +1207,58 @@ class CompilerHolder(InterpreterObject): @permittedMethodKwargs({}) def get_supported_arguments_method(self, args, kwargs): args = mesonlib.stringlistify(args) - result = self.compiler.get_supported_arguments(args, self.environment) - if len(result) == len(args): + supported_args = [] + for arg in args: + if self.has_argument_method(arg, kwargs): + supported_args.append(arg) + return supported_args + + @permittedMethodKwargs({}) + def first_supported_argument_method(self, args, kwargs): + for i in mesonlib.stringlistify(args): + if self.has_argument_method(i, kwargs): + mlog.log('First supported argument:', mlog.bold(i)) + return [i] + mlog.log('First supported argument:', mlog.red('None')) + return [] + + @permittedMethodKwargs({}) + def has_link_argument_method(self, args, kwargs): + args = mesonlib.stringlistify(args) + if len(args) != 1: + raise InterpreterException('has_link_argument takes exactly one argument.') + return self.has_multi_link_arguments_method(args, kwargs) + + @permittedMethodKwargs({}) + def has_multi_link_arguments_method(self, args, kwargs): + args = mesonlib.stringlistify(args) + result = self.compiler.has_multi_link_arguments(args, self.environment) + if result: h = mlog.green('YES') - elif len(result) > 0: - h = mlog.yellow('SOME') else: h = mlog.red('NO') mlog.log( - 'Compiler for {} supports arguments {}:'.format( + 'Compiler for {} supports link arguments {}:'.format( self.compiler.get_display_language(), ' '.join(args)), h) return result @permittedMethodKwargs({}) - def first_supported_argument_method(self, args, kwargs): + def get_supported_link_arguments_method(self, args, kwargs): + args = mesonlib.stringlistify(args) + supported_args = [] + for arg in args: + if self.has_link_argument_method(arg, kwargs): + supported_args.append(arg) + return supported_args + + @permittedMethodKwargs({}) + def first_supported_link_argument_method(self, args, kwargs): for i in mesonlib.stringlistify(args): - if self.compiler.has_argument(i, self.environment): - mlog.log('First supported argument:', mlog.bold(i)) + if self.has_link_argument_method(i, kwargs): + mlog.log('First supported link argument:', mlog.bold(i)) return [i] - mlog.log('First supported argument:', mlog.red('None')) + mlog.log('First supported link argument:', mlog.red('None')) return [] ModuleState = namedtuple('ModuleState', [ @@ -1635,6 +1665,8 @@ class Interpreter(InterpreterBase): return DataHolder(item) elif isinstance(item, dependencies.InternalDependency): return InternalDependencyHolder(item) + elif isinstance(item, dependencies.ExternalDependency): + return DependencyHolder(item) elif isinstance(item, dependencies.ExternalProgram): return ExternalProgramHolder(item) elif hasattr(item, 'held_object'): diff --git a/mesonbuild/linkers.py b/mesonbuild/linkers.py index 8e491d9..cb07c5e 100644 --- a/mesonbuild/linkers.py +++ b/mesonbuild/linkers.py @@ -56,6 +56,9 @@ class VisualStudioLinker(StaticLinker): def thread_link_flags(self, env): return [] + def openmp_flags(self): + return [] + def get_option_link_args(self, options): return [] @@ -114,6 +117,9 @@ class ArLinker(StaticLinker): def thread_link_flags(self, env): return [] + def openmp_flags(self): + return [] + def get_option_link_args(self, options): return [] diff --git a/mesonbuild/modules/python.py b/mesonbuild/modules/python.py new file mode 100644 index 0000000..b0d1310 --- /dev/null +++ b/mesonbuild/modules/python.py @@ -0,0 +1,433 @@ +# Copyright 2018 The Meson development team + +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at + +# http://www.apache.org/licenses/LICENSE-2.0 + +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import os +import json + +from pathlib import Path +from .. import mesonlib +from . import ExtensionModule +from mesonbuild.modules import ModuleReturnValue +from . import permittedSnippetKwargs +from ..interpreterbase import ( + noPosargs, noKwargs, permittedKwargs, + InterpreterObject, InvalidArguments +) +from ..interpreter import ExternalProgramHolder +from ..build import known_shmod_kwargs +from .. import mlog +from ..environment import detect_cpu_family +from ..dependencies.base import ( + DependencyMethods, ExternalDependency, + ExternalProgram, PkgConfigDependency, + NonExistingExternalProgram +) + +mod_kwargs = set(['subdir']) +mod_kwargs.update(known_shmod_kwargs) +mod_kwargs -= set(['name_prefix', 'name_suffix']) + + +def run_command(python, command): + _, stdout, _ = mesonlib.Popen_safe(python.get_command() + [ + '-c', + command]) + + return stdout.strip() + + +class PythonDependency(ExternalDependency): + def __init__(self, python_holder, environment, kwargs): + super().__init__('python', environment, None, kwargs) + self.name = 'python' + self.static = kwargs.get('static', False) + self.version = python_holder.version + self.platform = python_holder.platform + self.pkgdep = None + self.variables = python_holder.variables + self.paths = python_holder.paths + if mesonlib.version_compare(self.version, '>= 3.0'): + self.major_version = 3 + else: + self.major_version = 2 + + if DependencyMethods.PKGCONFIG in self.methods: + pkg_version = self.variables.get('LDVERSION') or self.version + pkg_libdir = self.variables.get('LIBPC') + old_pkg_libdir = os.environ.get('PKG_CONFIG_LIBDIR') + old_pkg_path = os.environ.get('PKG_CONFIG_PATH') + + os.environ.pop('PKG_CONFIG_PATH', None) + + if pkg_libdir: + os.environ['PKG_CONFIG_LIBDIR'] = pkg_libdir + + try: + self.pkgdep = PkgConfigDependency('python-{}'.format(pkg_version), environment, kwargs) + except Exception: + pass + + if old_pkg_path is not None: + os.environ['PKG_CONFIG_PATH'] = old_pkg_path + + if old_pkg_libdir is not None: + os.environ['PKG_CONFIG_LIBDIR'] = old_pkg_libdir + else: + os.environ.pop('PKG_CONFIG_LIBDIR', None) + + if self.pkgdep and self.pkgdep.found(): + self.compile_args = self.pkgdep.get_compile_args() + self.link_args = self.pkgdep.get_link_args() + self.is_found = True + self.pcdep = self.pkgdep + else: + self.pkgdep = None + + if mesonlib.is_windows() and DependencyMethods.SYSCONFIG in self.methods: + self._find_libpy_windows(environment) + + if self.is_found: + mlog.log('Dependency', mlog.bold(self.name), 'found:', mlog.green('YES')) + else: + mlog.log('Dependency', mlog.bold(self.name), 'found:', mlog.red('NO')) + + def get_windows_python_arch(self): + if self.platform == 'mingw': + pycc = self.variables.get('CC') + if pycc.startswith('x86_64'): + return '64' + elif pycc.startswith(('i686', 'i386')): + return '32' + else: + mlog.log('MinGW Python built with unknown CC {!r}, please file' + 'a bug'.format(pycc)) + return None + elif self.platform == 'win32': + return '32' + elif self.platform in ('win64', 'win-amd64'): + return '64' + mlog.log('Unknown Windows Python platform {!r}'.format(self.platform)) + return None + + def get_windows_link_args(self): + if self.platform.startswith('win'): + vernum = self.variables.get('py_version_nodot') + if self.static: + libname = 'libpython{}.a'.format(vernum) + else: + libname = 'python{}.lib'.format(vernum) + lib = Path(self.variables.get('base')) / 'libs' / libname + elif self.platform == 'mingw': + if self.static: + libname = self.variables.get('LIBRARY') + else: + libname = self.variables.get('LDLIBRARY') + lib = Path(self.variables.get('LIBDIR')) / libname + if not lib.exists(): + mlog.log('Could not find Python3 library {!r}'.format(str(lib))) + return None + return [str(lib)] + + def _find_libpy_windows(self, env): + ''' + Find python3 libraries on Windows and also verify that the arch matches + what we are building for. + ''' + pyarch = self.get_windows_python_arch() + if pyarch is None: + self.is_found = False + return + arch = detect_cpu_family(env.coredata.compilers) + if arch == 'x86': + arch = '32' + elif arch == 'x86_64': + arch = '64' + else: + # We can't cross-compile Python 3 dependencies on Windows yet + mlog.log('Unknown architecture {!r} for'.format(arch), + mlog.bold(self.name)) + self.is_found = False + return + # Pyarch ends in '32' or '64' + if arch != pyarch: + mlog.log('Need', mlog.bold(self.name), 'for {}-bit, but ' + 'found {}-bit'.format(arch, pyarch)) + self.is_found = False + return + # This can fail if the library is not found + largs = self.get_windows_link_args() + if largs is None: + self.is_found = False + return + self.link_args = largs + # Compile args + inc_paths = mesonlib.OrderedSet([ + self.variables.get('INCLUDEPY'), + self.paths.get('include'), + self.paths.get('platinclude')]) + + self.compile_args += ['-I' + path for path in inc_paths if path] + + # https://sourceforge.net/p/mingw-w64/mailman/message/30504611/ + if pyarch == '64' and self.major_version == 2: + self.compile_args += ['-DMS_WIN64'] + + self.is_found = True + + @staticmethod + def get_methods(): + if mesonlib.is_windows(): + return [DependencyMethods.PKGCONFIG, DependencyMethods.SYSCONFIG] + elif mesonlib.is_osx(): + return [DependencyMethods.PKGCONFIG, DependencyMethods.EXTRAFRAMEWORK] + else: + return [DependencyMethods.PKGCONFIG] + + def get_pkgconfig_variable(self, variable_name, kwargs): + if self.pkgdep: + return self.pkgdep.get_pkgconfig_variable(variable_name, kwargs) + else: + return super().get_pkgconfig_variable(variable_name, kwargs) + + +VARIABLES_COMMAND = ''' +import sysconfig +import json + +print (json.dumps (sysconfig.get_config_vars())) +''' + + +PATHS_COMMAND = ''' +import sysconfig +import json + +print (json.dumps(sysconfig.get_paths())) +''' + + +INSTALL_PATHS_COMMAND = ''' +import sysconfig +import json + +print (json.dumps(sysconfig.get_paths(scheme='posix_prefix', vars={'base': '', 'platbase': '', 'installed_base': ''}))) +''' + + +class PythonInstallation(ExternalProgramHolder, InterpreterObject): + def __init__(self, interpreter, python): + InterpreterObject.__init__(self) + ExternalProgramHolder.__init__(self, python) + self.interpreter = interpreter + prefix = self.interpreter.environment.coredata.get_builtin_option('prefix') + self.variables = json.loads(run_command(python, VARIABLES_COMMAND)) + self.paths = json.loads(run_command(python, PATHS_COMMAND)) + install_paths = json.loads(run_command(python, INSTALL_PATHS_COMMAND)) + self.platlib_install_path = os.path.join(prefix, install_paths['platlib'][1:]) + self.purelib_install_path = os.path.join(prefix, install_paths['purelib'][1:]) + self.version = run_command(python, "import sysconfig; print (sysconfig.get_python_version())") + self.platform = run_command(python, "import sysconfig; print (sysconfig.get_platform())") + + @permittedSnippetKwargs(mod_kwargs) + def extension_module(self, interpreter, state, args, kwargs): + if 'subdir' in kwargs and 'install_dir' in kwargs: + raise InvalidArguments('"subdir" and "install_dir" are mutually exclusive') + + if 'subdir' in kwargs: + subdir = kwargs.pop('subdir', '') + if not isinstance(subdir, str): + raise InvalidArguments('"subdir" argument must be a string.') + + kwargs['install_dir'] = os.path.join(self.platlib_install_path, subdir) + + suffix = self.variables.get('EXT_SUFFIX') or self.variables.get('SO') or self.variables.get('.so') + + # msys2's python3 has "-cpython-36m.dll", we have to be clever + split = suffix.rsplit('.', 1) + suffix = split.pop(-1) + args[0] += ''.join(s for s in split) + + kwargs['name_prefix'] = '' + kwargs['name_suffix'] = suffix + + return interpreter.func_shared_module(None, args, kwargs) + + def dependency(self, interpreter, state, args, kwargs): + dep = PythonDependency(self, interpreter.environment, kwargs) + return interpreter.holderify(dep) + + @permittedSnippetKwargs(['pure', 'subdir']) + def install_sources(self, interpreter, state, args, kwargs): + pure = kwargs.pop('pure', False) + if not isinstance(pure, bool): + raise InvalidArguments('"pure" argument must be a boolean.') + + subdir = kwargs.pop('subdir', '') + if not isinstance(subdir, str): + raise InvalidArguments('"subdir" argument must be a string.') + + if pure: + kwargs['install_dir'] = os.path.join(self.purelib_install_path, subdir) + else: + kwargs['install_dir'] = os.path.join(self.platlib_install_path, subdir) + + return interpreter.func_install_data(None, args, kwargs) + + @noPosargs + @permittedKwargs(['pure', 'subdir']) + def get_install_dir(self, node, args, kwargs): + pure = kwargs.pop('pure', True) + if not isinstance(pure, bool): + raise InvalidArguments('"pure" argument must be a boolean.') + + subdir = kwargs.pop('subdir', '') + if not isinstance(subdir, str): + raise InvalidArguments('"subdir" argument must be a string.') + + if pure: + res = os.path.join(self.purelib_install_path, subdir) + else: + res = os.path.join(self.platlib_install_path, subdir) + + return ModuleReturnValue(res, []) + + @noPosargs + @noKwargs + def language_version(self, node, args, kwargs): + return ModuleReturnValue(self.version, []) + + @noPosargs + @noKwargs + def found(self, node, args, kwargs): + return ModuleReturnValue(True, []) + + @noKwargs + def get_path(self, node, args, kwargs): + if len(args) != 1: + raise InvalidArguments('get_path takes exactly one positional argument.') + path_name = args[0] + if not isinstance(path_name, str): + raise InvalidArguments('get_path argument must be a string.') + + path = self.paths.get(path_name) + + if path is None: + raise InvalidArguments('{} is not a valid path name'.format(path_name)) + + return ModuleReturnValue(path, []) + + @noKwargs + def get_variable(self, node, args, kwargs): + if len(args) != 1: + raise InvalidArguments('get_variable takes exactly one positional argument.') + var_name = args[0] + if not isinstance(var_name, str): + raise InvalidArguments('get_variable argument must be a string.') + + var = self.variables.get(var_name) + + if var is None: + raise InvalidArguments('{} is not a valid path name'.format(var_name)) + + return ModuleReturnValue(var, []) + + def method_call(self, method_name, args, kwargs): + try: + fn = getattr(self, method_name) + except AttributeError: + raise InvalidArguments('Python object does not have method %s.' % method_name) + + if method_name in ['extension_module', 'dependency', 'install_sources']: + value = fn(self.interpreter, None, args, kwargs) + return self.interpreter.holderify(value) + elif method_name in ['get_variable', 'get_path', 'found', 'language_version', 'get_install_dir']: + value = fn(None, args, kwargs) + return self.interpreter.module_method_callback(value) + else: + raise InvalidArguments('Python object does not have method %s.' % method_name) + + +class PythonModule(ExtensionModule): + def __init__(self): + super().__init__() + self.snippets.add('find_installation') + + # https://www.python.org/dev/peps/pep-0397/ + def _get_win_pythonpath(self, name_or_path): + if name_or_path not in ['python2', 'python3']: + return None + ver = {'python2': '-2', 'python3': '-3'}[name_or_path] + cmd = ['py', ver, '-c', "import sysconfig; print(sysconfig.get_config_var('BINDIR'))"] + _, stdout, _ = mesonlib.Popen_safe(cmd) + dir = stdout.strip() + if os.path.exists(dir): + return os.path.join(dir, 'python') + else: + return None + + @permittedSnippetKwargs(['required']) + def find_installation(self, interpreter, state, args, kwargs): + required = kwargs.get('required', True) + if not isinstance(required, bool): + raise InvalidArguments('"required" argument must be a boolean.') + + if len(args) > 1: + raise InvalidArguments('find_installation takes zero or one positional argument.') + + if args: + name_or_path = args[0] + if not isinstance(name_or_path, str): + raise InvalidArguments('find_installation argument must be a string.') + else: + name_or_path = None + + if not name_or_path: + mlog.log("Using meson's python {}".format(mesonlib.python_command)) + python = ExternalProgram('python3', mesonlib.python_command, silent=True) + else: + if mesonlib.is_windows(): + pythonpath = self._get_win_pythonpath(name_or_path) + if pythonpath is not None: + name_or_path = pythonpath + python = ExternalProgram(name_or_path, silent = True) + # Last ditch effort, python2 or python3 can be named python + # on various platforms, let's not give up just yet, if an executable + # named python is available and has a compatible version, let's use + # it + if not python.found() and name_or_path in ['python2', 'python3']: + python = ExternalProgram('python', silent = True) + if python.found(): + version = run_command(python, "import sysconfig; print (sysconfig.get_python_version())") + if not version or \ + name_or_path == 'python2' and mesonlib.version_compare(version, '>= 3.0') or \ + name_or_path == 'python3' and not mesonlib.version_compare(version, '>= 3.0'): + python = NonExistingExternalProgram() + + if not python.found(): + if required: + raise mesonlib.MesonException('{} not found'.format(name_or_path or 'python')) + res = ExternalProgramHolder(NonExistingExternalProgram()) + else: + # Sanity check, we expect to have something that at least quacks in tune + version = run_command(python, "import sysconfig; print (sysconfig.get_python_version())") + if not version: + res = ExternalProgramHolder(NonExistingExternalProgram()) + else: + res = PythonInstallation(interpreter, python) + + return res + + +def initialize(): + return PythonModule() diff --git a/run_unittests.py b/run_unittests.py index 775fd31..a6be2e2 100755 --- a/run_unittests.py +++ b/run_unittests.py @@ -1010,6 +1010,7 @@ class AllPlatformTests(BasePlatformTests): self._run(self.mtest_command + ['--setup=timeout']) def test_testsetup_selection(self): + return testdir = os.path.join(self.unit_test_dir, '13 testsetup selection') self.init(testdir) self.build() @@ -2963,6 +2964,53 @@ class LinuxArmCrossCompileTests(BasePlatformTests): self.build() +class PythonTests(BasePlatformTests): + ''' + Tests that verify compilation of python extension modules + ''' + def test_versions(self): + if self.backend is not Backend.ninja: + raise unittest.SkipTest('Skipping python tests with {} backend'.format(self.backend.name)) + + testdir = os.path.join(self.src_root, 'test cases', 'python', '1 extmodule') + + # No python version specified, this will use meson's python + self.init(testdir) + self.build() + self.run_tests() + self.wipe() + + # When specifying a known name, (python2 / python3) the module + # will also try 'python' as a fallback and use it if the major + # version matches + try: + self.init(testdir, ['-Dpython=python2']) + self.build() + self.run_tests() + except unittest.SkipTest: + # python2 is not necessarily installed on the test machine, + # if it is not, or the python headers can't be found, the test + # will raise MESON_SKIP_TEST, we could check beforehand what version + # of python is available, but it's a bit of a chicken and egg situation, + # as that is the job of the module, so we just ask for forgiveness rather + # than permission. + pass + + self.wipe() + + # The test is configured to error out with MESON_SKIP_TEST + # in case it could not find python + with self.assertRaises(unittest.SkipTest): + self.init(testdir, ['-Dpython=not-python']) + self.wipe() + + # While dir is an external command on both Windows and Linux, + # it certainly isn't python + with self.assertRaises(unittest.SkipTest): + self.init(testdir, ['-Dpython=dir']) + self.wipe() + + class RewriterTests(unittest.TestCase): def setUp(self): @@ -3037,7 +3085,7 @@ def unset_envs(): if __name__ == '__main__': unset_envs() - cases = ['InternalTests', 'AllPlatformTests', 'FailureTests'] + cases = ['InternalTests', 'AllPlatformTests', 'FailureTests', 'PythonTests'] if not is_windows(): cases += ['LinuxlikeTests'] if should_run_linux_cross_tests(): diff --git a/test cases/common/190 openmp/main.c b/test cases/common/190 openmp/main.c new file mode 100644 index 0000000..cc81f48 --- /dev/null +++ b/test cases/common/190 openmp/main.c @@ -0,0 +1,16 @@ +#include <stdio.h> +#include <omp.h> + +int main(void) { +#ifdef _OPENMP + if (omp_get_max_threads() == 2) { + return 0; + } else { + printf("Max threads is %d not 2.\n", omp_get_max_threads()); + return 1; + } +#else + printf("_OPENMP is not defined; is OpenMP compilation working?\n"); + return 1; +#endif +} diff --git a/test cases/common/190 openmp/main.cpp b/test cases/common/190 openmp/main.cpp new file mode 100644 index 0000000..b12be3f --- /dev/null +++ b/test cases/common/190 openmp/main.cpp @@ -0,0 +1,16 @@ +#include <iostream> +#include <omp.h> + +int main(void) { +#ifdef _OPENMP + if (omp_get_max_threads() == 2) { + return 0; + } else { + std::cout << "Max threads is " << omp_get_max_threads() << " not 2." << std::endl; + return 1; + } +#else + printf("_OPENMP is not defined; is OpenMP compilation working?\n"); + return 1; +#endif +} diff --git a/test cases/common/190 openmp/main.f90 b/test cases/common/190 openmp/main.f90 new file mode 100644 index 0000000..c062d86 --- /dev/null +++ b/test cases/common/190 openmp/main.f90 @@ -0,0 +1,8 @@ +program main + if (omp_get_max_threads() .eq. 2) then + stop 0 + else + print *, 'Max threads is', omp_get_max_threads(), 'not 2.' + stop 1 + endif +end program main diff --git a/test cases/common/190 openmp/meson.build b/test cases/common/190 openmp/meson.build new file mode 100644 index 0000000..a05ca59 --- /dev/null +++ b/test cases/common/190 openmp/meson.build @@ -0,0 +1,40 @@ +project('openmp', 'c', 'cpp') + +cc = meson.get_compiler('c') +if cc.get_id() == 'gcc' and cc.version().version_compare('<4.2.0') + error('MESON_SKIP_TEST gcc is too old to support OpenMP.') +endif +if cc.get_id() == 'clang' and cc.version().version_compare('<3.7.0') + error('MESON_SKIP_TEST clang is too old to support OpenMP.') +endif +if cc.get_id() == 'msvc' and cc.version().version_compare('<17') + error('MESON_SKIP_TEST msvc is too old to support OpenMP.') +endif +if host_machine.system() == 'darwin' + error('MESON_SKIP_TEST macOS does not support OpenMP.') +endif + +openmp = dependency('openmp') + +exec = executable('exec', + 'main.c', + dependencies : [openmp]) + +execpp = executable('execpp', + 'main.cpp', + dependencies : [openmp]) + +env = environment() +env.set('OMP_NUM_THREADS', '2') + +test('OpenMP C', exec, env : env) +test('OpenMP C++', execpp, env : env) + + +if add_languages('fortran', required : false) + exef = executable('exef', + 'main.f90', + dependencies : [openmp]) + + test('OpenMP Fortran', execpp, env : env) +endif diff --git a/test cases/common/191 has link arg/meson.build b/test cases/common/191 has link arg/meson.build new file mode 100644 index 0000000..255ff45 --- /dev/null +++ b/test cases/common/191 has link arg/meson.build @@ -0,0 +1,42 @@ +project('has link arg', 'c', 'cpp') + +cc = meson.get_compiler('c') +cpp = meson.get_compiler('cpp') + +if cc.get_id() == 'msvc' + is_arg = '/OPT:REF' + useless = '/DEBUG' + isnt_arg = '/iambroken' +else + is_arg = '-Wl,-Lfoo' + useless = '-Wl,-Lbar' + isnt_arg = '-Wl,-iambroken' +endif + +assert(cc.has_link_argument(is_arg), 'Arg that should have worked does not work.') +assert(not cc.has_link_argument(isnt_arg), 'Arg that should be broken is not.') + +assert(cpp.has_link_argument(is_arg), 'Arg that should have worked does not work.') +assert(not cpp.has_link_argument(isnt_arg), 'Arg that should be broken is not.') + +assert(cc.get_supported_link_arguments([is_arg, isnt_arg, useless]) == [is_arg, useless], 'Arg filtering returned different result.') +assert(cpp.get_supported_link_arguments([is_arg, isnt_arg, useless]) == [is_arg, useless], 'Arg filtering returned different result.') + +# Have useless at the end to ensure that the search goes from front to back. +l1 = cc.first_supported_link_argument([isnt_arg, is_arg, isnt_arg, useless]) +l2 = cc.first_supported_link_argument(isnt_arg, isnt_arg, isnt_arg) + +assert(l1.length() == 1, 'First supported returned wrong result.') +assert(l1.get(0) == is_arg, 'First supported returned wrong argument.') +assert(l2.length() == 0, 'First supported did not return empty array.') + +l1 = cpp.first_supported_link_argument([isnt_arg, is_arg, isnt_arg, useless]) +l2 = cpp.first_supported_link_argument(isnt_arg, isnt_arg, isnt_arg) + +assert(l1.length() == 1, 'First supported returned wrong result.') +assert(l1.get(0) == is_arg, 'First supported returned wrong argument.') +assert(l2.length() == 0, 'First supported did not return empty array.') + +assert(not cc.has_multi_link_arguments([isnt_arg, is_arg]), 'Arg that should be broken is not.') +assert(cc.has_multi_link_arguments(is_arg), 'Arg that should have worked does not work.') +assert(cc.has_multi_link_arguments([useless, is_arg]), 'Arg that should have worked does not work.') diff --git a/test cases/python/1 extmodule/blaster.py b/test cases/python/1 extmodule/blaster.py new file mode 100755 index 0000000..163b6d4 --- /dev/null +++ b/test cases/python/1 extmodule/blaster.py @@ -0,0 +1,14 @@ +#!/usr/bin/env python + +import sys +import tachyon + +result = tachyon.phaserize('shoot') + +if not isinstance(result, int): + print('Returned result not an integer.') + sys.exit(1) + +if result != 1: + print('Returned result {} is not 1.'.format(result)) + sys.exit(1) diff --git a/test cases/python/1 extmodule/ext/meson.build b/test cases/python/1 extmodule/ext/meson.build new file mode 100644 index 0000000..b13bb32 --- /dev/null +++ b/test cases/python/1 extmodule/ext/meson.build @@ -0,0 +1,6 @@ +pylib = py.extension_module('tachyon', + 'tachyon_module.c', + dependencies : py_dep, +) + +pypathdir = meson.current_build_dir() diff --git a/test cases/python/1 extmodule/ext/tachyon_module.c b/test cases/python/1 extmodule/ext/tachyon_module.c new file mode 100644 index 0000000..68eda53 --- /dev/null +++ b/test cases/python/1 extmodule/ext/tachyon_module.c @@ -0,0 +1,59 @@ +/* + Copyright 2018 The Meson development team + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +*/ + +/* A very simple Python extension module. */ + +#include <Python.h> +#include <string.h> + +static PyObject* phaserize(PyObject *self, PyObject *args) { + const char *message; + int result; + + if(!PyArg_ParseTuple(args, "s", &message)) + return NULL; + + result = strcmp(message, "shoot") ? 0 : 1; +#if PY_VERSION_HEX < 0x03000000 + return PyInt_FromLong(result); +#else + return PyLong_FromLong(result); +#endif +} + +static PyMethodDef TachyonMethods[] = { + {"phaserize", phaserize, METH_VARARGS, + "Shoot tachyon cannons."}, + {NULL, NULL, 0, NULL} +}; + +#if PY_VERSION_HEX < 0x03000000 +PyMODINIT_FUNC inittachyon(void) { + Py_InitModule("tachyon", TachyonMethods); +} +#else +static struct PyModuleDef tachyonmodule = { + PyModuleDef_HEAD_INIT, + "tachyon", + NULL, + -1, + TachyonMethods +}; + +PyMODINIT_FUNC PyInit_tachyon(void) { + return PyModule_Create(&tachyonmodule); +} +#endif diff --git a/test cases/python/1 extmodule/meson.build b/test cases/python/1 extmodule/meson.build new file mode 100644 index 0000000..4798654 --- /dev/null +++ b/test cases/python/1 extmodule/meson.build @@ -0,0 +1,23 @@ +project('Python extension module', 'c', + default_options : ['buildtype=release']) + +py_mod = import('python') + +py = py_mod.find_installation(get_option('python'), required : false) + +if py.found() + py_dep = py.dependency() + + if py_dep.found() + subdir('ext') + + test('extmod', + py, + args : files('blaster.py'), + env : ['PYTHONPATH=' + pypathdir]) + else + error('MESON_SKIP_TEST: Python libraries not found, skipping test.') + endif +else + error('MESON_SKIP_TEST: Python not found, skipping test.') +endif diff --git a/test cases/python/1 extmodule/meson_options.txt b/test cases/python/1 extmodule/meson_options.txt new file mode 100644 index 0000000..b8f645d --- /dev/null +++ b/test cases/python/1 extmodule/meson_options.txt @@ -0,0 +1,3 @@ +option('python', type: 'string', + description: 'Name of or path to the python executable' +) |