From b92d9295c3d7d946a5302fd1072bef92b5388185 Mon Sep 17 00:00:00 2001 From: Benedikt Reinartz Date: Sat, 13 Feb 2021 13:10:44 +0100 Subject: [PATCH 1/9] Replace clr module by loading through clr_loader clr_loader is based on CFFI and allows loading pythonnet on different .NET runtime implementations, currently .NET Framework, .NET Core and Mono. Apart from dropping the respective old code, this requires the following changes: - Move libpython discovery into Python by vendoring and adjusting `find_libpython` - Adjust the GIL handling in the startup code as CFFI releases the GIL when calling the external function - Remove the intermittent `configure` command as it is not required anymore - Adjust a few test-cases - Remove `_AtExit` Due to issues with the reference counts, the `atexit` callback is currently essentially a no-op until these are fixed. --- .github/workflows/main.yml | 53 +-- .gitignore | 3 - Directory.Build.props | 1 - appveyor.yml | 3 +- clr.py | 39 +- pythonnet.sln | 2 - pythonnet/__init__.py | 74 +++- pythonnet/find_libpython/__init__.py | 399 ++++++++++++++++++++ pythonnet/find_libpython/__main__.py | 2 + pythonnet/mono/.gitkeep | 0 pythonnet/netfx/.gitkeep | 0 pythonnet/util/__init__.py | 1 + requirements.txt | 2 +- setup.py | 125 +------ src/clrmodule/ClrModule.cs | 113 ------ src/clrmodule/Properties/AssemblyInfo.cs | 5 - src/clrmodule/clrmodule.csproj | 24 -- src/embed_tests/TestNativeTypeOffset.cs | 7 +- src/monoclr/clrmod.c | 215 ----------- src/runtime/Python.Runtime.csproj | 3 +- src/runtime/loader.cs | 83 +++++ src/runtime/moduleobject.cs | 6 - src/runtime/native/TypeOffset.cs | 1 - src/runtime/pythonengine.cs | 14 +- src/runtime/runtime.cs | 441 +++++++++++------------ src/tests/test_method.py | 3 - src/tests/test_sysargv.py | 5 +- tools/geninterop/geninterop.py | 4 +- 28 files changed, 804 insertions(+), 824 deletions(-) create mode 100644 pythonnet/find_libpython/__init__.py create mode 100644 pythonnet/find_libpython/__main__.py delete mode 100644 pythonnet/mono/.gitkeep delete mode 100644 pythonnet/netfx/.gitkeep create mode 100644 pythonnet/util/__init__.py delete mode 100644 src/clrmodule/ClrModule.cs delete mode 100644 src/clrmodule/Properties/AssemblyInfo.cs delete mode 100644 src/clrmodule/clrmodule.csproj delete mode 100644 src/monoclr/clrmod.c create mode 100644 src/runtime/loader.cs diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 3c8fabcc1..10959ea4f 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -12,33 +12,9 @@ jobs: fail-fast: false matrix: os: [windows, ubuntu, macos] - pyver_minor: [6, 7, 8, 9] + python: ["3.6", "3.7", "3.8", "3.9"] platform: [x64] shutdown_mode: [Normal, Soft] - include: - - os: ubuntu - pyver_minor: 6 - dll_suffix: m - - os: ubuntu - pyver_minor: 7 - dll_suffix: m - - - os: macos - dll_prefix: lib - dll_pyver_major: '3.' - dll_suffix: m - - os: ubuntu - dll_prefix: lib - dll_pyver_major: '3.' - - os: windows - dll_pyver_major: '3' - - - os: ubuntu - dll_ext: .so - - os: windows - dll_ext: .dll - - os: macos - dll_ext: .dylib env: PYTHONNET_SHUTDOWN_MODE: ${{ matrix.SHUTDOWN_MODE }} @@ -56,10 +32,10 @@ jobs: - name: Setup .NET uses: actions/setup-dotnet@v1 - - name: Set up Python 3.${{ matrix.pyver_minor }} + - name: Set up Python ${{ matrix.python }} uses: actions/setup-python@v2 with: - python-version: 3.${{ matrix.pyver_minor }} + python-version: ${{ matrix.python }} architecture: ${{ matrix.platform }} - name: Install dependencies @@ -68,31 +44,26 @@ jobs: - name: Build and Install run: | - python setup.py configure pip install -v . - # TODO this should be gone once clr module sets PythonDLL or preloads it - - name: Python Tests - run: pytest - if: ${{ matrix.os != 'macos' }} - env: - PYTHONNET_PYDLL: ${{ matrix.DLL_PREFIX }}python${{matrix.DLL_PYVER_MAJOR}}${{matrix.PYVER_MINOR}}${{matrix.DLL_SUFFIX}}${{matrix.DLL_EXT}} + - name: Set Python DLL path (non Windows) + if: ${{ matrix.os != 'windows' }} + run: | + python -m pythonnet.find_libpython --export >> $GITHUB_ENV + - name: Set Python DLL path (Windows) + if: ${{ matrix.os == 'windows' }} + run: | + python -m pythonnet.find_libpython --export | Out-File -FilePath $env:GITHUB_ENV -Encoding utf8 -Append + - name: Python Tests run: pytest - if: ${{ matrix.os == 'macos' }} - name: Embedding tests run: dotnet test --runtime any-${{ matrix.platform }} src/embed_tests/ - if: ${{ matrix.os != 'macos' }} # Not working right now, doesn't find libpython - env: - PYTHONNET_PYDLL: ${{ matrix.DLL_PREFIX }}python${{matrix.DLL_PYVER_MAJOR}}${{matrix.PYVER_MINOR}}${{matrix.DLL_SUFFIX}}${{matrix.DLL_EXT}} - name: Python tests run from .NET run: dotnet test --runtime any-${{ matrix.platform }} src/python_tests_runner/ - if: ${{ matrix.os == 'windows' }} # Not working for others right now - env: - PYTHONNET_PYDLL: ${{ matrix.DLL_PREFIX }}python${{matrix.DLL_PYVER_MAJOR}}${{matrix.PYVER_MINOR}}${{matrix.DLL_SUFFIX}}${{matrix.DLL_EXT}} # TODO: Run perf tests # TODO: Run mono tests on Windows? diff --git a/.gitignore b/.gitignore index 673681317..cdb152157 100644 --- a/.gitignore +++ b/.gitignore @@ -1,8 +1,5 @@ /src/runtime/interopNative.cs -# Configuration data -configured.props - # General binaries and Build results *.dll *.exe diff --git a/Directory.Build.props b/Directory.Build.props index 55d091b4e..edc8ba513 100644 --- a/Directory.Build.props +++ b/Directory.Build.props @@ -18,5 +18,4 @@ runtime; build; native; contentfiles; analyzers; buildtransitive - diff --git a/appveyor.yml b/appveyor.yml index 21e816f38..cc3815c62 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -30,9 +30,9 @@ environment: init: # Update Environment Variables based on matrix/platform - set PY_VER=%PYTHON_VERSION:.=% - - set PYTHONNET_PYDLL=python%PY_VER%.dll - set PYTHON=C:\PYTHON%PY_VER% - if %PLATFORM%==x64 (set PYTHON=%PYTHON%-x64) + - set PYTHONNET_PYDLL=%PYTHON%\python%PY_VER%.dll # Put desired Python version first in PATH - set PATH=%PYTHON%;%PYTHON%\Scripts;%PATH% @@ -42,7 +42,6 @@ install: - pip install --upgrade -r requirements.txt --quiet build_script: - - python setup.py configure # Create clean `sdist`. Only used for releases - python setup.py --quiet sdist - python setup.py bdist_wheel diff --git a/clr.py b/clr.py index 711333dd2..20a975f96 100644 --- a/clr.py +++ b/clr.py @@ -2,40 +2,5 @@ Legacy Python.NET loader for backwards compatibility """ -def _get_netfx_path(): - import os, sys - - if sys.maxsize > 2 ** 32: - arch = "amd64" - else: - arch = "x86" - - return os.path.join(os.path.dirname(__file__), "pythonnet", "netfx", arch, "clr.pyd") - - -def _get_mono_path(): - import os, glob - - paths = glob.glob(os.path.join(os.path.dirname(__file__), "pythonnet", "mono", "clr.*so")) - return paths[0] - - -def _load_clr(): - import sys - from importlib import util - - if sys.platform == "win32": - path = _get_netfx_path() - else: - path = _get_mono_path() - - del sys.modules[__name__] - - spec = util.spec_from_file_location("clr", path) - clr = util.module_from_spec(spec) - spec.loader.exec_module(clr) - - sys.modules[__name__] = clr - - -_load_clr() +from pythonnet import load +load() diff --git a/pythonnet.sln b/pythonnet.sln index c80ee8e60..30f4fd344 100644 --- a/pythonnet.sln +++ b/pythonnet.sln @@ -6,8 +6,6 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Python.Runtime", "src\runti EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Console", "src\console\Console.csproj", "{E6B01706-00BA-4144-9029-186AC42FBE9A}" EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "clrmodule", "src\clrmodule\clrmodule.csproj", "{F9F5FA13-BC52-4C0B-BC1C-FE3C0B8FCCDD}" -EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Python.EmbeddingTest", "src\embed_tests\Python.EmbeddingTest.csproj", "{819E089B-4770-400E-93C6-4F7A35F0EA12}" EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Python.Test", "src\testing\Python.Test.csproj", "{14EF9518-5BB7-4F83-8686-015BD2CC788E}" diff --git a/pythonnet/__init__.py b/pythonnet/__init__.py index 5c197e146..692fd5700 100644 --- a/pythonnet/__init__.py +++ b/pythonnet/__init__.py @@ -1,3 +1,71 @@ -def get_assembly_path(): - import os - return os.path.dirname(__file__) + "/runtime/Python.Runtime.dll" +import os +import sys +import clr_loader + +_RUNTIME = None +_LOADER_ASSEMBLY = None +_FFI = None +_LOADED = False + + +def set_runtime(runtime): + global _RUNTIME + if _LOADED: + raise RuntimeError("The runtime {runtime} has already been loaded".format(_RUNTIME)) + + _RUNTIME = runtime + + +def set_default_runtime() -> None: + if sys.platform == 'win32': + set_runtime(clr_loader.get_netfx()) + else: + set_runtime(clr_loader.get_mono()) + + +def load(): + global _FFI, _LOADED, _LOADER_ASSEMBLY + + if _LOADED: + return + + from .find_libpython import linked_libpython + from os.path import join, dirname + + if _RUNTIME is None: + # TODO: Warn, in the future the runtime must be set explicitly, either + # as a config/env variable or via set_runtime + set_default_runtime() + + dll_path = join(dirname(__file__), "runtime", "Python.Runtime.dll") + libpython = linked_libpython() + + if libpython and _FFI is None and sys.platform != "win32": + # Load and leak libpython handle s.t. the .NET runtime doesn't dlcloses + # it + import posix + + import cffi + _FFI = cffi.FFI() + _FFI.dlopen(libpython, posix.RTLD_NODELETE | posix.RTLD_LOCAL) + + _LOADER_ASSEMBLY = _RUNTIME.get_assembly(dll_path) + + func = _LOADER_ASSEMBLY["Python.Runtime.Loader.Initialize"] + if func(f"{libpython or ''}".encode("utf8")) != 0: + raise RuntimeError("Failed to initialize Python.Runtime.dll") + + import atexit + atexit.register(unload) + + +def unload(): + global _RUNTIME + if _LOADER_ASSEMBLY is not None: + func = _LOADER_ASSEMBLY["Python.Runtime.Loader.Shutdown"] + if func(b"") != 0: + raise RuntimeError("Failed to call Python.NET shutdown") + + if _RUNTIME is not None: + # TODO: Add explicit `close` to clr_loader + _RUNTIME = None diff --git a/pythonnet/find_libpython/__init__.py b/pythonnet/find_libpython/__init__.py new file mode 100644 index 000000000..185540c8f --- /dev/null +++ b/pythonnet/find_libpython/__init__.py @@ -0,0 +1,399 @@ +#!/usr/bin/env python + +""" +Locate libpython associated with this Python executable. +""" + +# License +# +# Copyright 2018, Takafumi Arakaki +# +# Permission is hereby granted, free of charge, to any person obtaining +# a copy of this software and associated documentation files (the +# "Software"), to deal in the Software without restriction, including +# without limitation the rights to use, copy, modify, merge, publish, +# distribute, sublicense, and/or sell copies of the Software, and to +# permit persons to whom the Software is furnished to do so, subject to +# the following conditions: +# +# The above copyright notice and this permission notice shall be +# included in all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +from __future__ import print_function, absolute_import + +from logging import getLogger +import ctypes.util +import functools +import os +import sys +import sysconfig + +logger = getLogger("find_libpython") + +is_windows = os.name == "nt" +is_apple = sys.platform == "darwin" + +SHLIB_SUFFIX = sysconfig.get_config_var("SHLIB_SUFFIX") +if SHLIB_SUFFIX is None: + if is_windows: + SHLIB_SUFFIX = ".dll" + else: + SHLIB_SUFFIX = ".so" +if is_apple: + # sysconfig.get_config_var("SHLIB_SUFFIX") can be ".so" in macOS. + # Let's not use the value from sysconfig. + SHLIB_SUFFIX = ".dylib" + + +def linked_libpython(): + """ + Find the linked libpython using dladdr (in *nix). + + Returns + ------- + path : str or None + A path to linked libpython. Return `None` if statically linked. + """ + if is_windows: + return _linked_libpython_windows() + return _linked_libpython_unix() + + +class Dl_info(ctypes.Structure): + _fields_ = [ + ("dli_fname", ctypes.c_char_p), + ("dli_fbase", ctypes.c_void_p), + ("dli_sname", ctypes.c_char_p), + ("dli_saddr", ctypes.c_void_p), + ] + + +def _linked_libpython_unix(): + libdl = ctypes.CDLL(ctypes.util.find_library("dl")) + libdl.dladdr.argtypes = [ctypes.c_void_p, ctypes.POINTER(Dl_info)] + libdl.dladdr.restype = ctypes.c_int + + dlinfo = Dl_info() + retcode = libdl.dladdr( + ctypes.cast(ctypes.pythonapi.Py_GetVersion, ctypes.c_void_p), + ctypes.pointer(dlinfo)) + if retcode == 0: # means error + return None + path = os.path.realpath(dlinfo.dli_fname.decode()) + if path == os.path.realpath(sys.executable): + return None + return path + + +def _linked_libpython_windows(): + """ + Based on: https://stackoverflow.com/a/16659821 + """ + from ctypes.wintypes import HANDLE, LPWSTR, DWORD + + GetModuleFileName = ctypes.windll.kernel32.GetModuleFileNameW + GetModuleFileName.argtypes = [HANDLE, LPWSTR, DWORD] + GetModuleFileName.restype = DWORD + + MAX_PATH = 260 + try: + buf = ctypes.create_unicode_buffer(MAX_PATH) + GetModuleFileName(ctypes.pythonapi._handle, buf, MAX_PATH) + return buf.value + except (ValueError, OSError): + return None + + + +def library_name(name, suffix=SHLIB_SUFFIX, is_windows=is_windows): + """ + Convert a file basename `name` to a library name (no "lib" and ".so" etc.) + + >>> library_name("libpython3.7m.so") # doctest: +SKIP + 'python3.7m' + >>> library_name("libpython3.7m.so", suffix=".so", is_windows=False) + 'python3.7m' + >>> library_name("libpython3.7m.dylib", suffix=".dylib", is_windows=False) + 'python3.7m' + >>> library_name("python37.dll", suffix=".dll", is_windows=True) + 'python37' + """ + if not is_windows and name.startswith("lib"): + name = name[len("lib"):] + if suffix and name.endswith(suffix): + name = name[:-len(suffix)] + return name + + +def append_truthy(list, item): + if item: + list.append(item) + + +def uniquifying(items): + """ + Yield items while excluding the duplicates and preserving the order. + + >>> list(uniquifying([1, 2, 1, 2, 3])) + [1, 2, 3] + """ + seen = set() + for x in items: + if x not in seen: + yield x + seen.add(x) + + +def uniquified(func): + """ Wrap iterator returned from `func` by `uniquifying`. """ + @functools.wraps(func) + def wrapper(*args, **kwds): + return uniquifying(func(*args, **kwds)) + return wrapper + + +@uniquified +def candidate_names(suffix=SHLIB_SUFFIX): + """ + Iterate over candidate file names of libpython. + + Yields + ------ + name : str + Candidate name libpython. + """ + LDLIBRARY = sysconfig.get_config_var("LDLIBRARY") + if LDLIBRARY and not LDLIBRARY.endswith(".a"): + yield LDLIBRARY + + LIBRARY = sysconfig.get_config_var("LIBRARY") + if LIBRARY and not LIBRARY.endswith(".a"): + yield os.path.splitext(LIBRARY)[0] + suffix + + dlprefix = "" if is_windows else "lib" + sysdata = dict( + v=sys.version_info, + # VERSION is X.Y in Linux/macOS and XY in Windows: + VERSION=(sysconfig.get_python_version() or + "{v.major}.{v.minor}".format(v=sys.version_info) or + sysconfig.get_config_var("VERSION")), + ABIFLAGS=(sysconfig.get_config_var("ABIFLAGS") or + sysconfig.get_config_var("abiflags") or ""), + ) + + for stem in [ + "python{VERSION}{ABIFLAGS}".format(**sysdata), + "python{VERSION}".format(**sysdata), + "python{v.major}".format(**sysdata), + "python", + ]: + yield dlprefix + stem + suffix + + + +@uniquified +def candidate_paths(suffix=SHLIB_SUFFIX): + """ + Iterate over candidate paths of libpython. + + Yields + ------ + path : str or None + Candidate path to libpython. The path may not be a fullpath + and may not exist. + """ + + yield linked_libpython() + + # List candidates for directories in which libpython may exist + lib_dirs = [] + append_truthy(lib_dirs, sysconfig.get_config_var('LIBPL')) + append_truthy(lib_dirs, sysconfig.get_config_var('srcdir')) + append_truthy(lib_dirs, sysconfig.get_config_var("LIBDIR")) + + # LIBPL seems to be the right config_var to use. It is the one + # used in python-config when shared library is not enabled: + # https://github.com/python/cpython/blob/v3.7.0/Misc/python-config.in#L55-L57 + # + # But we try other places just in case. + + if is_windows: + lib_dirs.append(os.path.join(os.path.dirname(sys.executable))) + else: + lib_dirs.append(os.path.join( + os.path.dirname(os.path.dirname(sys.executable)), + "lib")) + + # For macOS: + append_truthy(lib_dirs, sysconfig.get_config_var("PYTHONFRAMEWORKPREFIX")) + + lib_dirs.append(sys.exec_prefix) + lib_dirs.append(os.path.join(sys.exec_prefix, "lib")) + + lib_basenames = list(candidate_names(suffix=suffix)) + + for directory in lib_dirs: + for basename in lib_basenames: + yield os.path.join(directory, basename) + + # In macOS and Windows, ctypes.util.find_library returns a full path: + for basename in lib_basenames: + yield ctypes.util.find_library(library_name(basename)) + +# Possibly useful links: +# * https://packages.ubuntu.com/bionic/amd64/libpython3.6/filelist +# * https://github.com/Valloric/ycmd/issues/518 +# * https://github.com/Valloric/ycmd/pull/519 + + +def normalize_path(path, suffix=SHLIB_SUFFIX, is_apple=is_apple): + """ + Normalize shared library `path` to a real path. + + If `path` is not a full path, `None` is returned. If `path` does + not exists, append `SHLIB_SUFFIX` and check if it exists. + Finally, the path is canonicalized by following the symlinks. + + Parameters + ---------- + path : str ot None + A candidate path to a shared library. + """ + if not path: + return None + if not os.path.isabs(path): + return None + if os.path.exists(path): + return os.path.realpath(path) + if os.path.exists(path + suffix): + return os.path.realpath(path + suffix) + if is_apple: + return normalize_path(_remove_suffix_apple(path), + suffix=".so", is_apple=False) + return None + + +def _remove_suffix_apple(path): + """ + Strip off .so or .dylib. + + >>> _remove_suffix_apple("libpython.so") + 'libpython' + >>> _remove_suffix_apple("libpython.dylib") + 'libpython' + >>> _remove_suffix_apple("libpython3.7") + 'libpython3.7' + """ + if path.endswith(".dylib"): + return path[:-len(".dylib")] + if path.endswith(".so"): + return path[:-len(".so")] + return path + + +@uniquified +def finding_libpython(): + """ + Iterate over existing libpython paths. + + The first item is likely to be the best one. + + Yields + ------ + path : str + Existing path to a libpython. + """ + logger.debug("is_windows = %s", is_windows) + logger.debug("is_apple = %s", is_apple) + for path in candidate_paths(): + logger.debug("Candidate: %s", path) + normalized = normalize_path(path) + if normalized: + logger.debug("Found: %s", normalized) + yield normalized + else: + logger.debug("Not found.") + + +def find_libpython(): + """ + Return a path (`str`) to libpython or `None` if not found. + + Parameters + ---------- + path : str or None + Existing path to the (supposedly) correct libpython. + """ + for path in finding_libpython(): + return os.path.realpath(path) + + +def print_all(items): + for x in items: + print(x) + + +def cli_find_libpython(cli_op, verbose, export): + import logging + # Importing `logging` module here so that using `logging.debug` + # instead of `logger.debug` outside of this function becomes an + # error. + + if verbose: + logging.basicConfig( + format="%(levelname)s %(message)s", + level=logging.DEBUG) + + if cli_op == "list-all": + print_all(finding_libpython()) + elif cli_op == "candidate-names": + print_all(candidate_names()) + elif cli_op == "candidate-paths": + print_all(p for p in candidate_paths() if p and os.path.isabs(p)) + else: + path = find_libpython() + if path is None: + return 1 + if export: + print(f"PYTHONNET_PYDLL={path}") + else: + print(path, end="") + + +def main(args=None): + import argparse + parser = argparse.ArgumentParser( + description=__doc__) + parser.add_argument( + "--verbose", "-v", action="store_true", + help="Print debugging information.") + + group = parser.add_mutually_exclusive_group() + group.add_argument( + "--list-all", + action="store_const", dest="cli_op", const="list-all", + help="Print list of all paths found.") + group.add_argument( + "--candidate-names", + action="store_const", dest="cli_op", const="candidate-names", + help="Print list of candidate names of libpython.") + group.add_argument( + "--candidate-paths", + action="store_const", dest="cli_op", const="candidate-paths", + help="Print list of candidate paths of libpython.") + group.add_argument( + "--export", + action="store_true", + help="Print as an environment export expression" + ) + + ns = parser.parse_args(args) + parser.exit(cli_find_libpython(**vars(ns))) diff --git a/pythonnet/find_libpython/__main__.py b/pythonnet/find_libpython/__main__.py new file mode 100644 index 000000000..031df43e1 --- /dev/null +++ b/pythonnet/find_libpython/__main__.py @@ -0,0 +1,2 @@ +from . import main +main() diff --git a/pythonnet/mono/.gitkeep b/pythonnet/mono/.gitkeep deleted file mode 100644 index e69de29bb..000000000 diff --git a/pythonnet/netfx/.gitkeep b/pythonnet/netfx/.gitkeep deleted file mode 100644 index e69de29bb..000000000 diff --git a/pythonnet/util/__init__.py b/pythonnet/util/__init__.py new file mode 100644 index 000000000..75d4bad8c --- /dev/null +++ b/pythonnet/util/__init__.py @@ -0,0 +1 @@ +from .find_libpython import find_libpython diff --git a/requirements.txt b/requirements.txt index 6f25858bc..f5aedfc3f 100644 --- a/requirements.txt +++ b/requirements.txt @@ -6,7 +6,7 @@ psutil coverage codecov -# Platform specific requirements wheel pycparser setuptools +clr-loader diff --git a/setup.py b/setup.py index 06a26ef95..ffa18902e 100644 --- a/setup.py +++ b/setup.py @@ -8,79 +8,9 @@ import sys, os -BUILD_MONO = True -BUILD_NETFX = True - PY_MAJOR = sys.version_info[0] PY_MINOR = sys.version_info[1] -CONFIGURED_PROPS = "configured.props" - - -def _get_interop_filename(): - """interopXX.cs is auto-generated as part of the build. - For common windows platforms pre-generated files are included - as most windows users won't have Clang installed, which is - required to generate the file. - """ - interop_filename = "interop{0}{1}{2}.cs".format( - PY_MAJOR, PY_MINOR, getattr(sys, "abiflags", "") - ) - return os.path.join("src", "runtime", interop_filename) - - -# Write configuration to configured.props. This will go away once all of these -# can be decided at runtime. -def _write_configure_props(): - defines = [ - "PYTHON{0}{1}".format(PY_MAJOR, PY_MINOR), - ] - - if sys.platform == "win32": - defines.append("WINDOWS") - - if hasattr(sys, "abiflags"): - if "d" in sys.abiflags: - defines.append("PYTHON_WITH_PYDEBUG") - if "m" in sys.abiflags: - defines.append("PYTHON_WITH_PYMALLOC") - - # check the interop file exists, and create it if it doesn't - interop_file = _get_interop_filename() - if not os.path.exists(interop_file): - print("Creating {0}".format(interop_file)) - geninterop = os.path.join("tools", "geninterop", "geninterop.py") - check_call([sys.executable, geninterop, interop_file]) - - import xml.etree.ElementTree as ET - - proj = ET.Element("Project") - props = ET.SubElement(proj, "PropertyGroup") - f = ET.SubElement(props, "PythonInteropFile") - f.text = os.path.basename(interop_file) - - c = ET.SubElement(props, "ConfiguredConstants") - c.text = " ".join(defines) - - ET.ElementTree(proj).write(CONFIGURED_PROPS) - - -class configure(Command): - """Configure command""" - - description = "Configure the pythonnet build" - user_options = [] - - def initialize_options(self): - pass - - def finalize_options(self): - pass - - def run(self): - self.announce("Writing configured.props...", level=distutils.log.INFO) - _write_configure_props() - class DotnetLib: def __init__(self, name, path, **kwargs): @@ -121,7 +51,6 @@ def finalize_options(self): def run(self): dotnet_modules = self.distribution.dotnet_libs - self.run_command("configure") for lib in dotnet_modules: output = os.path.join( @@ -188,7 +117,6 @@ def install_for_development(self): cmdclass = { "build": build, "build_dotnet": build_dotnet, - "configure": configure, "develop": develop, } @@ -204,54 +132,6 @@ def install_for_development(self): ) ] -if BUILD_NETFX: - dotnet_libs.extend( - [ - DotnetLib( - "clrmodule-amd64", - "src/clrmodule/", - runtime="win-x64", - output="pythonnet/netfx/amd64", - rename={"clr.dll": "clr.pyd"}, - ), - DotnetLib( - "clrmodule-x86", - "src/clrmodule/", - runtime="win-x86", - output="pythonnet/netfx/x86", - rename={"clr.dll": "clr.pyd"}, - ), - ] - ) - -ext_modules = [] - -if BUILD_MONO: - try: - mono_libs = check_output( - "pkg-config --libs mono-2", shell=True, encoding="utf8" - ) - mono_cflags = check_output( - "pkg-config --cflags mono-2", shell=True, encoding="utf8" - ) - cflags = mono_cflags.strip() - libs = mono_libs.strip() - - # build the clr python module - clr_ext = Extension( - "pythonnet.mono.clr", - language="c++", - sources=["src/monoclr/clrmod.c"], - extra_compile_args=cflags.split(" "), - extra_link_args=libs.split(" "), - ) - ext_modules.append(clr_ext) - except Exception: - print( - "Failed to find mono libraries via pkg-config, skipping the Mono CLR loader" - ) - - setup( cmdclass=cmdclass, name="pythonnet", @@ -261,12 +141,11 @@ def install_for_development(self): license="MIT", author="The Contributors of the Python.NET Project", author_email="pythonnet@python.org", - packages=["pythonnet"], - install_requires=["pycparser"], + packages=["pythonnet", "pythonnet.find_libpython"], + install_requires=["pycparser", "clr_loader"], long_description=long_description, # data_files=[("{install_platlib}", ["{build_lib}/pythonnet"])], py_modules=["clr"], - ext_modules=ext_modules, dotnet_libs=dotnet_libs, classifiers=[ "Development Status :: 5 - Production/Stable", diff --git a/src/clrmodule/ClrModule.cs b/src/clrmodule/ClrModule.cs deleted file mode 100644 index ab0f6da9f..000000000 --- a/src/clrmodule/ClrModule.cs +++ /dev/null @@ -1,113 +0,0 @@ -//============================================================================ -// This file replaces the hand-maintained stub that used to implement clr.dll. -// This is a line-by-line port from IL back to C#. -// We now use RGiesecke.DllExport on the required static init method so it can be -// loaded by a standard CPython interpreter as an extension module. When it -// is loaded, it bootstraps the managed runtime integration layer and defers -// to it to do initialization and put the clr module into sys.modules, etc. - -// The "USE_PYTHON_RUNTIME_*" defines control what extra evidence is used -// to help the CLR find the appropriate Python.Runtime assembly. - -// If defined, the "pythonRuntimeVersionString" variable must be set to -// Python.Runtime's current version. -#define USE_PYTHON_RUNTIME_VERSION - -// If defined, the "PythonRuntimePublicKeyTokenData" data array must be -// set to Python.Runtime's public key token. (sn -T Python.Runtin.dll) -#define USE_PYTHON_RUNTIME_PUBLIC_KEY_TOKEN - -// If DEBUG is defined in the Build Properties, a few Console.WriteLine -// calls are made to indicate what's going on during the load... -//============================================================================ -using System; -using System.Diagnostics; -using System.Globalization; -using System.IO; -using System.Reflection; -using System.Runtime.InteropServices; -using NXPorts.Attributes; - -public class clrModule -{ - [DllExport("PyInit_clr", CallingConvention.StdCall)] - public static IntPtr PyInit_clr() - { - DebugPrint("Attempting to load 'Python.Runtime' using standard binding rules."); -#if USE_PYTHON_RUNTIME_PUBLIC_KEY_TOKEN - var pythonRuntimePublicKeyTokenData = new byte[] { 0x50, 0x00, 0xfe, 0xa6, 0xcb, 0xa7, 0x02, 0xdd }; -#endif - - // Attempt to find and load Python.Runtime using standard assembly binding rules. - // This roughly translates into looking in order: - // - GAC - // - ApplicationBase - // - A PrivateBinPath under ApplicationBase - // With an unsigned assembly, the GAC is skipped. - var pythonRuntimeName = new AssemblyName("Python.Runtime") - { -#if USE_PYTHON_RUNTIME_VERSION - // Has no effect until SNK works. Keep updated anyways. - Version = new Version("2.5.0"), -#endif - CultureInfo = CultureInfo.InvariantCulture - }; -#if USE_PYTHON_RUNTIME_PUBLIC_KEY_TOKEN - pythonRuntimeName.SetPublicKeyToken(pythonRuntimePublicKeyTokenData); -#endif - // We've got the AssemblyName with optional features; try to load it. - Assembly pythonRuntime; - try - { - pythonRuntime = Assembly.Load(pythonRuntimeName); - DebugPrint("Success loading 'Python.Runtime' using standard binding rules."); - } - catch (IOException ex) - { - DebugPrint($"'Python.Runtime' not found using standard binding rules: {ex}"); - try - { - // If the above fails for any reason, we fallback to attempting to load "Python.Runtime.dll" - // from the directory this assembly is running in. "This assembly" is probably "clr.pyd", - // sitting somewhere in PYTHONPATH. This is using Assembly.LoadFrom, and inherits all the - // caveats of that call. See MSDN docs for details. - // Suzanne Cook's blog is also an excellent source of info on this: - // http://blogs.msdn.com/suzcook/ - // http://blogs.msdn.com/suzcook/archive/2003/05/29/57143.aspx - // http://blogs.msdn.com/suzcook/archive/2003/06/13/57180.aspx - - Assembly executingAssembly = Assembly.GetExecutingAssembly(); - string assemblyDirectory = Path.GetDirectoryName(executingAssembly.Location); - if (assemblyDirectory == null) - { - throw new InvalidOperationException(executingAssembly.Location); - } - string pythonRuntimeDllPath = Path.Combine(assemblyDirectory, "Python.Runtime.dll"); - DebugPrint($"Attempting to load Python.Runtime from: '{pythonRuntimeDllPath}'."); - pythonRuntime = Assembly.LoadFrom(pythonRuntimeDllPath); - DebugPrint($"Success loading 'Python.Runtime' from: '{pythonRuntimeDllPath}'."); - } - catch (InvalidOperationException) - { - DebugPrint("Could not load 'Python.Runtime'."); - return IntPtr.Zero; - } - } - - // Once here, we've successfully loaded SOME version of Python.Runtime - // So now we get the PythonEngine and execute the InitExt method on it. - Type pythonEngineType = pythonRuntime.GetType("Python.Runtime.PythonEngine"); - - return (IntPtr)pythonEngineType.InvokeMember("InitExt", BindingFlags.InvokeMethod, null, null, null); - } - - /// - /// Substitute for Debug.Writeline(...). Ideally we would use Debug.Writeline - /// but haven't been able to configure the TRACE from within Python. - /// - [Conditional("DEBUG")] - private static void DebugPrint(string str) - { - Console.WriteLine(str); - } -} diff --git a/src/clrmodule/Properties/AssemblyInfo.cs b/src/clrmodule/Properties/AssemblyInfo.cs deleted file mode 100644 index 5e2e05ed4..000000000 --- a/src/clrmodule/Properties/AssemblyInfo.cs +++ /dev/null @@ -1,5 +0,0 @@ -using System.Reflection; -using System.Runtime.InteropServices; - -// The following GUID is for the ID of the typelib if this project is exposed to COM -[assembly: Guid("ae10d6a4-55c2-482f-9716-9988e6c169e3")] diff --git a/src/clrmodule/clrmodule.csproj b/src/clrmodule/clrmodule.csproj deleted file mode 100644 index 8595fd0ba..000000000 --- a/src/clrmodule/clrmodule.csproj +++ /dev/null @@ -1,24 +0,0 @@ - - - net472 - win-x86;win-x64 - clr - - - - - - - 1.0.0 - all - runtime; build; native; contentfiles; analyzers - - - - - x86 - - - x64 - - diff --git a/src/embed_tests/TestNativeTypeOffset.cs b/src/embed_tests/TestNativeTypeOffset.cs index 03812c6fe..7dd5a765e 100644 --- a/src/embed_tests/TestNativeTypeOffset.cs +++ b/src/embed_tests/TestNativeTypeOffset.cs @@ -34,11 +34,12 @@ public void Dispose() public void LoadNativeTypeOffsetClass() { PyObject sys = Py.Import("sys"); - string attributeName = "abiflags"; - if (sys.HasAttr(attributeName) && !string.IsNullOrEmpty(sys.GetAttr(attributeName).ToString())) + // We can safely ignore the "m" abi flag + var abiflags = sys.GetAttr("abiflags", "".ToPython()).ToString().Replace("m", ""); + if (!string.IsNullOrEmpty(abiflags)) { string typeName = "Python.Runtime.NativeTypeOffset, Python.Runtime"; - Assert.NotNull(Type.GetType(typeName), $"{typeName} does not exist and sys.{attributeName} is not empty"); + Assert.NotNull(Type.GetType(typeName), $"{typeName} does not exist and sys.abiflags={abiflags}"); } } } diff --git a/src/monoclr/clrmod.c b/src/monoclr/clrmod.c deleted file mode 100644 index cdfd89342..000000000 --- a/src/monoclr/clrmod.c +++ /dev/null @@ -1,215 +0,0 @@ -// #define Py_LIMITED_API 0x03050000 -#include - -#include "stdlib.h" - -#define MONO_VERSION "v4.0.30319.1" -#define MONO_DOMAIN "Python" - -#include -#include -#include -#include -#include - -#ifndef _WIN32 -#include "dirent.h" -#include "dlfcn.h" -#include "libgen.h" -#include "alloca.h" -#endif - -typedef struct -{ - MonoDomain *domain; - MonoAssembly *pr_assm; - MonoMethod *shutdown; - const char *pr_file; - char *error; - char *init_name; - char *shutdown_name; - PyObject *module; -} PyNet_Args; - -PyNet_Args *PyNet_Init(void); -static PyNet_Args *pn_args; - -PyMODINIT_FUNC -PyInit_clr(void) -{ - pn_args = PyNet_Init(); - if (pn_args->error) - { - return NULL; - } - - return pn_args->module; -} - -void PyNet_Finalize(PyNet_Args *); -void main_thread_handler(PyNet_Args *user_data); - -// initialize Mono and PythonNet -PyNet_Args *PyNet_Init() -{ - PyObject *pn_module; - PyObject *pn_path; - PyNet_Args *pn_args; - pn_args = (PyNet_Args *)malloc(sizeof(PyNet_Args)); - - pn_module = PyImport_ImportModule("pythonnet"); - if (pn_module == NULL) - { - pn_args->error = "Failed to import pythonnet"; - return pn_args; - } - - pn_path = PyObject_CallMethod(pn_module, "get_assembly_path", NULL); - if (pn_path == NULL) - { - Py_DecRef(pn_module); - pn_args->error = "Failed to get assembly path"; - return pn_args; - } - - pn_args->pr_file = PyUnicode_AsUTF8(pn_path); - pn_args->error = NULL; - pn_args->shutdown = NULL; - pn_args->module = NULL; - -#ifdef __linux__ - // Force preload libmono-2.0 as global. Without this, on some systems - // symbols from libmono are not found by libmononative (which implements - // some of the System.* namespaces). Since the only happened on Linux so - // far, we hardcode the library name, load the symbols into the global - // namespace and leak the handle. - dlopen("libmono-2.0.so", RTLD_LAZY | RTLD_GLOBAL); -#endif - - pn_args->init_name = "Python.Runtime:InitExt()"; - pn_args->shutdown_name = "Python.Runtime:Shutdown()"; - - pn_args->domain = mono_jit_init_version(MONO_DOMAIN, MONO_VERSION); - - // XXX: Skip setting config for now, should be derived from pr_file - // mono_domain_set_config(pn_args->domain, ".", "Python.Runtime.dll.config"); - - /* - * Load the default Mono configuration file, this is needed - * if you are planning on using the dllmaps defined on the - * system configuration - */ - mono_config_parse(NULL); - - main_thread_handler(pn_args); - - if (pn_args->error != NULL) - { - PyErr_SetString(PyExc_ImportError, pn_args->error); - } - return pn_args; -} - -char *PyNet_ExceptionToString(MonoObject *e); - -// Shuts down PythonNet and cleans up Mono -void PyNet_Finalize(PyNet_Args *pn_args) -{ - MonoObject *exception = NULL; - - if (pn_args->shutdown) - { - mono_runtime_invoke(pn_args->shutdown, NULL, NULL, &exception); - if (exception) - { - pn_args->error = PyNet_ExceptionToString(exception); - } - pn_args->shutdown = NULL; - } - - if (pn_args->domain) - { - mono_jit_cleanup(pn_args->domain); - pn_args->domain = NULL; - } - free(pn_args); -} - -MonoMethod *getMethodFromClass(MonoClass *cls, char *name) -{ - MonoMethodDesc *mdesc; - MonoMethod *method; - - mdesc = mono_method_desc_new(name, 1); - method = mono_method_desc_search_in_class(mdesc, cls); - mono_method_desc_free(mdesc); - - return method; -} - -void main_thread_handler(PyNet_Args *user_data) -{ - PyNet_Args *pn_args = user_data; - MonoMethod *init; - MonoImage *pr_image; - MonoClass *pythonengine; - MonoObject *exception = NULL; - MonoObject *init_result; - - pn_args->pr_assm = mono_domain_assembly_open(pn_args->domain, pn_args->pr_file); - if (!pn_args->pr_assm) - { - pn_args->error = "Unable to load assembly"; - return; - } - - pr_image = mono_assembly_get_image(pn_args->pr_assm); - if (!pr_image) - { - pn_args->error = "Unable to get image"; - return; - } - - pythonengine = mono_class_from_name(pr_image, "Python.Runtime", "PythonEngine"); - if (!pythonengine) - { - pn_args->error = "Unable to load class PythonEngine from Python.Runtime"; - return; - } - - init = getMethodFromClass(pythonengine, pn_args->init_name); - if (!init) - { - pn_args->error = "Unable to fetch Init method from PythonEngine"; - return; - } - - pn_args->shutdown = getMethodFromClass(pythonengine, pn_args->shutdown_name); - if (!pn_args->shutdown) - { - pn_args->error = "Unable to fetch shutdown method from PythonEngine"; - return; - } - - init_result = mono_runtime_invoke(init, NULL, NULL, &exception); - if (exception) - { - pn_args->error = PyNet_ExceptionToString(exception); - return; - } - - pn_args->module = *(PyObject**)mono_object_unbox(init_result); -} - -// Get string from a Mono exception -char *PyNet_ExceptionToString(MonoObject *e) -{ - MonoMethodDesc *mdesc = mono_method_desc_new(":ToString()", 0 /*FALSE*/); - MonoMethod *mmethod = mono_method_desc_search_in_class(mdesc, mono_get_object_class()); - mono_method_desc_free(mdesc); - - mmethod = mono_object_get_virtual_method(e, mmethod); - MonoString *monoString = (MonoString*) mono_runtime_invoke(mmethod, e, NULL, NULL); - mono_runtime_invoke(mmethod, e, NULL, NULL); - return mono_string_to_utf8(monoString); -} diff --git a/src/runtime/Python.Runtime.csproj b/src/runtime/Python.Runtime.csproj index ef530d69a..0311dbf9a 100644 --- a/src/runtime/Python.Runtime.csproj +++ b/src/runtime/Python.Runtime.csproj @@ -30,7 +30,8 @@ - $(DefineConstants);$(ConfiguredConstants) + ..\..\pythonnet\runtime + false diff --git a/src/runtime/loader.cs b/src/runtime/loader.cs new file mode 100644 index 000000000..d5f31b247 --- /dev/null +++ b/src/runtime/loader.cs @@ -0,0 +1,83 @@ +using System.Diagnostics; +using System; +using System.Runtime.InteropServices; +using System.Text; +using System.Threading; + +namespace Python.Runtime +{ + using static Runtime; + + [Obsolete("Only to be used from within Python")] + static class Loader + { + public unsafe static int Initialize(IntPtr data, int size) + { + IntPtr gs = IntPtr.Zero; + try + { + var dllPath = Encoding.UTF8.GetString((byte*)data.ToPointer(), size); + + if (!string.IsNullOrEmpty(dllPath)) + { + PythonDLL = dllPath; + } + else + { + PythonDLL = null; + } + + gs = PyGILState_Ensure(); + + // Console.WriteLine("Startup thread"); + PythonEngine.InitExt(); + // Console.WriteLine("Startup finished"); + } + catch (Exception exc) + { + Console.Error.Write( + $"Failed to initialize pythonnet: {exc}\n{exc.StackTrace}" + ); + return 1; + } + finally + { + if (gs != IntPtr.Zero) + { + PyGILState_Release(gs); + } + } + return 0; + } + + public unsafe static int Shutdown(IntPtr data, int size) + { + IntPtr gs = IntPtr.Zero; + try + { + var command = Encoding.UTF8.GetString((byte*)data.ToPointer(), size); + + if (command == "full_shutdown") + { + gs = PyGILState_Ensure(); + PythonEngine.Shutdown(); + } + } + catch (Exception exc) + { + Console.Error.Write( + $"Failed to shutdown pythonnet: {exc}\n{exc.StackTrace}" + ); + return 1; + } + finally + { + if (gs != IntPtr.Zero) + { + PyGILState_Release(gs); + } + } + return 0; + } + } +} diff --git a/src/runtime/moduleobject.cs b/src/runtime/moduleobject.cs index 3fdd99b9a..41167e322 100644 --- a/src/runtime/moduleobject.cs +++ b/src/runtime/moduleobject.cs @@ -534,11 +534,5 @@ public static string[] ListAssemblies(bool verbose) } return names; } - - [ModuleFunction] - public static int _AtExit() - { - return Runtime.AtExit(); - } } } diff --git a/src/runtime/native/TypeOffset.cs b/src/runtime/native/TypeOffset.cs index 4c1bcefa0..9f5ed671b 100644 --- a/src/runtime/native/TypeOffset.cs +++ b/src/runtime/native/TypeOffset.cs @@ -147,7 +147,6 @@ static void ValidateRequiredOffsetsPresent(PropertyInfo[] offsetProperties) { "__instancecheck__", "__subclasscheck__", - "_AtExit", "AddReference", "FinalizeObject", "FindAssembly", diff --git a/src/runtime/pythonengine.cs b/src/runtime/pythonengine.cs index b5334fabc..35ea3f6d2 100644 --- a/src/runtime/pythonengine.cs +++ b/src/runtime/pythonengine.cs @@ -198,18 +198,6 @@ public static void Initialize(IEnumerable args, bool setSysArgv = true, Py.SetArgv(args); } - if (mode == ShutdownMode.Normal) - { - // TOOD: Check if this can be remove completely or not. - // register the atexit callback (this doesn't use Py_AtExit as the C atexit - // callbacks are called after python is fully finalized but the python ones - // are called while the python engine is still running). - //string code = - // "import atexit, clr\n" + - // "atexit.register(clr._AtExit)\n"; - //PythonEngine.Exec(code); - } - // Load the clr.py resource into the clr module NewReference clr = Python.Runtime.ImportHook.GetCLRModule(); BorrowedReference clr_dict = Runtime.PyModule_GetDict(clr); @@ -266,7 +254,7 @@ public static IntPtr InitExt() { try { - Initialize(setSysArgv: false); + Initialize(setSysArgv: false, mode: ShutdownMode.Extension); // Trickery - when the import hook is installed into an already // running Python, the standard import machinery is still in diff --git a/src/runtime/runtime.cs b/src/runtime/runtime.cs index 990ac2a9f..ec7f5e446 100644 --- a/src/runtime/runtime.cs +++ b/src/runtime/runtime.cs @@ -133,7 +133,10 @@ internal static void Initialize(bool initSigs = false, ShutdownMode mode = Shutd // If we're coming back from a domain reload or a soft shutdown, // we have previously released the thread state. Restore the main // thread state here. - PyGILState_Ensure(); + if (mode != ShutdownMode.Extension) + { + PyGILState_Ensure(); + } } MainManagedThreadId = Thread.CurrentThread.ManagedThreadId; @@ -356,7 +359,7 @@ internal static void Shutdown(ShutdownMode mode) Finalizer.Shutdown(); InternString.Shutdown(); - if (mode != ShutdownMode.Normal) + if (mode != ShutdownMode.Normal && mode != ShutdownMode.Extension) { PyGC_Collect(); if (mode == ShutdownMode.Soft) @@ -387,7 +390,10 @@ internal static void Shutdown(ShutdownMode mode) else { ResetPyMembers(); - Py_Finalize(); + if (mode != ShutdownMode.Extension) + { + Py_Finalize(); + } } } @@ -412,16 +418,6 @@ internal static ShutdownMode GetDefaultShutdownMode() return ShutdownMode.Normal; } - // called *without* the GIL acquired by clr._AtExit - internal static int AtExit() - { - lock (IsFinalizingLock) - { - IsFinalizing = true; - } - return 0; - } - private static void RunExitFuncs() { PyObject atexit; @@ -781,7 +777,7 @@ internal static unsafe long Refcount(IntPtr op) /// Limit this function usage for Testing and Py_Debug builds /// /// PyObject Ptr - + internal static void Py_IncRef(IntPtr ob) => Delegates.Py_IncRef(ob); /// @@ -789,59 +785,59 @@ internal static unsafe long Refcount(IntPtr op) /// Limit this function usage for Testing and Py_Debug builds /// /// PyObject Ptr - + internal static void Py_DecRef(IntPtr ob) => Delegates.Py_DecRef(ob); - + internal static void Py_Initialize() => Delegates.Py_Initialize(); - + internal static void Py_InitializeEx(int initsigs) => Delegates.Py_InitializeEx(initsigs); - + internal static int Py_IsInitialized() => Delegates.Py_IsInitialized(); - + internal static void Py_Finalize() => Delegates.Py_Finalize(); - + internal static IntPtr Py_NewInterpreter() => Delegates.Py_NewInterpreter(); - + internal static void Py_EndInterpreter(IntPtr threadState) => Delegates.Py_EndInterpreter(threadState); - + internal static IntPtr PyThreadState_New(IntPtr istate) => Delegates.PyThreadState_New(istate); - + internal static IntPtr PyThreadState_Get() => Delegates.PyThreadState_Get(); - + internal static IntPtr _PyThreadState_UncheckedGet() => Delegates._PyThreadState_UncheckedGet(); - + internal static IntPtr PyThread_get_key_value(IntPtr key) => Delegates.PyThread_get_key_value(key); - + internal static int PyThread_get_thread_ident() => Delegates.PyThread_get_thread_ident(); - + internal static int PyThread_set_key_value(IntPtr key, IntPtr value) => Delegates.PyThread_set_key_value(key, value); - + internal static IntPtr PyThreadState_Swap(IntPtr key) => Delegates.PyThreadState_Swap(key); - + internal static IntPtr PyGILState_Ensure() => Delegates.PyGILState_Ensure(); - + internal static void PyGILState_Release(IntPtr gs) => Delegates.PyGILState_Release(gs); - + internal static IntPtr PyGILState_GetThisThreadState() => Delegates.PyGILState_GetThisThreadState(); - + public static int Py_Main(int argc, string[] argv) { var marshaler = StrArrayMarshaler.GetInstance(null); @@ -858,67 +854,67 @@ public static int Py_Main(int argc, string[] argv) internal static void PyEval_InitThreads() => Delegates.PyEval_InitThreads(); - + internal static int PyEval_ThreadsInitialized() => Delegates.PyEval_ThreadsInitialized(); - + internal static void PyEval_AcquireLock() => Delegates.PyEval_AcquireLock(); - + internal static void PyEval_ReleaseLock() => Delegates.PyEval_ReleaseLock(); - + internal static void PyEval_AcquireThread(IntPtr tstate) => Delegates.PyEval_AcquireThread(tstate); - + internal static void PyEval_ReleaseThread(IntPtr tstate) => Delegates.PyEval_ReleaseThread(tstate); - + internal static IntPtr PyEval_SaveThread() => Delegates.PyEval_SaveThread(); - + internal static void PyEval_RestoreThread(IntPtr tstate) => Delegates.PyEval_RestoreThread(tstate); - + internal static BorrowedReference PyEval_GetBuiltins() => Delegates.PyEval_GetBuiltins(); - + internal static BorrowedReference PyEval_GetGlobals() => Delegates.PyEval_GetGlobals(); - + internal static IntPtr PyEval_GetLocals() => Delegates.PyEval_GetLocals(); - + internal static IntPtr Py_GetProgramName() => Delegates.Py_GetProgramName(); - + internal static void Py_SetProgramName(IntPtr name) => Delegates.Py_SetProgramName(name); - + internal static IntPtr Py_GetPythonHome() => Delegates.Py_GetPythonHome(); - + internal static void Py_SetPythonHome(IntPtr home) => Delegates.Py_SetPythonHome(home); - + internal static IntPtr Py_GetPath() => Delegates.Py_GetPath(); - + internal static void Py_SetPath(IntPtr home) => Delegates.Py_SetPath(home); - + internal static IntPtr Py_GetVersion() => Delegates.Py_GetVersion(); - + internal static IntPtr Py_GetPlatform() => Delegates.Py_GetPlatform(); - + internal static IntPtr Py_GetCopyright() => Delegates.Py_GetCopyright(); - + internal static IntPtr Py_GetCompiler() => Delegates.Py_GetCompiler(); - + internal static IntPtr Py_GetBuildInfo() => Delegates.Py_GetBuildInfo(); const PyCompilerFlags Utf8String = PyCompilerFlags.IGNORE_COOKIE | PyCompilerFlags.SOURCE_IS_UTF8; @@ -956,10 +952,10 @@ internal static IntPtr PyImport_ExecCodeModule(string name, IntPtr code) internal static IntPtr PyCFunction_NewEx(IntPtr ml, IntPtr self, IntPtr mod) => Delegates.PyCFunction_NewEx(ml, self, mod); - + internal static IntPtr PyCFunction_Call(IntPtr func, IntPtr args, IntPtr kw) => Delegates.PyCFunction_Call(func, args, kw); - + internal static IntPtr PyMethod_New(IntPtr func, IntPtr self, IntPtr cls) => Delegates.PyMethod_New(func, self, cls); @@ -1021,7 +1017,7 @@ internal static bool PyObject_IsIterable(IntPtr pointer) return tp_iter != IntPtr.Zero; } - + internal static int PyObject_HasAttrString(BorrowedReference pointer, string name) { using var namePtr = new StrPtr(name, Encoding.UTF8); @@ -1034,7 +1030,7 @@ internal static IntPtr PyObject_GetAttrString(IntPtr pointer, string name) return Delegates.PyObject_GetAttrString(pointer, namePtr); } - + internal static IntPtr PyObject_GetAttrString(IntPtr pointer, StrPtr name) => Delegates.PyObject_GetAttrString(pointer, name); @@ -1057,25 +1053,25 @@ internal static IntPtr PyObject_GetAttr(IntPtr pointer, IntPtr name) internal static int PyObject_SetAttr(IntPtr pointer, IntPtr name, IntPtr value) => Delegates.PyObject_SetAttr(pointer, name, value); - + internal static IntPtr PyObject_GetItem(IntPtr pointer, IntPtr key) => Delegates.PyObject_GetItem(pointer, key); - + internal static int PyObject_SetItem(IntPtr pointer, IntPtr key, IntPtr value) => Delegates.PyObject_SetItem(pointer, key, value); - + internal static int PyObject_DelItem(IntPtr pointer, IntPtr key) => Delegates.PyObject_DelItem(pointer, key); - + internal static IntPtr PyObject_GetIter(IntPtr op) => Delegates.PyObject_GetIter(op); - + internal static IntPtr PyObject_Call(IntPtr pointer, IntPtr args, IntPtr kw) => Delegates.PyObject_Call(pointer, args, kw); - + internal static IntPtr PyObject_CallObject(IntPtr pointer, IntPtr args) => Delegates.PyObject_CallObject(pointer, args); - + internal static int PyObject_RichCompareBool(IntPtr value1, IntPtr value2, int opid) => Delegates.PyObject_RichCompareBool(value1, value2, opid); internal static int PyObject_Compare(IntPtr value1, IntPtr value2) @@ -1103,20 +1099,20 @@ internal static int PyObject_Compare(IntPtr value1, IntPtr value2) return -1; } - + internal static int PyObject_IsInstance(IntPtr ob, IntPtr type) => Delegates.PyObject_IsInstance(ob, type); - + internal static int PyObject_IsSubclass(IntPtr ob, IntPtr type) => Delegates.PyObject_IsSubclass(ob, type); - + internal static int PyCallable_Check(IntPtr pointer) => Delegates.PyCallable_Check(pointer); internal static int PyObject_IsTrue(IntPtr pointer) => PyObject_IsTrue(new BorrowedReference(pointer)); internal static int PyObject_IsTrue(BorrowedReference pointer) => Delegates.PyObject_IsTrue(pointer); - + internal static int PyObject_Not(IntPtr pointer) => Delegates.PyObject_Not(pointer); internal static long PyObject_Size(IntPtr pointer) @@ -1124,22 +1120,22 @@ internal static long PyObject_Size(IntPtr pointer) return (long)_PyObject_Size(pointer); } - + private static IntPtr _PyObject_Size(IntPtr pointer) => Delegates._PyObject_Size(pointer); - + internal static nint PyObject_Hash(IntPtr op) => Delegates.PyObject_Hash(op); - + internal static IntPtr PyObject_Repr(IntPtr pointer) => Delegates.PyObject_Repr(pointer); - + internal static IntPtr PyObject_Str(IntPtr pointer) => Delegates.PyObject_Str(pointer); - + internal static IntPtr PyObject_Unicode(IntPtr pointer) => Delegates.PyObject_Unicode(pointer); - + internal static IntPtr PyObject_Dir(IntPtr pointer) => Delegates.PyObject_Dir(pointer); #if PYTHON_WITH_PYDEBUG @@ -1151,13 +1147,13 @@ internal static long PyObject_Size(IntPtr pointer) // Python buffer API //==================================================================== - + internal static int PyObject_GetBuffer(IntPtr exporter, ref Py_buffer view, int flags) => Delegates.PyObject_GetBuffer(exporter, ref view, flags); - + internal static void PyBuffer_Release(ref Py_buffer view) => Delegates.PyBuffer_Release(ref view); - + internal static IntPtr PyBuffer_SizeFromFormat(string format) { using var formatPtr = new StrPtr(format, Encoding.ASCII); @@ -1166,35 +1162,35 @@ internal static IntPtr PyBuffer_SizeFromFormat(string format) internal static int PyBuffer_IsContiguous(ref Py_buffer view, char order) => Delegates.PyBuffer_IsContiguous(ref view, order); - + internal static IntPtr PyBuffer_GetPointer(ref Py_buffer view, IntPtr[] indices) => Delegates.PyBuffer_GetPointer(ref view, indices); - + internal static int PyBuffer_FromContiguous(ref Py_buffer view, IntPtr buf, IntPtr len, char fort) => Delegates.PyBuffer_FromContiguous(ref view, buf, len, fort); - + internal static int PyBuffer_ToContiguous(IntPtr buf, ref Py_buffer src, IntPtr len, char order) => Delegates.PyBuffer_ToContiguous(buf, ref src, len, order); - + internal static void PyBuffer_FillContiguousStrides(int ndims, IntPtr shape, IntPtr strides, int itemsize, char order) => Delegates.PyBuffer_FillContiguousStrides(ndims, shape, strides, itemsize, order); - + internal static int PyBuffer_FillInfo(ref Py_buffer view, IntPtr exporter, IntPtr buf, IntPtr len, int _readonly, int flags) => Delegates.PyBuffer_FillInfo(ref view, exporter, buf, len, _readonly, flags); //==================================================================== // Python number API //==================================================================== - + internal static IntPtr PyNumber_Int(IntPtr ob) => Delegates.PyNumber_Int(ob); - + internal static IntPtr PyNumber_Long(IntPtr ob) => Delegates.PyNumber_Long(ob); - + internal static IntPtr PyNumber_Float(IntPtr ob) => Delegates.PyNumber_Float(ob); - + internal static bool PyNumber_Check(IntPtr ob) => Delegates.PyNumber_Check(ob); internal static bool PyInt_Check(BorrowedReference ob) @@ -1221,25 +1217,25 @@ internal static IntPtr PyInt_FromInt64(long value) return PyInt_FromLong(v); } - + private static IntPtr PyInt_FromLong(IntPtr value) => Delegates.PyInt_FromLong(value); - + internal static int PyInt_AsLong(IntPtr value) => Delegates.PyInt_AsLong(value); - + internal static bool PyLong_Check(IntPtr ob) { return PyObject_TYPE(ob) == PyLongType; } - + internal static IntPtr PyLong_FromLong(long value) => Delegates.PyLong_FromLong(value); - + internal static IntPtr PyLong_FromUnsignedLong32(uint value) => Delegates.PyLong_FromUnsignedLong32(value); - + internal static IntPtr PyLong_FromUnsignedLong64(ulong value) => Delegates.PyLong_FromUnsignedLong64(value); internal static IntPtr PyLong_FromUnsignedLong(object value) @@ -1250,16 +1246,16 @@ internal static IntPtr PyLong_FromUnsignedLong(object value) return PyLong_FromUnsignedLong64(Convert.ToUInt64(value)); } - + internal static IntPtr PyLong_FromDouble(double value) => Delegates.PyLong_FromDouble(value); - + internal static IntPtr PyLong_FromLongLong(long value) => Delegates.PyLong_FromLongLong(value); - + internal static IntPtr PyLong_FromUnsignedLongLong(ulong value) => Delegates.PyLong_FromUnsignedLongLong(value); - + internal static IntPtr PyLong_FromString(string value, IntPtr end, int radix) { using var valPtr = new StrPtr(value, Encoding.UTF8); @@ -1267,11 +1263,11 @@ internal static IntPtr PyLong_FromString(string value, IntPtr end, int radix) } - + internal static nuint PyLong_AsUnsignedSize_t(IntPtr value) => Delegates.PyLong_AsUnsignedSize_t(value); - + internal static nint PyLong_AsSignedSize_t(IntPtr value) => Delegates.PyLong_AsSignedSize_t(new BorrowedReference(value)); - + internal static nint PyLong_AsSignedSize_t(BorrowedReference value) => Delegates.PyLong_AsSignedSize_t(value); /// @@ -1282,7 +1278,7 @@ internal static IntPtr PyLong_FromString(string value, IntPtr end, int radix) /// In most cases you need to check that value is an instance of PyLongObject /// before using this function using . /// - + internal static long PyExplicitlyConvertToInt64(IntPtr value) => Delegates.PyExplicitlyConvertToInt64(value); internal static ulong PyLong_AsUnsignedLongLong(IntPtr value) => Delegates.PyLong_AsUnsignedLongLong(value); @@ -1301,91 +1297,91 @@ internal static bool PyFloat_Check(IntPtr ob) /// /// Convert a Python integer pylong to a C void pointer. If pylong cannot be converted, an OverflowError will be raised. This is only assured to produce a usable void pointer for values created with PyLong_FromVoidPtr(). /// - + internal static IntPtr PyLong_AsVoidPtr(BorrowedReference ob) => Delegates.PyLong_AsVoidPtr(ob); - + internal static IntPtr PyFloat_FromDouble(double value) => Delegates.PyFloat_FromDouble(value); - + internal static NewReference PyFloat_FromString(BorrowedReference value) => Delegates.PyFloat_FromString(value); - + internal static double PyFloat_AsDouble(IntPtr ob) => Delegates.PyFloat_AsDouble(ob); - + internal static IntPtr PyNumber_Add(IntPtr o1, IntPtr o2) => Delegates.PyNumber_Add(o1, o2); - + internal static IntPtr PyNumber_Subtract(IntPtr o1, IntPtr o2) => Delegates.PyNumber_Subtract(o1, o2); - + internal static IntPtr PyNumber_Multiply(IntPtr o1, IntPtr o2) => Delegates.PyNumber_Multiply(o1, o2); - + internal static IntPtr PyNumber_TrueDivide(IntPtr o1, IntPtr o2) => Delegates.PyNumber_TrueDivide(o1, o2); - + internal static IntPtr PyNumber_And(IntPtr o1, IntPtr o2) => Delegates.PyNumber_And(o1, o2); - + internal static IntPtr PyNumber_Xor(IntPtr o1, IntPtr o2) => Delegates.PyNumber_Xor(o1, o2); - + internal static IntPtr PyNumber_Or(IntPtr o1, IntPtr o2) => Delegates.PyNumber_Or(o1, o2); - + internal static IntPtr PyNumber_Lshift(IntPtr o1, IntPtr o2) => Delegates.PyNumber_Lshift(o1, o2); - + internal static IntPtr PyNumber_Rshift(IntPtr o1, IntPtr o2) => Delegates.PyNumber_Rshift(o1, o2); - + internal static IntPtr PyNumber_Power(IntPtr o1, IntPtr o2) => Delegates.PyNumber_Power(o1, o2); - + internal static IntPtr PyNumber_Remainder(IntPtr o1, IntPtr o2) => Delegates.PyNumber_Remainder(o1, o2); - + internal static IntPtr PyNumber_InPlaceAdd(IntPtr o1, IntPtr o2) => Delegates.PyNumber_InPlaceAdd(o1, o2); - + internal static IntPtr PyNumber_InPlaceSubtract(IntPtr o1, IntPtr o2) => Delegates.PyNumber_InPlaceSubtract(o1, o2); - + internal static IntPtr PyNumber_InPlaceMultiply(IntPtr o1, IntPtr o2) => Delegates.PyNumber_InPlaceMultiply(o1, o2); - + internal static IntPtr PyNumber_InPlaceTrueDivide(IntPtr o1, IntPtr o2) => Delegates.PyNumber_InPlaceTrueDivide(o1, o2); - + internal static IntPtr PyNumber_InPlaceAnd(IntPtr o1, IntPtr o2) => Delegates.PyNumber_InPlaceAnd(o1, o2); - + internal static IntPtr PyNumber_InPlaceXor(IntPtr o1, IntPtr o2) => Delegates.PyNumber_InPlaceXor(o1, o2); - + internal static IntPtr PyNumber_InPlaceOr(IntPtr o1, IntPtr o2) => Delegates.PyNumber_InPlaceOr(o1, o2); - + internal static IntPtr PyNumber_InPlaceLshift(IntPtr o1, IntPtr o2) => Delegates.PyNumber_InPlaceLshift(o1, o2); - + internal static IntPtr PyNumber_InPlaceRshift(IntPtr o1, IntPtr o2) => Delegates.PyNumber_InPlaceRshift(o1, o2); - + internal static IntPtr PyNumber_InPlacePower(IntPtr o1, IntPtr o2) => Delegates.PyNumber_InPlacePower(o1, o2); - + internal static IntPtr PyNumber_InPlaceRemainder(IntPtr o1, IntPtr o2) => Delegates.PyNumber_InPlaceRemainder(o1, o2); - + internal static IntPtr PyNumber_Negative(IntPtr o1) => Delegates.PyNumber_Negative(o1); - + internal static IntPtr PyNumber_Positive(IntPtr o1) => Delegates.PyNumber_Positive(o1); - + internal static IntPtr PyNumber_Invert(IntPtr o1) => Delegates.PyNumber_Invert(o1); @@ -1393,9 +1389,9 @@ internal static bool PyFloat_Check(IntPtr ob) // Python sequence API //==================================================================== - + internal static bool PySequence_Check(IntPtr pointer) => Delegates.PySequence_Check(pointer); - + internal static NewReference PySequence_GetItem(BorrowedReference pointer, nint index) => Delegates.PySequence_GetItem(pointer, index); internal static int PySequence_SetItem(IntPtr pointer, long index, IntPtr value) @@ -1403,7 +1399,7 @@ internal static int PySequence_SetItem(IntPtr pointer, long index, IntPtr value) return PySequence_SetItem(pointer, new IntPtr(index), value); } - + private static int PySequence_SetItem(IntPtr pointer, IntPtr index, IntPtr value) => Delegates.PySequence_SetItem(pointer, index, value); internal static int PySequence_DelItem(IntPtr pointer, long index) @@ -1411,7 +1407,7 @@ internal static int PySequence_DelItem(IntPtr pointer, long index) return PySequence_DelItem(pointer, new IntPtr(index)); } - + private static int PySequence_DelItem(IntPtr pointer, IntPtr index) => Delegates.PySequence_DelItem(pointer, index); internal static IntPtr PySequence_GetSlice(IntPtr pointer, long i1, long i2) @@ -1419,7 +1415,7 @@ internal static IntPtr PySequence_GetSlice(IntPtr pointer, long i1, long i2) return PySequence_GetSlice(pointer, new IntPtr(i1), new IntPtr(i2)); } - + private static IntPtr PySequence_GetSlice(IntPtr pointer, IntPtr i1, IntPtr i2) => Delegates.PySequence_GetSlice(pointer, i1, i2); internal static int PySequence_SetSlice(IntPtr pointer, long i1, long i2, IntPtr v) @@ -1427,7 +1423,7 @@ internal static int PySequence_SetSlice(IntPtr pointer, long i1, long i2, IntPtr return PySequence_SetSlice(pointer, new IntPtr(i1), new IntPtr(i2), v); } - + private static int PySequence_SetSlice(IntPtr pointer, IntPtr i1, IntPtr i2, IntPtr v) => Delegates.PySequence_SetSlice(pointer, i1, i2, v); internal static int PySequence_DelSlice(IntPtr pointer, long i1, long i2) @@ -1435,17 +1431,17 @@ internal static int PySequence_DelSlice(IntPtr pointer, long i1, long i2) return PySequence_DelSlice(pointer, new IntPtr(i1), new IntPtr(i2)); } - + private static int PySequence_DelSlice(IntPtr pointer, IntPtr i1, IntPtr i2) => Delegates.PySequence_DelSlice(pointer, i1, i2); [Obsolete] internal static nint PySequence_Size(IntPtr pointer) => PySequence_Size(new BorrowedReference(pointer)); internal static nint PySequence_Size(BorrowedReference pointer) => Delegates.PySequence_Size(pointer); - + internal static int PySequence_Contains(IntPtr pointer, IntPtr item) => Delegates.PySequence_Contains(pointer, item); - + internal static IntPtr PySequence_Concat(IntPtr pointer, IntPtr other) => Delegates.PySequence_Concat(pointer, other); internal static IntPtr PySequence_Repeat(IntPtr pointer, long count) @@ -1453,10 +1449,10 @@ internal static IntPtr PySequence_Repeat(IntPtr pointer, long count) return PySequence_Repeat(pointer, new IntPtr(count)); } - + private static IntPtr PySequence_Repeat(IntPtr pointer, IntPtr count) => Delegates.PySequence_Repeat(pointer, count); - + internal static int PySequence_Index(IntPtr pointer, IntPtr item) => Delegates.PySequence_Index(pointer, item); internal static long PySequence_Count(IntPtr pointer, IntPtr value) @@ -1464,13 +1460,13 @@ internal static long PySequence_Count(IntPtr pointer, IntPtr value) return (long)_PySequence_Count(pointer, value); } - + private static IntPtr _PySequence_Count(IntPtr pointer, IntPtr value) => Delegates._PySequence_Count(pointer, value); - + internal static IntPtr PySequence_Tuple(IntPtr pointer) => Delegates.PySequence_Tuple(pointer); - + internal static IntPtr PySequence_List(IntPtr pointer) => Delegates.PySequence_List(pointer); @@ -1500,7 +1496,7 @@ internal static IntPtr PyString_FromString(string value) return PyUnicode_FromKindAndData(2, (IntPtr)ptr, value.Length); } - + internal static IntPtr EmptyPyBytes() { byte* bytes = stackalloc byte[1]; @@ -1513,7 +1509,7 @@ internal static long PyBytes_Size(IntPtr op) return (long)_PyBytes_Size(op); } - + private static IntPtr _PyBytes_Size(IntPtr op) => Delegates._PyBytes_Size(op); internal static IntPtr PyBytes_AS_STRING(IntPtr ob) @@ -1527,10 +1523,10 @@ internal static IntPtr PyUnicode_FromStringAndSize(IntPtr value, long size) return PyUnicode_FromStringAndSize(value, new IntPtr(size)); } - + private static IntPtr PyUnicode_FromStringAndSize(IntPtr value, IntPtr size) => Delegates.PyUnicode_FromStringAndSize(value, size); - + internal static IntPtr PyUnicode_AsUTF8(IntPtr unicode) => Delegates.PyUnicode_AsUTF8(unicode); internal static bool PyUnicode_Check(IntPtr ob) @@ -1538,10 +1534,10 @@ internal static bool PyUnicode_Check(IntPtr ob) return PyObject_TYPE(ob) == PyUnicodeType; } - + internal static IntPtr PyUnicode_FromObject(IntPtr ob) => Delegates.PyUnicode_FromObject(ob); - + internal static IntPtr PyUnicode_FromEncodedObject(IntPtr ob, IntPtr enc, IntPtr err) => Delegates.PyUnicode_FromEncodedObject(ob, enc, err); internal static IntPtr PyUnicode_FromKindAndData(int kind, IntPtr s, long size) @@ -1549,7 +1545,7 @@ internal static IntPtr PyUnicode_FromKindAndData(int kind, IntPtr s, long size) return PyUnicode_FromKindAndData(kind, s, new IntPtr(size)); } - + private static IntPtr PyUnicode_FromKindAndData(int kind, IntPtr s, IntPtr size) => Delegates.PyUnicode_FromKindAndData(kind, s, size); @@ -1559,7 +1555,7 @@ internal static IntPtr PyUnicode_FromUnicode(string s, long size) return PyUnicode_FromKindAndData(2, (IntPtr)ptr, size); } - + internal static int PyUnicode_GetMax() => Delegates.PyUnicode_GetMax(); internal static long PyUnicode_GetSize(IntPtr ob) @@ -1567,10 +1563,10 @@ internal static long PyUnicode_GetSize(IntPtr ob) return (long)_PyUnicode_GetSize(ob); } - + private static IntPtr _PyUnicode_GetSize(IntPtr ob) => Delegates._PyUnicode_GetSize(ob); - + internal static IntPtr PyUnicode_AsUnicode(IntPtr ob) => Delegates.PyUnicode_AsUnicode(ob); internal static NewReference PyUnicode_AsUTF16String(BorrowedReference ob) => Delegates.PyUnicode_AsUTF16String(ob); @@ -1583,7 +1579,7 @@ internal static IntPtr PyUnicode_FromString(string s) return PyUnicode_FromUnicode(s, s.Length); } - + internal static IntPtr PyUnicode_InternFromString(string s) { using var ptr = new StrPtr(s, Encoding.UTF8); @@ -1634,13 +1630,13 @@ internal static bool PyDict_Check(IntPtr ob) return PyObject_TYPE(ob) == PyDictType; } - + internal static IntPtr PyDict_New() => Delegates.PyDict_New(); - + internal static int PyDict_Next(IntPtr p, out IntPtr ppos, out IntPtr pkey, out IntPtr pvalue) => Delegates.PyDict_Next(p, out ppos, out pkey, out pvalue); - + internal static IntPtr PyDictProxy_New(IntPtr dict) => Delegates.PyDictProxy_New(dict); /// @@ -1694,7 +1690,7 @@ internal static int PyDict_SetItemString(BorrowedReference dict, string key, Bor internal static int PyDict_DelItem(BorrowedReference pointer, BorrowedReference key) => Delegates.PyDict_DelItem(pointer, key); - + internal static int PyDict_DelItemString(BorrowedReference pointer, string key) { using var keyPtr = new StrPtr(key, Encoding.UTF8); @@ -1710,19 +1706,19 @@ internal static IntPtr PyDict_Keys(IntPtr pointer) .DangerousMoveToPointerOrNull(); internal static NewReference PyDict_Keys(BorrowedReference pointer) => Delegates.PyDict_Keys(pointer); - + internal static IntPtr PyDict_Values(IntPtr pointer) => Delegates.PyDict_Values(pointer); - + internal static NewReference PyDict_Items(BorrowedReference pointer) => Delegates.PyDict_Items(pointer); - + internal static IntPtr PyDict_Copy(IntPtr pointer) => Delegates.PyDict_Copy(pointer); - + internal static int PyDict_Update(BorrowedReference pointer, BorrowedReference other) => Delegates.PyDict_Update(pointer, other); - + internal static void PyDict_Clear(IntPtr pointer) => Delegates.PyDict_Clear(pointer); internal static long PyDict_Size(IntPtr pointer) @@ -1730,19 +1726,19 @@ internal static long PyDict_Size(IntPtr pointer) return (long)_PyDict_Size(pointer); } - + internal static IntPtr _PyDict_Size(IntPtr pointer) => Delegates._PyDict_Size(pointer); internal static NewReference PySet_New(BorrowedReference iterable) => Delegates.PySet_New(iterable); - + internal static int PySet_Add(BorrowedReference set, BorrowedReference key) => Delegates.PySet_Add(set, key); /// /// Return 1 if found, 0 if not found, and -1 if an error is encountered. /// - + internal static int PySet_Contains(BorrowedReference anyset, BorrowedReference key) => Delegates.PySet_Contains(anyset, key); //==================================================================== @@ -1759,10 +1755,10 @@ internal static IntPtr PyList_New(long size) return PyList_New(new IntPtr(size)); } - + private static IntPtr PyList_New(IntPtr size) => Delegates.PyList_New(size); - + internal static IntPtr PyList_AsTuple(IntPtr pointer) => Delegates.PyList_AsTuple(pointer); internal static BorrowedReference PyList_GetItem(BorrowedReference pointer, long index) @@ -1770,7 +1766,7 @@ internal static BorrowedReference PyList_GetItem(BorrowedReference pointer, long return PyList_GetItem(pointer, new IntPtr(index)); } - + private static BorrowedReference PyList_GetItem(BorrowedReference pointer, IntPtr index) => Delegates.PyList_GetItem(pointer, index); internal static int PyList_SetItem(IntPtr pointer, long index, IntPtr value) @@ -1778,7 +1774,7 @@ internal static int PyList_SetItem(IntPtr pointer, long index, IntPtr value) return PyList_SetItem(pointer, new IntPtr(index), value); } - + private static int PyList_SetItem(IntPtr pointer, IntPtr index, IntPtr value) => Delegates.PyList_SetItem(pointer, index, value); internal static int PyList_Insert(BorrowedReference pointer, long index, IntPtr value) @@ -1786,16 +1782,16 @@ internal static int PyList_Insert(BorrowedReference pointer, long index, IntPtr return PyList_Insert(pointer, new IntPtr(index), value); } - + private static int PyList_Insert(BorrowedReference pointer, IntPtr index, IntPtr value) => Delegates.PyList_Insert(pointer, index, value); - + internal static int PyList_Append(BorrowedReference pointer, IntPtr value) => Delegates.PyList_Append(pointer, value); - + internal static int PyList_Reverse(BorrowedReference pointer) => Delegates.PyList_Reverse(pointer); - + internal static int PyList_Sort(BorrowedReference pointer) => Delegates.PyList_Sort(pointer); internal static IntPtr PyList_GetSlice(IntPtr pointer, long start, long end) @@ -1803,7 +1799,7 @@ internal static IntPtr PyList_GetSlice(IntPtr pointer, long start, long end) return PyList_GetSlice(pointer, new IntPtr(start), new IntPtr(end)); } - + private static IntPtr PyList_GetSlice(IntPtr pointer, IntPtr start, IntPtr end) => Delegates.PyList_GetSlice(pointer, start, end); internal static int PyList_SetSlice(IntPtr pointer, long start, long end, IntPtr value) @@ -1811,10 +1807,10 @@ internal static int PyList_SetSlice(IntPtr pointer, long start, long end, IntPtr return PyList_SetSlice(pointer, new IntPtr(start), new IntPtr(end), value); } - + private static int PyList_SetSlice(IntPtr pointer, IntPtr start, IntPtr end, IntPtr value) => Delegates.PyList_SetSlice(pointer, start, end, value); - + internal static nint PyList_Size(BorrowedReference pointer) => Delegates.PyList_Size(pointer); //==================================================================== @@ -1835,7 +1831,7 @@ internal static IntPtr PyTuple_New(long size) return PyTuple_New(new IntPtr(size)); } - + private static IntPtr PyTuple_New(IntPtr size) => Delegates.PyTuple_New(size); internal static BorrowedReference PyTuple_GetItem(BorrowedReference pointer, long index) @@ -1846,7 +1842,7 @@ internal static IntPtr PyTuple_GetItem(IntPtr pointer, long index) .DangerousGetAddressOrNull(); } - + private static BorrowedReference PyTuple_GetItem(BorrowedReference pointer, IntPtr index) => Delegates.PyTuple_GetItem(pointer, index); internal static int PyTuple_SetItem(IntPtr pointer, long index, IntPtr value) @@ -1854,7 +1850,7 @@ internal static int PyTuple_SetItem(IntPtr pointer, long index, IntPtr value) return PyTuple_SetItem(pointer, new IntPtr(index), value); } - + private static int PyTuple_SetItem(IntPtr pointer, IntPtr index, IntPtr value) => Delegates.PyTuple_SetItem(pointer, index, value); internal static IntPtr PyTuple_GetSlice(IntPtr pointer, long start, long end) @@ -1862,7 +1858,7 @@ internal static IntPtr PyTuple_GetSlice(IntPtr pointer, long start, long end) return PyTuple_GetSlice(pointer, new IntPtr(start), new IntPtr(end)); } - + private static IntPtr PyTuple_GetSlice(IntPtr pointer, IntPtr start, IntPtr end) => Delegates.PyTuple_GetSlice(pointer, start, end); @@ -1881,7 +1877,7 @@ internal static bool PyIter_Check(IntPtr pointer) return tp_iternext != IntPtr.Zero && tp_iternext != _PyObject_NextNotImplemented; } - + internal static IntPtr PyIter_Next(IntPtr pointer) => Delegates.PyIter_Next(new BorrowedReference(pointer)).DangerousMoveToPointerOrNull(); internal static NewReference PyIter_Next(BorrowedReference pointer) => Delegates.PyIter_Next(pointer); @@ -1891,7 +1887,7 @@ internal static IntPtr PyIter_Next(IntPtr pointer) // Python module API //==================================================================== - + internal static NewReference PyModule_New(string name) { using var namePtr = new StrPtr(name, Encoding.UTF8); @@ -1903,18 +1899,18 @@ internal static string PyModule_GetName(IntPtr module) internal static BorrowedReference PyModule_GetDict(BorrowedReference module) => Delegates.PyModule_GetDict(module); - + internal static string PyModule_GetFilename(IntPtr module) => Delegates.PyModule_GetFilename(module).ToString(Encoding.UTF8); #if PYTHON_WITH_PYDEBUG [DllImport(_PythonDll, EntryPoint = "PyModule_Create2TraceRefs", CallingConvention = CallingConvention.Cdecl)] #else - + #endif internal static IntPtr PyModule_Create2(IntPtr module, int apiver) => Delegates.PyModule_Create2(module, apiver); - + internal static IntPtr PyImport_Import(IntPtr name) => Delegates.PyImport_Import(name); /// @@ -1929,7 +1925,7 @@ internal static IntPtr PyImport_ImportModule(string name) internal static IntPtr PyImport_ReloadModule(IntPtr module) => Delegates.PyImport_ReloadModule(module); - + internal static BorrowedReference PyImport_AddModule(string name) { using var namePtr = new StrPtr(name, Encoding.UTF8); @@ -1938,7 +1934,7 @@ internal static BorrowedReference PyImport_AddModule(string name) internal static BorrowedReference PyImport_GetModuleDict() => Delegates.PyImport_GetModuleDict(); - + internal static void PySys_SetArgvEx(int argc, string[] argv, int updatepath) { var marshaler = StrArrayMarshaler.GetInstance(null); @@ -1979,7 +1975,7 @@ internal static bool PyType_Check(IntPtr ob) return PyObject_TypeCheck(ob, PyTypeType); } - + internal static void PyType_Modified(IntPtr type) => Delegates.PyType_Modified(type); internal static bool PyType_IsSubtype(BorrowedReference t1, IntPtr ofType) => PyType_IsSubtype(t1, new BorrowedReference(ofType)); @@ -2000,7 +1996,7 @@ internal static bool PyType_IsSameAsOrSubtype(BorrowedReference type, BorrowedRe return (type == ofType) || PyType_IsSubtype(type, ofType); } - + internal static IntPtr PyType_GenericNew(IntPtr type, IntPtr args, IntPtr kw) => Delegates.PyType_GenericNew(type, args, kw); internal static IntPtr PyType_GenericAlloc(IntPtr type, long n) @@ -2008,37 +2004,37 @@ internal static IntPtr PyType_GenericAlloc(IntPtr type, long n) return PyType_GenericAlloc(type, new IntPtr(n)); } - + private static IntPtr PyType_GenericAlloc(IntPtr type, IntPtr n) => Delegates.PyType_GenericAlloc(type, n); /// /// Finalize a type object. This should be called on all type objects to finish their initialization. This function is responsible for adding inherited slots from a type’s base class. Return 0 on success, or return -1 and sets an exception on error. /// - + internal static int PyType_Ready(IntPtr type) => Delegates.PyType_Ready(type); - + internal static IntPtr _PyType_Lookup(IntPtr type, IntPtr name) => Delegates._PyType_Lookup(type, name); - + internal static IntPtr PyObject_GenericGetAttr(IntPtr obj, IntPtr name) => Delegates.PyObject_GenericGetAttr(obj, name); - + internal static int PyObject_GenericSetAttr(IntPtr obj, IntPtr name, IntPtr value) => Delegates.PyObject_GenericSetAttr(obj, name, value); - + internal static BorrowedReference* _PyObject_GetDictPtr(BorrowedReference obj) => Delegates._PyObject_GetDictPtr(obj); - + internal static void PyObject_GC_Del(IntPtr tp) => Delegates.PyObject_GC_Del(tp); - + internal static void PyObject_GC_Track(IntPtr tp) => Delegates.PyObject_GC_Track(tp); - + internal static void PyObject_GC_UnTrack(IntPtr tp) => Delegates.PyObject_GC_UnTrack(tp); - + internal static void _PyObject_Dump(IntPtr ob) => Delegates._PyObject_Dump(ob); //==================================================================== @@ -2050,7 +2046,7 @@ internal static IntPtr PyMem_Malloc(long size) return PyMem_Malloc(new IntPtr(size)); } - + private static IntPtr PyMem_Malloc(IntPtr size) => Delegates.PyMem_Malloc(size); internal static IntPtr PyMem_Realloc(IntPtr ptr, long size) @@ -2058,10 +2054,10 @@ internal static IntPtr PyMem_Realloc(IntPtr ptr, long size) return PyMem_Realloc(ptr, new IntPtr(size)); } - + private static IntPtr PyMem_Realloc(IntPtr ptr, IntPtr size) => Delegates.PyMem_Realloc(ptr, size); - + internal static void PyMem_Free(IntPtr ptr) => Delegates.PyMem_Free(ptr); @@ -2069,7 +2065,7 @@ internal static IntPtr PyMem_Realloc(IntPtr ptr, long size) // Python exception API //==================================================================== - + internal static void PyErr_SetString(IntPtr ob, string message) { using var msgPtr = new StrPtr(message, Encoding.UTF8); @@ -2078,40 +2074,40 @@ internal static void PyErr_SetString(IntPtr ob, string message) internal static void PyErr_SetObject(BorrowedReference type, BorrowedReference exceptionObject) => Delegates.PyErr_SetObject(type, exceptionObject); - + internal static IntPtr PyErr_SetFromErrno(IntPtr ob) => Delegates.PyErr_SetFromErrno(ob); - + internal static void PyErr_SetNone(IntPtr ob) => Delegates.PyErr_SetNone(ob); - + internal static int PyErr_ExceptionMatches(IntPtr exception) => Delegates.PyErr_ExceptionMatches(exception); - + internal static int PyErr_GivenExceptionMatches(IntPtr ob, IntPtr val) => Delegates.PyErr_GivenExceptionMatches(ob, val); - + internal static void PyErr_NormalizeException(ref IntPtr ob, ref IntPtr val, ref IntPtr tb) => Delegates.PyErr_NormalizeException(ref ob, ref val, ref tb); - + internal static IntPtr PyErr_Occurred() => Delegates.PyErr_Occurred(); - + internal static void PyErr_Fetch(out IntPtr ob, out IntPtr val, out IntPtr tb) => Delegates.PyErr_Fetch(out ob, out val, out tb); - + internal static void PyErr_Restore(IntPtr ob, IntPtr val, IntPtr tb) => Delegates.PyErr_Restore(ob, val, tb); - + internal static void PyErr_Clear() => Delegates.PyErr_Clear(); - + internal static void PyErr_Print() => Delegates.PyErr_Print(); /// /// Set the cause associated with the exception to cause. Use NULL to clear it. There is no type check to make sure that cause is either an exception instance or None. This steals a reference to cause. /// - + internal static void PyException_SetCause(IntPtr ex, IntPtr cause) => Delegates.PyException_SetCause(ex, cause); //==================================================================== @@ -2121,7 +2117,7 @@ internal static void PyErr_SetString(IntPtr ob, string message) internal static NewReference PyCell_Get(BorrowedReference cell) => Delegates.PyCell_Get(cell); - + internal static int PyCell_Set(BorrowedReference cell, IntPtr value) => Delegates.PyCell_Set(cell, value); //==================================================================== @@ -2134,7 +2130,7 @@ internal static void PyErr_SetString(IntPtr ob, string message) internal const long _PyGC_REFS_TENTATIVELY_UNREACHABLE = -4; - + internal static IntPtr PyGC_Collect() => Delegates.PyGC_Collect(); internal static IntPtr _Py_AS_GC(BorrowedReference ob) @@ -2196,18 +2192,18 @@ internal static IntPtr PyCapsule_GetPointer(BorrowedReference capsule, IntPtr na // Miscellaneous //==================================================================== - + internal static IntPtr PyMethod_Self(IntPtr ob) => Delegates.PyMethod_Self(ob); - + internal static IntPtr PyMethod_Function(IntPtr ob) => Delegates.PyMethod_Function(ob); - + internal static int Py_AddPendingCall(IntPtr func, IntPtr arg) => Delegates.Py_AddPendingCall(func, arg); - + internal static int PyThreadState_SetAsyncExcLLP64(uint id, IntPtr exc) => Delegates.PyThreadState_SetAsyncExcLLP64(id, exc); - + internal static int PyThreadState_SetAsyncExcLP64(ulong id, IntPtr exc) => Delegates.PyThreadState_SetAsyncExcLP64(id, exc); @@ -2789,6 +2785,7 @@ public enum ShutdownMode Normal, Soft, Reload, + Extension, } diff --git a/src/tests/test_method.py b/src/tests/test_method.py index 2826ad467..9bdb571c0 100644 --- a/src/tests/test_method.py +++ b/src/tests/test_method.py @@ -899,9 +899,6 @@ def test_object_in_multiparam_exception(): c = e.__cause__ assert c.GetType().FullName == 'System.AggregateException' assert len(c.InnerExceptions) == 2 - message = 'One or more errors occurred.' - s = str(c) - assert s[0:len(message)] == message def test_case_sensitive(): """Test that case-sensitivity is respected. GH#81""" diff --git a/src/tests/test_sysargv.py b/src/tests/test_sysargv.py index dd62bc632..d856ec902 100644 --- a/src/tests/test_sysargv.py +++ b/src/tests/test_sysargv.py @@ -2,6 +2,7 @@ import sys from subprocess import check_output +from ast import literal_eval def test_sys_argv_state(filepath): @@ -11,5 +12,5 @@ def test_sys_argv_state(filepath): script = filepath("argv-fixture.py") out = check_output([sys.executable, script, "foo", "bar"]) - assert b"foo" in out - assert b"bar" in out + out = literal_eval(out.decode("ascii")) + assert out[-2:] == ["foo", "bar"] diff --git a/tools/geninterop/geninterop.py b/tools/geninterop/geninterop.py index 0d5b83b30..0c80c1904 100644 --- a/tools/geninterop/geninterop.py +++ b/tools/geninterop/geninterop.py @@ -225,8 +225,6 @@ def preprocess_python_headers(): if hasattr(sys, "abiflags"): if "d" in sys.abiflags: defines.extend(("-D", "PYTHON_WITH_PYDEBUG")) - if "m" in sys.abiflags: - defines.extend(("-D", "PYTHON_WITH_PYMALLOC")) if "u" in sys.abiflags: defines.extend(("-D", "PYTHON_WITH_WIDE_UNICODE")) @@ -245,7 +243,7 @@ def preprocess_python_headers(): def gen_interop_head(writer): filename = os.path.basename(__file__) - abi_flags = getattr(sys, "abiflags", "") + abi_flags = getattr(sys, "abiflags", "").replace("m", "") py_ver = "{0}.{1}".format(PY_MAJOR, PY_MINOR) class_definition = """ // Auto-generated by %s. From fdb71447a4374f54dc0c346474b90c55e5796e25 Mon Sep 17 00:00:00 2001 From: Benedikt Reinartz Date: Sun, 14 Feb 2021 12:40:54 +0100 Subject: [PATCH 2/9] Add Changelog entry --- CHANGELOG.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 1a1fd340e..9bee653e8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -37,6 +37,8 @@ when .NET expects an integer [#1342][i1342] - BREAKING: to call Python from .NET `Runtime.PythonDLL` property must be set to Python DLL name or the DLL must be loaded in advance. This must be done before calling any other Python.NET functions. - Sign Runtime DLL with a strong name +- Implement loading through `clr_loader` instead of the included `ClrModule`, enables + support for .NET Core ### Fixed From f01a78c6fd7bb34c2461e803e6ee1d757e17740b Mon Sep 17 00:00:00 2001 From: Benedikt Reinartz Date: Sat, 13 Feb 2021 13:11:06 +0100 Subject: [PATCH 3/9] Fix domain reload tests and activate them on macOS --- src/domain_tests/test_domain_reload.py | 31 +++++++++----------------- 1 file changed, 10 insertions(+), 21 deletions(-) diff --git a/src/domain_tests/test_domain_reload.py b/src/domain_tests/test_domain_reload.py index fa6f42b9e..a7cd2fa4d 100644 --- a/src/domain_tests/test_domain_reload.py +++ b/src/domain_tests/test_domain_reload.py @@ -4,6 +4,12 @@ import pytest +from pythonnet.find_libpython import find_libpython +libpython = find_libpython() + +pytestmark = pytest.mark.xfail(libpython is None, reason="Can't find suitable libpython") + + def _run_test(testname): dirname = os.path.split(__file__)[0] exename = os.path.join(dirname, 'bin', 'Python.DomainReloadTests.exe') @@ -12,90 +18,73 @@ def _run_test(testname): if platform.system() != 'Windows': args = ['mono'] + args - proc = subprocess.Popen(args) + env = os.environ.copy() + env["PYTHONNET_PYDLL"] = libpython + + proc = subprocess.Popen(args, env=env) proc.wait() assert proc.returncode == 0 -@pytest.mark.skipif(platform.system() == 'Darwin', reason='FIXME: macos can\'t find the python library') def test_rename_class(): _run_test('class_rename') -@pytest.mark.skipif(platform.system() == 'Darwin', reason='FIXME: macos can\'t find the python library') def test_rename_class_member_static_function(): _run_test('static_member_rename') -@pytest.mark.skipif(platform.system() == 'Darwin', reason='FIXME: macos can\'t find the python library') def test_rename_class_member_function(): _run_test('member_rename') -@pytest.mark.skipif(platform.system() == 'Darwin', reason='FIXME: macos can\'t find the python library') def test_rename_class_member_field(): _run_test('field_rename') -@pytest.mark.skipif(platform.system() == 'Darwin', reason='FIXME: macos can\'t find the python library') def test_rename_class_member_property(): _run_test('property_rename') -@pytest.mark.skipif(platform.system() == 'Darwin', reason='FIXME: macos can\'t find the python library') def test_rename_namespace(): _run_test('namespace_rename') -@pytest.mark.skipif(platform.system() == 'Darwin', reason='FIXME: macos can\'t find the python library') def test_field_visibility_change(): _run_test("field_visibility_change") -@pytest.mark.skipif(platform.system() == 'Darwin', reason='FIXME: macos can\'t find the python library') def test_method_visibility_change(): _run_test("method_visibility_change") -@pytest.mark.skipif(platform.system() == 'Darwin', reason='FIXME: macos can\'t find the python library') def test_property_visibility_change(): _run_test("property_visibility_change") -@pytest.mark.skipif(platform.system() == 'Darwin', reason='FIXME: macos can\'t find the python library') def test_class_visibility_change(): _run_test("class_visibility_change") @pytest.mark.skip(reason='FIXME: Domain reload fails when Python points to a .NET object which points back to Python objects') -@pytest.mark.skipif(platform.system() == 'Darwin', reason='FIXME: macos can\'t find the python library') def test_method_parameters_change(): _run_test("method_parameters_change") -@pytest.mark.skipif(platform.system() == 'Darwin', reason='FIXME: macos can\'t find the python library') def test_method_return_type_change(): _run_test("method_return_type_change") -@pytest.mark.skipif(platform.system() == 'Darwin', reason='FIXME: macos can\'t find the python library') def test_field_type_change(): _run_test("field_type_change") -@pytest.mark.skipif(platform.system() == 'Darwin', reason='FIXME: macos can\'t find the python library') @pytest.mark.xfail(reason="Events not yet serializable") def test_rename_event(): _run_test('event_rename') -@pytest.mark.skipif(platform.system() == 'Darwin', reason='FIXME: macos can\'t find the python library') @pytest.mark.xfail(reason="newly instanced object uses PyType_GenericAlloc") def test_construct_removed_class(): _run_test("construct_removed_class") -@pytest.mark.skipif(platform.system() == 'Darwin', reason='FIXME: macos can\'t find the python library') def test_out_to_ref_param(): _run_test("out_to_ref_param") -@pytest.mark.skipif(platform.system() == 'Darwin', reason='FIXME: macos can\'t find the python library') def test_ref_to_out_param(): _run_test("ref_to_out_param") -@pytest.mark.skipif(platform.system() == 'Darwin', reason='FIXME: macos can\'t find the python library') def test_ref_to_in_param(): _run_test("ref_to_in_param") -@pytest.mark.skipif(platform.system() == 'Darwin', reason='FIXME: macos can\'t find the python library') def test_in_to_ref_param(): _run_test("in_to_ref_param") -@pytest.mark.skipif(platform.system() == 'Darwin', reason='FIXME: macos can\'t find the python library') def test_nested_type(): _run_test("nested_type") From 0d7e43aedf07c7ac897c921cbdec00af15367b37 Mon Sep 17 00:00:00 2001 From: Benedikt Reinartz Date: Sat, 13 Feb 2021 23:07:28 +0100 Subject: [PATCH 4/9] Run tests on .NET Core --- .github/workflows/main.yml | 14 ++- pyproject.toml | 6 ++ setup.cfg | 4 - src/testing/Python.Test.csproj | 7 +- src/tests/conftest.py | 56 ---------- src/tests/fixtures/netstandard2.0/.gitkeep | 0 {src/tests => tests}/__init__.py | 0 {src/tests => tests}/_missing_import.py | 0 tests/conftest.py | 101 ++++++++++++++++++ {src/tests => tests}/fixtures/.gitkeep | 0 {src/tests => tests}/fixtures/argv-fixture.py | 0 {src/tests => tests}/importtest.py | 0 {src/tests => tests}/leaktest.py | 0 {src/tests => tests}/profile.py | 0 {src/tests => tests}/runtests.py | 0 {src/tests => tests}/stress.py | 0 {src/tests => tests}/stresstest.py | 0 {src/tests => tests}/test_array.py | 0 {src/tests => tests}/test_callback.py | 0 {src/tests => tests}/test_class.py | 0 {src/tests => tests}/test_clrmethod.py | 0 {src/tests => tests}/test_constructors.py | 0 {src/tests => tests}/test_conversion.py | 0 {src/tests => tests}/test_delegate.py | 0 {src/tests => tests}/test_docstring.py | 0 {src/tests => tests}/test_engine.py | 0 {src/tests => tests}/test_enum.py | 0 {src/tests => tests}/test_event.py | 0 {src/tests => tests}/test_exceptions.py | 0 {src/tests => tests}/test_field.py | 0 {src/tests => tests}/test_generic.py | 0 {src/tests => tests}/test_import.py | 0 {src/tests => tests}/test_indexer.py | 0 {src/tests => tests}/test_interface.py | 0 {src/tests => tests}/test_method.py | 0 {src/tests => tests}/test_module.py | 0 {src/tests => tests}/test_mp_length.py | 0 {src/tests => tests}/test_property.py | 0 {src/tests => tests}/test_recursive_types.py | 0 {src/tests => tests}/test_repr.py | 0 {src/tests => tests}/test_subclass.py | 0 {src/tests => tests}/test_sysargv.py | 0 {src/tests => tests}/test_thread.py | 0 {src/tests => tests}/tests.pyproj | 0 {src/tests => tests}/utils.py | 0 45 files changed, 120 insertions(+), 68 deletions(-) delete mode 100644 setup.cfg delete mode 100644 src/tests/conftest.py delete mode 100644 src/tests/fixtures/netstandard2.0/.gitkeep rename {src/tests => tests}/__init__.py (100%) rename {src/tests => tests}/_missing_import.py (100%) create mode 100644 tests/conftest.py rename {src/tests => tests}/fixtures/.gitkeep (100%) rename {src/tests => tests}/fixtures/argv-fixture.py (100%) rename {src/tests => tests}/importtest.py (100%) rename {src/tests => tests}/leaktest.py (100%) rename {src/tests => tests}/profile.py (100%) rename {src/tests => tests}/runtests.py (100%) rename {src/tests => tests}/stress.py (100%) rename {src/tests => tests}/stresstest.py (100%) rename {src/tests => tests}/test_array.py (100%) rename {src/tests => tests}/test_callback.py (100%) rename {src/tests => tests}/test_class.py (100%) rename {src/tests => tests}/test_clrmethod.py (100%) rename {src/tests => tests}/test_constructors.py (100%) rename {src/tests => tests}/test_conversion.py (100%) rename {src/tests => tests}/test_delegate.py (100%) rename {src/tests => tests}/test_docstring.py (100%) rename {src/tests => tests}/test_engine.py (100%) rename {src/tests => tests}/test_enum.py (100%) rename {src/tests => tests}/test_event.py (100%) rename {src/tests => tests}/test_exceptions.py (100%) rename {src/tests => tests}/test_field.py (100%) rename {src/tests => tests}/test_generic.py (100%) rename {src/tests => tests}/test_import.py (100%) rename {src/tests => tests}/test_indexer.py (100%) rename {src/tests => tests}/test_interface.py (100%) rename {src/tests => tests}/test_method.py (100%) rename {src/tests => tests}/test_module.py (100%) rename {src/tests => tests}/test_mp_length.py (100%) rename {src/tests => tests}/test_property.py (100%) rename {src/tests => tests}/test_recursive_types.py (100%) rename {src/tests => tests}/test_repr.py (100%) rename {src/tests => tests}/test_subclass.py (100%) rename {src/tests => tests}/test_sysargv.py (100%) rename {src/tests => tests}/test_thread.py (100%) rename {src/tests => tests}/tests.pyproj (100%) rename {src/tests => tests}/utils.py (100%) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 10959ea4f..2dd75c529 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -55,9 +55,17 @@ jobs: if: ${{ matrix.os == 'windows' }} run: | python -m pythonnet.find_libpython --export | Out-File -FilePath $env:GITHUB_ENV -Encoding utf8 -Append - - - name: Python Tests - run: pytest + + - name: Python Tests (Mono) + if: ${{ matrix.os != 'windows' }} + run: pytest --runtime mono + + - name: Python Tests (.NET Core) + run: pytest --runtime netcore + + - name: Python Tests (.NET Framework) + if: ${{ matrix.os == 'windows' }} + run: pytest --runtime netfx - name: Embedding tests run: dotnet test --runtime any-${{ matrix.platform }} src/embed_tests/ diff --git a/pyproject.toml b/pyproject.toml index 83a58d126..9bcf734c6 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,3 +1,9 @@ [build-system] requires = ["setuptools>=42", "wheel", "pycparser"] build-backend = "setuptools.build_meta" + +[tool.pytest.ini_options] +xfail_strict = true +testpaths = [ + "tests", +] diff --git a/setup.cfg b/setup.cfg deleted file mode 100644 index 19c6f9fc9..000000000 --- a/setup.cfg +++ /dev/null @@ -1,4 +0,0 @@ -[tool:pytest] -xfail_strict = True -# -r fsxX: show extra summary info for: (f)ailed, (s)kip, (x)failed, (X)passed -addopts = -r fsxX --color=yes --durations=5 diff --git a/src/testing/Python.Test.csproj b/src/testing/Python.Test.csproj index e6e11c1da..4b7e4d93b 100644 --- a/src/testing/Python.Test.csproj +++ b/src/testing/Python.Test.csproj @@ -1,14 +1,11 @@ - netstandard2.0 + netstandard2.0;net5.0 true + true - - - - diff --git a/src/tests/conftest.py b/src/tests/conftest.py deleted file mode 100644 index 17085e3ef..000000000 --- a/src/tests/conftest.py +++ /dev/null @@ -1,56 +0,0 @@ -# -*- coding: utf-8 -*- -# TODO: move tests one out of src to project root. -# TODO: travis has numpy on their workers. Maybe add tests? - -"""Helpers for testing.""" - -import ctypes -import os -import sys -import sysconfig - -import pytest - -# Add path for `Python.Test` -cwd = os.path.dirname(__file__) -fixtures_path = os.path.join(cwd, "fixtures") - -BUILD_TEST = True -if BUILD_TEST: - from subprocess import check_call - test_proj_path = os.path.join(cwd, "..", "testing") - check_call(["dotnet", "build", test_proj_path, "-o", fixtures_path]) - -sys.path.append(fixtures_path) - -import clr - -# Add References for tests -clr.AddReference("Python.Test") -clr.AddReference("System.Collections") -clr.AddReference("System.Data") - - -def pytest_report_header(config): - """Generate extra report headers""" - # FIXME: https://github.com/pytest-dev/pytest/issues/2257 - is_64bits = sys.maxsize > 2**32 - arch = "x64" if is_64bits else "x86" - ucs = ctypes.sizeof(ctypes.c_wchar) - libdir = sysconfig.get_config_var("LIBDIR") - shared = bool(sysconfig.get_config_var("Py_ENABLE_SHARED")) - - header = ("Arch: {arch}, UCS: {ucs}, LIBDIR: {libdir}, " - "Py_ENABLE_SHARED: {shared}".format(**locals())) - return header - - -@pytest.fixture() -def filepath(): - """Returns full filepath for file in `fixtures` directory.""" - - def make_filepath(filename): - # http://stackoverflow.com/questions/18011902/parameter-to-a-fixture - return os.path.join(fixtures_path, filename) - - return make_filepath diff --git a/src/tests/fixtures/netstandard2.0/.gitkeep b/src/tests/fixtures/netstandard2.0/.gitkeep deleted file mode 100644 index e69de29bb..000000000 diff --git a/src/tests/__init__.py b/tests/__init__.py similarity index 100% rename from src/tests/__init__.py rename to tests/__init__.py diff --git a/src/tests/_missing_import.py b/tests/_missing_import.py similarity index 100% rename from src/tests/_missing_import.py rename to tests/_missing_import.py diff --git a/tests/conftest.py b/tests/conftest.py new file mode 100644 index 000000000..aa57f2a1f --- /dev/null +++ b/tests/conftest.py @@ -0,0 +1,101 @@ +# -*- coding: utf-8 -*- +# TODO: move tests one out of src to project root. +# TODO: travis has numpy on their workers. Maybe add tests? + +"""Helpers for testing.""" + +import ctypes +import os +import sys +import sysconfig +from subprocess import check_call +from tempfile import mkdtemp +import shutil + +import pytest + +from pythonnet import set_runtime + +# Add path for `Python.Test` +cwd = os.path.dirname(__file__) +fixtures_path = os.path.join(cwd, "fixtures") +sys.path.append(fixtures_path) + +def pytest_addoption(parser): + parser.addoption( + "--runtime", + action="store", + default="default", + help="Must be one of default, netcore, netfx and mono" + ) + +def pytest_configure(config): + global bin_path + runtime_opt = config.getoption("runtime") + + test_proj_path = os.path.join(cwd, "..", "src", "testing") + + if runtime_opt not in ["netcore", "netfx", "mono", "default"]: + raise RuntimeError(f"Invalid runtime: {runtime_opt}") + + bin_path = mkdtemp() + + # tmpdir_factory.mktemp(f"pythonnet-{runtime_opt}") + + fw = "net5.0" if runtime_opt == "netcore" else "netstandard2.0" + + check_call(["dotnet", "publish", "-f", fw, "-o", bin_path, test_proj_path]) + + sys.path.append(bin_path) + + if runtime_opt == "default": + pass + elif runtime_opt == "netfx": + from clr_loader import get_netfx + runtime = get_netfx() + set_runtime(runtime) + elif runtime_opt == "mono": + from clr_loader import get_mono + runtime = get_mono() + set_runtime(runtime) + elif runtime_opt == "netcore": + from clr_loader import get_coreclr + rt_config_path = os.path.join(bin_path, "Python.Test.runtimeconfig.json") + runtime = get_coreclr(rt_config_path) + set_runtime(runtime) + + import clr + clr.AddReference("Python.Test") + clr.AddReference("System") + clr.AddReference("System.Collections") + clr.AddReference("System.Data") + clr.AddReference("System.Xml") + + +def pytest_unconfigure(config): + global bin_path + shutil.rmtree(bin_path) + +def pytest_report_header(config): + """Generate extra report headers""" + # FIXME: https://github.com/pytest-dev/pytest/issues/2257 + is_64bits = sys.maxsize > 2**32 + arch = "x64" if is_64bits else "x86" + ucs = ctypes.sizeof(ctypes.c_wchar) + libdir = sysconfig.get_config_var("LIBDIR") + shared = bool(sysconfig.get_config_var("Py_ENABLE_SHARED")) + + header = ("Arch: {arch}, UCS: {ucs}, LIBDIR: {libdir}, " + "Py_ENABLE_SHARED: {shared}".format(**locals())) + return header + + +@pytest.fixture() +def filepath(): + """Returns full filepath for file in `fixtures` directory.""" + + def make_filepath(filename): + # http://stackoverflow.com/questions/18011902/parameter-to-a-fixture + return os.path.join(fixtures_path, filename) + + return make_filepath diff --git a/src/tests/fixtures/.gitkeep b/tests/fixtures/.gitkeep similarity index 100% rename from src/tests/fixtures/.gitkeep rename to tests/fixtures/.gitkeep diff --git a/src/tests/fixtures/argv-fixture.py b/tests/fixtures/argv-fixture.py similarity index 100% rename from src/tests/fixtures/argv-fixture.py rename to tests/fixtures/argv-fixture.py diff --git a/src/tests/importtest.py b/tests/importtest.py similarity index 100% rename from src/tests/importtest.py rename to tests/importtest.py diff --git a/src/tests/leaktest.py b/tests/leaktest.py similarity index 100% rename from src/tests/leaktest.py rename to tests/leaktest.py diff --git a/src/tests/profile.py b/tests/profile.py similarity index 100% rename from src/tests/profile.py rename to tests/profile.py diff --git a/src/tests/runtests.py b/tests/runtests.py similarity index 100% rename from src/tests/runtests.py rename to tests/runtests.py diff --git a/src/tests/stress.py b/tests/stress.py similarity index 100% rename from src/tests/stress.py rename to tests/stress.py diff --git a/src/tests/stresstest.py b/tests/stresstest.py similarity index 100% rename from src/tests/stresstest.py rename to tests/stresstest.py diff --git a/src/tests/test_array.py b/tests/test_array.py similarity index 100% rename from src/tests/test_array.py rename to tests/test_array.py diff --git a/src/tests/test_callback.py b/tests/test_callback.py similarity index 100% rename from src/tests/test_callback.py rename to tests/test_callback.py diff --git a/src/tests/test_class.py b/tests/test_class.py similarity index 100% rename from src/tests/test_class.py rename to tests/test_class.py diff --git a/src/tests/test_clrmethod.py b/tests/test_clrmethod.py similarity index 100% rename from src/tests/test_clrmethod.py rename to tests/test_clrmethod.py diff --git a/src/tests/test_constructors.py b/tests/test_constructors.py similarity index 100% rename from src/tests/test_constructors.py rename to tests/test_constructors.py diff --git a/src/tests/test_conversion.py b/tests/test_conversion.py similarity index 100% rename from src/tests/test_conversion.py rename to tests/test_conversion.py diff --git a/src/tests/test_delegate.py b/tests/test_delegate.py similarity index 100% rename from src/tests/test_delegate.py rename to tests/test_delegate.py diff --git a/src/tests/test_docstring.py b/tests/test_docstring.py similarity index 100% rename from src/tests/test_docstring.py rename to tests/test_docstring.py diff --git a/src/tests/test_engine.py b/tests/test_engine.py similarity index 100% rename from src/tests/test_engine.py rename to tests/test_engine.py diff --git a/src/tests/test_enum.py b/tests/test_enum.py similarity index 100% rename from src/tests/test_enum.py rename to tests/test_enum.py diff --git a/src/tests/test_event.py b/tests/test_event.py similarity index 100% rename from src/tests/test_event.py rename to tests/test_event.py diff --git a/src/tests/test_exceptions.py b/tests/test_exceptions.py similarity index 100% rename from src/tests/test_exceptions.py rename to tests/test_exceptions.py diff --git a/src/tests/test_field.py b/tests/test_field.py similarity index 100% rename from src/tests/test_field.py rename to tests/test_field.py diff --git a/src/tests/test_generic.py b/tests/test_generic.py similarity index 100% rename from src/tests/test_generic.py rename to tests/test_generic.py diff --git a/src/tests/test_import.py b/tests/test_import.py similarity index 100% rename from src/tests/test_import.py rename to tests/test_import.py diff --git a/src/tests/test_indexer.py b/tests/test_indexer.py similarity index 100% rename from src/tests/test_indexer.py rename to tests/test_indexer.py diff --git a/src/tests/test_interface.py b/tests/test_interface.py similarity index 100% rename from src/tests/test_interface.py rename to tests/test_interface.py diff --git a/src/tests/test_method.py b/tests/test_method.py similarity index 100% rename from src/tests/test_method.py rename to tests/test_method.py diff --git a/src/tests/test_module.py b/tests/test_module.py similarity index 100% rename from src/tests/test_module.py rename to tests/test_module.py diff --git a/src/tests/test_mp_length.py b/tests/test_mp_length.py similarity index 100% rename from src/tests/test_mp_length.py rename to tests/test_mp_length.py diff --git a/src/tests/test_property.py b/tests/test_property.py similarity index 100% rename from src/tests/test_property.py rename to tests/test_property.py diff --git a/src/tests/test_recursive_types.py b/tests/test_recursive_types.py similarity index 100% rename from src/tests/test_recursive_types.py rename to tests/test_recursive_types.py diff --git a/src/tests/test_repr.py b/tests/test_repr.py similarity index 100% rename from src/tests/test_repr.py rename to tests/test_repr.py diff --git a/src/tests/test_subclass.py b/tests/test_subclass.py similarity index 100% rename from src/tests/test_subclass.py rename to tests/test_subclass.py diff --git a/src/tests/test_sysargv.py b/tests/test_sysargv.py similarity index 100% rename from src/tests/test_sysargv.py rename to tests/test_sysargv.py diff --git a/src/tests/test_thread.py b/tests/test_thread.py similarity index 100% rename from src/tests/test_thread.py rename to tests/test_thread.py diff --git a/src/tests/tests.pyproj b/tests/tests.pyproj similarity index 100% rename from src/tests/tests.pyproj rename to tests/tests.pyproj diff --git a/src/tests/utils.py b/tests/utils.py similarity index 100% rename from src/tests/utils.py rename to tests/utils.py From 67032ea055ed6a6e9ea02a5cf2cb8598961e20d7 Mon Sep 17 00:00:00 2001 From: Benedikt Reinartz Date: Sun, 14 Feb 2021 12:03:37 +0100 Subject: [PATCH 5/9] Vendor System.Drawing.Point for testing on .NET Core --- src/testing/arraytest.cs | 12 ++++++++++++ tests/fixtures/.gitkeep | 0 tests/test_array.py | 4 +--- tests/test_class.py | 4 +--- 4 files changed, 14 insertions(+), 6 deletions(-) delete mode 100644 tests/fixtures/.gitkeep diff --git a/src/testing/arraytest.cs b/src/testing/arraytest.cs index 946684962..a3c94e019 100644 --- a/src/testing/arraytest.cs +++ b/src/testing/arraytest.cs @@ -314,4 +314,16 @@ public static Spam[][] EchoRangeAA(Spam[][] items) return items; } } + + public struct Point + { + public Point(float x, float y) + { + X = x; + Y = y; + } + + public float X { get; set; } + public float Y { get; set; } + } } diff --git a/tests/fixtures/.gitkeep b/tests/fixtures/.gitkeep deleted file mode 100644 index e69de29bb..000000000 diff --git a/tests/test_array.py b/tests/test_array.py index 232c89ac7..2b1a289ad 100644 --- a/tests/test_array.py +++ b/tests/test_array.py @@ -1166,10 +1166,8 @@ def test_boxed_value_type_mutation_result(): # to accidentally write code like the following which is not really # mutating value types in-place but changing boxed copies. - clr.AddReference('System.Drawing') - - from System.Drawing import Point from System import Array + from Python.Test import Point items = Array.CreateInstance(Point, 5) diff --git a/tests/test_class.py b/tests/test_class.py index 4666631f7..f961b3975 100644 --- a/tests/test_class.py +++ b/tests/test_class.py @@ -126,9 +126,7 @@ def __init__(self, v): def test_struct_construction(): """Test construction of structs.""" - clr.AddReference('System.Drawing') - - from System.Drawing import Point + from Python.Test import Point p = Point() assert p.X == 0 From 8bc458b370822ae86038862ab24b0c2663e7a9a2 Mon Sep 17 00:00:00 2001 From: Benedikt Reinartz Date: Sun, 14 Feb 2021 12:09:46 +0100 Subject: [PATCH 6/9] Use approximate comparison for single max/min value comparison --- tests/test_conversion.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/test_conversion.py b/tests/test_conversion.py index 3b290b947..aea95e164 100644 --- a/tests/test_conversion.py +++ b/tests/test_conversion.py @@ -406,8 +406,8 @@ def test_uint64_conversion(): def test_single_conversion(): """Test single conversion.""" - assert System.Single.MaxValue == 3.402823e38 - assert System.Single.MinValue == -3.402823e38 + assert System.Single.MaxValue == pytest.approx(3.402823e38) + assert System.Single.MinValue == pytest.approx(-3.402823e38) ob = ConversionTest() assert ob.SingleField == 0.0 From d46fa1e6aa00a291782584018911ee787b0044e5 Mon Sep 17 00:00:00 2001 From: Benedikt Reinartz Date: Sun, 14 Feb 2021 12:39:51 +0100 Subject: [PATCH 7/9] Adjust the import tests to use only commonly available deps --- tests/conftest.py | 4 ---- tests/test_module.py | 47 ++++++++++++++++++++++++++------------------ 2 files changed, 28 insertions(+), 23 deletions(-) diff --git a/tests/conftest.py b/tests/conftest.py index aa57f2a1f..3f9436dd9 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -66,10 +66,6 @@ def pytest_configure(config): import clr clr.AddReference("Python.Test") - clr.AddReference("System") - clr.AddReference("System.Collections") - clr.AddReference("System.Data") - clr.AddReference("System.Xml") def pytest_unconfigure(config): diff --git a/tests/test_module.py b/tests/test_module.py index dcdb0248e..d0378e91e 100644 --- a/tests/test_module.py +++ b/tests/test_module.py @@ -30,7 +30,7 @@ def test_import_clr(): def test_version_clr(): import clr - assert clr.__version__ >= "2.2.0" + assert clr.__version__ >= "3.0.0" def test_preload_var(): @@ -111,12 +111,13 @@ def test_dotted_name_import(): def test_multiple_dotted_name_import(): """Test an import bug with multiple dotted imports.""" - import System.Data - assert is_clr_module(System.Data) - assert System.Data.__name__ == 'System.Data' - import System.Data - assert is_clr_module(System.Data) - assert System.Data.__name__ == 'System.Data' + + import System.Reflection + assert is_clr_module(System.Reflection) + assert System.Reflection.__name__ == 'System.Reflection' + import System.Reflection + assert is_clr_module(System.Reflection) + assert System.Reflection.__name__ == 'System.Reflection' def test_dotted_name_import_with_alias(): @@ -192,11 +193,11 @@ def test_dotted_name_import_from_with_alias(): def test_from_module_import_star(): """Test from module import * behavior.""" - clr.AddReference('System.Xml') - + clr.AddReference("System") + count = len(locals().keys()) - m = __import__('System.Xml', globals(), locals(), ['*']) - assert m.__name__ == 'System.Xml' + m = __import__('System', globals(), locals(), ['*']) + assert m.__name__ == 'System' assert is_clr_module(m) assert len(locals().keys()) > count + 1 @@ -212,7 +213,11 @@ def test_implicit_assembly_load(): import Microsoft.Build with warnings.catch_warnings(record=True) as w: - clr.AddReference("System.Windows.Forms") + try: + clr.AddReference("System.Windows.Forms") + except Exception: + pytest.skip() + import System.Windows.Forms as Forms assert is_clr_module(Forms) assert Forms.__name__ == 'System.Windows.Forms' @@ -227,11 +232,11 @@ def test_explicit_assembly_load(): from System.Reflection import Assembly import System, sys - assembly = Assembly.LoadWithPartialName('System.Data') + assembly = Assembly.LoadWithPartialName('System.Runtime') assert assembly is not None - import System.Data - assert 'System.Data' in sys.modules + import System.Runtime + assert 'System.Runtime' in sys.modules assembly = Assembly.LoadWithPartialName('SpamSpamSpamSpamEggsAndSpam') assert assembly is None @@ -275,12 +280,14 @@ def test_module_lookup_recursion(): def test_module_get_attr(): """Test module getattr behavior.""" + import System + import System.Runtime int_type = System.Int32 assert is_clr_class(int_type) - module = System.Xml + module = System.Runtime assert is_clr_module(module) with pytest.raises(AttributeError): @@ -324,7 +331,6 @@ def test_clr_list_assemblies(): from clr import ListAssemblies verbose = list(ListAssemblies(True)) short = list(ListAssemblies(False)) - assert u'mscorlib' in short assert u'System' in short assert u'Culture=' in verbose[0] assert u'Version=' in verbose[0] @@ -377,8 +383,11 @@ def test_assembly_load_thread_safety(): _ = Dictionary[Guid, DateTime]() ModuleTest.JoinThreads() +@pytest.mark.skipif() def test_assembly_load_recursion_bug(): """Test fix for recursion bug documented in #627""" - from System.Configuration import ConfigurationManager - content = dir(ConfigurationManager) + sys_config = pytest.importorskip( + "System.Configuration", reason="System.Configuration can't be imported" + ) + content = dir(sys_config.ConfigurationManager) assert len(content) > 5, content From f0011a51fd494740f9768caf2074e5275ae7a0d1 Mon Sep 17 00:00:00 2001 From: Benedikt Reinartz Date: Sun, 14 Feb 2021 12:47:20 +0100 Subject: [PATCH 8/9] Fix PythonTestRunner to work with new pytest setup --- .../Python.PythonTestsRunner.csproj | 1 + src/python_tests_runner/PythonTestRunner.cs | 3 ++- tests/conftest.py | 11 ++++++++++- 3 files changed, 13 insertions(+), 2 deletions(-) diff --git a/src/python_tests_runner/Python.PythonTestsRunner.csproj b/src/python_tests_runner/Python.PythonTestsRunner.csproj index 2d6544614..1006b2148 100644 --- a/src/python_tests_runner/Python.PythonTestsRunner.csproj +++ b/src/python_tests_runner/Python.PythonTestsRunner.csproj @@ -6,6 +6,7 @@ + diff --git a/src/python_tests_runner/PythonTestRunner.cs b/src/python_tests_runner/PythonTestRunner.cs index 79b15700e..36e8049d4 100644 --- a/src/python_tests_runner/PythonTestRunner.cs +++ b/src/python_tests_runner/PythonTestRunner.cs @@ -8,6 +8,7 @@ using NUnit.Framework; using Python.Runtime; +using Python.Test; namespace Python.PythonTestsRunner { @@ -50,7 +51,7 @@ public void RunPythonTest(string testFile, string testName) { folder = Path.GetDirectoryName(folder); } - folder = Path.Combine(folder, "tests"); + folder = Path.Combine(folder, "..", "tests"); string path = Path.Combine(folder, testFile + ".py"); if (!File.Exists(path)) throw new FileNotFoundException("Cannot find test file", path); diff --git a/tests/conftest.py b/tests/conftest.py index 3f9436dd9..cf3341f01 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -31,6 +31,12 @@ def pytest_addoption(parser): def pytest_configure(config): global bin_path + if "clr" in sys.modules: + # Already loaded (e.g. by the C# test runner), skip build + import clr + clr.AddReference("Python.Test") + return + runtime_opt = config.getoption("runtime") test_proj_path = os.path.join(cwd, "..", "src", "testing") @@ -70,7 +76,10 @@ def pytest_configure(config): def pytest_unconfigure(config): global bin_path - shutil.rmtree(bin_path) + try: + shutil.rmtree(bin_path) + except Exception: + pass def pytest_report_header(config): """Generate extra report headers""" From c1a01b72fad0f54e87edb2432ad00c135728150c Mon Sep 17 00:00:00 2001 From: Benedikt Reinartz Date: Sun, 14 Feb 2021 15:21:27 +0100 Subject: [PATCH 9/9] Drop references to the obsolete call --- pythonnet.sln | 1 - src/runtime/native/ABI.cs | 3 +-- 2 files changed, 1 insertion(+), 3 deletions(-) diff --git a/pythonnet.sln b/pythonnet.sln index 30f4fd344..e02948c18 100644 --- a/pythonnet.sln +++ b/pythonnet.sln @@ -53,7 +53,6 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Python.PythonTestsRunner", EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Build", "Build", "{142A6752-C2C2-4F95-B982-193418001B65}" ProjectSection(SolutionItems) = preProject - configured.props = configured.props Directory.Build.props = Directory.Build.props EndProjectSection EndProject diff --git a/src/runtime/native/ABI.cs b/src/runtime/native/ABI.cs index e95b259c5..3264531de 100644 --- a/src/runtime/native/ABI.cs +++ b/src/runtime/native/ABI.cs @@ -24,8 +24,7 @@ internal static void Initialize(Version version, BorrowedReference pyType) if (typeOffsetsClass is null) { var types = thisAssembly.GetTypes().Select(type => type.Name).Where(name => name.StartsWith("TypeOffset")); - string message = $"Searching for {className}, found {string.Join(",", types)}. " + - "If you are building Python.NET from source, make sure you have run 'python setup.py configure' to fill in configured.props"; + string message = $"Searching for {className}, found {string.Join(",", types)}."; throw new NotSupportedException($"Python ABI v{version} is not supported: {message}"); } var typeOffsets = (ITypeOffsets)Activator.CreateInstance(typeOffsetsClass);