summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--.github/workflows/ci.yml33
-rw-r--r--runtime/doc/builtin.txt1
-rw-r--r--runtime/doc/eval.txt19
-rw-r--r--runtime/doc/if_pyth.txt26
-rw-r--r--runtime/doc/tags6
-rw-r--r--runtime/doc/various.txt2
-rw-r--r--src/Make_cyg_ming.mak6
-rw-r--r--src/Make_mvc.mak6
-rwxr-xr-xsrc/auto/configure48
-rw-r--r--src/config.h.in3
-rw-r--r--src/configure.ac34
-rw-r--r--src/evalfunc.c7
-rw-r--r--src/evalvars.c5
-rw-r--r--src/if_py_both.h549
-rw-r--r--src/if_python3.c167
-rw-r--r--src/proto/if_python3.pro1
-rw-r--r--src/version.c6
-rw-r--r--src/vim.h3
18 files changed, 769 insertions, 153 deletions
diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index 273dedf3be..98bff17d42 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -51,8 +51,14 @@ jobs:
- features: huge
coverage: true
- features: huge
+ compiler: clang
+ extra: none
+ interface: dynamic
+ python3: stable-abi
+ - features: huge
compiler: gcc
coverage: true
+ interface: dynamic
extra: testgui
uchar: true
luaver: lua5.4
@@ -141,7 +147,16 @@ jobs:
;;
huge)
echo "TEST=scripttests test_libvterm"
- echo "CONFOPT=--enable-perlinterp --enable-pythoninterp --enable-python3interp --enable-rubyinterp --enable-luainterp --enable-tclinterp"
+ if ${{ matrix.interface == 'dynamic' }}; then
+ if ${{ matrix.python3 == 'stable-abi' }}; then
+ PYTHON3_FLAGS="--with-python3-stable-abi=3.8"
+ else
+ PYTHON3_FLAGS=""
+ fi
+ echo "CONFOPT=--enable-perlinterp=dynamic --enable-pythoninterp=dynamic --enable-python3interp=dynamic --enable-rubyinterp=dynamic --enable-luainterp=dynamic --enable-tclinterp=dynamic ${PYTHON3_FLAGS}"
+ else
+ echo "CONFOPT=--enable-perlinterp --enable-pythoninterp --enable-python3interp --enable-rubyinterp --enable-luainterp --enable-tclinterp"
+ fi
;;
esac
@@ -369,8 +384,8 @@ jobs:
fail-fast: false
matrix:
include:
- - { features: HUGE, toolchain: msvc, VIMDLL: no, GUI: no, arch: x64 }
- - { features: HUGE, toolchain: mingw, VIMDLL: yes, GUI: yes, arch: x86, coverage: yes }
+ - { features: HUGE, toolchain: msvc, VIMDLL: no, GUI: no, arch: x64, python3: stable }
+ - { features: HUGE, toolchain: mingw, VIMDLL: yes, GUI: yes, arch: x86, python3: stable, coverage: yes }
- { features: HUGE, toolchain: msvc, VIMDLL: no, GUI: yes, arch: x86 }
- { features: HUGE, toolchain: mingw, VIMDLL: yes, GUI: no, arch: x64, coverage: yes }
- { features: NORMAL, toolchain: msvc, VIMDLL: yes, GUI: no, arch: x86 }
@@ -501,6 +516,11 @@ jobs:
) else (
set GUI=${{ matrix.GUI }}
)
+ if "${{ matrix.python3 }}"=="stable" (
+ set PYTHON3_STABLE=yes
+ ) else (
+ set PYTHON3_STABLE=no
+ )
if "${{ matrix.features }}"=="HUGE" (
nmake -nologo -f Make_mvc.mak ^
FEATURES=${{ matrix.features }} ^
@@ -508,6 +528,7 @@ jobs:
DYNAMIC_LUA=yes LUA=%LUA_DIR% ^
DYNAMIC_PYTHON=yes PYTHON=%PYTHON_DIR% ^
DYNAMIC_PYTHON3=yes PYTHON3=%PYTHON3_DIR% ^
+ DYNAMIC_PYTHON3_STABLE_ABI=%PYTHON3_STABLE% ^
DYNAMIC_SODIUM=yes SODIUM=%SODIUM_DIR%
) else (
nmake -nologo -f Make_mvc.mak ^
@@ -525,6 +546,11 @@ jobs:
else
GUI=${{ matrix.GUI }}
fi
+ if [ "${{ matrix.python3 }}" = "stable" ]; then
+ PYTHON3_STABLE=yes
+ else
+ PYTHON3_STABLE=no
+ fi
if [ "${{ matrix.features }}" = "HUGE" ]; then
mingw32-make -f Make_ming.mak -j2 \
FEATURES=${{ matrix.features }} \
@@ -532,6 +558,7 @@ jobs:
DYNAMIC_LUA=yes LUA=${LUA_DIR_SLASH} \
DYNAMIC_PYTHON=yes PYTHON=${PYTHON_DIR} \
DYNAMIC_PYTHON3=yes PYTHON3=${PYTHON3_DIR} \
+ DYNAMIC_PYTHON3_STABLE_ABI=${PYTHON3_STABLE} \
DYNAMIC_SODIUM=yes SODIUM=${SODIUM_DIR} \
STATIC_STDCPLUS=yes COVERAGE=${{ matrix.coverage }}
else
diff --git a/runtime/doc/builtin.txt b/runtime/doc/builtin.txt
index e9bdad2edf..99bc9e2be7 100644
--- a/runtime/doc/builtin.txt
+++ b/runtime/doc/builtin.txt
@@ -10986,6 +10986,7 @@ python_dynamic Python 2.x interface is dynamically loaded. |has-python|
python3 Python 3.x interface available. |has-python|
python3_compiled Compiled with Python 3.x interface. |has-python|
python3_dynamic Python 3.x interface is dynamically loaded. |has-python|
+python3_stable Python 3.x interface is using Python Stable ABI. |has-python|
pythonx Python 2.x and/or 3.x interface available. |python_x|
qnx QNX version of Vim.
quickfix Compiled with |quickfix| support.
diff --git a/runtime/doc/eval.txt b/runtime/doc/eval.txt
index 7e26605f84..4f9a3c99f5 100644
--- a/runtime/doc/eval.txt
+++ b/runtime/doc/eval.txt
@@ -2424,6 +2424,25 @@ v:progpath Contains the command with which Vim was invoked, in a form
".exe" is not added to v:progpath.
Read-only.
+ *v:python3_version* *python3-version-variable*
+v:python3_version
+ Version of Python 3 that Vim was built against. When
+ Python is loaded dynamically (|python-dynamic|), this version
+ should exactly match the Python library up to the minor
+ version (e.g. 3.10.2 and 3.10.3 are compatible as the minor
+ version is "10", whereas 3.9.4 and 3.10.3 are not compatible).
+ When |python-stable-abi| is used, this will be the minimum Python
+ version that you can use instead. (e.g. if v:python3_version
+ indicates 3.9, you can use 3.9, 3.10, or anything above).
+
+ This number is encoded as a hex number following Python ABI
+ versioning conventions. Do the following to have a
+ human-readable full version in hex: >
+ echo printf("%08X", v:python3_version)
+< You can obtain only the minor version by doing: >
+ echo and(v:python3_version>>16,0xff)
+< Read-only.
+
*v:register* *register-variable*
v:register The name of the register in effect for the current normal mode
command (regardless of whether that command actually used a
diff --git a/runtime/doc/if_pyth.txt b/runtime/doc/if_pyth.txt
index c2a0094b63..3d3b92a662 100644
--- a/runtime/doc/if_pyth.txt
+++ b/runtime/doc/if_pyth.txt
@@ -769,7 +769,19 @@ Unix ~
The 'pythondll' or 'pythonthreedll' option can be used to specify the Python
shared library file instead of DYNAMIC_PYTHON_DLL or DYNAMIC_PYTHON3_DLL file
what were specified at compile time. The version of the shared library must
-match the Python 2.x or Python 3 version Vim was compiled with.
+match the Python 2.x or Python 3 version (|v:python3_version|) Vim was
+compiled with unless using |python3-stable-abi|.
+
+
+Stable ABI and mixing Python versions ~
+ *python-stable* *python-stable-abi* *python3-stable-abi*
+If Vim was not compiled with Stable ABI (only available for Python 3), the
+version of the Python shared library must match the version that Vim was
+compiled with. Otherwise, mixing versions could result in unexpected crashes
+and failures. With Stable ABI, this restriction is relaxed, and any Python 3
+library with version of at least |v:python3_version| will work. See
+|has-python| for how to check if Stable ABI is supported, or see if version
+output includes |+python3/dyn-stable|.
==============================================================================
10. Python 3 *python3*
@@ -881,6 +893,18 @@ python support: >
endif
endif
+When loading the library dynamically, Vim can be compiled to support Python 3
+Stable ABI (|python3-stable-abi|) which allows you to load a different version
+of Python 3 library than the one Vim was compiled with. To check it: >
+ if has('python3_dynamic')
+ if has('python3_stable')
+ echo 'support Python 3 Stable ABI.'
+ else
+ echo 'does not support Python 3 Stable ABI.'
+ echo 'only use Python 3 version ' .. v:python3_version
+ endif
+ endif
+
This also tells you whether Python is dynamically loaded, which will fail if
the runtime library cannot be found.
diff --git a/runtime/doc/tags b/runtime/doc/tags
index ce1918f26a..673c0db654 100644
--- a/runtime/doc/tags
+++ b/runtime/doc/tags
@@ -1434,6 +1434,7 @@ $quote eval.txt /*$quote*
+python/dyn various.txt /*+python\/dyn*
+python3 various.txt /*+python3*
+python3/dyn various.txt /*+python3\/dyn*
++python3/dyn-stable various.txt /*+python3\/dyn-stable*
+quickfix various.txt /*+quickfix*
+reltime various.txt /*+reltime*
+rightleft various.txt /*+rightleft*
@@ -9294,6 +9295,8 @@ python-path_hook if_pyth.txt /*python-path_hook*
python-pyeval if_pyth.txt /*python-pyeval*
python-range if_pyth.txt /*python-range*
python-special-path if_pyth.txt /*python-special-path*
+python-stable if_pyth.txt /*python-stable*
+python-stable-abi if_pyth.txt /*python-stable-abi*
python-strwidth if_pyth.txt /*python-strwidth*
python-tabpage if_pyth.txt /*python-tabpage*
python-tabpages if_pyth.txt /*python-tabpages*
@@ -9306,6 +9309,8 @@ python.vim syntax.txt /*python.vim*
python2-directory if_pyth.txt /*python2-directory*
python3 if_pyth.txt /*python3*
python3-directory if_pyth.txt /*python3-directory*
+python3-stable-abi if_pyth.txt /*python3-stable-abi*
+python3-version-variable eval.txt /*python3-version-variable*
python_x if_pyth.txt /*python_x*
python_x-special-comments if_pyth.txt /*python_x-special-comments*
pythonx if_pyth.txt /*pythonx*
@@ -10632,6 +10637,7 @@ v:prevcount eval.txt /*v:prevcount*
v:profiling eval.txt /*v:profiling*
v:progname eval.txt /*v:progname*
v:progpath eval.txt /*v:progpath*
+v:python3_version eval.txt /*v:python3_version*
v:register eval.txt /*v:register*
v:scrollstart eval.txt /*v:scrollstart*
v:searchforward eval.txt /*v:searchforward*
diff --git a/runtime/doc/various.txt b/runtime/doc/various.txt
index a11e166c38..e478c8266e 100644
--- a/runtime/doc/various.txt
+++ b/runtime/doc/various.txt
@@ -450,6 +450,8 @@ m *+python* Python 2 interface |python|
m *+python/dyn* Python 2 interface |python-dynamic| |/dyn|
m *+python3* Python 3 interface |python|
m *+python3/dyn* Python 3 interface |python-dynamic| |/dyn|
+m *+python3/dyn-stable*
+ Python 3 interface |python-dynamic| |python-stable| |/dyn|
N *+quickfix* |:make| and |quickfix| commands
N *+reltime* |reltime()| function, 'hlsearch'/'incsearch' timeout,
'redrawtime' option
diff --git a/src/Make_cyg_ming.mak b/src/Make_cyg_ming.mak
index 426125663c..e4fe0be8fb 100644
--- a/src/Make_cyg_ming.mak
+++ b/src/Make_cyg_ming.mak
@@ -412,6 +412,9 @@ PYTHON3INC=-I $(PYTHON3)/include
else
PYTHON3INC=-I $(PYTHON3)/win32inc
endif
+ ifeq ($(DYNAMIC_PYTHON3_STABLE_ABI),yes)
+PYTHON3INC += -DPy_LIMITED_API=0x3080000
+ endif
endif
endif
@@ -594,6 +597,9 @@ ifdef PYTHON3
CFLAGS += -DFEAT_PYTHON3
ifeq (yes, $(DYNAMIC_PYTHON3))
CFLAGS += -DDYNAMIC_PYTHON3 -DDYNAMIC_PYTHON3_DLL=\"$(DYNAMIC_PYTHON3_DLL)\"
+ ifeq (yes, $(DYNAMIC_PYTHON3_STABLE_ABI))
+CFLAGS += -DDYNAMIC_PYTHON3_STABLE_ABI
+ endif
else
CFLAGS += -DPYTHON3_DLL=\"$(DYNAMIC_PYTHON3_DLL)\"
endif
diff --git a/src/Make_mvc.mak b/src/Make_mvc.mak
index f98d905042..e1d42fdcb9 100644
--- a/src/Make_mvc.mak
+++ b/src/Make_mvc.mak
@@ -950,7 +950,13 @@ PYTHON3_INC = /I "$(PYTHON3)\Include" /I "$(PYTHON3)\PC"
! if "$(DYNAMIC_PYTHON3)" == "yes"
CFLAGS = $(CFLAGS) -DDYNAMIC_PYTHON3 \
-DDYNAMIC_PYTHON3_DLL=\"$(DYNAMIC_PYTHON3_DLL)\"
+! if "$(DYNAMIC_PYTHON3_STABLE_ABI)" == "yes"
+CFLAGS = $(CFLAGS) -DDYNAMIC_PYTHON3_STABLE_ABI
+PYTHON3_INC = $(PYTHON3_INC) -DPy_LIMITED_API=0x3080000
+PYTHON3_LIB = /nodefaultlib:python3.lib
+! else
PYTHON3_LIB = /nodefaultlib:python$(PYTHON3_VER).lib
+! endif
! else
CFLAGS = $(CFLAGS) -DPYTHON3_DLL=\"$(DYNAMIC_PYTHON3_DLL)\"
PYTHON3_LIB = "$(PYTHON3)\libs\python$(PYTHON3_VER).lib"
diff --git a/src/auto/configure b/src/auto/configure
index b914aec84c..9fd6888191 100755
--- a/src/auto/configure
+++ b/src/auto/configure
@@ -680,6 +680,7 @@ PYTHON3_SRC
PYTHON3_CFLAGS_EXTRA
PYTHON3_CFLAGS
PYTHON3_LIBS
+vi_cv_var_python3_stable_abi
vi_cv_path_python3
PYTHON_OBJ
PYTHON_SRC
@@ -811,6 +812,7 @@ with_python_command
with_python_config_dir
enable_python3interp
with_python3_command
+with_python3_stable_abi
with_python3_config_dir
enable_tclinterp
with_tclsh
@@ -1531,6 +1533,7 @@ Optional Packages:
--with-python-command=NAME name of the Python 2 command (default: python2 or python)
--with-python-config-dir=PATH Python's config directory (deprecated)
--with-python3-command=NAME name of the Python 3 command (default: python3 or python)
+ --with-python3-stable-abi=VERSION stable ABI version to target (e.g. 3.8)
--with-python3-config-dir=PATH Python's config directory (deprecated)
--with-tclsh=PATH which tclsh to use (default: tclsh8.0)
--with-ruby-command=RUBY name of the Ruby command (default: ruby)
@@ -6753,6 +6756,34 @@ $as_echo_n "checking Python is 3.0 or better... " >&6; }
{ $as_echo "$as_me:${as_lineno-$LINENO}: result: yep" >&5
$as_echo "yep" >&6; }
+ { $as_echo "$as_me:${as_lineno-$LINENO}: checking --with-python3-stable-abi argument" >&5
+$as_echo_n "checking --with-python3-stable-abi argument... " >&6; }
+
+
+# Check whether --with-python3-stable-abi was given.
+if test "${with_python3_stable_abi+set}" = set; then :
+ withval=$with_python3_stable_abi; vi_cv_var_python3_stable_abi="$withval"; { $as_echo "$as_me:${as_lineno-$LINENO}: result: $vi_cv_var_python3_stable_abi" >&5
+$as_echo "$vi_cv_var_python3_stable_abi" >&6; }
+else
+ { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5
+$as_echo "no" >&6; }
+fi
+
+ if test "X$vi_cv_var_python3_stable_abi" != "X"; then
+ if ${vi_cv_var_python3_stable_abi_hex+:} false; then :
+ $as_echo_n "(cached) " >&6
+else
+
+ vi_cv_var_python3_stable_abi_hex=`
+ ${vi_cv_path_python3} -c \
+ "major_minor='${vi_cv_var_python3_stable_abi}'.split('.'); print('0x{0:X}'.format( (int(major_minor.__getitem__(0))<<24) + (int(major_minor.__getitem__(1))<<16) ))"`
+fi
+
+ if test "X$vi_cv_var_python3_stable_abi_hex" == "X"; then
+ as_fn_error $? "can't parse Python 3 stable ABI version. It should be \"<major>.<minor>\"" "$LINENO" 5
+ fi
+ fi
+
{ $as_echo "$as_me:${as_lineno-$LINENO}: checking Python's abiflags" >&5
$as_echo_n "checking Python's abiflags... " >&6; }
if ${vi_cv_var_python3_abiflags+:} false; then :
@@ -6897,9 +6928,12 @@ $as_echo "$vi_cv_dll_name_python3" >&6; }
else
PYTHON3_CFLAGS="-I${vi_cv_path_python3_pfx}/include/python${vi_cv_var_python3_version}${vi_cv_var_python3_abiflags} -I${vi_cv_path_python3_epfx}/include/python${vi_cv_var_python3_version}${vi_cv_var_python3_abiflags}"
fi
- if test "X$have_python3_config_dir" = "X1" -a "$enable_python3interp" = "dynamic"; then
- PYTHON3_CFLAGS="${PYTHON3_CFLAGS} -DPYTHON3_HOME='L\"${vi_cv_path_python3_pfx}\"'"
- fi
+ if test "X$have_python3_config_dir" = "X1" -a "$enable_python3interp" = "dynamic"; then
+ PYTHON3_CFLAGS="${PYTHON3_CFLAGS} -DPYTHON3_HOME='L\"${vi_cv_path_python3_pfx}\"'"
+ fi
+ if test "X$vi_cv_var_python3_stable_abi_hex" != "X"; then
+ PYTHON3_CFLAGS="${PYTHON3_CFLAGS} -DPy_LIMITED_API=${vi_cv_var_python3_stable_abi_hex}"
+ fi
PYTHON3_SRC="if_python3.c"
PYTHON3_OBJ="objects/if_python3.o"
@@ -7009,6 +7043,10 @@ if test "$python_ok" = yes && test "$python3_ok" = yes; then
$as_echo "#define DYNAMIC_PYTHON3 1" >>confdefs.h
+ if test "X$vi_cv_var_python3_stable_abi_hex" != "X"; then
+ $as_echo "#define DYNAMIC_PYTHON3_STABLE_ABI 1" >>confdefs.h
+
+ fi
{ $as_echo "$as_me:${as_lineno-$LINENO}: checking whether we can do without RTLD_GLOBAL for Python" >&5
$as_echo_n "checking whether we can do without RTLD_GLOBAL for Python... " >&6; }
cflags_save=$CFLAGS
@@ -7190,6 +7228,10 @@ rm -f core conftest.err conftest.$ac_objext \
elif test "$python3_ok" = yes && test "$enable_python3interp" = "dynamic"; then
$as_echo "#define DYNAMIC_PYTHON3 1" >>confdefs.h
+ if test "X$vi_cv_var_python3_stable_abi_hex" != "X"; then
+ $as_echo "#define DYNAMIC_PYTHON3_STABLE_ABI 1" >>confdefs.h
+
+ fi
PYTHON3_SRC="if_python3.c"
PYTHON3_OBJ="objects/if_python3.o"
PYTHON3_CFLAGS="$PYTHON3_CFLAGS -DDYNAMIC_PYTHON3_DLL=\\\"${vi_cv_dll_name_python3}\\\""
diff --git a/src/config.h.in b/src/config.h.in
index d0257ccabe..93972ca0cc 100644
--- a/src/config.h.in
+++ b/src/config.h.in
@@ -354,6 +354,9 @@
/* Define for linking via dlopen() or LoadLibrary() */
#undef DYNAMIC_PYTHON3
+/* Define if compiled against Python 3 stable ABI / limited API */
+#undef DYNAMIC_PYTHON3_STABLE_ABI
+
/* Define if dynamic python does not require RTLD_GLOBAL */
#undef PY_NO_RTLD_GLOBAL
diff --git a/src/configure.ac b/src/configure.ac
index a3c5f8da8c..cefd0c3b31 100644
--- a/src/configure.ac
+++ b/src/configure.ac
@@ -1503,6 +1503,23 @@ if test "$enable_python3interp" = "yes" -o "$enable_python3interp" = "dynamic";
then
AC_MSG_RESULT(yep)
+ dnl -- get the stable ABI version if passed in
+ AC_MSG_CHECKING(--with-python3-stable-abi argument)
+ AC_SUBST(vi_cv_var_python3_stable_abi)
+ AC_ARG_WITH(python3-stable-abi, [ --with-python3-stable-abi=VERSION stable ABI version to target (e.g. 3.8)],
+ vi_cv_var_python3_stable_abi="$withval"; AC_MSG_RESULT($vi_cv_var_python3_stable_abi),
+ AC_MSG_RESULT(no))
+ if test "X$vi_cv_var_python3_stable_abi" != "X"; then
+ AC_CACHE_VAL(vi_cv_var_python3_stable_abi_hex,
+ [
+ vi_cv_var_python3_stable_abi_hex=`
+ ${vi_cv_path_python3} -c \
+ "major_minor='${vi_cv_var_python3_stable_abi}'.split('.'); print('0x{0:X}'.format( (int(major_minor.__getitem__(0))<<24) + (int(major_minor.__getitem__(1))<<16) ))"` ])
+ if test "X$vi_cv_var_python3_stable_abi_hex" == "X"; then
+ AC_MSG_ERROR([can't parse Python 3 stable ABI version. It should be "<major>.<minor>"])
+ fi
+ fi
+
dnl -- get abiflags for python 3.2 or higher (PEP 3149)
AC_CACHE_CHECK(Python's abiflags,vi_cv_var_python3_abiflags,
[
@@ -1609,10 +1626,13 @@ eof
else
PYTHON3_CFLAGS="-I${vi_cv_path_python3_pfx}/include/python${vi_cv_var_python3_version}${vi_cv_var_python3_abiflags} -I${vi_cv_path_python3_epfx}/include/python${vi_cv_var_python3_version}${vi_cv_var_python3_abiflags}"
fi
- if test "X$have_python3_config_dir" = "X1" -a "$enable_python3interp" = "dynamic"; then
- dnl Define PYTHON3_HOME if --with-python-config-dir was used
- PYTHON3_CFLAGS="${PYTHON3_CFLAGS} -DPYTHON3_HOME='L\"${vi_cv_path_python3_pfx}\"'"
- fi
+ if test "X$have_python3_config_dir" = "X1" -a "$enable_python3interp" = "dynamic"; then
+ dnl Define PYTHON3_HOME if --with-python-config-dir was used
+ PYTHON3_CFLAGS="${PYTHON3_CFLAGS} -DPYTHON3_HOME='L\"${vi_cv_path_python3_pfx}\"'"
+ fi
+ if test "X$vi_cv_var_python3_stable_abi_hex" != "X"; then
+ PYTHON3_CFLAGS="${PYTHON3_CFLAGS} -DPy_LIMITED_API=${vi_cv_var_python3_stable_abi_hex}"
+ fi
PYTHON3_SRC="if_python3.c"
PYTHON3_OBJ="objects/if_python3.o"
@@ -1693,6 +1713,9 @@ dnl with dlopen(), dlsym(), dlclose()
if test "$python_ok" = yes && test "$python3_ok" = yes; then
AC_DEFINE(DYNAMIC_PYTHON)
AC_DEFINE(DYNAMIC_PYTHON3)
+ if test "X$vi_cv_var_python3_stable_abi_hex" != "X"; then
+ AC_DEFINE(DYNAMIC_PYTHON3_STABLE_ABI)
+ fi
AC_MSG_CHECKING(whether we can do without RTLD_GLOBAL for Python)
cflags_save=$CFLAGS
CFLAGS="$CFLAGS $PYTHON_CFLAGS"
@@ -1816,6 +1839,9 @@ elif test "$python_ok" = yes; then
fi
elif test "$python3_ok" = yes && test "$enable_python3interp" = "dynamic"; then
AC_DEFINE(DYNAMIC_PYTHON3)
+ if test "X$vi_cv_var_python3_stable_abi_hex" != "X"; then
+ AC_DEFINE(DYNAMIC_PYTHON3_STABLE_ABI)
+ fi
PYTHON3_SRC="if_python3.c"
PYTHON3_OBJ="objects/if_python3.o"
PYTHON3_CFLAGS="$PYTHON3_CFLAGS -DDYNAMIC_PYTHON3_DLL=\\\"${vi_cv_dll_name_python3}\\\""
diff --git a/src/evalfunc.c b/src/evalfunc.c
index 8561e94d22..3e020bcde0 100644
--- a/src/evalfunc.c
+++ b/src/evalfunc.c
@@ -6167,6 +6167,13 @@ f_has(typval_T *argvars, typval_T *rettv)
0
#endif
},
+ {"python3_stable",
+#if defined(FEAT_PYTHON3) && defined(DYNAMIC_PYTHON3_STABLE_ABI)
+ 1
+#else
+ 0
+#endif
+ },
{"python3",
#if defined(FEAT_PYTHON3) && !defined(DYNAMIC_PYTHON3)
1
diff --git a/src/evalvars.c b/src/evalvars.c
index cb31966bd2..bd096dfd7b 100644
--- a/src/evalvars.c
+++ b/src/evalvars.c
@@ -157,6 +157,7 @@ static struct vimvar
{VV_NAME("sizeoflong", VAR_NUMBER), NULL, VV_RO},
{VV_NAME("sizeofpointer", VAR_NUMBER), NULL, VV_RO},
{VV_NAME("maxcol", VAR_NUMBER), NULL, VV_RO},
+ {VV_NAME("python3_version", VAR_NUMBER), NULL, VV_RO},
};
// shorthand
@@ -264,6 +265,10 @@ evalvars_init(void)
set_vim_var_dict(VV_COLORNAMES, dict_alloc());
+#ifdef FEAT_PYTHON3
+ set_vim_var_nr(VV_PYTHON3_VERSION, python3_version());
+#endif
+
// Default for v:register is not 0 but '"'. This is adjusted once the
// clipboard has been setup by calling reset_reg_var().
set_reg_var(0);
diff --git a/src/if_py_both.h b/src/if_py_both.h
index 110de234fd..ff18098605 100644
--- a/src/if_py_both.h
+++ b/src/if_py_both.h
@@ -30,9 +30,285 @@ static const char *vim_special_path = "_vim_path_";
#define PyErr_FORMAT2(exc, str, arg1, arg2) PyErr_Format(exc, _(str), arg1,arg2)
#define PyErr_VIM_FORMAT(str, arg) PyErr_FORMAT(VimError, str, arg)
-#define Py_TYPE_NAME(obj) ((obj)->ob_type->tp_name == NULL \
+#ifdef USE_LIMITED_API
+// Limited Python API. Need to call only exposed functions and remap macros.
+// PyTypeObject is an opaque struct.
+
+typedef struct {
+ lenfunc sq_length;
+ binaryfunc sq_concat;
+ ssizeargfunc sq_repeat;
+ ssizeargfunc sq_item;
+ void *was_sq_slice;
+ ssizeobjargproc sq_ass_item;
+ void *was_sq_ass_slice;
+ objobjproc sq_contains;
+
+ binaryfunc sq_inplace_concat;
+ ssizeargfunc sq_inplace_repeat;
+} PySequenceMethods;
+
+typedef struct {
+ lenfunc mp_length;
+ binaryfunc mp_subscript;
+ objobjargproc mp_ass_subscript;
+} PyMappingMethods;
+
+// This struct emulates the concrete _typeobject struct to allow the code to
+// work the same way in both limited and full Python APIs.
+struct typeobject_wrapper {
+ const char *tp_name;
+ Py_ssize_t tp_basicsize;
+ unsigned long tp_flags;
+
+ // When adding new slots below, also need to make sure we add ADD_TP_SLOT
+ // call in AddHeapType for it.
+
+ destructor tp_dealloc;
+ reprfunc tp_repr;
+
+ PySequenceMethods *tp_as_sequence;
+ PyMappingMethods *tp_as_mapping;
+
+ ternaryfunc tp_call;
+ getattrofunc tp_getattro;
+ setattrofunc tp_setattro;
+
+ const char *tp_doc;
+
+ traverseproc tp_traverse;
+
+ inquiry tp_clear;
+
+ getiterfunc tp_iter;
+ iternextfunc tp_iternext;
+
+ struct PyMethodDef *tp_methods;
+ struct _typeobject *tp_base;
+ allocfunc tp_alloc;
+ newfunc tp_new;
+ freefunc tp_free;
+};
+
+# define DEFINE_PY_TYPE_OBJECT(type) \
+ static struct typeobject_wrapper type; \
+ static PyTypeObject* type##Ptr = NULL
+
+// PyObject_HEAD_INIT_TYPE and PyObject_FINISH_INIT_TYPE need to come in pairs
+// We first initialize with NULL because the type is not allocated until
+// init_types() is called later. It's in FINISH_INIT_TYPE where we fill the
+// type in with the newly allocated type.
+# define PyObject_HEAD_INIT_TYPE(type) PyObject_HEAD_INIT(NULL)
+# define PyObject_FINISH_INIT_TYPE(obj, type) obj.ob_base.ob_type = type##Ptr
+
+# define Py_TYPE_GET_TP_ALLOC(type) ((allocfunc)PyType_GetSlot(type, Py_tp_alloc))
+# define Py_TYPE_GET_TP_METHODS(type) ((PyMethodDef *)PyType_GetSlot(type, Py_tp_methods))
+
+// PyObject_NEW is not part of stable ABI, but PyObject_Malloc/Init are.
+PyObject* Vim_PyObject_New(PyTypeObject *type, size_t objsize)
+{
+ PyObject *obj = (PyObject *)PyObject_Malloc(objsize);
+ if (obj == NULL)
+ return PyErr_NoMemory();
+ return PyObject_Init(obj, type);
+}
+# undef PyObject_NEW
+# define PyObject_NEW(type, typeobj) ((type *)Vim_PyObject_New(typeobj, sizeof(type)))
+
+// This is a somewhat convoluted because limited API doesn't expose an easy way
+// to get the tp_name field, and so we have to manually reconstruct it as
+// "__module__.__name__" (with __module__ omitted for builtins to emulate
+// Python behavior). Also, some of the more convenient functions like
+// PyUnicode_AsUTF8AndSize and PyType_GetQualName() are not available until
+// late Python 3 versions, and won't be available if you set Py_LIMITED_API too
+// low.
+# define PyErr_FORMAT_TYPE(msg, obj) \
+ do { \
+ PyObject* qualname = PyObject_GetAttrString((PyObject*)(obj)->ob_type, "__qualname__"); \
+ if (qualname == NULL) \
+ { \
+ PyErr_FORMAT(PyExc_TypeError, msg, "(NULL)"); \
+ break; \
+ } \
+ PyObject* module = PyObject_GetAttrString((PyObject*)(obj)->ob_type, "__module__"); \
+ PyObject* full; \
+ if (module == NULL || PyUnicode_CompareWithASCIIString(module, "builtins") == 0 \
+ || PyUnicode_CompareWithASCIIString(module, "__main__") == 0) \
+ { \
+ full = qualname; \
+ Py_INCREF(full); \
+ } \
+ else \
+ full = PyUnicode_FromFormat("%U.%U", module, qualname); \
+ PyObject* full_bytes = PyUnicode_AsUTF8String(full); \
+ const char* full_str = PyBytes_AsString(full_bytes); \
+ full_str = full_str == NULL ? "(NULL)" : full_str; \
+ PyErr_FORMAT(PyExc_TypeError, msg, full_str); \
+ Py_DECREF(qualname); \
+ Py_XDECREF(module); \
+ Py_XDECREF(full); \
+ Py_XDECREF(full_bytes); \
+ } while(0)
+
+# define PyList_GET_ITEM(list, i) PyList_GetItem(list, i)
+# define PyList_GET_SIZE(o) PyList_Size(o)
+# define PyTuple_GET_ITEM(o, pos) PyTuple_GetItem(o, pos)
+# define PyTuple_GET_SIZE(o) PyTuple_Size(o)
+
+// PyList_SET_ITEM and PyList_SetItem have slightly different behaviors. The
+// former will leave the old item dangling, and the latter will decref on it.
+// Since we only use this on new lists, this difference doesn't matter.
+# define PyList_SET_ITEM(list, i, item) PyList_SetItem(list, i, item)
+
+# if Py_LIMITED_API < 0x03080000
+// PyIter_check only became part of stable ABI in 3.8, and there is no easy way
+// to check for it in the API. We simply return false as a compromise. This
+// does mean we should avoid compiling with stable ABI < 3.8.
+# undef PyIter_Check
+# define PyIter_Check(obj) (FALSE)
+# endif
+
+PyTypeObject* AddHeapType(struct typeobject_wrapper* type_object)
+{
+ PyType_Spec type_spec;
+ type_spec.name = type_object->tp_name;
+ type_spec.basicsize = type_object->tp_basicsize;
+ type_spec.itemsize = 0;
+ type_spec.flags = type_object->tp_flags;
+
+ // We just need to statically allocate a large enough buffer that can hold
+ // all slots. We need to leave a null-terminated slot at the end.
+ PyType_Slot slots[40] = { {0, NULL} };
+ size_t slot_i = 0;
+
+# define ADD_TP_SLOT(slot_name) \
+ if (slot_i >= 40) return NULL; /* this should never happen */ \
+ if (type_object->slot_name != NULL) \
+ { \
+ slots[slot_i].slot = Py_##slot_name; \
+ slots[slot_i].pfunc = (void*)type_object->slot_name; \
+ ++slot_i; \
+ }
+# define ADD_TP_SUB_SLOT(sub_slot, slot_name) \
+ if (slot_i >= 40) return NULL; /* this should never happen */ \
+ if (type_object->sub_slot != NULL && type_object->sub_slot->slot_name != NULL) \
+ { \
+ slots[slot_i].slot = Py_##slot_name; \
+ slots[slot_i].pfunc = (void*)type_object->sub_slot->slot_name; \
+ ++slot_i; \
+ }
+
+ ADD_TP_SLOT(tp_dealloc)
+ ADD_TP_SLOT(tp_repr)
+ ADD_TP_SLOT(tp_call)
+ ADD_TP_SLOT(tp_getattro)
+ ADD_TP_SLOT(tp_setattro)
+ ADD_TP_SLOT(tp_doc)
+ ADD_TP_SLOT(tp_traverse)
+ ADD_TP_SLOT(tp_clear)
+ ADD_TP_SLOT(tp_iter)
+ ADD_TP_SLOT(tp_iternext)
+ ADD_TP_SLOT(tp_methods)
+ ADD_TP_SLOT(tp_base)
+ ADD_TP_SLOT(tp_alloc)
+ ADD_TP_SLOT(tp_new)
+ ADD_TP_SLOT(tp_free)
+
+ ADD_TP_SUB_SLOT(tp_as_sequence, sq_length)
+ ADD_TP_SUB_SLOT(tp_as_sequence, sq_concat)
+ ADD_TP_SUB_SLOT(tp_as_sequence, sq_repeat)
+ ADD_TP_SUB_SLOT(tp_as_sequence, sq_item)
+ ADD_TP_SUB_SLOT(tp_as_sequence, sq_ass_item)
+ ADD_TP_SUB_SLOT(tp_as_sequence, sq_contains)
+ ADD_TP_SUB_SLOT(tp_as_sequence, sq_inplace_concat)
+ ADD_TP_SUB_SLOT(tp_as_sequence, sq_inplace_repeat)
+
+ ADD_TP_SUB_SLOT(tp_as_mapping, mp_length)
+ ADD_TP_SUB_SLOT(tp_as_mapping, mp_subscript)
+ ADD_TP_SUB_SLOT(tp_as_mapping, mp_ass_subscript)
+# undef ADD_TP_SLOT
+# undef ADD_TP_SUB_SLOT
+
+ type_spec.slots = slots;
+
+ PyObject* newtype = PyType_FromSpec(&type_spec);
+ return (PyTypeObject*)newtype;
+}
+
+// Add a heap type, since static types do not work in limited API
+// Each PYTYPE_READY is paired with PYTYPE_CLEANUP.
+//
+// Note that we don't call Py_DECREF(type##Ptr) in clean up. The reason for
+// that in 3.7, it's possible to de-allocate a heap type before all instances
+// are cleared, leading to a crash, whereas in 3.8 the semantics were changed
+// and instances hold strong references to types. Since these types are
+// designed to be static, just keep them around to avoid having to write
+// version-specific handling. Vim does not re-start the Python runtime so there
+// will be no long-term leak.
+# define PYTYPE_READY(type) \
+ type##Ptr = AddHeapType(&(type)); \
+ if (type##Ptr == NULL) \
+ return -1;
+# define PYTYPE_CLEANUP(type) \
+ type##Ptr = NULL;
+
+// Limited API does not provide PyRun_* functions. Need to implement manually
+// using PyCompile and PyEval.
+PyObject* Vim_PyRun_String(const char *str, int start, PyObject *globals, PyObject *locals)
+{
+ // Just pass "" for filename for now.
+ PyObject* compiled = Py_CompileString(str, "", start);
+ if (compiled == NULL)
+ return NULL;
+
+ PyObject* eval_result = PyEval_EvalCode(compiled, globals, locals);
+ Py_DECREF(compiled);
+ return eval_result;
+}
+int Vim_PyRun_SimpleString(const char *str)
+{
+ // This function emulates CPython's implementation.
+ PyObject* m = PyImport_AddModule("__main__");
+ if (m == NULL)
+ return -1;
+ PyObject* d = PyModule_GetDict(m);
+ PyObject* output = Vim_PyRun_String(str, Py_file_input, d, d);
+ if (output == NULL)
+ {
+ PyErr_PrintEx(TRUE);
+ return -1;
+ }
+ Py_DECREF(output);
+ return 0;
+}
+#define PyRun_String Vim_PyRun_String
+#define PyRun_SimpleString Vim_PyRun_SimpleString
+
+#else // !defined(USE_LIMITED_API)
+
+// Full Python API. Can make use of structs and macros directly.
+# define DEFINE_PY_TYPE_OBJECT(type) \
+ static PyTypeObject type; \
+ static PyTypeObject* type##Ptr = &type
+# define PyObject_HEAD_INIT_TYPE(type) PyObject_HEAD_INIT(&type)
+
+# define Py_TYPE_GET_TP_ALLOC(type) type->tp_alloc
+# define Py_TYPE_GET_TP_METHODS(type) type->tp_methods
+
+# define Py_TYPE_NAME(obj) ((obj)->ob_type->tp_name == NULL \
? "(NULL)" \
: (obj)->ob_type->tp_name)
+# define PyErr_FORMAT_TYPE(msg, obj) \
+ PyErr_FORMAT(PyExc_TypeError, msg, \
+ Py_TYPE_NAME(obj))
+
+// Add a static type
+# define PYTYPE_READY(type) \
+ if (PyType_Ready(type##Ptr)) \
+ return -1;
+
+#endif
+
#define RAISE_NO_EMPTY_KEYS PyErr_SET_STRING(PyExc_ValueError, \
N_("empty keys are not allowed"))
@@ -45,8 +321,7 @@ static const char *vim_special_path = "_vim_path_";
#define RAISE_KEY_ADD_FAIL(key) \
PyErr_VIM_FORMAT(N_("failed to add key '%s' to dictionary"), key)
#define RAISE_INVALID_INDEX_TYPE(idx) \
- PyErr_FORMAT(PyExc_TypeError, N_("index must be int or slice, not %s"), \
- Py_TYPE_NAME(idx));
+ PyErr_FORMAT_TYPE(N_("index must be int or slice, not %s"), idx);
#define INVALID_BUFFER_VALUE ((buf_T *)(-1))
#define INVALID_WINDOW_VALUE ((win_T *)(-1))
@@ -144,13 +419,11 @@ StringToChars(PyObject *obj, PyObject **todecref)
else
{
#if PY_MAJOR_VERSION < 3
- PyErr_FORMAT(PyExc_TypeError,
- N_("expected str() or unicode() instance, but got %s"),
- Py_TYPE_NAME(obj));
+ PyErr_FORMAT_TYPE(N_("expected str() or unicode() instance, but got %s"),
+ obj);
#else
- PyErr_FORMAT(PyExc_TypeError,
- N_("expected bytes() or str() instance, but got %s"),
- Py_TYPE_NAME(obj));
+ PyErr_FORMAT_TYPE(N_("expected bytes() or str() instance, but got %s"),
+ obj);
#endif
return NULL;
}
@@ -198,15 +471,15 @@ NumberToLong(PyObject *obj, long *result, int flags)
else
{
#if PY_MAJOR_VERSION < 3
- PyErr_FORMAT(PyExc_TypeError,
+ PyErr_FORMAT_TYPE(
N_("expected int(), long() or something supporting "
"coercing to long(), but got %s"),
- Py_TYPE_NAME(obj));
+ obj);
#else
- PyErr_FORMAT(PyExc_TypeError,
+ PyErr_FORMAT_TYPE(
N_("expected int() or something supporting coercing to int(), "
"but got %s"),
- Py_TYPE_NAME(obj));
+ obj);
#endif
return -1;
}
@@ -278,7 +551,7 @@ ObjectDir(PyObject *self, char **attributes)
return NULL;
if (self)
- for (method = self->ob_type->