diff --git a/recipe/build_base.bat b/recipe/build_base.bat index a1eb1755..1596242d 100644 --- a/recipe/build_base.bat +++ b/recipe/build_base.bat @@ -1,6 +1,9 @@ setlocal EnableDelayedExpansion echo on +:: Avoids fetching nuget.exe from the internet. +set PYTHON=%CONDA_PYTHON_EXE% + :: Compile python, extensions and external libraries if "%ARCH%"=="64" ( set PLATFORM=x64 @@ -44,7 +47,7 @@ if "%DEBUG_C%"=="yes" ( set PGO=--pgo ) -if "%PY_GIL_DISABLED%" == "yes" ( +if "%PY_FREETHREADING%" == "yes" ( set "FREETHREADING=--disable-gil" set "THREAD=t" set "EXE_T=%VER%t" diff --git a/recipe/build_base.sh b/recipe/build_base.sh index 615fd05c..5018e48d 100644 --- a/recipe/build_base.sh +++ b/recipe/build_base.sh @@ -33,30 +33,16 @@ _buildd_shared=build-shared _ENABLE_SHARED=--enable-shared # We *still* build a shared lib here for non-static embedded use cases _DISABLE_SHARED=--disable-shared -# Hack to allow easily comparing static vs shared interpreter performance -# .. hack because we just build it shared in both the build-static and -# build-shared directories. -# Yes this hack is a bit confusing, sorry about that. -if [[ ${PY_INTERP_LINKAGE_NATURE} == shared ]]; then - _DISABLE_SHARED=--enable-shared - _ENABLE_SHARED=--enable-shared -fi # For debugging builds, set this to no to disable profile-guided optimization -if [[ ${DEBUG_C} == yes ]]; then +if [[ ${PY_INTERP_DEBUG} == yes ]]; then _OPTIMIZED=no else _OPTIMIZED=yes fi -if [[ ${target_platform} == linux-ppc64le ]]; then - _OPTIMIZED=no - # ppc64le cdt need to be rebuilt with files in powerpc64le-conda-linux-gnu instead of powerpc64le-conda_cos7-linux-gnu. In the mean time: - cp --force --archive --update --link $BUILD_PREFIX/powerpc64le-conda_cos7-linux-gnu/. $BUILD_PREFIX/powerpc64le-conda-linux-gnu -fi - declare -a _dbg_opts -if [[ ${DEBUG_PY} == yes ]]; then +if [[ ${PY_INTERP_DEBUG} == yes ]]; then # This Python will not be usable with non-debug Python modules. _dbg_opts+=(--with-pydebug) DBG=d @@ -64,15 +50,16 @@ else DBG= fi -if [[ ${PY_GIL_DISABLED} == yes ]]; then +if [[ ${PY_FREETHREADING} == yes ]]; then # This Python will not be usable with non-free threading Python modules. THREAD=t else THREAD= fi -ABIFLAGS=${DBG} +ABIFLAGS=${DBG}${THREAD} VERABI=${VER}${THREAD}${DBG} +VERABI_NO_DBG=${VER}${THREAD} # Make sure the "python" value in conda_build_config.yaml is up to date. test "${PY_VER}" = "${VER}" @@ -83,7 +70,7 @@ test "${PY_VER}" = "${VER}" unset _PYTHON_SYSCONFIGDATA_NAME unset _CONDA_PYTHON_SYSCONFIGDATA_NAME -# Prevent lib/python${VERABI}/_sysconfigdata_*.py from ending up with full paths to these things +# Prevent lib/python${VERABI_NO_DBG}/_sysconfigdata_*.py from ending up with full paths to these things # in _build_env because _build_env will not get found during prefix replacement, only _h_env_placeh ... AR=$(basename "${AR}") @@ -111,6 +98,12 @@ if [[ ${_OPTIMIZED} = yes ]]; then CXXFLAGS=$(echo "${CXXFLAGS}" | sed "s/-O2/-O3/g") fi +if [[ ${PY_INTERP_DEBUG} == yes ]]; then + CPPFLAGS=$(echo "${CPPFLAGS}" | sed "s/-O2/-O0/g") + CFLAGS=$(echo "${CFLAGS}" | sed "s/-O2/-O0/g") + CXXFLAGS=$(echo "${CXXFLAGS}" | sed "s/-O2/-O0/g") +fi + if [[ "$target_platform" == linux-* ]]; then cp ${PREFIX}/include/uuid/uuid.h ${PREFIX}/include/uuid.h fi @@ -258,7 +251,7 @@ _common_configure_args+=("--with-tcltk-libs=-L${PREFIX}/lib -ltcl${TCLTK_VER} -l _common_configure_args+=(--with-platlibdir=lib) _common_configure_args+=(--with-system-libmpdec=yes) -if [[ "${DEBUG_PY}" == "yes" || "${target_platform}" != *"64" || ${PY_GIL_DISABLED} == yes ]]; then +if [[ "${PY_INTERP_DEBUG}" == "yes" || "${target_platform}" != *"-64" || ${PY_FREETHREADING} == yes ]]; then _common_configure_args+=(--enable-experimental-jit=no) else _common_configure_args+=(--enable-experimental-jit=yes-off) @@ -269,7 +262,7 @@ if [[ "${target_platform}" == osx-* ]]; then _common_configure_args+=(--with-tail-call-interp) fi -if [[ ${PY_GIL_DISABLED} == yes ]]; then +if [[ ${PY_FREETHREADING} == yes ]]; then _common_configure_args+=(--disable-gil) fi @@ -365,7 +358,7 @@ fi # build a static library with PIC objects and without LTO/PGO make -j${CPU_COUNT} -C ${_buildd_shared} \ EXTRA_CFLAGS="${EXTRA_CFLAGS}" \ - LIBRARY=libpython${VERABI}-pic.a libpython${VERABI}-pic.a + LIBRARY=libpython${VERABI_NO_DBG}-pic.a libpython${VERABI_NO_DBG}-pic.a make -C ${_buildd_static} install @@ -397,15 +390,17 @@ fi SYSCONFIG=$(find ${_buildd_static}/$(cat ${_buildd_static}/pybuilddir.txt) -name "_sysconfigdata*.py" -print0) cat ${SYSCONFIG} | ${SYS_PYTHON} "${RECIPE_DIR}"/replace-word-pairs.py \ "${_FLAGS_REPLACE[@]}" \ - > ${PREFIX}/lib/python${VERABI}/$(basename ${SYSCONFIG}) -MAKEFILE=$(find ${PREFIX}/lib/python${VERABI}/ -path "*config-*/Makefile" -print0) + > ${PREFIX}/lib/python${VERABI_NO_DBG}/$(basename ${SYSCONFIG}) +BUILD_DETAILS=${_buildd_shared}/$(cat ${_buildd_shared}/pybuilddir.txt)/build-details.json +cp ${BUILD_DETAILS} ${PREFIX}/lib/python${VERABI_NO_DBG}/ +MAKEFILE=$(find ${PREFIX}/lib/python${VERABI_NO_DBG}/ -path "*config-*/Makefile" -print0) cp ${MAKEFILE} /tmp/Makefile-$$ cat /tmp/Makefile-$$ | ${SYS_PYTHON} "${RECIPE_DIR}"/replace-word-pairs.py \ "${_FLAGS_REPLACE[@]}" \ > ${MAKEFILE} # Check to see that our differences took. -# echo diff -urN ${SYSCONFIG} ${PREFIX}/lib/python${VERABI}/$(basename ${SYSCONFIG}) -# diff -urN ${SYSCONFIG} ${PREFIX}/lib/python${VERABI}/$(basename ${SYSCONFIG}) +# echo diff -urN ${SYSCONFIG} ${PREFIX}/lib/python${VERABI_NO_DBG}/$(basename ${SYSCONFIG}) +# diff -urN ${SYSCONFIG} ${PREFIX}/lib/python${VERABI_NO_DBG}/$(basename ${SYSCONFIG}) # Python installs python${VER}m and python${VER}, one as a hardlink to the other. conda-build breaks these # by copying. Since the executable may be static it may be very large so change one to be a symlink @@ -424,7 +419,7 @@ ln -s ${PREFIX}/bin/python${VER} ${PREFIX}/bin/python3.1 # Remove test data to save space # Though keep `support` as some things use that. # TODO :: Make a subpackage for this once we implement multi-level testing. -pushd ${PREFIX}/lib/python${VERABI} +pushd ${PREFIX}/lib/python${VERABI_NO_DBG} mkdir test_keep mv test/__init__.py test/support test/test_support* test/test_script_helper* test_keep/ rm -rf test */test @@ -437,33 +432,17 @@ pushd ${PREFIX} chmod +w lib/libpython${VERABI}.a ${STRIP} -S lib/libpython${VERABI}.a fi - CONFIG_LIBPYTHON=$(find lib/python${VERABI}/config-${VERABI}* -name "libpython${VERABI}.a") + CONFIG_LIBPYTHON=$(find lib/python${VERABI_NO_DBG}/config-${VERABI}* -name "libpython${VERABI}.a") if [[ -f lib/libpython${VERABI}.a ]] && [[ -f ${CONFIG_LIBPYTHON} ]]; then chmod +w ${CONFIG_LIBPYTHON} rm ${CONFIG_LIBPYTHON} fi popd -# OLD_HOST is with CentOS version in them. When building this recipe -# with the compilers from conda-forge OLD_HOST != HOST, but when building -# with the compilers from defaults OLD_HOST == HOST. Both cases are handled in the -# code below -case "$target_platform" in - linux-64) - OLD_HOST=$(echo ${HOST} | sed -e 's/-conda-/-conda_cos6-/g') - ;; - linux-*) - OLD_HOST=$(echo ${HOST} | sed -e 's/-conda-/-conda_cos7-/g') - ;; - *) - OLD_HOST=$HOST - ;; -esac - # Copy sysconfig that gets recorded to a non-default name # using the new compilers with python will require setting _PYTHON_SYSCONFIGDATA_NAME # to the name of this file (minus the .py extension) -pushd "${PREFIX}"/lib/python${VERABI} +pushd "${PREFIX}"/lib/python${VERABI_NO_DBG} # On Python 3.5 _sysconfigdata.py was getting copied in here and compiled for some reason. # This breaks our attempt to find the right one as recorded_name. find lib-dynload -name "_sysconfigdata*.py*" -exec rm {} \; @@ -478,9 +457,9 @@ pushd "${PREFIX}"/lib/python${VERABI} # Append the conda-forge zoneinfo to the end sed -i.bak "s@zoneinfo'@zoneinfo:$PREFIX/share/tzinfo'@g" sysconfigfile # Remove osx sysroot as it depends on the build machine - sed -i.bak "s@-isysroot @@g" sysconfigfile - # make sure $CONDA_BUILD_SYSROOT is not empty ... - if [[ ${HOST} =~ .*darwin.* ]] && [[ -n ${CONDA_BUILD_SYSROOT} ]]; then + # be sure CONDA_BUILD_SYSROOT has value, as other we will remove here instead spaces + if [[ "${target_platform}" == osx-* ]] && [[ -n ${CONDA_BUILD_SYSROOT} ]]; then + sed -i.bak "s@-isysroot @@g" sysconfigfile sed -i.bak "s@$CONDA_BUILD_SYSROOT @@g" sysconfigfile fi # Remove unfilled config option @@ -490,16 +469,12 @@ pushd "${PREFIX}"/lib/python${VERABI} sed -i.bak "s/'GNULD': 'yes'/'GNULD': 'no'/g" sysconfigfile cp sysconfigfile ${our_compilers_name} - sed -i.bak "s@${HOST}@${OLD_HOST}@g" sysconfigfile - old_compiler_name=_sysconfigdata_$(echo ${OLD_HOST} | sed -e 's/[.-]/_/g').py - cp sysconfigfile ${old_compiler_name} - # For system gcc remove the triple - sed -i.bak "s@$OLD_HOST-c++@g++@g" sysconfigfile - sed -i.bak "s@$OLD_HOST-@@g" sysconfigfile + sed -i.bak "s@$HOST-c++@g++@g" sysconfigfile + sed -i.bak "s@$HOST-@@g" sysconfigfile if [[ "$target_platform" == linux* ]]; then # For linux, make sure the system gcc uses our linker - sed -i.bak "s@-pthread@-pthread -B $PREFIX/compiler_compat@g" sysconfigfile + sed -i.bak "s@-pthread@-pthread -B $PREFIX/share/python_compiler_compat@g" sysconfigfile fi # Don't set -march and -mtune for system gcc sed -i.bak "s@-march=[^( |\\\"|\\\')]*@@g" sysconfigfile @@ -524,14 +499,22 @@ pushd "${PREFIX}"/lib/python${VERABI} popd if [[ ${HOST} =~ .*linux.* ]]; then - mkdir -p ${PREFIX}/compiler_compat - ln -s ${PREFIX}/bin/${HOST}-ld ${PREFIX}/compiler_compat/ld - echo "Files in this folder are to enhance backwards compatibility of anaconda software with older compilers." > ${PREFIX}/compiler_compat/README - echo "See: https://github.com/conda/conda/issues/6030 for more information." >> ${PREFIX}/compiler_compat/README + mkdir -p ${PREFIX}/share/python_compiler_compat + ln -s ${PREFIX}/bin/${HOST}-ld ${PREFIX}/share/python_compiler_compat/ld + echo "Files in this folder are to enhance backwards compatibility of anaconda software with older compilers." > ${PREFIX}/share/python_compiler_compat/README + echo "See: https://github.com/conda/conda/issues/6030 for more information." >> ${PREFIX}/share/python_compiler_compat/README fi python -c "import compileall,os;compileall.compile_dir(os.environ['PREFIX'])" rm ${PREFIX}/lib/libpython${VERABI}.a + +if [[ ${PY_INTERP_DEBUG} == yes ]]; then + rm ${PREFIX}/bin/python${VER} + ln -s ${PREFIX}/bin/python${VERABI} ${PREFIX}/bin/python${VER} + ln -s ${PREFIX}/lib/libpython${VERABI}${SHLIB_EXT} ${PREFIX}/lib/libpython${VERABI_NO_DBG}${SHLIB_EXT} + ln -s ${PREFIX}/include/python${VERABI} ${PREFIX}/include/python${VER} +fi + if [[ "$target_platform" == linux-* ]]; then rm ${PREFIX}/include/uuid.h fi @@ -549,7 +532,7 @@ fi # /lib/python3.13t/site-packages. # Note that these directories are not added to sys.path if they do not exist. SP_DIR="${PREFIX}/lib/python${PY_VER}${THREAD}/site-packages" -if [[ ${PY_GIL_DISABLED} == yes ]]; then +if [[ ${PY_FREETHREADING} == yes ]]; then echo "${PREFIX}/lib/python${PY_VER}/site-packages" >> $SP_DIR/conda-site.pth fi # Workaround for https://github.com/conda/conda/issues/10969 diff --git a/recipe/build_static.sh b/recipe/build_static.sh index 9b5f3908..f7724373 100644 --- a/recipe/build_static.sh +++ b/recipe/build_static.sh @@ -1,46 +1,33 @@ #!/bin/bash set -ex -cd ${SRC_DIR} - _buildd_static=build-static _buildd_shared=build-shared -if [[ ${DEBUG_PY} == yes ]]; then +if [[ ${PY_INTERP_DEBUG} == yes ]]; then DBG=d else DBG= fi -if [[ ${PY_GIL_DISABLED} == yes ]]; then +if [[ ${PY_FREETHREADING} == yes ]]; then + # This Python will not be usable with non-free threading Python modules. THREAD=t else THREAD= fi -VER=${PKG_VERSION%.*} -VERABI=${VER}${DBG}${THREAD} -case "$target_platform" in - linux-64) - OLD_HOST=$(echo ${HOST} | sed -e 's/-conda_cos6//g') - OLD_HOST=$(echo ${OLD_HOST} | sed -e 's/-conda_cos7//g') - OLD_HOST=$(echo ${OLD_HOST} | sed -e 's/-conda//g') - ;; - linux-*) - OLD_HOST=$(echo ${HOST} | sed -e 's/-conda_cos7//g') - OLD_HOST=$(echo ${OLD_HOST} | sed -e 's/-conda//g') - ;; - *) - OLD_HOST=$HOST - ;; -esac +VER=${PKG_VERSION%.*} +ABIFLAGS=${DBG}${THREAD} +VERABI=${VER}${THREAD}${DBG} +VERABI_NO_DBG=${VER}${THREAD} cp -pf ${_buildd_static}/libpython${VERABI}.a ${PREFIX}/lib/libpython${VERABI}.a if [[ ${HOST} =~ .*linux.* ]]; then - pushd ${PREFIX}/lib/python${VERABI}/config-${VERABI}-${OLD_HOST} + pushd ${PREFIX}/lib/python${VERABI_NO_DBG}/config-${VERABI}-${HOST/-conda/} elif [[ ${HOST} =~ .*darwin.* ]]; then - pushd ${PREFIX}/lib/python${VERABI}/config-${VERABI}-darwin + pushd ${PREFIX}/lib/python${VERABI_NO_DBG}/config-${VERABI}-darwin fi ln -s ../../libpython${VERABI}.a libpython${VERABI}.a popd # If the LTO info in the normal lib is problematic (using different compilers for example # we also provide a 'nolto' version). -cp -pf ${_buildd_shared}/libpython${VERABI}-pic.a ${PREFIX}/lib/libpython${VERABI}.nolto.a +cp -pf ${_buildd_shared}/libpython${VERABI_NO_DBG}-pic.a ${PREFIX}/lib/libpython${VERABI_NO_DBG}.nolto.a diff --git a/recipe/conda_build_config.yaml b/recipe/conda_build_config.yaml index b8c77e32..aabd797f 100644 --- a/recipe/conda_build_config.yaml +++ b/recipe/conda_build_config.yaml @@ -4,7 +4,9 @@ python_impl: - cpython numpy: - 2.5 -gil_type: - - normal - # Will be enabled as part of https://anaconda.atlassian.net/browse/PKG-5855 - - disabled # [not (s390x or (osx and x86_64))] +freethreading: + - yes + - no # [not (s390x or (osx and x86_64))] +build_type: + - release + - debug # [not win] diff --git a/recipe/meta.yaml b/recipe/meta.yaml index 030624bf..4950b032 100644 --- a/recipe/meta.yaml +++ b/recipe/meta.yaml @@ -14,55 +14,21 @@ # Sanitize build system env. var tweak parameters # (passed to the build scripts via script_env). -{% set from_source_control = os.environ.get('CONDA_BUILD_FROM_SOURCE_CONTROL', '') %} -{% if from_source_control == '' or from_source_control == 'no' %} - {% set from_source_control = 'no' %} -{% else %} - {% set from_source_control = 'yes' %} -{% endif %} -{% set linkage_nature = os.environ.get('PY_INTERP_LINKAGE_NATURE', '') %} -{% if linkage_nature != '' %} - {% set linkage_nature = "_" ~ linkage_nature %} -{% endif %} -{% if linkage_nature == 'shared' %} - {% set linkage_nature_env = 'shared' %} -{% else %} - {% set linkage_nature_env = 'static' %} -{% endif %} -{% set dbg_abi = "" %} -{% set debug = os.environ.get('PY_INTERP_DEBUG', '') %} -{% if debug != '' and debug != 'no' %} - {% set py_interp_debug = "yes" %} - {% set debug = "_dbg" %} - {% set dbg_abi = "d" %} # [win] -{% else %} - {% set py_interp_debug = "no" %} -{% endif %} - -# no-GIL, set using conda_build_config.yaml -{% if gil_type is defined and gil_type == "disabled" %} - {% set py_gil_disabled = "yes" %} - {% set abi_suffix = "t" %} -{% else %} - {% set py_gil_disabled = "no" %} - {% set abi_suffix = "" %} - {% set build_number = build_number + 100 %} -{% endif %} -{% set abi_tag = "cp" + ver2nd + abi_suffix %} +{% set abi_tag = "cp" + ver2nd + ("t" if freethreading == "yes" else "") %} +{% set build_number = build_number + (0 if freethreading == "yes" else 100) %} +{% set py_interp_debug = "yes" if build_type == "debug" else "no" %} +{% set debug = "_debug" if build_type == "debug" else "" %} +{% set site_packages_path_unix = ("lib/python" + ver2 + "t/site-packages" if freethreading == "yes" else "lib/python" + ver2 + "/site-packages") %} +{% set site_packages_path = "Lib/site-packages" if win else site_packages_path_unix %} package: name: python-split version: {{ version }}{{ dev }} source: -{% if from_source_control == 'yes' %} - - git_url: https://github.com/python/CPython.git - git_tag: v{{ version }}{{ dev }} -{% else %} - url: https://www.python.org/ftp/python/{{ version }}/Python-{{ version }}{{ dev }}.tar.xz # md5 from: https://www.python.org/downloads/release/python-{{ ver3nd }}/ sha256: 6a935ae234a67e6549894373b0cfeb8361182d03b21442328ae9598ab7422127 -{% endif %} patches: - patches/0001-Win32-Change-FD_SETSIZE-from-512-to-2048.patch - patches/0002-Win32-Do-not-download-externals.patch @@ -93,18 +59,14 @@ source: - patches/0023-branding.patch - patches/0024-Unvendor-zlib-ng.patch - patches/0025-Unvendor-zstd.patch - - patches/0026-xxlimited-freethreading-stable-abi-link.patch # [win] + - patches/0026-xxlimited-freethreading-stable-abi-link.patch # [win and freethreading == "yes"] build: number: {{ build_number }} requirements: build: -{% if from_source_control == 'yes' %} - - git -{% else %} - msys2-gcc-libs # [win] -{% endif %} outputs: - name: python @@ -134,32 +96,21 @@ outputs: # - lib/libpython3.so # [linux] # - lib/python{{ ver2 }}/lib-dynload/_hashlib.cpython-{{ ver2nd }}-x86_64-linux-gnu.so # [linux] # - lib/libpython3.dylib # [osx] - # match python.org compiler standard - skip: true # [win and int(float(vc)) < 14] -{% if 'conda-forge' in channel_targets %} - skip_compile_pyc: - - '*.py' # [build_platform != target_platform] -{% endif %} - string: {{ dev_ }}h{{ PKG_HASH }}_{{ PKG_BUILDNUM }}{{ linkage_nature }}{{ debug }}_{{ abi_tag }} - track_features: # [gil_type == "disabled"] - - free-threading # [gil_type == "disabled"] + # match python.org compiler standard (vc14+ only for 3.15) + skip: true # [win and vc < 14] + string: {{ dev_ }}h{{ PKG_HASH }}_{{ PKG_BUILDNUM }}{{ debug }}_{{ abi_tag }} + python_site_packages_path: {{ site_packages_path }} + track_features: # [freethreading == "yes"] + - py_freethreading # [freethreading == "yes"] run_exports: noarch: - python weak: - python_abi {{ ver2 }}.* *_{{ abi_tag }} + - python {{ ver2 }}.* *_debug_{{ abi_tag }} # [build_type == "debug"] script_env: - - PY_INTERP_LINKAGE_NATURE={{ linkage_nature_env }} - PY_INTERP_DEBUG={{ py_interp_debug }} - - PY_GIL_DISABLED={{ py_gil_disabled }} - # Putting these here means they get emitted to build_env_setup.{sh,bat} meaning we can launch IDEs - # after sourcing or calling that script without examine the contents of conda_build.{sh,bat} for - # important env. vars. -{% if 'conda-forge' in channel_targets %} - - CONDA_FORGE=yes -{% else %} - - CONDA_FORGE=no -{% endif %} + - PY_FREETHREADING={{ freethreading }} missing_dso_whitelist: # [win or linux] - '**/MSVCR71.dll' # [win] - '**/MSVCR80.dll' # [win] @@ -189,9 +140,7 @@ outputs: - ld_impl_{{ target_platform }} # [linux] # called in build_base.sh - ripgrep # [not win] -{% if 'conda-forge' in channel_targets %} - binutils_impl_{{ target_platform }} # [linux] -{% endif %} # https://github.com/python/cpython/blob/main/Tools/jit/README.md#installing-llvm: # "LLVM version 21 is the officially supported version" - clang-21 # [not win] @@ -223,22 +172,12 @@ outputs: - python_abi * *_{{ abi_tag }} - ld_impl_{{ target_platform }} >=2.35.1 # [linux] - tzdata -{% if 'conda-forge' in channel_targets %} - - ncurses # [unix] - run_constrained: - - python_abi {{ ver2 }}.* *_cp{{ ver2nd }} -{% endif %} test: -{% if 'conda-forge' in channel_targets %} - downstreams: - - cython - - setuptools -{% endif %} requires: - ripgrep - - cmake-no-system + - cmake - make # [unix] - - ninja-base + - ninja - {{ stdlib('c') }} - {{ compiler('c') }} # Tried to use enable_language(C) to avoid needing this. It does not work. @@ -248,7 +187,6 @@ outputs: - tests/cython/* - tests/prefix-replacement/* - run_test.py - - run_test_py315.py commands: - echo on # [win] - set # [win] @@ -282,56 +220,49 @@ outputs: - if not exist %PREFIX%\\Scripts\\idle-script.py exit 1 # [win] - if not exist %PREFIX%\\Scripts\\idle.exe exit 1 # [win] - if not exist %PREFIX%\\Scripts\\pydoc.exe exit 1 # [win] - - if not exist %PREFIX%\\include\\pyconfig.h exit 1 # [win] - - if not exist %PREFIX%\\libs\\python{{ ver2nd }}{{ abi_suffix }}.lib exit 1 # [win] + - if not exist %PREFIX%\include\pyconfig.h exit 1 # [win] + - if not exist %PREFIX%\libs\python3t.lib exit 1 # [win and freethreading == "yes"] + - if not exist %PREFIX%\libs\python{{ ver2nd }}t.lib exit 1 # [win and freethreading == "yes"] + - if not exist %PREFIX%\libs\python3.lib exit 1 # [win and freethreading == "no"] + - if not exist %PREFIX%\libs\python{{ ver2nd }}.lib exit 1 # [win and freethreading == "no"] - pushd tests - pushd prefix-replacement # [unix] - bash build-and-test.sh # [unix] - popd # [unix] - pushd cmake -{% if gil_type is defined and gil_type == "disabled" %} - # TODO: cmake does not know about the free-threading ABI and fails to - # identify libpython3.13t.dylib as a valid PythonLibs target. - # Skip this test for the time being. - # https://gitlab.kitware.com/cmake/cmake/-/issues/26016 - - cmake -GNinja -DPY_VER={{ version }} --debug-find --trace --debug-output --debug-trycompile . || true # [unix] - - cmake -GNinja -DPY_VER={{ version }} --debug-find --trace --debug-output --debug-trycompile . || type nul>nul # [win] -{% else %} +{% if freethreading == "no" %} + # TODO: use the new FindPython3 which supports freethreading - cmake -GNinja -DPY_VER={{ version }} --debug-find --trace --debug-output --debug-trycompile . {% endif %} - popd - popd - python run_test.py - - export RUN_TEST_REQUIRE_315=1 && python run_test_py315.py # [unix] - - set RUN_TEST_REQUIRE_315=1 && python run_test_py315.py # [win] - test ! -f default.profraw # [osx] # Test workaround for https://github.com/conda/conda/issues/10969 - python3.1 --version # [unix] # Test for segfault on osx-64 with libffi=3.4, see https://bugs.python.org/issue44556 - python -c "from ctypes import CFUNCTYPE; CFUNCTYPE(None)(id)" -{% if bootstrap != "true" %} - name: libpython-static script: build_static.sh # [unix] script: build_static.bat # [win] build: number: {{ build_number }} force_use_keys: - - gil_type + - freethreading + - build_type activate_in_script: true ignore_run_exports: - python_abi - string: h{{ PKG_HASH }}_{{ PKG_BUILDNUM }}{{ linkage_nature }}{{ debug }}_{{ abi_tag }} + string: h{{ PKG_HASH }}_{{ PKG_BUILDNUM }}{{ debug }}_{{ abi_tag }} script_env: - - PY_GIL_DISABLED={{ py_gil_disabled }} + - PY_INTERP_DEBUG={{ py_interp_debug }} + - PY_FREETHREADING={{ freethreading }} requirements: build: - {{ stdlib('c') }} - {{ compiler('c') }} - {{ compiler('cxx') }} -{% if from_source_control == 'yes' %} - - git -{% endif %} host: - {{ pin_subpackage('python', exact=True) }} run: @@ -347,22 +278,22 @@ outputs: - ripgrep commands: - - pushd tests # [unix] - - pushd prefix-replacement # [unix] - - test -f ${PREFIX}/lib/libpython${PKG_VERSION%.*}{{ abi_suffix }}.a # [unix] - - test -f ${PREFIX}/lib/libpython${PKG_VERSION%.*}{{ abi_suffix }}.nolto.a # [unix] - - test -f ${PREFIX}/lib/python${PKG_VERSION%.*}{{ abi_suffix }}/config-${PKG_VERSION%.*}{{ abi_suffix }}-darwin/libpython${PKG_VERSION%.*}{{ abi_suffix }}.a # [osx] + - VER=${PKG_VERSION%.*} # [not win] + - VERABI=${VER} # [not win] + - VERABI=${VERABI}t # [not win and freethreading == "yes"] + - VERABI_NO_DBG=${VERABI} # [not win] + - VERABI=${VERABI}d # [not win and build_type == "debug"] + - test -f ${PREFIX}/lib/libpython${VERABI}.a # [unix] + - test -f ${PREFIX}/lib/libpython${VERABI_NO_DBG}.nolto.a # [unix] + - test -f ${PREFIX}/lib/python${VERABI_NO_DBG}/config-${VERABI}-darwin/libpython${VERABI}.a # [osx] + - pushd tests/prefix-replacement # [unix] - bash build-and-test.sh # [unix] - popd # [unix] - - popd # [unix] -{% endif %} - name: python-freethreading build: noarch: generic -{% if gil_type == "normal" %} - skip: True -{% endif %} + skip: true # [freethreading == "no"] requirements: - python {{ version }}{{ dev }}.* - python_abi * *_{{ abi_tag }} @@ -370,9 +301,7 @@ outputs: - name: python-gil build: noarch: generic -{% if gil_type == "disabled" %} - skip: True -{% endif %} + skip: true # [freethreading == "yes"] requirements: - python {{ version }}{{ dev }}.* - python_abi * *_{{ abi_tag }} @@ -381,10 +310,10 @@ outputs: script: install_jit_activation.sh # [not win] script: install_jit_activation.bat # [win] build: -{% if gil_type == "disabled" %} - skip: True -{% endif %} - skip: true # [py<313] + skip: true # [freethreading == "yes"] + skip: true # [build_type == "debug"] + # Mirror build_base.sh JIT gate: yes-off only when not debug, not freethreading, and target_platform matches *-64 + skip: true # [not linux64 and not win64 and not (osx and x86_64)] noarch: generic requirements: - python {{ version }}{{ dev }} diff --git a/recipe/run_test.py b/recipe/run_test.py index 97f9dc3f..67af9345 100644 --- a/recipe/run_test.py +++ b/recipe/run_test.py @@ -17,6 +17,8 @@ print('tuple.__itemsize__:', tuple.__itemsize__) if sys.platform == 'win32': assert 'MSC v.19' in sys.version +if hasattr(sys, "abiflags"): + print('sys.abiflags', sys.abiflags) print('sys.maxunicode:', sys.maxunicode) print('platform.architecture:', platform.architecture()) print('platform.python_version:', platform.python_version()) @@ -67,12 +69,18 @@ import test.support import unicodedata import zlib +import compression.zstd from os import urandom import os t = 100 * b'Foo ' assert lzma.decompress(lzma.compress(t)) == t +if os.getenv('PY_INTERP_DEBUG') == 'yes': + if sys.platform != 'win32': + assert 'd' in sys.abiflags + assert 'gettotalrefcount' in dir(sys) + if sys.platform != 'win32': if not (ppc64le or armv7l): import _curses diff --git a/recipe/run_test_py315.py b/recipe/run_test_py315.py deleted file mode 100644 index 63a29749..00000000 --- a/recipe/run_test_py315.py +++ /dev/null @@ -1,366 +0,0 @@ -# Python 3.15 conda-build feature test — verified against 3.15.0b3 (2026 series). -# -# Design contract: -# - HARD checks (interpreter identity, core stdlib, ABI/build sanity) -> failure -# fails the conda-build test phase (non-zero exit). -# - SOFT checks (new PEP feature probes) -> reported as PASS/SKIP, never fail -# the build UNLESS one of: -# * RUN_TEST_STRICT=1 in the environment (manual promote SOFT->HARD), or -# * we are running on rc1 or later (auto-promote — PEP feature spellings -# are frozen at the RC boundary per PEP 790, so a SKIP after that date -# means we're building against a stale probe). -# -# Sources: -# - Python 3.15 What's New: https://docs.python.org/3.15/whatsnew/3.15.html -# - PEP 790 (release schedule): https://peps.python.org/pep-0790/ — RC1 2026-08-04 -# - PEP-specific references embedded in each probe below. - -from __future__ import annotations - -import builtins -import importlib.util -import os -import platform -import site -import sys -import sysconfig -from dataclasses import dataclass - -PASS, SKIP, FAIL = "PASS", "SKIP", "FAIL" -TARGET_VERSION = (3, 15) -IS_TARGET = sys.version_info[:2] == TARGET_VERSION - -# Manual override for local development / CI hardening. -STRICT_ENV = os.environ.get("RUN_TEST_STRICT", "0") == "1" - -# Auto-promote at rc1 — after 2026-08-04 the PEP APIs are frozen and any SKIP -# from a feature probe means the probe is stale, not that the feature is absent. -# Rationale: probes are advisory during beta only. -AUTO_STRICT = IS_TARGET and sys.version_info.releaselevel != "beta" -STRICT = STRICT_ENV or AUTO_STRICT - -# For the dedicated python-3.15 feedstock, set RUN_TEST_REQUIRE_315=1 so a -# non-3.15 interpreter is a HARD failure instead of a skip (catches bad matrices). -REQUIRE_TARGET = os.environ.get("RUN_TEST_REQUIRE_315", "0") == "1" - - -@dataclass -class Result: - name: str - status: str - detail: str = "" - hard: bool = False # if True, a FAIL aborts the test phase - - -_results: list[Result] = [] - - -def record(name: str, status: str, detail: str = "", *, hard: bool = False) -> None: - _results.append(Result(name, status, detail, hard)) - - -def _spec_exists(modname: str) -> bool: - # `find_spec` is safer than `import` — it does not execute module top-level - # code, which matters for probes on packages that may hold heavy imports. - try: - return importlib.util.find_spec(modname) is not None - except (ImportError, ValueError): - return False - - -def _compiles(src: str) -> bool: - # Probe a syntax-level PEP without executing it. - try: - compile(src, "", "exec") - return True - except SyntaxError: - return False - - -# --------------------------------------------------------------------------- # -# HARD checks — must hold for any correct 3.15 build -# --------------------------------------------------------------------------- # - -def check_interpreter_identity() -> None: - ver = (f"{platform.python_version()} " - f"({sys.version_info.releaselevel} {sys.version_info.serial})") - if IS_TARGET: - record("interpreter 3.15", PASS, ver, hard=True) - elif REQUIRE_TARGET: - record("interpreter 3.15", FAIL, - f"expected 3.15, got {sys.version_info[0]}.{sys.version_info[1]}", - hard=True) - else: - record("interpreter 3.15", SKIP, f"not target ({ver}) -> probes skipped") - - -def check_core_stdlib() -> None: - # C-extension stdlib modules are the usual silent-failure surface: a build - # that lacks system libs (ssl/lzma/sqlite/...) imports the pure parts fine - # but breaks at runtime. Importing them here turns that into a build failure. - required = ["ssl", "ctypes", "sqlite3", "lzma", "bz2", "zlib", - "hashlib", "decimal", "readline", "curses"] - missing = [] - for mod in required: - # readline/curses are absent on Windows by design. - if os.name == "nt" and mod in {"readline", "curses"}: - continue - try: - __import__(mod) - except ImportError as exc: - missing.append(f"{mod} ({exc})") - if missing: - record("core C-extension stdlib", FAIL, "; ".join(missing), hard=True) - else: - record("core C-extension stdlib", PASS, "all importable", hard=True) - - -def check_build_sanity() -> None: - # Cross-check the build prefix matches the test env's interpreter. - prefix_ok = sys.prefix and os.path.isdir(sys.prefix) - abiflags = getattr(sys, "abiflags", "") - record( - "build sanity", - PASS if prefix_ok else FAIL, - f"prefix={sys.prefix} abiflags='{abiflags}'", - hard=True, - ) - - -# --------------------------------------------------------------------------- # -# SOFT probes — 3.15 new features (verified against b3; frozen at rc1) -# --------------------------------------------------------------------------- # - -def probe_pep686_utf8_default() -> None: - # PEP 686: UTF-8 is the unconditional default in 3.15 (independent of locale). - # Behavioral change is most visible on Windows; on POSIX with UTF-8 locales - # it was already effectively UTF-8. sys.flags.utf8_mode == 1 by default now. - mode = getattr(sys.flags, "utf8_mode", None) - if mode == 1: - record("PEP 686 UTF-8 default", PASS, "sys.flags.utf8_mode=1") - else: - record("PEP 686 UTF-8 default", SKIP, f"utf8_mode={mode}") - - -def probe_pep810_lazy_imports() -> None: - # PEP 810: `lazy` soft keyword — module-scope only. Syntax alone enables it; - # no __future__ import is required. Belt-and-suspenders: compile probe AND - # a runtime attribute (sys.set_lazy_imports) that also lands in 3.15. - compile_ok = _compiles("lazy import os\n") - runtime_ok = hasattr(sys, "set_lazy_imports") - if compile_ok and runtime_ok: - record("PEP 810 lazy imports", PASS, "syntax + sys.set_lazy_imports") - elif compile_ok or runtime_ok: - record("PEP 810 lazy imports", PASS, - f"partial: compile={compile_ok} runtime={runtime_ok}") - else: - record("PEP 810 lazy imports", SKIP, "neither syntax nor runtime API") - - -def probe_pep798_unpack_comprehension() -> None: - # PEP 798: unpacking in comprehensions. NOTE: unbracketed genexp-call form - # `f(*x for x in xs)` still raises SyntaxError — do not probe that here. - src = "result = [*xs for xs in [[1, 2], [3]]]" - if not _compiles(src): - record("PEP 798 unpack-in-comprehension", SKIP, "syntax not accepted") - return - ns: dict = {} - try: - exec(src, ns) - ok = ns.get("result") == [1, 2, 3] - record("PEP 798 unpack-in-comprehension", - PASS if ok else FAIL, f"result={ns.get('result')}") - except Exception as exc: # noqa: BLE001 - feature probe - record("PEP 798 unpack-in-comprehension", FAIL, repr(exc)) - - -def probe_pep814_frozendict() -> None: - # PEP 814: `frozendict` is a true BUILTIN (not types.frozendict). - # Also NOT a subclass of dict — inherits directly from object; use - # isinstance(x, (dict, frozendict)) or collections.abc.Mapping in consumers. - if not hasattr(builtins, "frozendict"): - record("PEP 814 frozendict", SKIP, "builtin absent") - return - fd = builtins.frozendict({"a": 1}) - # Sanity: constructs, hashable, not a dict subclass. - ok = hash(fd) is not None and not isinstance(fd, dict) - record("PEP 814 frozendict", - PASS if ok else FAIL, - f"hashable={ok}, not dict-subclass={not isinstance(fd, dict)}") - - -def probe_pep661_sentinel() -> None: - # PEP 661: `sentinel` is a BUILTIN lowercase callable — sentinel("NAME"). - # NOT a module import. The typing_extensions backport is capital-S Sentinel, - # but the 3.15 builtin is lowercase. - if not hasattr(builtins, "sentinel"): - record("PEP 661 sentinel", SKIP, "builtin absent") - return - try: - missing = builtins.sentinel("MISSING") - # Sanity: repr is stable and identity-comparable to itself. - ok = missing is missing and "MISSING" in repr(missing) - record("PEP 661 sentinel", PASS if ok else FAIL, repr(missing)) - except Exception as exc: # noqa: BLE001 - feature probe - record("PEP 661 sentinel", FAIL, repr(exc)) - - -def probe_pep799_profiling() -> None: - # PEP 799: dedicated `profiling` package. Tachyon (the sampling profiler) - # IS `profiling.sampling`. - # `profiling.tracing` is the relocated cProfile (cProfile remains as alias). - if not _spec_exists("profiling"): - record("PEP 799 profiling/Tachyon", SKIP, "package absent") - return - subs = [s for s in ("profiling.sampling", "profiling.tracing") - if _spec_exists(s)] - if subs: - record("PEP 799 profiling/Tachyon", PASS, ", ".join(subs)) - else: - record("PEP 799 profiling/Tachyon", FAIL, - "package present but sampling/tracing missing") - - -def probe_typing_features() -> None: - # PEP 747 TypeForm — annotation for values that are themselves type exprs. - import typing - record("PEP 747 TypeForm", - PASS if hasattr(typing, "TypeForm") else SKIP, - "typing.TypeForm") - - # PEP 728 TypedDict extra_items: functional keyword form. Introspect via - # __extra_items__ (default: typing.NoExtraItems sentinel, not None). - # Passing both closed= and extra_items= raises TypeError at runtime. - try: - td = typing.TypedDict("X", {"a": int}, extra_items=int) # type: ignore[call-arg] - no_extra = getattr(typing, "NoExtraItems", None) - extras_ok = getattr(td, "__extra_items__", no_extra) is int - default_ok = no_extra is not None - record("PEP 728 TypedDict extra_items", - PASS if (extras_ok and default_ok) else FAIL, - f"extra_items=int accepted, NoExtraItems={'ok' if default_ok else 'missing'}") - except TypeError as exc: - record("PEP 728 TypedDict extra_items", SKIP, f"rejected: {exc}") - - # PEP 800: `@typing.disjoint_base` — a CLASS DECORATOR, not a flag/attr. - # Attribute-existence is a valid capability probe; note in detail that - # semantically it's a decorator so downstream code should apply it, not read it. - has_db = hasattr(typing, "disjoint_base") - record("PEP 800 disjoint bases", - PASS if has_db else SKIP, - "typing.disjoint_base decorator" if has_db else "absent") - - -def probe_pep831_frame_pointers() -> None: - # PEP 831: -fno-omit-frame-pointer enabled by default on supporting platforms. - # No dedicated sysconfig variable — grep the compiler flags. Reflects build - # config, not runtime state. Opt-out is --without-frame-pointers at configure. - cflags = sysconfig.get_config_var("CFLAGS") or "" - if "-fno-omit-frame-pointer" in cflags: - record("PEP 831 frame pointers", PASS, "-fno-omit-frame-pointer in CFLAGS") - else: - # On Windows and some cross-compiles the flag simply doesn't apply. - record("PEP 831 frame pointers", SKIP, "not present in CFLAGS") - - -def probe_pep829_startup_config() -> None: - # PEP 829: `.start` files + site.StartupState. `.start` entries are - # `pkg.mod:callable` specs resolved by pkgutil.resolve_name and invoked - # at interpreter startup by site. Only Python-visible probe is StartupState. - record("PEP 829 startup config", - PASS if hasattr(site, "StartupState") else SKIP, - "site.StartupState") - - -def probe_build_variant_info() -> None: - # Informational: free-threading (PEP 703/793) and JIT state for this build. - # NOTE: Py_GIL_DISABLED is DEFINED AS 0 on non-FT Windows builds — using - # `is not None` would incorrectly report FT on those. Coerce to bool. - gil_disabled = bool(sysconfig.get_config_var("Py_GIL_DISABLED")) - is_gil = getattr(sys, "_is_gil_enabled", lambda: None)() - record("free-threaded build (info)", SKIP, - f"Py_GIL_DISABLED={gil_disabled} gil_enabled={is_gil}") - - # JIT: sys._jit exposes THREE methods. is_enabled() is the most meaningful - # ("JIT actually on"); is_active() is documented as unreliable outside JIT - # self-tests. The JIT is OFF BY DEFAULT — is_available() can be True while - # is_enabled() is False. Enable at runtime with PYTHON_JIT=1 if compiled in. - jit = getattr(sys, "_jit", None) - if jit is not None: - avail = getattr(jit, "is_available", lambda: "?")() - enabled = getattr(jit, "is_enabled", lambda: "?")() - active = getattr(jit, "is_active", lambda: "?")() - record("JIT (info)", SKIP, - f"available={avail} enabled={enabled} active={active}") - else: - record("JIT (info)", SKIP, "sys._jit absent") - - -def probe_cabi_notes() -> None: - # PEP 782 PyBytesWriter and PEP 803/820/793 free-threaded stable ABI (abi3t) - # are C-API surfaces; they cannot be exercised from a pure-Python test - # without compiling an extension. Recorded as N/A for visibility. - record("PEP 782 PyBytesWriter (C-API)", SKIP, "needs C ext to test") - record("PEP 803/820/793 abi3t (C-API)", SKIP, "needs C ext to test") - - -# --------------------------------------------------------------------------- # - -def main() -> int: - # Universal sanity: runs on every Python version. - check_interpreter_identity() - check_core_stdlib() - check_build_sanity() - - # 3.15-only: PEP feature probes are skipped on any other version. - if IS_TARGET: - probe_pep686_utf8_default() - probe_pep810_lazy_imports() - probe_pep798_unpack_comprehension() - probe_pep814_frozendict() - probe_pep661_sentinel() - probe_pep799_profiling() - probe_typing_features() - probe_pep831_frame_pointers() - probe_pep829_startup_config() - probe_build_variant_info() - probe_cabi_notes() - else: - record("3.15 feature probes", SKIP, - f"interpreter {sys.version_info[0]}.{sys.version_info[1]} " - "is not 3.15") - - width = max(len(r.name) for r in _results) - print("\n=== Python 3.15 conda-build feature test ===") - print(f"interpreter: {sys.executable}") - print(f"releaselevel: {sys.version_info.releaselevel}") - print(f"strict mode: {STRICT} " - f"(env={STRICT_ENV}, auto={AUTO_STRICT})\n") - for r in _results: - tag = "[HARD]" if r.hard else "[soft]" - print(f"{tag} {r.status:<4} {r.name.ljust(width)} {r.detail}") - - hard_failed = [r for r in _results if r.hard and r.status == FAIL] - soft_failed = [r for r in _results if not r.hard and r.status == FAIL] - soft_skipped = [r for r in _results if not r.hard and r.status == SKIP] - - print() - print(f"summary: {sum(r.status == PASS for r in _results)} pass / " - f"{sum(r.status == SKIP for r in _results)} skip / " - f"{sum(r.status == FAIL for r in _results)} fail") - - if hard_failed: - return 1 - if STRICT and soft_failed: - return 2 - # In auto-strict (rc1+), a SKIP on a probe that should have landed is also - # a signal — but we deliberately don't fail on skip to keep the surface - # small. Emit a warning so it shows up in CI logs. - if AUTO_STRICT and soft_skipped: - print(f"WARNING: {len(soft_skipped)} SOFT probes SKIPPED at " - f"releaselevel={sys.version_info.releaselevel}; review probes.") - return 0 - - -if __name__ == "__main__": - raise SystemExit(main())