diff options
-rw-r--r-- | .github/workflows/ci.yml | 33 | ||||
-rw-r--r-- | runtime/doc/builtin.txt | 1 | ||||
-rw-r--r-- | runtime/doc/eval.txt | 19 | ||||
-rw-r--r-- | runtime/doc/if_pyth.txt | 26 | ||||
-rw-r--r-- | runtime/doc/tags | 6 | ||||
-rw-r--r-- | runtime/doc/various.txt | 2 | ||||
-rw-r--r-- | src/Make_cyg_ming.mak | 6 | ||||
-rw-r--r-- | src/Make_mvc.mak | 6 | ||||
-rwxr-xr-x | src/auto/configure | 48 | ||||
-rw-r--r-- | src/config.h.in | 3 | ||||
-rw-r--r-- | src/configure.ac | 34 | ||||
-rw-r--r-- | src/evalfunc.c | 7 | ||||
-rw-r--r-- | src/evalvars.c | 5 | ||||
-rw-r--r-- | src/if_py_both.h | 549 | ||||
-rw-r--r-- | src/if_python3.c | 167 | ||||
-rw-r--r-- | src/proto/if_python3.pro | 1 | ||||
-rw-r--r-- | src/version.c | 6 | ||||
-rw-r--r-- | src/vim.h | 3 |
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->tp_methods ; method->ml_name != NULL ; ++method) + for (method = Py_TYPE_GET_TP_METHODS(self->ob_type) ; method->ml_name != NULL ; ++method) if (add_string(ret, (char *)method->ml_name)) { Py_DECREF(ret); @@ -308,7 +581,7 @@ ObjectDir(PyObject *self, char **attributes) // Function to write a line, points to either msg() or emsg(). typedef int (*writefn)(char *); -static PyTypeObject OutputType; +DEFINE_PY_TYPE_OBJECT(OutputType); typedef struct { @@ -514,14 +787,14 @@ static struct PyMethodDef OutputMethods[] = { |