Merge pull request #3240 from MathieuDuponchelle/python_module

Implement a generic python module
pull/3372/head
Jussi Pakkanen 7 years ago committed by GitHub
commit 3fc1ca8687
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 1
      ciimage/Dockerfile
  2. 195
      docs/markdown/Python-module.md
  3. 6
      docs/markdown/snippets/python-module.md
  4. 1
      docs/sitemap.txt
  5. 1
      docs/theme/extra/templates/navbar_links.html
  6. 2
      mesonbuild/interpreter.py
  7. 433
      mesonbuild/modules/python.py
  8. 50
      run_unittests.py
  9. 14
      test cases/python/1 extmodule/blaster.py
  10. 6
      test cases/python/1 extmodule/ext/meson.build
  11. 59
      test cases/python/1 extmodule/ext/tachyon_module.c
  12. 23
      test cases/python/1 extmodule/meson.build
  13. 3
      test cases/python/1 extmodule/meson_options.txt

@ -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'

@ -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

@ -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.

@ -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

@ -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"), \

@ -1635,6 +1635,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'):

@ -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()

@ -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():

@ -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)

@ -0,0 +1,6 @@
pylib = py.extension_module('tachyon',
'tachyon_module.c',
dependencies : py_dep,
)
pypathdir = meson.current_build_dir()

@ -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

@ -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

@ -0,0 +1,3 @@
option('python', type: 'string',
description: 'Name of or path to the python executable'
)
Loading…
Cancel
Save