diff options
-rw-r--r-- | docs/markdown/Builtin-options.md | 3 | ||||
-rw-r--r-- | docs/markdown/Python-module.md | 9 | ||||
-rw-r--r-- | man/meson.1 | 441 | ||||
-rw-r--r-- | mesonbuild/backend/ninjabackend.py | 34 | ||||
-rw-r--r-- | mesonbuild/dependencies/python.py | 135 | ||||
-rw-r--r-- | mesonbuild/modules/python.py | 14 | ||||
-rw-r--r-- | test cases/python/9 extmodule limited api/limited.c | 14 | ||||
-rw-r--r-- | test cases/python/9 extmodule limited api/meson.build | 7 | ||||
-rw-r--r-- | test cases/python/9 extmodule limited api/test_limited.py | 6 | ||||
-rw-r--r-- | unittests/pythontests.py | 31 |
10 files changed, 606 insertions, 88 deletions
diff --git a/docs/markdown/Builtin-options.md b/docs/markdown/Builtin-options.md index b4039d6..6adc421 100644 --- a/docs/markdown/Builtin-options.md +++ b/docs/markdown/Builtin-options.md @@ -408,7 +408,8 @@ interpreter directly, even if it is a venv. Setting to `venv` will instead use the paths for the virtualenv the python found installation comes from (or fail if it is not a virtualenv). Setting to `auto` will check if the found installation is a virtualenv, and use `venv` or `system` as appropriate (but -never `prefix`). This option is mutually exclusive with the `platlibdir`/`purelibdir`. +never `prefix`). Note that Conda environments are treated as `system`. +This option is mutually exclusive with the `platlibdir`/`purelibdir`. For backwards compatibility purposes, the default `install_env` is `prefix`. diff --git a/docs/markdown/Python-module.md b/docs/markdown/Python-module.md index 05ae57d..c02eed9 100644 --- a/docs/markdown/Python-module.md +++ b/docs/markdown/Python-module.md @@ -12,6 +12,15 @@ authors: This module provides support for finding and building extensions against python installations, be they python 2 or 3. +If you want to build and package Python extension modules using tools +compatible with [PEP-517](https://peps.python.org/pep-0517/), check out +[meson-python](https://mesonbuild.com/meson-python/index.html). + +If you are building Python extension modules against a Python interpreter +located in a venv or Conda environment, you probably want to set +`python.install_venv=auto`; +see [Python module options](Builtin-options.md#python-module) for details. + *Added 0.46.0* ## Functions diff --git a/man/meson.1 b/man/meson.1 index 0221faa..10a5eb9 100644 --- a/man/meson.1 +++ b/man/meson.1 @@ -83,6 +83,93 @@ To set values, use the \-D command line argument like this. .B meson configure \-Dopt1=value1 \-Dopt2=value2 +.SH The dist command + +.B meson dist +generates a release archive. + +.B meson dist [ +.I options +.B ] + +.SS "options:" +.TP +\fB\-h, \-\-help\fR +show this help message and exit + +.TP +\fB\-C WD\fR +directory to cd into before running + +.TP +\fB\-\-allow-dirty\fR +Allow even when repository contains uncommitted changes. + +.TP +\fB\-\-formats FORMATS\fR +Comma separated list of archive types to create. Supports xztar +(default), gztar, and zip. + +.TP +\fB\-\-include\-subprojects\fR +Include source code of subprojects that have been used for the build. + +.TP +\fB\-\-no\-tests\fR +Do not build and test generated packages. + +.SH The install command + +.B meson install +installs the project. + +.B meson install [ +.I options +.B ] + +.SS "options:" + +.TP +\fB\-h, \-\-help\fR +show this help message and exit + +.TP +\fB\-C WD\fR +directory to cd into before running + +.TP +\fB\-\-no-rebuild\fR +Do not rebuild before installing. + +.TP +\fB\-\-only\-changed\fR +Only overwrite files that are older than the copied file. + +.TP +\fB\-\-quiet\fR +Do not print every file that was installed. + +.TP +\fB\-\-destdir DESTDIR\fR +Sets or overrides DESTDIR environment. (Since 0.57.0) + +.TP +\fB\-\-dry\-run, \-n\fR +Doesn't actually install, but print logs. (Since 0.57.0) + +.TP +\fB\-\-skip\-subprojects [SKIP_SUBPROJECTS]\fR +Do not install files from given subprojects. (Since 0.58.0) + +.TP +\fB\-\-tags TAGS\fR +Install only targets having one of the given tags. (Since 0.60.0) + +.TP +\fB\-\-strip\fR +Strip targets even if strip option was not set during +configure. (Since 0.62.0) + .SH The introspect command Meson introspect is a command designed to make it simple to integrate with @@ -113,6 +200,68 @@ print all unit tests \fB\-\-help\fR print command line help +.SH The init command + +.B meson init +creates a new project + +.B meson init [ +.I options +.B ] [ +.I sourcefile... +.B ] + +.SS "positional arguments:" +.TP +sourcefile... +source files. default: all recognized files in current directory + +.SS "options:" +.TP +\fB\-h, \-\-help\fR +show this help message and exit + +.TP +\fB\-C WD\fR +directory to cd into before running + +.TP +\fB\-n NAME, \-\-name NAME\fR +project name. default: name of current directory + +.TP +\fB\-e EXECUTABLE, \-\-executable EXECUTABLE\fR +executable name. default: project name + +.TP +\fB\-d DEPS, \-\-deps DEPS\fR +dependencies, comma-separated + +.TP +\fB\-l {c,cpp,cs,cuda,d,fortran,java,objc,objcpp,rust,vala}, \ +\-\-language {c,cpp,cs,cuda,d,fortran,java,objc,objcpp,rust,vala}\fR +project language. default: autodetected based on source files + +.TP +\fB\-b, \-\-build +build after generation + +.TP +\fB\-\-builddir BUILDDIR\fR +directory for build + +.TP +\fB\-f, \-\-force\fR +force overwrite of existing files and directories. + +.TP +\fB\-\-type {executable,library}\fR +project type. default: executable based project + +.TP +\fB\-\-version VERSION\fR +project version. default: 0.1 + .SH The test command .B meson test @@ -214,6 +363,298 @@ show available versions of the specified project \fBstatus\fR show installed and available versions of currently used subprojects +.SH The subprojects command + +.B meson subprojects +is used to manage subprojects. + +.B meson subprojects [ +.I options +.B ] [ +.I command +.B ] + +.SS "options:" +.TP +\fB\-h, \-\-help\fR +show this help message and exit + +.SS "commands:" +.TP +\fBupdate\fR +Update all subprojects from wrap files + +.TP +\fBcheckout\fR +Checkout a branch (git only) + +.TP +\fBdownload\fR +Ensure subprojects are fetched, even if not in use. Already downloaded +subprojects are not modified. This can be used to pre-fetch all +subprojects and avoid downloads during configure. + +.TP +\fBforeach\fR +Execute a command in each subproject directory. + +.TP +\fBpurge\fR +Remove all wrap-based subproject artifacts + +.TP +\fBpackagefiles\fR +Manage the packagefiles overlay + +.SH The rewrite command + +.B meson rewrite +modifies the project definition. + +.B meson rewrite [ +.I options +.B ] [ +.I command +.B ] + +.SS "options:" + +.TP +\fB\-h, \-\-help\fR +show this help message and exit + +.TP +\fB\-s SRCDIR, \-\-sourcedir SRCDIR\fR +Path to source directory. + +.TP +\fB\-V, \-\-verbose\fR +Enable verbose output + +.TP +\fB\-S, \-\-skip\-errors\fR +Skip errors instead of aborting + +.SS "commands:" + +.TP +\fBtarget (tgt)\fR +Modify a target + +.TP +\fBkwargs\fR +Modify keyword arguments + +.TP +\fBdefault-options (def)\fR +Modify the project default options + +.TP +\fBcommand (cmd)\fR +Execute a JSON array of commands + +.SH The compile command + +.B meson compile +builds the project. + +.B meson compile [ +.I options +.B ] [ +.I TARGET... +.B ] + +.SS "positional arguments:" +.TP +\fBTARGET\fR +Targets to build. Target has the following format: +[PATH_TO_TARGET/]TARGET_NAME.TARGET_SUFFIX[:TARGET_TYPE]. + +.SS "options:" + +.TP +\fB\-h, \-\-help\fR +show this help message and exit + +.TP +\fB\-\-clean\fR +Clean the build directory. + +.TP +\fB\-C WD\fR +directory to cd into before running + +.TP +\fB\-j JOBS, \-\-jobs JOBS\fR +The number of worker jobs to run (if supported). If the value is less +than 1 the build program will guess. + +.TP +\fB\-l LOAD_AVERAGE, \-\-load-average LOAD_AVERAGE\fR +The system load average to try to maintain (if supported). + +.TP +\fB\-v, \-\-verbose\fR +Show more verbose output. + +.TP +\fB\-\-ninja\-args NINJA_ARGS\fR +Arguments to pass to `ninja` (applied only on `ninja` backend). + +.TP +\fB\-\-vs\-args VS_ARGS\fR +Arguments to pass to `msbuild` (applied only on `vs` backend). + +.TP +\fB\-\-xcode\-args XCODE_ARGS\fR +Arguments to pass to `xcodebuild` (applied only on `xcode` backend). + +.SH The devenv command + +.B meson devenv +runs commands in the developer environment. + +.B meson devenv [ +.I options +.B ] [ +.I command +.B ] + +.SS "positional arguments:" + +.TP +\fBcommand\fR +Command to run in developer environment (default: interactive shell) + +.SS "options:" + +.TP +\fB\-h, \-\-help\fR +show this help message and exit + +.TP +\fB\-C BUILDDIR\fR +Path to build directory + +.TP +\fB\-\-workdir WORKDIR, \-w WORKDIR\fR +Directory to cd into before running (default: builddir, Since 1.0.0) + +.TP +\fB\-\-dump [DUMP]\fR +Only print required environment (Since 0.62.0) Takes an optional file +path (Since 1.1.0) + +.TP +\fB\-\-dump-format {sh,export,vscode}\fR +Format used with --dump (Since 1.1.0) + +.SH The env2mfile command + +.B meson env2mfile +converts the current environment to a cross or native file. + +.B meson env2mfile [ +.I options +.B ] + +.SS "options:" + +.TP +\fB\-h, \-\-help\fR +show this help message and exit + +.TP +\fB\-\-debarch DEBARCH\fR +The dpkg architecture to generate. + +.TP +\fB\-\-gccsuffix GCCSUFFIX\fR +A particular gcc version suffix if necessary. + +.TP +\fB\-o OUTFILE\fR +The output file. + +.TP +\fB\-\-cross\fR +Generate a cross compilation file. + +.TP +\fB\-\-native\fR +Generate a native compilation file. + +.TP +\fB\-\-system SYSTEM\fR +Define system for cross compilation. + +.TP +\fB\-\-subsystem SUBSYSTEM\fR +Define subsystem for cross compilation. + +.TP +\fB\-\-kernel KERNEL\fR +Define kernel for cross compilation. + +.TP +\fB\-\-cpu CPU\fR +Define cpu for cross compilation. + +.TP +\fB\-\-cpu-family CPU_FAMILY\fR +Define cpu family for cross compilation. + +.TP +\fB\-\-endian {big,little}\fR +Define endianness for cross compilation. + +.SH The format command + +.B meson format +formats a meson source file. + +.B meson format [ +.I options +.B ] [ +.I sources... +.B ] + +.SS "positional arguments:" + +.TP +\fBsources...\fR +meson source files + +.SS "options:" + +.TP +\fB-h, --help\fR +show this help message and exit + +.TP +\fB-q, --check-only\fR +exit with 1 if files would be modified by meson format + +.TP +\fB-i, --inplace\fR +format files in-place + +.TP +\fB-r, --recursive\fR +recurse subdirs (requires --check-only or --inplace option) + +.TP +\fB-c meson.format, --configuration meson.format\fR +read configuration from meson.format + +.TP +\fB-e, --editor-config\fR +try to read configuration from .editorconfig + +.TP +\fB-o OUTPUT, --output OUTPUT\fR +output file (implies having exactly one input) + .SH EXIT STATUS .TP diff --git a/mesonbuild/backend/ninjabackend.py b/mesonbuild/backend/ninjabackend.py index 759ae9a..3052967 100644 --- a/mesonbuild/backend/ninjabackend.py +++ b/mesonbuild/backend/ninjabackend.py @@ -1412,7 +1412,6 @@ class NinjaBackend(backends.Backend): outname_rel = os.path.join(self.get_target_dir(target), fname) src_list = target.get_sources() resources = target.get_java_resources() - class_list = [] compiler = target.compilers['java'] c = 'c' m = 'm' @@ -1430,10 +1429,8 @@ class NinjaBackend(backends.Backend): if rel_src.endswith('.java'): gen_src_list.append(raw_src) - compile_args = self.determine_single_java_compile_args(target, compiler) - for src in src_list + gen_src_list: - plain_class_path = self.generate_single_java_compile(src, target, compiler, compile_args) - class_list.append(plain_class_path) + compile_args = self.determine_java_compile_args(target, compiler) + class_list = self.generate_java_compile(src_list + gen_src_list, target, compiler, compile_args) class_dep_list = [os.path.join(self.get_target_private_dir(target), i) for i in class_list] manifest_path = os.path.join(self.get_target_private_dir(target), 'META-INF', 'MANIFEST.MF') manifest_fullpath = os.path.join(self.environment.get_build_dir(), manifest_path) @@ -1530,7 +1527,8 @@ class NinjaBackend(backends.Backend): self.generate_generator_list_rules(target) self.create_target_source_introspection(target, compiler, commands, rel_srcs, generated_rel_srcs) - def determine_single_java_compile_args(self, target, compiler): + def determine_java_compile_args(self, target, compiler): + args = [] args = self.generate_basic_compiler_args(target, compiler) args += target.get_java_args() args += compiler.get_output_args(self.get_target_private_dir(target)) @@ -1544,20 +1542,30 @@ class NinjaBackend(backends.Backend): args += ['-sourcepath', sourcepath] return args - def generate_single_java_compile(self, src, target, compiler, args): + def generate_java_compile(self, srcs, target, compiler, args): deps = [os.path.join(self.get_target_dir(l), l.get_filename()) for l in target.link_targets] generated_sources = self.get_target_generated_sources(target) for rel_src in generated_sources.keys(): if rel_src.endswith('.java'): deps.append(rel_src) - rel_src = src.rel_to_builddir(self.build_to_src) - plain_class_path = src.fname[:-4] + 'class' - rel_obj = os.path.join(self.get_target_private_dir(target), plain_class_path) - element = NinjaBuildElement(self.all_outputs, rel_obj, self.compiler_to_rule_name(compiler), rel_src) + + rel_srcs = [] + plain_class_paths = [] + rel_objs = [] + for src in srcs: + rel_src = src.rel_to_builddir(self.build_to_src) + rel_srcs.append(rel_src) + + plain_class_path = src.fname[:-4] + 'class' + plain_class_paths.append(plain_class_path) + rel_obj = os.path.join(self.get_target_private_dir(target), plain_class_path) + rel_objs.append(rel_obj) + element = NinjaBuildElement(self.all_outputs, rel_objs, self.compiler_to_rule_name(compiler), rel_srcs) element.add_dep(deps) element.add_item('ARGS', args) + element.add_item('FOR_JAR', self.get_target_filename(target)) self.add_build(element) - return plain_class_path + return plain_class_paths def generate_java_link(self): rule = 'java_LINKER' @@ -2376,7 +2384,7 @@ class NinjaBackend(backends.Backend): def generate_java_compile_rule(self, compiler): rule = self.compiler_to_rule_name(compiler) command = compiler.get_exelist() + ['$ARGS', '$in'] - description = 'Compiling Java object $in' + description = 'Compiling Java sources for $FOR_JAR' self.add_rule(NinjaRule(rule, command, [], description)) def generate_cs_compile_rule(self, compiler: 'CsCompiler') -> None: diff --git a/mesonbuild/dependencies/python.py b/mesonbuild/dependencies/python.py index 27340eb..17c807c 100644 --- a/mesonbuild/dependencies/python.py +++ b/mesonbuild/dependencies/python.py @@ -161,69 +161,6 @@ class _PythonDependencyBase(_Base): else: self.major_version = 2 - -class PythonPkgConfigDependency(PkgConfigDependency, _PythonDependencyBase): - - def __init__(self, name: str, environment: 'Environment', - kwargs: T.Dict[str, T.Any], installation: 'BasicPythonExternalProgram', - libpc: bool = False): - if libpc: - mlog.debug(f'Searching for {name!r} via pkgconfig lookup in LIBPC') - else: - mlog.debug(f'Searching for {name!r} via fallback pkgconfig lookup in default paths') - - PkgConfigDependency.__init__(self, name, environment, kwargs) - _PythonDependencyBase.__init__(self, installation, kwargs.get('embed', False)) - - if libpc and not self.is_found: - mlog.debug(f'"python-{self.version}" could not be found in LIBPC, this is likely due to a relocated python installation') - - # pkg-config files are usually accurate starting with python 3.8 - if not self.link_libpython and mesonlib.version_compare(self.version, '< 3.8'): - self.link_args = [] - - -class PythonFrameworkDependency(ExtraFrameworkDependency, _PythonDependencyBase): - - def __init__(self, name: str, environment: 'Environment', - kwargs: T.Dict[str, T.Any], installation: 'BasicPythonExternalProgram'): - ExtraFrameworkDependency.__init__(self, name, environment, kwargs) - _PythonDependencyBase.__init__(self, installation, kwargs.get('embed', False)) - - -class PythonSystemDependency(SystemDependency, _PythonDependencyBase): - - def __init__(self, name: str, environment: 'Environment', - kwargs: T.Dict[str, T.Any], installation: 'BasicPythonExternalProgram'): - SystemDependency.__init__(self, name, environment, kwargs) - _PythonDependencyBase.__init__(self, installation, kwargs.get('embed', False)) - - # match pkg-config behavior - if self.link_libpython: - # link args - if mesonlib.is_windows(): - self.find_libpy_windows(environment, limited_api=False) - else: - self.find_libpy(environment) - else: - self.is_found = True - - # 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/ - # https://github.com/python/cpython/pull/100137 - if mesonlib.is_windows() and self.get_windows_python_arch().endswith('64') and mesonlib.version_compare(self.version, '<3.12'): - self.compile_args += ['-DMS_WIN64='] - - if not self.clib_compiler.has_header('Python.h', '', environment, extra_args=self.compile_args)[0]: - self.is_found = False - def find_libpy(self, environment: 'Environment') -> None: if self.is_pypy: if self.major_version == 3: @@ -311,9 +248,15 @@ class PythonSystemDependency(SystemDependency, _PythonDependencyBase): lib = Path(self.variables.get('base_prefix')) / libpath elif self.platform.startswith('mingw'): if self.static: - libname = self.variables.get('LIBRARY') + if limited_api: + libname = self.variables.get('ABI3DLLLIBRARY') + else: + libname = self.variables.get('LIBRARY') else: - libname = self.variables.get('LDLIBRARY') + if limited_api: + libname = self.variables.get('ABI3LDLIBRARY') + else: + libname = self.variables.get('LDLIBRARY') lib = Path(self.variables.get('LIBDIR')) / libname else: raise mesonlib.MesonBugException( @@ -347,6 +290,68 @@ class PythonSystemDependency(SystemDependency, _PythonDependencyBase): self.link_args = largs self.is_found = True +class PythonPkgConfigDependency(PkgConfigDependency, _PythonDependencyBase): + + def __init__(self, name: str, environment: 'Environment', + kwargs: T.Dict[str, T.Any], installation: 'BasicPythonExternalProgram', + libpc: bool = False): + if libpc: + mlog.debug(f'Searching for {name!r} via pkgconfig lookup in LIBPC') + else: + mlog.debug(f'Searching for {name!r} via fallback pkgconfig lookup in default paths') + + PkgConfigDependency.__init__(self, name, environment, kwargs) + _PythonDependencyBase.__init__(self, installation, kwargs.get('embed', False)) + + if libpc and not self.is_found: + mlog.debug(f'"python-{self.version}" could not be found in LIBPC, this is likely due to a relocated python installation') + + # pkg-config files are usually accurate starting with python 3.8 + if not self.link_libpython and mesonlib.version_compare(self.version, '< 3.8'): + self.link_args = [] + + +class PythonFrameworkDependency(ExtraFrameworkDependency, _PythonDependencyBase): + + def __init__(self, name: str, environment: 'Environment', + kwargs: T.Dict[str, T.Any], installation: 'BasicPythonExternalProgram'): + ExtraFrameworkDependency.__init__(self, name, environment, kwargs) + _PythonDependencyBase.__init__(self, installation, kwargs.get('embed', False)) + + +class PythonSystemDependency(SystemDependency, _PythonDependencyBase): + + def __init__(self, name: str, environment: 'Environment', + kwargs: T.Dict[str, T.Any], installation: 'BasicPythonExternalProgram'): + SystemDependency.__init__(self, name, environment, kwargs) + _PythonDependencyBase.__init__(self, installation, kwargs.get('embed', False)) + + # match pkg-config behavior + if self.link_libpython: + # link args + if mesonlib.is_windows(): + self.find_libpy_windows(environment, limited_api=False) + else: + self.find_libpy(environment) + else: + self.is_found = True + + # 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/ + # https://github.com/python/cpython/pull/100137 + if mesonlib.is_windows() and self.get_windows_python_arch().endswith('64') and mesonlib.version_compare(self.version, '<3.12'): + self.compile_args += ['-DMS_WIN64='] + + if not self.clib_compiler.has_header('Python.h', '', environment, extra_args=self.compile_args)[0]: + self.is_found = False + @staticmethod def log_tried() -> str: return 'sysconfig' diff --git a/mesonbuild/modules/python.py b/mesonbuild/modules/python.py index b599a5e..013723d 100644 --- a/mesonbuild/modules/python.py +++ b/mesonbuild/modules/python.py @@ -184,13 +184,9 @@ class PythonInstallation(_ExternalProgramHolder['PythonExternalProgram']): new_cpp_args.append(limited_api_definition) kwargs['cpp_args'] = new_cpp_args - # When compiled under MSVC, Python's PC/pyconfig.h forcibly inserts pythonMAJOR.MINOR.lib - # into the linker path when not running in debug mode via a series #pragma comment(lib, "") - # directives. We manually override these here as this interferes with the intended - # use of the 'limited_api' kwarg + # On Windows, the limited API DLL is python3.dll, not python3X.dll. for_machine = kwargs['native'] - compilers = self.interpreter.environment.coredata.compilers[for_machine] - if any(compiler.get_id() == 'msvc' for compiler in compilers.values()): + if self.interpreter.environment.machines[for_machine].is_windows(): pydep_copy = copy.copy(pydep) pydep_copy.find_libpy_windows(self.env, limited_api=True) if not pydep_copy.found(): @@ -199,6 +195,12 @@ class PythonInstallation(_ExternalProgramHolder['PythonExternalProgram']): new_deps.remove(pydep) new_deps.append(pydep_copy) + # When compiled under MSVC, Python's PC/pyconfig.h forcibly inserts pythonMAJOR.MINOR.lib + # into the linker path when not running in debug mode via a series #pragma comment(lib, "") + # directives. We manually override these here as this interferes with the intended + # use of the 'limited_api' kwarg + compilers = self.interpreter.environment.coredata.compilers[for_machine] + if any(compiler.get_id() == 'msvc' for compiler in compilers.values()): pyver = pydep.version.replace('.', '') python_windows_debug_link_exception = f'/NODEFAULTLIB:python{pyver}_d.lib' python_windows_release_link_exception = f'/NODEFAULTLIB:python{pyver}.lib' diff --git a/test cases/python/9 extmodule limited api/limited.c b/test cases/python/9 extmodule limited api/limited.c index 0d1c718..b977419 100644 --- a/test cases/python/9 extmodule limited api/limited.c +++ b/test cases/python/9 extmodule limited api/limited.c @@ -6,12 +6,22 @@ #error Wrong value for Py_LIMITED_API #endif +static PyObject * +hello(PyObject * Py_UNUSED(self), PyObject * Py_UNUSED(args)) { + return PyUnicode_FromString("hello world"); +} + +static struct PyMethodDef methods[] = { + { "hello", hello, METH_NOARGS, NULL }, + { NULL, NULL, 0, NULL }, +}; + static struct PyModuleDef limited_module = { PyModuleDef_HEAD_INIT, - "limited_api_test", + "limited", NULL, -1, - NULL + methods }; PyMODINIT_FUNC PyInit_limited(void) { diff --git a/test cases/python/9 extmodule limited api/meson.build b/test cases/python/9 extmodule limited api/meson.build index 68afc96..bdf1b7b 100644 --- a/test cases/python/9 extmodule limited api/meson.build +++ b/test cases/python/9 extmodule limited api/meson.build @@ -14,3 +14,10 @@ ext_mod = py.extension_module('not_limited', 'not_limited.c', install: true, ) + +test('load-test', + py, + args: [files('test_limited.py')], + env: { 'PYTHONPATH': meson.current_build_dir() }, + workdir: meson.current_source_dir() +) diff --git a/test cases/python/9 extmodule limited api/test_limited.py b/test cases/python/9 extmodule limited api/test_limited.py new file mode 100644 index 0000000..fcbf67b --- /dev/null +++ b/test cases/python/9 extmodule limited api/test_limited.py @@ -0,0 +1,6 @@ +from limited import hello + +def test_hello(): + assert hello() == "hello world" + +test_hello() diff --git a/unittests/pythontests.py b/unittests/pythontests.py index 6079bd5..aaea906 100644 --- a/unittests/pythontests.py +++ b/unittests/pythontests.py @@ -11,7 +11,7 @@ from .allplatformstests import git_init from .baseplatformtests import BasePlatformTests from .helpers import * -from mesonbuild.mesonlib import MachineChoice, TemporaryDirectoryWinProof +from mesonbuild.mesonlib import MachineChoice, TemporaryDirectoryWinProof, is_windows from mesonbuild.modules.python import PythonModule class PythonTests(BasePlatformTests): @@ -86,3 +86,32 @@ python = pymod.find_installation('python3', required: true) if shutil.which('python2') or PythonModule._get_win_pythonpath('python2'): raise self.skipTest('python2 installed, already tested') self._test_bytecompile() + + def test_limited_api_linked_correct_lib(self): + if not is_windows(): + return self.skipTest('Test only run on Windows.') + + testdir = os.path.join(self.src_root, 'test cases', 'python', '9 extmodule limited api') + + self.init(testdir) + self.build() + + from importlib.machinery import EXTENSION_SUFFIXES + limited_suffix = EXTENSION_SUFFIXES[1] + + limited_library_path = os.path.join(self.builddir, f'limited{limited_suffix}') + self.assertPathExists(limited_library_path) + + limited_dep_name = 'python3.dll' + if shutil.which('dumpbin'): + # MSVC + output = subprocess.check_output(['dumpbin', '/DEPENDENTS', limited_library_path], + stderr=subprocess.STDOUT) + self.assertIn(limited_dep_name, output.decode()) + elif shutil.which('objdump'): + # mingw + output = subprocess.check_output(['objdump', '-p', limited_library_path], + stderr=subprocess.STDOUT) + self.assertIn(limited_dep_name, output.decode()) + else: + raise self.skipTest('Test needs either dumpbin(MSVC) or objdump(mingw).') |