Merge pull request #4616 from dcbaker/python-module-path

add path() method to python module
pull/4646/head
Jussi Pakkanen 6 years ago committed by GitHub
commit cb45e9e836
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 85
      mesonbuild/modules/python.py
  2. 1
      run_project_tests.py
  3. 0
      test cases/python/1 basic/gluon/__init__.py
  4. 2
      test cases/python/1 basic/gluon/gluonator.py
  5. 26
      test cases/python/1 basic/meson.build
  6. 9
      test cases/python/1 basic/prog.py
  7. 4
      test cases/python/1 basic/subdir/meson.build
  8. 12
      test cases/python/1 basic/subdir/subprog.py
  9. 14
      test cases/python/2 extmodule/blaster.py
  10. 6
      test cases/python/2 extmodule/ext/meson.build
  11. 49
      test cases/python/2 extmodule/ext/tachyon_module.c
  12. 28
      test cases/python/2 extmodule/meson.build
  13. 23
      test cases/python/3 cython/cytest.py
  14. 9
      test cases/python/3 cython/libdir/cstorer.pxd
  15. 11
      test cases/python/3 cython/libdir/meson.build
  16. 24
      test cases/python/3 cython/libdir/storer.c
  17. 8
      test cases/python/3 cython/libdir/storer.h
  18. 16
      test cases/python/3 cython/libdir/storer.pyx
  19. 20
      test cases/python/3 cython/meson.build
  20. 32
      test cases/python/4 custom target depends extmodule/blaster.py
  21. 8
      test cases/python/4 custom target depends extmodule/ext/lib/meson-tachyonlib.c
  22. 6
      test cases/python/4 custom target depends extmodule/ext/lib/meson-tachyonlib.h
  23. 4
      test cases/python/4 custom target depends extmodule/ext/lib/meson.build
  24. 6
      test cases/python/4 custom target depends extmodule/ext/meson.build
  25. 51
      test cases/python/4 custom target depends extmodule/ext/tachyon_module.c
  26. 35
      test cases/python/4 custom target depends extmodule/meson.build

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

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

@ -0,0 +1,2 @@
def gluoninate():
return 42

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

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

@ -0,0 +1,4 @@
test('subdir',
py,
args : files('subprog.py'),
env : 'PYTHONPATH=' + meson.source_root())

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

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

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

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

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

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

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

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

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

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

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

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

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

@ -0,0 +1,8 @@
#ifdef _MSC_VER
__declspec(dllexport)
#endif
const char*
tachyon_phaser_command (void)
{
return "shoot";
}

@ -0,0 +1,6 @@
#pragma once
#ifdef _MSC_VER
__declspec(dllimport)
#endif
const char* tachyon_phaser_command (void);

@ -0,0 +1,4 @@
libtachyon = shared_library('tachyonlib', 'meson-tachyonlib.c')
libtachyon_dep = declare_dependency(link_with : libtachyon,
include_directories : include_directories('.'))

@ -0,0 +1,6 @@
subdir('lib')
pylib = py3.extension_module('tachyon',
'tachyon_module.c',
dependencies : [libtachyon_dep, py3_dep],
)

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

@ -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
Loading…
Cancel
Save