diff options
26 files changed, 445 insertions, 44 deletions
diff --git a/mesonbuild/modules/python.py b/mesonbuild/modules/python.py index 621d104..c51b412 100644 --- a/mesonbuild/modules/python.py +++ b/mesonbuild/modules/python.py @@ -284,11 +284,12 @@ print (json.dumps ({ })) ''' -class PythonInstallation(ExternalProgramHolder, InterpreterObject): + +class PythonInstallation(ExternalProgramHolder): def __init__(self, interpreter, python, info): - InterpreterObject.__init__(self) ExternalProgramHolder.__init__(self, python) self.interpreter = interpreter + self.subproject = self.interpreter.subproject prefix = self.interpreter.environment.coredata.get_builtin_option('prefix') self.variables = info['variables'] self.paths = info['paths'] @@ -299,9 +300,22 @@ class PythonInstallation(ExternalProgramHolder, InterpreterObject): self.platform = info['platform'] self.is_pypy = info['is_pypy'] self.link_libpython = info['link_libpython'] + self.methods.update({ + 'extension_module': self.extension_module_method, + 'dependency': self.dependency_method, + 'install_sources': self.install_sources_method, + 'get_install_dir': self.get_install_dir_method, + 'language_version': self.language_version_method, + 'found': self.found_method, + 'has_path': self.has_path_method, + 'get_path': self.get_path_method, + 'has_variable': self.has_variable_method, + 'get_variable': self.get_variable_method, + 'path': self.path_method, + }) @permittedKwargs(mod_kwargs) - def extension_module(self, interpreter, state, args, kwargs): + def extension_module_method(self, args, kwargs): if 'subdir' in kwargs and 'install_dir' in kwargs: raise InvalidArguments('"subdir" and "install_dir" are mutually exclusive') @@ -320,7 +334,7 @@ class PythonInstallation(ExternalProgramHolder, InterpreterObject): for holder in mesonlib.extract_as_list(kwargs, 'dependencies'): dep = holder.held_object if isinstance(dep, PythonDependency): - holder = interpreter.holderify(dep.get_partial_dependency(compile_args=True)) + holder = self.interpreter.holderify(dep.get_partial_dependency(compile_args=True)) new_deps.append(holder) kwargs['dependencies'] = new_deps @@ -334,14 +348,14 @@ class PythonInstallation(ExternalProgramHolder, InterpreterObject): kwargs['name_prefix'] = '' kwargs['name_suffix'] = suffix - return interpreter.func_shared_module(None, args, kwargs) + return self.interpreter.func_shared_module(None, args, kwargs) - def dependency(self, interpreter, state, args, kwargs): - dep = PythonDependency(self, interpreter.environment, kwargs) - return interpreter.holderify(dep) + def dependency_method(self, args, kwargs): + dep = PythonDependency(self, self.interpreter.environment, kwargs) + return self.interpreter.holderify(dep) @permittedKwargs(['pure', 'subdir']) - def install_sources(self, interpreter, state, args, kwargs): + def install_sources_method(self, args, kwargs): pure = kwargs.pop('pure', False) if not isinstance(pure, bool): raise InvalidArguments('"pure" argument must be a boolean.') @@ -355,11 +369,11 @@ class PythonInstallation(ExternalProgramHolder, InterpreterObject): else: kwargs['install_dir'] = os.path.join(self.platlib_install_path, subdir) - return interpreter.func_install_data(None, args, kwargs) + return self.interpreter.holderify(self.interpreter.func_install_data(None, args, kwargs)) @noPosargs @permittedKwargs(['pure', 'subdir']) - def get_install_dir(self, node, args, kwargs): + def get_install_dir_method(self, args, kwargs): pure = kwargs.pop('pure', True) if not isinstance(pure, bool): raise InvalidArguments('"pure" argument must be a boolean.') @@ -373,30 +387,25 @@ class PythonInstallation(ExternalProgramHolder, InterpreterObject): 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, []) + return self.interpreter.module_method_callback(ModuleReturnValue(res, [])) @noPosargs @noKwargs - def found(self, node, args, kwargs): - return ModuleReturnValue(True, []) + def language_version_method(self, args, kwargs): + return self.interpreter.module_method_callback(ModuleReturnValue(self.version, [])) @noKwargs - def has_path(self, node, args, kwargs): + def has_path_method(self, args, kwargs): if len(args) != 1: raise InvalidArguments('has_path takes exactly one positional argument.') path_name = args[0] if not isinstance(path_name, str): raise InvalidArguments('has_path argument must be a string.') - return ModuleReturnValue(path_name in self.paths, []) + return self.interpreter.module_method_callback(ModuleReturnValue(path_name in self.paths, [])) @noKwargs - def get_path(self, node, args, kwargs): + def get_path_method(self, args, kwargs): if len(args) not in (1, 2): raise InvalidArguments('get_path must have one or two arguments.') path_name = args[0] @@ -411,20 +420,20 @@ class PythonInstallation(ExternalProgramHolder, InterpreterObject): else: raise InvalidArguments('{} is not a valid path name'.format(path_name)) - return ModuleReturnValue(path, []) + return self.interpreter.module_method_callback(ModuleReturnValue(path, [])) @noKwargs - def has_variable(self, node, args, kwargs): + def has_variable_method(self, args, kwargs): if len(args) != 1: raise InvalidArguments('has_variable takes exactly one positional argument.') var_name = args[0] if not isinstance(var_name, str): raise InvalidArguments('has_variable argument must be a string.') - return ModuleReturnValue(var_name in self.variables, []) + return self.interpreter.module_method_callback(ModuleReturnValue(var_name in self.variables, [])) @noKwargs - def get_variable(self, node, args, kwargs): + def get_variable_method(self, args, kwargs): if len(args) not in (1, 2): raise InvalidArguments('get_variable must have one or two arguments.') var_name = args[0] @@ -439,25 +448,13 @@ class PythonInstallation(ExternalProgramHolder, InterpreterObject): else: raise InvalidArguments('{} is not a valid variable name'.format(var_name)) - return ModuleReturnValue(var, []) + return self.interpreter.module_method_callback(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 not getattr(fn, 'no-args-flattening', False): - args = flatten(args) - - 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 ['has_variable', 'get_variable', 'has_path', '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) + @noPosargs + @noKwargs + @FeatureNew('Python module path method', '0.50.0') + def path_method(self, args, kwargs): + return super().path_method(args, kwargs) class PythonModule(ExtensionModule): diff --git a/run_project_tests.py b/run_project_tests.py index 0d64f47..02897ce 100755 --- a/run_project_tests.py +++ b/run_project_tests.py @@ -543,6 +543,7 @@ def detect_tests_to_run(): ('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), + ('python', 'python', backend is not Backend.ninja), ('fpga', 'fpga', shutil.which('yosys') is None), ('frameworks', 'frameworks', False), ('nasm', 'nasm', False), diff --git a/test cases/python/1 basic/gluon/__init__.py b/test cases/python/1 basic/gluon/__init__.py new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/test cases/python/1 basic/gluon/__init__.py diff --git a/test cases/python/1 basic/gluon/gluonator.py b/test cases/python/1 basic/gluon/gluonator.py new file mode 100644 index 0000000..b53d6de --- /dev/null +++ b/test cases/python/1 basic/gluon/gluonator.py @@ -0,0 +1,2 @@ +def gluoninate(): + return 42 diff --git a/test cases/python/1 basic/meson.build b/test cases/python/1 basic/meson.build new file mode 100644 index 0000000..f9a7433 --- /dev/null +++ b/test cases/python/1 basic/meson.build @@ -0,0 +1,26 @@ +project('python sample', 'c') + +py_mod = import('python') +py = py_mod.find_installation() + +py_version = py.language_version() +if py_version.version_compare('< 3.2') + error('MESON_SKIP_TEST python 3 required for tests') +endif + +py_purelib = py.get_path('purelib') +if not py_purelib.endswith('site-packages') + error('Python3 purelib path seems invalid? ' + py_purelib) +endif + +# could be 'lib64' or 'Lib' on some systems +py_platlib = py.get_path('platlib') +if not py_platlib.endswith('site-packages') + error('Python3 platlib path seems invalid? ' + py_platlib) +endif + +main = files('prog.py') + +test('toplevel', py, args : main) + +subdir('subdir') diff --git a/test cases/python/1 basic/prog.py b/test cases/python/1 basic/prog.py new file mode 100755 index 0000000..9d95aea --- /dev/null +++ b/test cases/python/1 basic/prog.py @@ -0,0 +1,9 @@ +#!/usr/bin/env python3 + +from gluon import gluonator +import sys + +print('Running mainprog from root dir.') + +if gluonator.gluoninate() != 42: + sys.exit(1) diff --git a/test cases/python/1 basic/subdir/meson.build b/test cases/python/1 basic/subdir/meson.build new file mode 100644 index 0000000..66957c1 --- /dev/null +++ b/test cases/python/1 basic/subdir/meson.build @@ -0,0 +1,4 @@ +test('subdir', + py, + args : files('subprog.py'), + env : 'PYTHONPATH=' + meson.source_root()) diff --git a/test cases/python/1 basic/subdir/subprog.py b/test cases/python/1 basic/subdir/subprog.py new file mode 100755 index 0000000..08652f0 --- /dev/null +++ b/test cases/python/1 basic/subdir/subprog.py @@ -0,0 +1,12 @@ +#!/usr/bin/env python3 + +# In order to run this program, PYTHONPATH must be set to +# point to source root. + +from gluon import gluonator +import sys + +print('Running mainprog from subdir.') + +if gluonator.gluoninate() != 42: + sys.exit(1) diff --git a/test cases/python/2 extmodule/blaster.py b/test cases/python/2 extmodule/blaster.py new file mode 100755 index 0000000..7e1eae6 --- /dev/null +++ b/test cases/python/2 extmodule/blaster.py @@ -0,0 +1,14 @@ +#!/usr/bin/env python3 + +import tachyon +import sys + +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/2 extmodule/ext/meson.build b/test cases/python/2 extmodule/ext/meson.build new file mode 100644 index 0000000..b13bb32 --- /dev/null +++ b/test cases/python/2 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/2 extmodule/ext/tachyon_module.c b/test cases/python/2 extmodule/ext/tachyon_module.c new file mode 100644 index 0000000..b2592e4 --- /dev/null +++ b/test cases/python/2 extmodule/ext/tachyon_module.c @@ -0,0 +1,49 @@ +/* + Copyright 2016 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; + return PyLong_FromLong(result); +} + +static PyMethodDef TachyonMethods[] = { + {"phaserize", phaserize, METH_VARARGS, + "Shoot tachyon cannons."}, + {NULL, NULL, 0, NULL} +}; + +static struct PyModuleDef tachyonmodule = { + PyModuleDef_HEAD_INIT, + "tachyon", + NULL, + -1, + TachyonMethods +}; + +PyMODINIT_FUNC PyInit_tachyon(void) { + return PyModule_Create(&tachyonmodule); +} diff --git a/test cases/python/2 extmodule/meson.build b/test cases/python/2 extmodule/meson.build new file mode 100644 index 0000000..b4eb960 --- /dev/null +++ b/test cases/python/2 extmodule/meson.build @@ -0,0 +1,28 @@ +project('Python extension module', 'c', + default_options : ['buildtype=release']) +# Because Windows Python ships only with optimized libs, +# we must build this project the same way. + +py_mod = import('python') +py = py_mod.find_installation() +py_dep = py.dependency() + +if py_dep.found() + subdir('ext') + + test('extmod', + py, + args : files('blaster.py'), + env : ['PYTHONPATH=' + pypathdir]) + + # Check we can apply a version constraint + dependency('python3', version: '>=@0@'.format(py_dep.version())) + +else + error('MESON_SKIP_TEST: Python3 libraries not found, skipping test.') +endif + +py3_pkg_dep = dependency('python3', method: 'pkg-config', required : false) +if py3_pkg_dep.found() + python_lib_dir = py3_pkg_dep.get_pkgconfig_variable('libdir') +endif diff --git a/test cases/python/3 cython/cytest.py b/test cases/python/3 cython/cytest.py new file mode 100755 index 0000000..43443dc --- /dev/null +++ b/test cases/python/3 cython/cytest.py @@ -0,0 +1,23 @@ +#!/usr/bin/env python3 + +from storer import Storer +import sys + +s = Storer() + +if s.get_value() != 0: + print('Initial value incorrect.') + sys.exit(1) + +s.set_value(42) + +if s.get_value() != 42: + print('Setting value failed.') + sys.exit(1) + +try: + s.set_value('not a number') + print('Using wrong argument type did not fail.') + sys.exit(1) +except TypeError: + pass diff --git a/test cases/python/3 cython/libdir/cstorer.pxd b/test cases/python/3 cython/libdir/cstorer.pxd new file mode 100644 index 0000000..7b730fc --- /dev/null +++ b/test cases/python/3 cython/libdir/cstorer.pxd @@ -0,0 +1,9 @@ + +cdef extern from "storer.h": + ctypedef struct Storer: + pass + + Storer* storer_new(); + void storer_destroy(Storer *s); + int storer_get_value(Storer *s); + void storer_set_value(Storer *s, int v); diff --git a/test cases/python/3 cython/libdir/meson.build b/test cases/python/3 cython/libdir/meson.build new file mode 100644 index 0000000..2b6ebc7 --- /dev/null +++ b/test cases/python/3 cython/libdir/meson.build @@ -0,0 +1,11 @@ +pyx_c = custom_target('storer_pyx', + output : 'storer_pyx.c', + input : 'storer.pyx', + command : [cython, '@INPUT@', '-o', '@OUTPUT@'], +) + +slib = py3.extension_module('storer', + 'storer.c', pyx_c, + dependencies : py3_dep) + +pydir = meson.current_build_dir() diff --git a/test cases/python/3 cython/libdir/storer.c b/test cases/python/3 cython/libdir/storer.c new file mode 100644 index 0000000..0199bb8 --- /dev/null +++ b/test cases/python/3 cython/libdir/storer.c @@ -0,0 +1,24 @@ +#include"storer.h" +#include<stdlib.h> + +struct _Storer { + int value; +}; + +Storer* storer_new() { + Storer *s = malloc(sizeof(struct _Storer)); + s->value = 0; + return s; +} + +void storer_destroy(Storer *s) { + free(s); +} + +int storer_get_value(Storer *s) { + return s->value; +} + +void storer_set_value(Storer *s, int v) { + s->value = v; +} diff --git a/test cases/python/3 cython/libdir/storer.h b/test cases/python/3 cython/libdir/storer.h new file mode 100644 index 0000000..4f71917 --- /dev/null +++ b/test cases/python/3 cython/libdir/storer.h @@ -0,0 +1,8 @@ +#pragma once + +typedef struct _Storer Storer; + +Storer* storer_new(); +void storer_destroy(Storer *s); +int storer_get_value(Storer *s); +void storer_set_value(Storer *s, int v); diff --git a/test cases/python/3 cython/libdir/storer.pyx b/test cases/python/3 cython/libdir/storer.pyx new file mode 100644 index 0000000..ed551dc --- /dev/null +++ b/test cases/python/3 cython/libdir/storer.pyx @@ -0,0 +1,16 @@ +cimport cstorer + +cdef class Storer: + cdef cstorer.Storer* _c_storer + + def __cinit__(self): + self._c_storer = cstorer.storer_new() + + def __dealloc__(self): + cstorer.storer_destroy(self._c_storer) + + cpdef int get_value(self): + return cstorer.storer_get_value(self._c_storer) + + cpdef set_value(self, int value): + cstorer.storer_set_value(self._c_storer, value) diff --git a/test cases/python/3 cython/meson.build b/test cases/python/3 cython/meson.build new file mode 100644 index 0000000..194920b --- /dev/null +++ b/test cases/python/3 cython/meson.build @@ -0,0 +1,20 @@ +project('cython', 'c', + default_options : ['warning_level=3']) + +cython = find_program('cython3', required : false) +py3_dep = dependency('python3', required : false) + +if cython.found() and py3_dep.found() + py_mod = import('python') + py3 = py_mod.find_installation() + py3_dep = py3.dependency() + subdir('libdir') + + test('cython tester', + py3, + args : files('cytest.py'), + env : ['PYTHONPATH=' + pydir] + ) +else + error('MESON_SKIP_TEST: Cython3 or Python3 libraries not found, skipping test.') +endif diff --git a/test cases/python/4 custom target depends extmodule/blaster.py b/test cases/python/4 custom target depends extmodule/blaster.py new file mode 100644 index 0000000..6106f6b --- /dev/null +++ b/test cases/python/4 custom target depends extmodule/blaster.py @@ -0,0 +1,32 @@ +#!/usr/bin/env python3 + +import os +import sys +import argparse + +from pathlib import Path + +filedir = Path(os.path.dirname(__file__)).resolve() +if list(filedir.glob('ext/*tachyon*')): + sys.path.insert(0, (filedir / 'ext').as_posix()) + +import tachyon + +parser = argparse.ArgumentParser() +parser.add_argument('-o', dest='output', default=None) + +options = parser.parse_args(sys.argv[1:]) + +result = tachyon.phaserize('shoot') + +if options.output: + with open(options.output, 'w') as f: + f.write('success') + +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/4 custom target depends extmodule/ext/lib/meson-tachyonlib.c b/test cases/python/4 custom target depends extmodule/ext/lib/meson-tachyonlib.c new file mode 100644 index 0000000..aeff296 --- /dev/null +++ b/test cases/python/4 custom target depends extmodule/ext/lib/meson-tachyonlib.c @@ -0,0 +1,8 @@ +#ifdef _MSC_VER +__declspec(dllexport) +#endif +const char* +tachyon_phaser_command (void) +{ + return "shoot"; +} diff --git a/test cases/python/4 custom target depends extmodule/ext/lib/meson-tachyonlib.h b/test cases/python/4 custom target depends extmodule/ext/lib/meson-tachyonlib.h new file mode 100644 index 0000000..dca71d3 --- /dev/null +++ b/test cases/python/4 custom target depends extmodule/ext/lib/meson-tachyonlib.h @@ -0,0 +1,6 @@ +#pragma once + +#ifdef _MSC_VER +__declspec(dllimport) +#endif +const char* tachyon_phaser_command (void); diff --git a/test cases/python/4 custom target depends extmodule/ext/lib/meson.build b/test cases/python/4 custom target depends extmodule/ext/lib/meson.build new file mode 100644 index 0000000..b1f8938 --- /dev/null +++ b/test cases/python/4 custom target depends extmodule/ext/lib/meson.build @@ -0,0 +1,4 @@ +libtachyon = shared_library('tachyonlib', 'meson-tachyonlib.c') + +libtachyon_dep = declare_dependency(link_with : libtachyon, + include_directories : include_directories('.')) diff --git a/test cases/python/4 custom target depends extmodule/ext/meson.build b/test cases/python/4 custom target depends extmodule/ext/meson.build new file mode 100644 index 0000000..1bb275d --- /dev/null +++ b/test cases/python/4 custom target depends extmodule/ext/meson.build @@ -0,0 +1,6 @@ +subdir('lib') + +pylib = py3.extension_module('tachyon', + 'tachyon_module.c', + dependencies : [libtachyon_dep, py3_dep], +) diff --git a/test cases/python/4 custom target depends extmodule/ext/tachyon_module.c b/test cases/python/4 custom target depends extmodule/ext/tachyon_module.c new file mode 100644 index 0000000..b48032b --- /dev/null +++ b/test cases/python/4 custom target depends extmodule/ext/tachyon_module.c @@ -0,0 +1,51 @@ +/* + Copyright 2016 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> + +#include "meson-tachyonlib.h" + +static PyObject* phaserize(PyObject *self, PyObject *args) { + const char *message; + int result; + + if(!PyArg_ParseTuple(args, "s", &message)) + return NULL; + + result = strcmp(message, tachyon_phaser_command()) ? 0 : 1; + return PyLong_FromLong(result); +} + +static PyMethodDef TachyonMethods[] = { + {"phaserize", phaserize, METH_VARARGS, + "Shoot tachyon cannons."}, + {NULL, NULL, 0, NULL} +}; + +static struct PyModuleDef tachyonmodule = { + PyModuleDef_HEAD_INIT, + "tachyon", + NULL, + -1, + TachyonMethods +}; + +PyMODINIT_FUNC PyInit_tachyon(void) { + return PyModule_Create(&tachyonmodule); +} diff --git a/test cases/python/4 custom target depends extmodule/meson.build b/test cases/python/4 custom target depends extmodule/meson.build new file mode 100644 index 0000000..4e2aff0 --- /dev/null +++ b/test cases/python/4 custom target depends extmodule/meson.build @@ -0,0 +1,35 @@ +project('Python extension module', 'c', + default_options : ['buildtype=release']) +# Because Windows Python ships only with optimized libs, +# we must build this project the same way. + +py_mod = import('python') +py3 = py_mod.find_installation() +py3_dep = py3.dependency(required : false) + +# Copy to the builddir so that blaster.py can find the built tachyon module +# FIXME: We should automatically detect this case and append the correct paths +# to PYTHONLIBDIR +blaster_py = configure_file(input : 'blaster.py', + output : 'blaster.py', + copy : true) + +check_exists = ''' +import os, sys +with open(sys.argv[1], 'rb') as f: + assert(f.read() == b'success') +''' +if py3_dep.found() + subdir('ext') + + out_txt = custom_target('tachyon flux', + input : blaster_py, + output : 'out.txt', + command : [py3, '@INPUT@', '-o', '@OUTPUT@'], + depends : pylib, + build_by_default: true) + + test('flux', py3, args : ['-c', check_exists, out_txt]) +else + error('MESON_SKIP_TEST: Python3 libraries not found, skipping test.') +endif |