diff --git a/.github/workflows/ci-macos.yml b/.github/workflows/ci-macos.yml index 9d2c70cbb78..d1a5d62f425 100644 --- a/.github/workflows/ci-macos.yml +++ b/.github/workflows/ci-macos.yml @@ -34,6 +34,7 @@ jobs: uses: ./.github/workflows/macos.yml with: stage: "1" + timeout: 14400 stage-2: uses: ./.github/workflows/macos.yml @@ -90,7 +91,7 @@ jobs: run: | git config --global user.email "nobody@example.com" git config --global user.name "Sage GitHub CI" - SAGE_ROOT=. SAGE_SRC=./src src/bin/sage-update-version $(git describe --tags) || echo "(ignoring error)" + SAGE_ROOT=. SAGE_SRC=./src src/bin/sage-update-version $(cat src/VERSION.txt).dev0 || echo "(ignoring error)" - name: make dist run: | ./configure --enable-download-from-upstream-url && make dist @@ -108,7 +109,7 @@ jobs: fail-fast: false max-parallel: 4 matrix: - os: [ macos-11, macos-12 ] + os: [ macos-11, macos-12, macos-14 ] tox_system_factor: [macos-nobootstrap] tox_packages_factor: [minimal] xcode_version_factor: [default] @@ -129,7 +130,7 @@ jobs: if: contains(matrix.tox_system_factor, 'nobootstrap') - name: Move homebrew away run: | - (cd /usr/local && for a in bin etc include lib opt sbin share; do sudo mv $a $a-moved; done) + (cd $(brew --prefix) && for a in bin etc include lib opt sbin share; do sudo mv $a $a-moved; done) - name: Select Xcode version run: | if [ ${{ matrix.xcode_version_factor }} != default ]; then sudo xcode-select -s /Applications/Xcode_${{ matrix.xcode_version_factor }}.app; fi @@ -140,7 +141,8 @@ jobs: # We use a high parallelization on purpose in order to catch possible parallelization bugs in the build scripts. # For doctesting, we use a lower parallelization to avoid timeouts. run: | - MAKE="make -j12" tox -e $TOX_ENV -- SAGE_NUM_THREADS=4 $TARGETS + (sleep 20000; pkill make) & + MAKE="make -j12" tox -e $TOX_ENV -- SAGE_NUM_THREADS=6 $TARGETS - name: Prepare logs artifact run: | mkdir -p "artifacts/$LOGS_ARTIFACT_NAME"; cp -r .tox/*/log "artifacts/$LOGS_ARTIFACT_NAME" diff --git a/.github/workflows/macos.yml b/.github/workflows/macos.yml index bfa4bc58f96..ccba31a3dcc 100644 --- a/.github/workflows/macos.yml +++ b/.github/workflows/macos.yml @@ -19,17 +19,21 @@ on: type: string # System configuration osversion_xcodeversion_toxenv_tuples: + # As of 2024-02, "runs-on: macos-latest" is macos-12. + # and "runs-on: macos-14" selects the new M1 runners. + # https://docs.github.com/en/actions/using-github-hosted-runners/about-github-hosted-runners/about-github-hosted-runners#standard-github-hosted-runners-for-public-repositories description: 'Stringified JSON object' default: >- - [["latest", "", "homebrew-macos-usrlocal-minimal"], - ["latest", "", "homebrew-macos-usrlocal-standard"], - ["11", "xcode_13.2.1", "homebrew-macos-usrlocal-minimal"], + [["11", "xcode_13.2.1", "homebrew-macos-usrlocal-minimal"], + ["12", "", "homebrew-macos-usrlocal-minimal"], ["12", "", "homebrew-macos-usrlocal-standard"], + ["12", "", "homebrew-macos-usrlocal-python3_xcode-standard"], + ["12", "", "homebrew-macos-usrlocal-maximal"], ["13", "xcode_15.0", "homebrew-macos-usrlocal-standard"], - ["latest", "", "homebrew-macos-usrlocal-maximal"], - ["latest", "", "homebrew-macos-usrlocal-python3_xcode-standard"], + ["14", "", "homebrew-macos-opthomebrew-standard"], ["latest", "", "conda-forge-macos-minimal"], - ["latest", "", "conda-forge-macos-standard"]] + ["latest", "", "conda-forge-macos-standard"], + ["14", "", "conda-forge-macos-standard"]] type: string extra_sage_packages: description: 'Extra Sage packages to install as system packages' @@ -41,6 +45,10 @@ on: free_disk_space: default: false type: boolean + timeout: + description: 'Elapsed time (seconds) at which to kill the build' + default: 20000 + type: number # # For use in upstream CIs. # @@ -74,10 +82,16 @@ jobs: repository: ${{ inputs.sage_repo }} ref: ${{ inputs.sage_ref }} fetch-depth: 10000 - + - uses: actions/setup-python@v5 + # As of 2024-02-03, the macOS M1 runners do not have preinstalled python or pipx. + # Installing pipx follows the approach of https://github.com/pypa/cibuildwheel/pull/1743 + id: python + with: + python-version: "3.8 - 3.12" + update-environment: false - name: Install test prerequisites run: | - brew install tox + "${{ steps.python.outputs.python-path }}" -m pip install pipx - name: Download upstream artifact uses: actions/download-artifact@v3 with: @@ -129,7 +143,8 @@ jobs: *) export TARGETS_PRE="${{ inputs.targets_pre }}" TARGETS="${{ inputs.targets }} TARGETS_OPTIONAL="${{ inputs.targets_optional }} ;; esac - MAKE="make -j12" tox -e $TOX_ENV -- SAGE_NUM_THREADS=4 $TARGETS + (sleep ${{ inputs.timeout }}; pkill make) & + MAKE="make -j12" EXTRA_SAGE_PACKAGES="${{ inputs.extra_sage_packages }}" "${{ steps.python.outputs.python-path }}" -m pipx run tox -e $TOX_ENV -- SAGE_NUM_THREADS=6 $TARGETS - name: Prepare logs artifact run: | mkdir -p "artifacts/$LOGS_ARTIFACT_NAME"; cp -r .tox/*/log "artifacts/$LOGS_ARTIFACT_NAME" diff --git a/CITATION.cff b/CITATION.cff index 79607a93e60..30175dfb598 100644 --- a/CITATION.cff +++ b/CITATION.cff @@ -4,8 +4,8 @@ title: SageMath abstract: SageMath is a free open-source mathematics software system. authors: - name: "The SageMath Developers" -version: 10.3.rc0 +version: 10.3.rc2 doi: 10.5281/zenodo.593563 -date-released: 2024-02-25 +date-released: 2024-03-04 repository-code: "https://github.com/sagemath/sage" url: "https://www.sagemath.org/" diff --git a/README.md b/README.md index 422582292af..8add84575d8 100644 --- a/README.md +++ b/README.md @@ -420,16 +420,47 @@ in the Installation Guide. Alternative Installation using PyPI --------------- -For installation of `sage` in python using `pip` you need to install `sagemath-standard`. First, activate your python virtual environment and follow these steps: +For installing Sage in a Python environment from PyPI, Sage provides the +`pip`-installable package [sagemath-standard](https://pypi.org/project/sagemath-standard/). - $ python3 -m pip install sage_conf - $ ls $(sage-config SAGE_SPKG_WHEELS) - $ python3 -m pip install $(sage-config SAGE_SPKG_WHEELS)/*.whl sage_setup - $ python3 -m pip install --no-build-isolation sagemath-standard +Unless you need to install Sage into a specific existing environment, we recommend +to create and activate a fresh virtual environment, for example `~/sage-venv/`: -You need to install `sage_conf`, a wheelhouse of various python packages. You can list the wheels using `ls $(sage-config SAGE_SPKG_WHEELS)`. After manual installation of these wheels, you can install the sage library, `sagemath-standard`. + $ python3 -m venv ~/sage-venv + $ source ~/sage-venv/bin/activate -**NOTE:** You can find `sage` and `sagemath` pip packages but with these packages, you will encounter `ModuleNotFoundError`. +As the first installation step, install [sage_conf](https://pypi.org/project/sage-conf/), +which builds various prerequisite packages in a subdirectory of `~/.sage/`: + + (sage-venv) $ python3 -m pip install -v sage_conf + +After a successful installation, a wheelhouse provides various Python packages. +You can list the wheels using the command: + + (sage-venv) $ ls $(sage-config SAGE_SPKG_WHEELS) + +If this gives an error saying that `sage-config` is not found, check any messages +that the `pip install` command may have printed. You may need to adjust your `PATH`, +for example by: + + $ export PATH="$(python3 -c 'import sysconfig; print(sysconfig.get_path("scripts", "posix_user"))'):$PATH" + +Now install the packages from the wheelhouse and the [sage_setup](https://pypi.org/project/sage-conf/) +package, and finally install the Sage library: + + (sage-venv) $ python3 -m pip install $(sage-config SAGE_SPKG_WHEELS)/*.whl sage_setup + (sage-venv) $ python3 -m pip install --no-build-isolation -v sagemath-standard + +The above instructions install the latest stable release of Sage. +To install the latest development version instead, add the switch `--pre` to all invocations of +`python3 -m pip install`. + +**NOTE:** PyPI has various other `pip`-installable packages with the word "sage" in their names. +Some of them are maintained by the SageMath project, some are provided by SageMath users for +various purposes, and others are entirely unrelated to SageMath. Do not use the packages +`sage` and `sagemath`. For a curated list of packages, see the chapter +[Packages and Features](https://doc.sagemath.org/html/en/reference/spkg/index.html) of the +Sage Reference Manual. SageMath Docker images ---------------------- diff --git a/VERSION.txt b/VERSION.txt index 8685f53e56f..0480b077d09 100644 --- a/VERSION.txt +++ b/VERSION.txt @@ -1 +1 @@ -SageMath version 10.3.rc0, Release Date: 2024-02-25 +SageMath version 10.3.rc2, Release Date: 2024-03-04 diff --git a/build/pkgs/cmake/spkg-configure.m4 b/build/pkgs/cmake/spkg-configure.m4 index ce36e8aa0cc..4210442c44c 100644 --- a/build/pkgs/cmake/spkg-configure.m4 +++ b/build/pkgs/cmake/spkg-configure.m4 @@ -1,13 +1,17 @@ -SAGE_SPKG_CONFIGURE( - [cmake], [ - AC_CACHE_CHECK([for cmake >= 3.11], [ac_cv_path_CMAKE], [ - AC_PATH_PROGS_FEATURE_CHECK([CMAKE], [cmake], [ - cmake_version=`$ac_path_CMAKE --version 2>&1 \ - | $SED -n -e 's/cmake version *\([[0-9]]*\.[[0-9]]*\.[[0-9]]*\)/\1/p'` - AS_IF([test -n "$cmake_version"], [ - AX_COMPARE_VERSION([$cmake_version], [ge], [3.11], [ - ac_cv_path_CMAKE="$ac_path_CMAKE" - ac_path_CMAKE_found=: +SAGE_SPKG_CONFIGURE([cmake], [dnl + AC_CACHE_CHECK([for cmake >= 3.11], [ac_cv_path_CMAKE], [dnl + dnl Do not accept cmake installed via https://pypi.org/project/cmake/ + dnl in the default user scheme; it will not work in our venv because + dnl we set PYTHONUSERBASE in sage-env. + WITH_SAGE_PYTHONUSERBASE([dnl + AC_PATH_PROGS_FEATURE_CHECK([CMAKE], [cmake], [dnl + cmake_version=`$ac_path_CMAKE --version 2>&1 \ + | $SED -n -e 's/cmake version *\([[0-9]]*\.[[0-9]]*\.[[0-9]]*\)/\1/p'` + AS_IF([test -n "$cmake_version"], [dnl + AX_COMPARE_VERSION([$cmake_version], [ge], [3.11], [dnl + ac_cv_path_CMAKE="$ac_path_CMAKE" + ac_path_CMAKE_found=: + ]) ]) ]) ]) diff --git a/build/pkgs/configure/checksums.ini b/build/pkgs/configure/checksums.ini index aa5eedfe15b..5118007d474 100644 --- a/build/pkgs/configure/checksums.ini +++ b/build/pkgs/configure/checksums.ini @@ -1,4 +1,4 @@ tarball=configure-VERSION.tar.gz -sha1=a3fc8c3bf9198d28a8f2d5a08cffdcf519150b77 -md5=32f4f1763765232dc7e7431d5e48e129 -cksum=708121316 +sha1=ff35813eb1168b754ab9470066b39aeaca9d462c +md5=834bc382880b0cfea48fa00bdff79534 +cksum=4002007446 diff --git a/build/pkgs/configure/package-version.txt b/build/pkgs/configure/package-version.txt index 87cd0d32195..e32f27ed547 100644 --- a/build/pkgs/configure/package-version.txt +++ b/build/pkgs/configure/package-version.txt @@ -1 +1 @@ -0fb793fa91a0b46de452036b521cbbaeee878340 +872ca39ec422cacd1005dd6b1ccd9737d5d88712 diff --git a/build/pkgs/dsdp/spkg-install.in b/build/pkgs/dsdp/spkg-install.in index 8c29005b9b1..5d0ecadf030 100644 --- a/build/pkgs/dsdp/spkg-install.in +++ b/build/pkgs/dsdp/spkg-install.in @@ -1,6 +1,7 @@ cd src cp ../patches/CMakeLists.txt . sdh_cmake -DCMAKE_BUILD_TYPE=Release \ + -DCMAKE_VERBOSE_MAKEFILE=ON \ -DBUILD_SHARED_LIBS=ON \ -DBLA_VENDOR=OpenBLAS \ -DBLAS_LIBRARIES="$(pkg-config --libs blas)" \ diff --git a/build/pkgs/flint/spkg-configure.m4 b/build/pkgs/flint/spkg-configure.m4 index 9576e4cc1e9..ddf60b596fd 100644 --- a/build/pkgs/flint/spkg-configure.m4 +++ b/build/pkgs/flint/spkg-configure.m4 @@ -1,11 +1,26 @@ SAGE_SPKG_CONFIGURE([flint], [ SAGE_SPKG_DEPCHECK([mpfr], [ - AC_CHECK_HEADER(flint/flint.h, [ + AC_CHECK_HEADER(flint/flint.h, [dnl dnl gr_get_fexpr appears in Flint 3.0 - AC_SEARCH_LIBS([gr_get_fexpr], [flint], [], [sage_spkg_install_flint=yes]) + AC_SEARCH_LIBS([gr_get_fexpr], [flint], [dnl + dnl Flint 3.1 is too new + AC_MSG_CHECKING([whether FLINT version is >= 3.1.0]) + AC_COMPILE_IFELSE([dnl + AC_LANG_PROGRAM([[#include + #if __FLINT_RELEASE >= 30100 + # error "FLINT 3.1 is too new" + #endif + ]]) + ], [dnl + AC_MSG_RESULT([no]) + ], [dnl + AC_MSG_RESULT([yes; too new]) + sage_spkg_install_flint=yes + ]) + ], [sage_spkg_install_flint=yes]) ], [sage_spkg_install_flint=yes]) ]) -], [], [], [ +], [], [], [dnl if test x$sage_spkg_install_flint = xyes; then AC_SUBST(SAGE_FLINT_PREFIX, ['$SAGE_LOCAL']) else diff --git a/build/pkgs/meson/checksums.ini b/build/pkgs/meson/checksums.ini index bbb4661c960..0d4a8f89a48 100644 --- a/build/pkgs/meson/checksums.ini +++ b/build/pkgs/meson/checksums.ini @@ -1,5 +1,5 @@ -tarball=meson-VERSION.tar.gz -sha1=97e766951553ec35315712f0a27d5554a010d4c3 -md5=69da4c63ef06c9d3bcc00ce89abb306f -cksum=2424401184 -upstream_url=https://pypi.io/packages/source/m/meson/meson-VERSION.tar.gz +tarball=meson-VERSION-py3-none-any.whl +sha1=baf5b9bc9ca97f18c7dc87cfaf0e1dc4d617a4cf +md5=d418e644c04e55872ce3d7b6de007dbe +cksum=559088366 +upstream_url=https://pypi.io/packages/py3/m/meson/meson-VERSION-py3-none-any.whl diff --git a/build/pkgs/meson/package-version.txt b/build/pkgs/meson/package-version.txt index 0495c4a88ca..3a3cd8cc8b0 100644 --- a/build/pkgs/meson/package-version.txt +++ b/build/pkgs/meson/package-version.txt @@ -1 +1 @@ -1.2.3 +1.3.1 diff --git a/build/pkgs/meson/spkg-configure.m4 b/build/pkgs/meson/spkg-configure.m4 index d85bd144a07..7ebfa601644 100644 --- a/build/pkgs/meson/spkg-configure.m4 +++ b/build/pkgs/meson/spkg-configure.m4 @@ -1,14 +1,20 @@ -SAGE_SPKG_CONFIGURE( - [meson], [ - dnl scipy 1.11.2 needs meson >= 1.1.0 - dnl contourpy needs meson >= 1.2.0 - AC_CACHE_CHECK([for meson >= 1.2.0], [ac_cv_path_MESON], [ - AC_PATH_PROGS_FEATURE_CHECK([MESON], [meson], [ - meson_version=`$ac_path_MESON --version 2>&1` - AS_IF([test -n "$meson_version"], [ - AX_COMPARE_VERSION([$meson_version], [ge], [1.2.0], [ - ac_cv_path_MESON="$ac_path_MESON" - ac_path_MESON_found=: +SAGE_SPKG_CONFIGURE([meson], [dnl + dnl scipy 1.11.2 needs meson >= 1.1.0 + dnl contourpy needs meson >= 1.2.0 + dnl meson_python needs meson >= 1.2.3 for Python >= 3.12 + AC_CACHE_CHECK([for meson >= 1.2.3], [ac_cv_path_MESON], [dnl + dnl Do not accept meson installed in the default user scheme; + dnl it will not work in our venv because we set PYTHONUSERBASE + dnl in sage-env. + WITH_SAGE_PYTHONUSERBASE([dnl + AC_PATH_PROGS_FEATURE_CHECK([MESON], [meson], [dnl + AS_IF([meson_version=$($ac_path_MESON --version 2>&1)], [dnl + AS_IF([test -n "$meson_version"], [dnl + AX_COMPARE_VERSION([$meson_version], [ge], [1.2.3], [dnl + ac_cv_path_MESON="$ac_path_MESON" + ac_path_MESON_found=: + ]) + ]) ]) ]) ]) diff --git a/build/pkgs/meson/spkg-install.in b/build/pkgs/meson/spkg-install.in deleted file mode 100644 index 37ac1a53437..00000000000 --- a/build/pkgs/meson/spkg-install.in +++ /dev/null @@ -1,2 +0,0 @@ -cd src -sdh_pip_install . diff --git a/build/pkgs/ninja_build/spkg-configure.m4 b/build/pkgs/ninja_build/spkg-configure.m4 index 5b83d189801..01ee6a30f10 100644 --- a/build/pkgs/ninja_build/spkg-configure.m4 +++ b/build/pkgs/ninja_build/spkg-configure.m4 @@ -1,16 +1,20 @@ -SAGE_SPKG_CONFIGURE( - [ninja_build], [ - dnl meson_python needs 1.8.2 or later - AC_CACHE_CHECK([for ninja >= 1.8.2], [ac_cv_path_NINJA], [ - AC_PATH_PROGS_FEATURE_CHECK([NINJA], [ninja], [ - dnl support both two- and three-component version schemes - dnl since samurai (a ninja alternative) uses two - ninja_version=`$ac_path_NINJA --version 2>&1 \ - | $SED -n -e 's/\([[0-9]]*\(\.[[0-9]]*\)\{1,2\}\).*/\1/p'` - AS_IF([test -n "$ninja_version"], [ - AX_COMPARE_VERSION([$ninja_version], [ge], [1.8.2], [ - ac_cv_path_NINJA="$ac_path_NINJA" - ac_path_NINJA_found=: +SAGE_SPKG_CONFIGURE([ninja_build], [dnl + dnl meson_python needs 1.8.2 or later + AC_CACHE_CHECK([for ninja >= 1.8.2], [ac_cv_path_NINJA], [dnl + dnl Do not accept ninja installed from https://pypi.org/project/ninja/ + dnl in the default user scheme; it will not work in our venv because + dnl we set PYTHONUSERBASE in sage-env. + WITH_SAGE_PYTHONUSERBASE([dnl + AC_PATH_PROGS_FEATURE_CHECK([NINJA], [ninja], [dnl + dnl support both two- and three-component version schemes + dnl since samurai (a ninja alternative) uses two + ninja_version=`$ac_path_NINJA --version 2>&1 \ + | $SED -n -e 's/\([[0-9]]*\(\.[[0-9]]*\)\{1,2\}\).*/\1/p'` + AS_IF([test -n "$ninja_version"], [dnl + AX_COMPARE_VERSION([$ninja_version], [ge], [1.8.2], [ + ac_cv_path_NINJA="$ac_path_NINJA" + ac_path_NINJA_found=: + ]) ]) ]) ]) diff --git a/build/pkgs/sage_conf/install-requires.txt b/build/pkgs/sage_conf/install-requires.txt index c89cf3e4fa5..09f1d4383d6 100644 --- a/build/pkgs/sage_conf/install-requires.txt +++ b/build/pkgs/sage_conf/install-requires.txt @@ -1,2 +1,2 @@ # This file is updated on every release by the sage-update-version script -sage-conf ~= 10.3rc0 +sage-conf ~= 10.3rc2 diff --git a/build/pkgs/sage_docbuild/install-requires.txt b/build/pkgs/sage_docbuild/install-requires.txt index 67181be111a..0308591fab7 100644 --- a/build/pkgs/sage_docbuild/install-requires.txt +++ b/build/pkgs/sage_docbuild/install-requires.txt @@ -1,2 +1,2 @@ # This file is updated on every release by the sage-update-version script -sage-docbuild ~= 10.3rc0 +sage-docbuild ~= 10.3rc2 diff --git a/build/pkgs/sage_setup/install-requires.txt b/build/pkgs/sage_setup/install-requires.txt index 9b55e69ca14..003624a1377 100644 --- a/build/pkgs/sage_setup/install-requires.txt +++ b/build/pkgs/sage_setup/install-requires.txt @@ -1,2 +1,2 @@ # This file is updated on every release by the sage-update-version script -sage-setup ~= 10.3rc0 +sage-setup ~= 10.3rc2 diff --git a/build/pkgs/sage_sws2rst/install-requires.txt b/build/pkgs/sage_sws2rst/install-requires.txt index 7d9753fe5ba..0f0f949e8e1 100644 --- a/build/pkgs/sage_sws2rst/install-requires.txt +++ b/build/pkgs/sage_sws2rst/install-requires.txt @@ -1,2 +1,2 @@ # This file is updated on every release by the sage-update-version script -sage-sws2rst ~= 10.3rc0 +sage-sws2rst ~= 10.3rc2 diff --git a/build/pkgs/sagelib/install-requires.txt b/build/pkgs/sagelib/install-requires.txt index b1821569ba4..a0c5acf57f9 100644 --- a/build/pkgs/sagelib/install-requires.txt +++ b/build/pkgs/sagelib/install-requires.txt @@ -1,2 +1,2 @@ # This file is updated on every release by the sage-update-version script -sagemath-standard ~= 10.3rc0 +sagemath-standard ~= 10.3rc2 diff --git a/build/pkgs/sagemath_bliss/install-requires.txt b/build/pkgs/sagemath_bliss/install-requires.txt index cf95cef67a7..54d8411557b 100644 --- a/build/pkgs/sagemath_bliss/install-requires.txt +++ b/build/pkgs/sagemath_bliss/install-requires.txt @@ -1,2 +1,2 @@ # This file is updated on every release by the sage-update-version script -sagemath-bliss ~= 10.3rc0 +sagemath-bliss ~= 10.3rc2 diff --git a/build/pkgs/sagemath_categories/install-requires.txt b/build/pkgs/sagemath_categories/install-requires.txt index e4ea2d5d0c4..4b5c9e13f31 100644 --- a/build/pkgs/sagemath_categories/install-requires.txt +++ b/build/pkgs/sagemath_categories/install-requires.txt @@ -1,2 +1,2 @@ # This file is updated on every release by the sage-update-version script -sagemath-categories ~= 10.3rc0 +sagemath-categories ~= 10.3rc2 diff --git a/build/pkgs/sagemath_coxeter3/install-requires.txt b/build/pkgs/sagemath_coxeter3/install-requires.txt index 6200a717bfb..49fc98261eb 100644 --- a/build/pkgs/sagemath_coxeter3/install-requires.txt +++ b/build/pkgs/sagemath_coxeter3/install-requires.txt @@ -1,2 +1,2 @@ # This file is updated on every release by the sage-update-version script -sagemath-coxeter3 ~= 10.3rc0 +sagemath-coxeter3 ~= 10.3rc2 diff --git a/build/pkgs/sagemath_environment/install-requires.txt b/build/pkgs/sagemath_environment/install-requires.txt index 4ce179501fd..af5d0708df9 100644 --- a/build/pkgs/sagemath_environment/install-requires.txt +++ b/build/pkgs/sagemath_environment/install-requires.txt @@ -1,2 +1,2 @@ # This file is updated on every release by the sage-update-version script -sagemath-environment ~= 10.3rc0 +sagemath-environment ~= 10.3rc2 diff --git a/build/pkgs/sagemath_mcqd/install-requires.txt b/build/pkgs/sagemath_mcqd/install-requires.txt index 9f21ede31b8..10521420f3c 100644 --- a/build/pkgs/sagemath_mcqd/install-requires.txt +++ b/build/pkgs/sagemath_mcqd/install-requires.txt @@ -1,2 +1,2 @@ # This file is updated on every release by the sage-update-version script -sagemath-mcqd ~= 10.3rc0 +sagemath-mcqd ~= 10.3rc2 diff --git a/build/pkgs/sagemath_meataxe/install-requires.txt b/build/pkgs/sagemath_meataxe/install-requires.txt index 2fb3b54c592..aa01559a846 100644 --- a/build/pkgs/sagemath_meataxe/install-requires.txt +++ b/build/pkgs/sagemath_meataxe/install-requires.txt @@ -1,2 +1,2 @@ # This file is updated on every release by the sage-update-version script -sagemath-meataxe ~= 10.3rc0 +sagemath-meataxe ~= 10.3rc2 diff --git a/build/pkgs/sagemath_objects/install-requires.txt b/build/pkgs/sagemath_objects/install-requires.txt index ea0025f36fa..65e5a6adcee 100644 --- a/build/pkgs/sagemath_objects/install-requires.txt +++ b/build/pkgs/sagemath_objects/install-requires.txt @@ -1,2 +1,2 @@ # This file is updated on every release by the sage-update-version script -sagemath-objects ~= 10.3rc0 +sagemath-objects ~= 10.3rc2 diff --git a/build/pkgs/sagemath_repl/install-requires.txt b/build/pkgs/sagemath_repl/install-requires.txt index cd5df803c32..8048e7ac060 100644 --- a/build/pkgs/sagemath_repl/install-requires.txt +++ b/build/pkgs/sagemath_repl/install-requires.txt @@ -1,2 +1,2 @@ # This file is updated on every release by the sage-update-version script -sagemath-repl ~= 10.3rc0 +sagemath-repl ~= 10.3rc2 diff --git a/build/pkgs/sagemath_sirocco/install-requires.txt b/build/pkgs/sagemath_sirocco/install-requires.txt index 052d8df5722..64ff32019a5 100644 --- a/build/pkgs/sagemath_sirocco/install-requires.txt +++ b/build/pkgs/sagemath_sirocco/install-requires.txt @@ -1,2 +1,2 @@ # This file is updated on every release by the sage-update-version script -sagemath-sirocco ~= 10.3rc0 +sagemath-sirocco ~= 10.3rc2 diff --git a/build/pkgs/sagemath_tdlib/install-requires.txt b/build/pkgs/sagemath_tdlib/install-requires.txt index ef4f0b40800..fad2b2a87eb 100644 --- a/build/pkgs/sagemath_tdlib/install-requires.txt +++ b/build/pkgs/sagemath_tdlib/install-requires.txt @@ -1,2 +1,2 @@ # This file is updated on every release by the sage-update-version script -sagemath-tdlib ~= 10.3rc0 +sagemath-tdlib ~= 10.3rc2 diff --git a/m4/sage_python_package_check.m4 b/m4/sage_python_package_check.m4 index e3854a44fe6..508495329e9 100644 --- a/m4/sage_python_package_check.m4 +++ b/m4/sage_python_package_check.m4 @@ -71,32 +71,19 @@ AC_DEFUN([SAGE_PYTHON_PACKAGE_CHECK], [ ) AC_MSG_CHECKING([for python package $1 ("${SAGE_PKG_VERSPEC}")]) - dnl To prevent user-site (pip install --user) packages from being - dnl detected as "system" packages, we poison PYTHONUSERBASE. The - dnl sage-env script also does this at runtime; we mimic that - dnl implementation to ensure that the behaviors at ./configure and - dnl runtime are identical. Beware that (as in sage-env) the poisoning - dnl is skipped if PYTHONUSERBASE is non-empty. In particular, if the - dnl user points PYTHONUSERBASE to any path (even the default), then - dnl his local pip packages will be detected. - PYTHONUSERBASE_SAVED="${PYTHONUSERBASE}" - AS_IF([test -z "${PYTHONUSERBASE}"], [ - PYTHONUSERBASE="${HOME}/.sage/local" + WITH_SAGE_PYTHONUSERBASE([dnl + dnl double-quote SAGE_PKG_VERSPEC because platform-specific + dnl dependencies like python_version<'3.11' will have single + dnl quotes in them. (We normalized the quotes earlier with sed.) + AS_IF( + [config.venv/bin/python3 -c dnl + "import pkg_resources; dnl + pkg_resources.require(\"${SAGE_PKG_VERSPEC}\".splitlines())" dnl + 2>&AS_MESSAGE_LOG_FD], + [AC_MSG_RESULT(yes)], + [AC_MSG_RESULT(no); sage_spkg_install_$1=yes] + ) ]) - - dnl double-quote SAGE_PKG_VERSPEC because platform-specific - dnl dependencies like python_version<'3.11' will have single - dnl quotes in them. (We normalized the quotes earlier with sed.) - AS_IF( - [PYTHONUSERBASE="${PYTHONUSERBASE}" config.venv/bin/python3 -c dnl - "import pkg_resources; dnl - pkg_resources.require(\"${SAGE_PKG_VERSPEC}\".splitlines())" dnl - 2>&AS_MESSAGE_LOG_FD], - [AC_MSG_RESULT(yes)], - [AC_MSG_RESULT(no); sage_spkg_install_$1=yes] - ) - - PYTHONUSERBASE="${PYTHONUSERBASE_SAVED}" ], [ dnl failed to create a venv for some reason AC_MSG_RESULT(no) @@ -128,3 +115,22 @@ AC_DEFUN([SAGE_PYTHON_PACKAGE_CHECK], [ AS_IF([test "${sage_use_system_$1}" = "yes"],[sage_use_system_$1=no]) ]) ]) + + +AC_DEFUN([WITH_SAGE_PYTHONUSERBASE], [dnl + dnl To prevent user-site (pip install --user) packages from being + dnl detected as "system" packages, we poison PYTHONUSERBASE. The + dnl sage-env script also does this at runtime; we mimic that + dnl implementation to ensure that the behaviors at ./configure and + dnl runtime are identical. Beware that (as in sage-env) the poisoning + dnl is skipped if PYTHONUSERBASE is non-empty. In particular, if the + dnl user points PYTHONUSERBASE to any path (even the default), then + dnl his local pip packages will be detected. + PYTHONUSERBASE_SAVED="${PYTHONUSERBASE}" + AS_IF([test -z "${PYTHONUSERBASE}"], [dnl + PYTHONUSERBASE="${HOME}/.sage/local" + export PYTHONUSERBASE + ]) + $1 + PYTHONUSERBASE="${PYTHONUSERBASE_SAVED}" +]) diff --git a/pkgs/sage-conf/VERSION.txt b/pkgs/sage-conf/VERSION.txt index 0ebca0e5b3f..b33291c9c02 100644 --- a/pkgs/sage-conf/VERSION.txt +++ b/pkgs/sage-conf/VERSION.txt @@ -1 +1 @@ -10.3.rc0 +10.3.rc2 diff --git a/pkgs/sage-conf/_sage_conf/__main__.py b/pkgs/sage-conf/_sage_conf/__main__.py index fa0a8888907..043fcb24328 100644 --- a/pkgs/sage-conf/_sage_conf/__main__.py +++ b/pkgs/sage-conf/_sage_conf/__main__.py @@ -1,22 +1,19 @@ # Entry point 'sage-config'. It does not depend on any packages. -from sage_conf import * - def _main(): from argparse import ArgumentParser from sys import exit, stdout - parser = ArgumentParser() + + import sage_conf + + parser = ArgumentParser(prog='sage-config') parser.add_argument('--version', help="show version", action="version", - version='%(prog)s ' + VERSION) + version='%(prog)s ' + sage_conf.VERSION) parser.add_argument("VARIABLE", nargs='?', help="output the value of VARIABLE") args = parser.parse_args() - d = globals() if args.VARIABLE: - stdout.write('{}\n'.format(d[args.VARIABLE])) + stdout.write('{}\n'.format(getattr(sage_conf, args.VARIABLE))) else: - for k, v in d.items(): + for k in dir(sage_conf): if not k.startswith('_'): - stdout.write('{}={}\n'.format(k, v)) - -if __name__ == "__main__": - _main() + stdout.write('{}={}\n'.format(k, getattr(sage_conf, k))) diff --git a/pkgs/sage-conf/sage_conf.py b/pkgs/sage-conf/sage_conf.py index dd36f946828..a5c554043ba 100644 --- a/pkgs/sage-conf/sage_conf.py +++ b/pkgs/sage-conf/sage_conf.py @@ -1,2 +1,6 @@ from _sage_conf._conf import * from _sage_conf.__main__ import _main + + +if __name__ == "__main__": + _main() diff --git a/pkgs/sage-conf_conda/VERSION.txt b/pkgs/sage-conf_conda/VERSION.txt index 0ebca0e5b3f..b33291c9c02 100644 --- a/pkgs/sage-conf_conda/VERSION.txt +++ b/pkgs/sage-conf_conda/VERSION.txt @@ -1 +1 @@ -10.3.rc0 +10.3.rc2 diff --git a/pkgs/sage-conf_pypi/VERSION.txt b/pkgs/sage-conf_pypi/VERSION.txt index 0ebca0e5b3f..b33291c9c02 100644 --- a/pkgs/sage-conf_pypi/VERSION.txt +++ b/pkgs/sage-conf_pypi/VERSION.txt @@ -1 +1 @@ -10.3.rc0 +10.3.rc2 diff --git a/pkgs/sage-docbuild/VERSION.txt b/pkgs/sage-docbuild/VERSION.txt index 0ebca0e5b3f..b33291c9c02 100644 --- a/pkgs/sage-docbuild/VERSION.txt +++ b/pkgs/sage-docbuild/VERSION.txt @@ -1 +1 @@ -10.3.rc0 +10.3.rc2 diff --git a/pkgs/sage-setup/VERSION.txt b/pkgs/sage-setup/VERSION.txt index 0ebca0e5b3f..b33291c9c02 100644 --- a/pkgs/sage-setup/VERSION.txt +++ b/pkgs/sage-setup/VERSION.txt @@ -1 +1 @@ -10.3.rc0 +10.3.rc2 diff --git a/pkgs/sage-sws2rst/VERSION.txt b/pkgs/sage-sws2rst/VERSION.txt index 0ebca0e5b3f..b33291c9c02 100644 --- a/pkgs/sage-sws2rst/VERSION.txt +++ b/pkgs/sage-sws2rst/VERSION.txt @@ -1 +1 @@ -10.3.rc0 +10.3.rc2 diff --git a/pkgs/sagemath-bliss/VERSION.txt b/pkgs/sagemath-bliss/VERSION.txt index 0ebca0e5b3f..b33291c9c02 100644 --- a/pkgs/sagemath-bliss/VERSION.txt +++ b/pkgs/sagemath-bliss/VERSION.txt @@ -1 +1 @@ -10.3.rc0 +10.3.rc2 diff --git a/pkgs/sagemath-categories/VERSION.txt b/pkgs/sagemath-categories/VERSION.txt index 0ebca0e5b3f..b33291c9c02 100644 --- a/pkgs/sagemath-categories/VERSION.txt +++ b/pkgs/sagemath-categories/VERSION.txt @@ -1 +1 @@ -10.3.rc0 +10.3.rc2 diff --git a/pkgs/sagemath-coxeter3/VERSION.txt b/pkgs/sagemath-coxeter3/VERSION.txt index 0ebca0e5b3f..b33291c9c02 100644 --- a/pkgs/sagemath-coxeter3/VERSION.txt +++ b/pkgs/sagemath-coxeter3/VERSION.txt @@ -1 +1 @@ -10.3.rc0 +10.3.rc2 diff --git a/pkgs/sagemath-environment/VERSION.txt b/pkgs/sagemath-environment/VERSION.txt index 0ebca0e5b3f..b33291c9c02 100644 --- a/pkgs/sagemath-environment/VERSION.txt +++ b/pkgs/sagemath-environment/VERSION.txt @@ -1 +1 @@ -10.3.rc0 +10.3.rc2 diff --git a/pkgs/sagemath-mcqd/VERSION.txt b/pkgs/sagemath-mcqd/VERSION.txt index 0ebca0e5b3f..b33291c9c02 100644 --- a/pkgs/sagemath-mcqd/VERSION.txt +++ b/pkgs/sagemath-mcqd/VERSION.txt @@ -1 +1 @@ -10.3.rc0 +10.3.rc2 diff --git a/pkgs/sagemath-meataxe/VERSION.txt b/pkgs/sagemath-meataxe/VERSION.txt index 0ebca0e5b3f..b33291c9c02 100644 --- a/pkgs/sagemath-meataxe/VERSION.txt +++ b/pkgs/sagemath-meataxe/VERSION.txt @@ -1 +1 @@ -10.3.rc0 +10.3.rc2 diff --git a/pkgs/sagemath-objects/VERSION.txt b/pkgs/sagemath-objects/VERSION.txt index 0ebca0e5b3f..b33291c9c02 100644 --- a/pkgs/sagemath-objects/VERSION.txt +++ b/pkgs/sagemath-objects/VERSION.txt @@ -1 +1 @@ -10.3.rc0 +10.3.rc2 diff --git a/pkgs/sagemath-repl/VERSION.txt b/pkgs/sagemath-repl/VERSION.txt index 0ebca0e5b3f..b33291c9c02 100644 --- a/pkgs/sagemath-repl/VERSION.txt +++ b/pkgs/sagemath-repl/VERSION.txt @@ -1 +1 @@ -10.3.rc0 +10.3.rc2 diff --git a/pkgs/sagemath-sirocco/VERSION.txt b/pkgs/sagemath-sirocco/VERSION.txt index 0ebca0e5b3f..b33291c9c02 100644 --- a/pkgs/sagemath-sirocco/VERSION.txt +++ b/pkgs/sagemath-sirocco/VERSION.txt @@ -1 +1 @@ -10.3.rc0 +10.3.rc2 diff --git a/pkgs/sagemath-tdlib/VERSION.txt b/pkgs/sagemath-tdlib/VERSION.txt index 0ebca0e5b3f..b33291c9c02 100644 --- a/pkgs/sagemath-tdlib/VERSION.txt +++ b/pkgs/sagemath-tdlib/VERSION.txt @@ -1 +1 @@ -10.3.rc0 +10.3.rc2 diff --git a/src/.relint.yml b/src/.relint.yml index 97bf2ac1dbc..61dcf109c88 100644 --- a/src/.relint.yml +++ b/src/.relint.yml @@ -71,4 +71,4 @@ magic doctest comments should appear on the "sage:" line, not "....:" lines # see optional_regex in src/sage/doctest/parsing.py # "indirect doctest" is from src/bin/sage-coverage - pattern: '^[ ]*[.][.][.][.]:.*#.*(arb216|arb218|py2|py3|long time|not implemented|not tested|known bug|optional|indirect doctest)' + pattern: '^[ ]*[.][.][.][.]:.*#.*(py2|py3|long time|not implemented|not tested|known bug|optional|indirect doctest)' diff --git a/src/VERSION.txt b/src/VERSION.txt index 0ebca0e5b3f..b33291c9c02 100644 --- a/src/VERSION.txt +++ b/src/VERSION.txt @@ -1 +1 @@ -10.3.rc0 +10.3.rc2 diff --git a/src/bin/sage-env b/src/bin/sage-env index 8fcfda48fb6..61901897bcd 100644 --- a/src/bin/sage-env +++ b/src/bin/sage-env @@ -127,7 +127,7 @@ fi # The compilers are set in order of priority by # 1) environment variables # 2) compiler installed by sage -# 3) compiler set at configuration time +# 3) compiler set at configuration time if [ -z "$CC" ]; then if [ -n "$SAGE_LOCAL" -a -x "$SAGE_LOCAL/bin/gcc" ]; then CC=gcc @@ -373,7 +373,14 @@ if [ -n "$SAGE_LOCAL" ]; then # On OS X, test whether "ld-classic" is present in the installed # version of the command-line tools. If so, we add "-ld_classic" # to LD_FLAGS. See #36599. - if [ "$UNAME" = "Darwin" ] && [ -x "$(xcode-select -p)/usr/bin/ld-classic" ] ; then + # When the full XCode is installed and in use, for example after + # "sudo xcode-select -s /Applications/Xcode.app", then "xcode-select -p" + # gives "/Applications/Xcode.app/Contents/Developer", but "ld-classic" + # is not in the subdirectory "usr/bin/" but rather in the subdirectory + # "Toolchains/XcodeDefault.xctoolchain/usr/bin/". See #37237. + # However, if LD is set explicitly, as it is within conda on macOS, + # do not not do this. + if [ "$UNAME" = "Darwin" ] && [ -z "$LD" ] && [ -x "$(xcode-select -p)/usr/bin/ld-classic" -o -x "$(xcode-select -p)/Toolchains/XcodeDefault.xctoolchain/usr/bin/ld-classic" ] ; then LDFLAGS="-L$SAGE_LOCAL/lib -Wl,-ld_classic,-rpath,$SAGE_LOCAL/lib $LDFLAGS" else LDFLAGS="-L$SAGE_LOCAL/lib -Wl,-rpath,$SAGE_LOCAL/lib $LDFLAGS" diff --git a/src/bin/sage-version.sh b/src/bin/sage-version.sh index d9db6163f53..f0cad7cda6a 100644 --- a/src/bin/sage-version.sh +++ b/src/bin/sage-version.sh @@ -4,6 +4,6 @@ # which stops "setup.py develop" from rewriting it as a Python file. : # This file is auto-generated by the sage-update-version script, do not edit! -SAGE_VERSION='10.3.rc0' -SAGE_RELEASE_DATE='2024-02-25' -SAGE_VERSION_BANNER='SageMath version 10.3.rc0, Release Date: 2024-02-25' +SAGE_VERSION='10.3.rc2' +SAGE_RELEASE_DATE='2024-03-04' +SAGE_VERSION_BANNER='SageMath version 10.3.rc2, Release Date: 2024-03-04' diff --git a/src/sage/doctest/control.py b/src/sage/doctest/control.py index 0660cfb5b73..79b2fbc90ca 100644 --- a/src/sage/doctest/control.py +++ b/src/sage/doctest/control.py @@ -58,14 +58,6 @@ auto_optional_tags = set() -try: - from sage.libs.arb.arb_version import version as arb_vers - arb_tag = 'arb2' + arb_vers().split('.')[1] - auto_optional_tags.add(arb_tag) -except ImportError: - pass - - class DocTestDefaults(SageObject): """ This class is used for doctesting the Sage doctest module. diff --git a/src/sage/doctest/parsing.py b/src/sage/doctest/parsing.py index 675de50e536..8c70c723b90 100644 --- a/src/sage/doctest/parsing.py +++ b/src/sage/doctest/parsing.py @@ -95,7 +95,7 @@ def fake_RIFtol(*args): ansi_escape_sequence = re.compile(r"(\x1b[@-Z\\-~]|\x1b\[.*?[@-~]|\x9b.*?[@-~])") special_optional_regex = ( - "arb216|arb218|py2|long time|not implemented|not tested|optional|needs|known bug" + "py2|long time|not implemented|not tested|optional|needs|known bug" ) tag_with_explanation_regex = r"((?:\w|[.])*)\s*(?:\((?P.*?)\))?" optional_regex = re.compile( @@ -136,8 +136,6 @@ def parse_optional_tags( - ``'not tested'`` - ``'known bug'`` (possible values are ``None``, ``linux`` and ``macos``) - ``'py2'`` - - ``'arb216'`` - - ``'arb218'`` - ``'optional - FEATURE...'`` or ``'needs FEATURE...'`` -- the dictionary will just have the key ``'FEATURE'`` @@ -877,7 +875,7 @@ class SageDocTestParser(doctest.DocTestParser): optional_tags: Union[bool, set[str]] optional_only: bool optionals: dict[str, int] - probed_tags: set[str] + probed_tags: Union[bool, set[str]] def __init__(self, optional_tags=(), long=False, *, probed_tags=(), file_optional_tags=()): r""" @@ -916,7 +914,10 @@ def __init__(self, optional_tags=(), long=False, *, probed_tags=(), file_optiona self.optional_tags.remove('sage') else: self.optional_only = True - self.probed_tags = set(probed_tags) + if probed_tags is True: + self.probed_tags = True + else: + self.probed_tags = set(probed_tags) self.file_optional_tags = set(file_optional_tags) def __eq__(self, other): diff --git a/src/sage/features/threejs.py b/src/sage/features/threejs.py index 4517523918d..4f65c5bd48e 100644 --- a/src/sage/features/threejs.py +++ b/src/sage/features/threejs.py @@ -25,8 +25,6 @@ def __init__(self): """ from sage.env import SAGE_SHARE, THREEJS_DIR - version = self.required_version() - threejs_search_path = THREEJS_DIR or ( os.path.join(SAGE_SHARE, "jupyter", "nbextensions", "threejs-sage"), os.path.join(SAGE_SHARE, "sagemath", "threejs-sage"), @@ -34,9 +32,15 @@ def __init__(self): os.path.join(SAGE_SHARE, "threejs-sage") ) + try: + version = self.required_version() + filename = os.path.join(version, "three.min.js") + except FileNotFoundError: + filename = 'unknown' + StaticFile.__init__( self, name="threejs", - filename=os.path.join(version, "three.min.js"), + filename=filename, spkg="threejs", type="standard", search_path=threejs_search_path, @@ -46,6 +50,11 @@ def required_version(self): """ Return the version of threejs that Sage requires. + Defining what version is required is delegated to the distribution package + that provides the file ``threejs-version.txt`` in :mod:`sage.ext_data.threejs`. + + If the file is not provided, :class:`FileNotFoundError` is raised. + EXAMPLES:: sage: from sage.features.threejs import Threejs diff --git a/src/sage/libs/arb/arb_version.pyx b/src/sage/libs/arb/arb_version.pyx deleted file mode 100644 index b8ab4d725e5..00000000000 --- a/src/sage/libs/arb/arb_version.pyx +++ /dev/null @@ -1,23 +0,0 @@ -# -*- coding: utf-8 -from sage.cpython.string cimport char_to_str - - -cdef extern from "arb_wrap.h": - char * arb_version - - -def version(): - """ - Get arb version - - TESTS:: - - sage: from sage.libs.arb.arb_version import version - sage: version().split('.')[0] - '2' - """ - try: - py_string = char_to_str(arb_version) - finally: - pass - return py_string diff --git a/src/sage/libs/flint/flint_wrap.h b/src/sage/libs/flint/flint_wrap.h index fcfe660a1f6..1302973779e 100644 --- a/src/sage/libs/flint/flint_wrap.h +++ b/src/sage/libs/flint/flint_wrap.h @@ -136,8 +136,6 @@ #include #include #include -#include -#include #include #include #include diff --git a/src/sage/libs/flint/fmpq.pxd b/src/sage/libs/flint/fmpq.pxd index 41f075326fe..61ebabac2b0 100644 --- a/src/sage/libs/flint/fmpq.pxd +++ b/src/sage/libs/flint/fmpq.pxd @@ -41,14 +41,12 @@ cdef extern from "flint_wrap.h": void fmpq_height(fmpz_t height, const fmpq_t x) noexcept flint_bitcnt_t fmpq_height_bits(const fmpq_t x) noexcept void fmpq_set_fmpz_frac(fmpq_t res, const fmpz_t p, const fmpz_t q) noexcept - void fmpq_get_mpz_frac(mpz_t a, mpz_t b, fmpq_t c) noexcept void fmpq_set_si(fmpq_t res, slong p, ulong q) noexcept void _fmpq_set_si(fmpz_t rnum, fmpz_t rden, slong p, ulong q) noexcept void fmpq_set_ui(fmpq_t res, ulong p, ulong q) noexcept void _fmpq_set_ui(fmpz_t rnum, fmpz_t rden, ulong p, ulong q) noexcept void fmpq_set_mpq(fmpq_t dest, const mpq_t src) noexcept int fmpq_set_str(fmpq_t dest, const char * s, int base) noexcept - void fmpq_init_set_mpz_frac_readonly(fmpq_t z, const mpz_t p, const mpz_t q) noexcept double fmpq_get_d(const fmpq_t f) noexcept void fmpq_get_mpq(mpq_t dest, const fmpq_t src) noexcept int fmpq_get_mpfr(mpfr_t dest, const fmpq_t src, mpfr_rnd_t rnd) noexcept diff --git a/src/sage/rings/complex_arb.pyx b/src/sage/rings/complex_arb.pyx index 4ee2ec45015..46c01da4eb6 100644 --- a/src/sage/rings/complex_arb.pyx +++ b/src/sage/rings/complex_arb.pyx @@ -4792,18 +4792,14 @@ cdef class ComplexBall(RingElement): sage: n = CBF(1,1) sage: m = CBF(-2/3, 3/5) - sage: n.elliptic_pi_inc(CBF.pi()/2, m) # arb216 - [0.8934793755173 +/- ...e-14] + [0.95707868710750 +/- ...e-15]*I - sage: n.elliptic_pi_inc(CBF.pi()/2, m) # arb218 - this is a regression, see :issue:28623 + sage: n.elliptic_pi_inc(CBF.pi()/2, m) # this is a regression, see :issue:28623 nan + nan*I sage: n.elliptic_pi(m) [0.8934793755173...] + [0.957078687107...]*I sage: n = CBF(2, 3/7) sage: m = CBF(-1/3, 2/9) - sage: n.elliptic_pi_inc(CBF.pi()/2, m) # arb216 - [0.2969588746419 +/- ...e-14] + [1.3188795332738 +/- ...e-14]*I - sage: n.elliptic_pi_inc(CBF.pi()/2, m) # arb218 - this is a regression, see :issue:28623 + sage: n.elliptic_pi_inc(CBF.pi()/2, m) # this is a regression, see :issue:28623 nan + nan*I sage: n.elliptic_pi(m) [0.296958874641...] + [1.318879533273...]*I diff --git a/src/sage/rings/finite_rings/conway_polynomials.py b/src/sage/rings/finite_rings/conway_polynomials.py index fcb3e2ab8d6..7d6e558f030 100644 --- a/src/sage/rings/finite_rings/conway_polynomials.py +++ b/src/sage/rings/finite_rings/conway_polynomials.py @@ -239,7 +239,7 @@ def polynomial(self, n): # TODO: something like the following # gcds = [n.gcd(d) for d in self.nodes.keys()] # xi = { m: (...) for m in gcds } - xi = {q: self.polynomial(n//q).any_root(K, n//q, assume_squarefree=True, assume_distinct_deg=True) + xi = {q: self.polynomial(n//q).any_root(K, n//q, assume_squarefree=True, assume_equal_deg=True) for q in n.prime_divisors()} # The following is needed to ensure that in the concrete instantiation diff --git a/src/sage/rings/polynomial/polynomial_element.pyx b/src/sage/rings/polynomial/polynomial_element.pyx index 94d5854976a..99f00618bc1 100644 --- a/src/sage/rings/polynomial/polynomial_element.pyx +++ b/src/sage/rings/polynomial/polynomial_element.pyx @@ -134,7 +134,6 @@ from sage.categories.morphism cimport Morphism from sage.misc.superseded import deprecation_cython as deprecation, deprecated_function_alias from sage.misc.cachefunc import cached_method - cpdef is_Polynomial(f) noexcept: """ Return ``True`` if ``f`` is of type univariate polynomial. @@ -2151,6 +2150,18 @@ cdef class Polynomial(CommutativePolynomial): True sage: f % factor == 0 True + + TESTS: + + Ensure that :issue:`37445` is fixed:: + + sage: R. = GF(13)[] + sage: def irr(d, R): return f.monic() if (f := R.random_element(d)).is_irreducible() else irr(d, R) + sage: f = prod(irr(6, R) for _ in range(10)) + sage: irr = f._cantor_zassenhaus_split_to_irreducible(6) + sage: assert irr.degree() == 6 + sage: assert f % irr == 0 + sage: assert irr.is_irreducible() """ R = self.parent() q = self.base_ring().order() @@ -2160,8 +2171,8 @@ cdef class Polynomial(CommutativePolynomial): return self # We expect to succeed with greater than 1/2 probability, - # so if we try 1000 times and fail, there's a bug somewhere. - for _ in range(1000): + # so if we try 100 times and fail, there's a bug somewhere. + for _ in range(100): # Sample a polynomial "uniformly" from R # TODO: once #37118 has been merged, this can be made cleaner, # as we will actually have access to uniform sampling. @@ -2174,7 +2185,7 @@ cdef class Polynomial(CommutativePolynomial): # Need to handle odd and even characteristic separately if q % 2: - h = self.gcd(pow(T, (q-1)//2, self) - 1) + h = self.gcd(pow(T, (q**degree-1)//2, self) - 1) else: # Compute the trace of T with field of order 2^k # sum T^(2^i) for i in range (degree * k) @@ -2200,9 +2211,9 @@ cdef class Polynomial(CommutativePolynomial): # If you are reaching this error, chances are there's a bug in the code. raise AssertionError(f"no splitting of degree {degree} found for {self}") - def _any_irreducible_factor_squarefree(self, degree=None): + def _any_irreducible_factor_squarefree(self, degree=None, ext_degree=None): """ - Helper function for any_irreducible_factor which computes + Helper function for :meth:`any_irreducible_factor` which computes an irreducible factor from self, assuming the input is squarefree. @@ -2210,10 +2221,13 @@ cdef class Polynomial(CommutativePolynomial): of self and then finds a factor with Cantor-Zassenhaus splitting. - If degree is not None, then only irreducible factors of degree - `degree` are searched for, otherwise the smallest degree factor + If ``degree`` is not ``None``, then only irreducible factors of degree + ``degree`` are searched for, otherwise the smallest degree factor is found. + If ``ext_degree`` is not ``None``, then only irreducible factors whose + degree divides ``ext_degree`` are returned. + EXAMPLES:: sage: # needs sage.rings.finite_rings @@ -2262,10 +2276,15 @@ cdef class Polynomial(CommutativePolynomial): # Otherwise we use the smallest possible d value for (poly, d) in self._distinct_degree_factorisation_squarefree(): - return poly._cantor_zassenhaus_split_to_irreducible(d) - raise ValueError(f"no irreducible factor could be computed from {self}") + if ext_degree is None: + return poly._cantor_zassenhaus_split_to_irreducible(d) + elif ZZ(d).divides(ext_degree): + return poly._cantor_zassenhaus_split_to_irreducible(d) + if d > ext_degree: + raise ValueError(f"no irreducible factor of degree {degree} dividing {ext_degree} could be computed from {self}") + raise AssertionError(f"no irreducible factor could be computed from {self}") - def any_irreducible_factor(self, degree=None, assume_squarefree=False, assume_distinct_deg=False): + def any_irreducible_factor(self, degree=None, assume_squarefree=False, assume_equal_deg=False, ext_degree=None): """ Return an irreducible factor of this polynomial. @@ -2281,11 +2300,15 @@ cdef class Polynomial(CommutativePolynomial): Used for polynomials over finite fields. If ``True``, this polynomial is assumed to be squarefree. - - ``assume_distinct_deg`` (boolean) -- (default: ``False``). + - ``assume_equal_deg`` (boolean) -- (default: ``False``). Used for polynomials over finite fields. If ``True``, this polynomial is assumed to be the product of irreducible polynomials of degree equal to ``degree``. + - ``ext_degree`` -- positive integer or ``None`` (default); + used for polynomials over finite fields. If not ``None`` only returns + irreducible factors of ``self`` whose degree divides ``ext_degree``. + EXAMPLES:: sage: # needs sage.rings.finite_rings @@ -2328,9 +2351,9 @@ cdef class Polynomial(CommutativePolynomial): sage: F = GF(163) sage: R. = F[] sage: h = (x + 57) * (x + 98) * (x + 117) * (x + 145) - sage: h.any_irreducible_factor(degree=1, assume_distinct_deg=True) # random + sage: h.any_irreducible_factor(degree=1, assume_equal_deg=True) # random x + 98 - sage: h.any_irreducible_factor(assume_distinct_deg=True) + sage: h.any_irreducible_factor(assume_equal_deg=True) Traceback (most recent call last): ... ValueError: degree must be known if distinct degree factorisation is assumed @@ -2359,7 +2382,7 @@ cdef class Polynomial(CommutativePolynomial): if degree < 1: raise ValueError(f"{degree = } must be positive") - if assume_distinct_deg and degree is None: + if assume_equal_deg and degree is None: raise ValueError("degree must be known if distinct degree factorisation is assumed") # When not working over a finite field, do the simple thing of factoring. @@ -2397,9 +2420,9 @@ cdef class Polynomial(CommutativePolynomial): # If we know the polynomial is square-free, we can start here if assume_squarefree: - if assume_distinct_deg: + if assume_equal_deg: return self._cantor_zassenhaus_split_to_irreducible(degree) - return self._any_irreducible_factor_squarefree(degree) + return self._any_irreducible_factor_squarefree(degree, ext_degree) # Otherwise we compute the squarefree decomposition and check each # polynomial for a root. If no poly has a root, we raise an error. @@ -2407,17 +2430,20 @@ cdef class Polynomial(CommutativePolynomial): SFD.sort() for poly, _ in SFD: try: - return poly._any_irreducible_factor_squarefree(degree) + return poly._any_irreducible_factor_squarefree(degree, ext_degree) except ValueError: pass # If degree has been set, there could just be no factor of the desired degree if degree: raise ValueError(f"polynomial {self} has no irreducible factor of degree {degree}") + # If ext_degree has been set, then there may be no irreducible factor of degree dividing ext_degree + if ext_degree: + raise ValueError(f"polynomial {self} has no irreducible factor of degree dividing {ext_degree}") # But if any degree is allowed then there should certainly be a factor if self has degree > 0 raise AssertionError(f"no irreducible factor was computed for {self}. Bug.") - def any_root(self, ring=None, degree=None, assume_squarefree=False, assume_distinct_deg=False): + def any_root(self, ring=None, degree=None, assume_squarefree=False, assume_equal_deg=False): """ Return a root of this polynomial in the given ring. @@ -2437,14 +2463,26 @@ cdef class Polynomial(CommutativePolynomial): finite fields. If ``True``, this polynomial is assumed to be squarefree. - - ``assume_distinct_deg`` (bool) -- Used for polynomials over + - ``assume_equal_deg`` (bool) -- Used for polynomials over finite fields. If ``True``, all factors of this polynomial - are assumed to have degree ``degree``. + are assumed to have degree ``degree``. Note that ``degree`` + must be set. .. WARNING:: Negative degree input will be deprecated. Instead use - ``assume_distinct_deg``. + ``assume_equal_deg``. + + .. NOTE:: + + For finite fields, ``any_root()`` is non-deterministic when + finding linear roots of a polynomial over the base ring. + However, if ``degree`` is greater than one, or ``ring`` is an + extension of the base ring, then the root computed is found + by attempting to return a root after factorisation. Roots found + in this way are deterministic. This may change in the future. + For all other rings or fields, roots are found by first + fully-factoring ``self`` and the output is deterministic. EXAMPLES:: @@ -2554,28 +2592,66 @@ cdef class Polynomial(CommutativePolynomial): # When not working over a finite field, do the simple thing of factoring for # roots and picking the first root. If none are available, raise an error. from sage.categories.finite_fields import FiniteFields - if not self.base_ring() in FiniteFields(): - rs = self.roots(ring=ring, multiplicities=False) - if rs: - return rs[0] - raise ValueError(f"polynomial {self} has no roots") + if self.base_ring() not in FiniteFields(): + if ring not in FiniteFields(): + rs = self.roots(ring=ring, multiplicities=False) + if rs: + return rs[0] + raise ValueError(f"polynomial {self} has no roots") + + # Ensure that a provided ring is appropriate for the function. From the + # above we know it is either None or a finite field. When it's a finite + # field we ensure there's a coercion from the base ring to ring. + if ring is not None: + if ring.coerce_map_from(self.base_ring()) is None: + raise ValueError(f"no coercion map can be computed from {self.base_ring()} to {ring}") # When the degree is none, we only look for a linear factor if degree is None: - # if a ring is given try and coerce the polynomial into this ring - if ring is not None: + # When ring is None, we attempt to find a linear factor of self + if ring is None: try: - self = self.change_ring(ring) + f = self.any_irreducible_factor(degree=1, assume_squarefree=assume_squarefree) except ValueError: - raise(f"cannot coerce polynomial {self} to the new ring: {ring}") + raise ValueError(f"no root of polynomial {self} can be computed") + return - f[0] / f[1] - # try and find a linear irreducible polynomial from f to compute a root + # When we have a ring, then we can find an irreducible factor of degree `d` providing + # that d divides the degree of the extension from the base ring to the given ring + allowed_extension_degree = ring.degree() // self.base_ring().degree() try: - f = self.any_irreducible_factor(degree=1, assume_squarefree=assume_squarefree) + f = self.any_irreducible_factor(assume_squarefree=assume_squarefree, ext_degree=allowed_extension_degree) except ValueError: - raise ValueError(f"no root of polynomial {self} can be computed") - - return - f[0] / f[1] + raise ValueError(f"no root of polynomial {self} can be computed over the ring {ring}") + # When d != 1 we then find the smallest extension + # TODO: What we should do here is compute some minimal + # extension F_ext = self.base_ring().extension(d, names="a") and find a + # root here and then coerce this root into the parent ring. This means we + # would work with the smallest possible extension. + # However, if we have some element of GF(p^k) and we try and coerce this to + # some element GF(p^(k*n)) this can fail, even though mathematically it + # should be fine. + # TODO: Additionally, if the above was solved, it would be faster to extend the base + # ring with the irreducible factor however, if the base ring is an extension + # then the type of self.base_ring().extension(f) is a Univariate Quotient Polynomial Ring + # and not a finite field. + + # When f has degree one we simply return the roots + # TODO: should we write something fast for degree two using + # the quadratic formula? + if f.degree().is_one(): + root = - f[0] / f[1] + return ring(root) + + # TODO: The proper thing to do here would be to call + # return f.change_ring(ring).any_root() + # but as we cannot work in the minimal extension (see above) working + # in the extension for f.change_ring(ring).any_root() is almost always + # much much much slower than using the call for roots() which uses + # C library bindings for all finite fields. + # Until the coercion system for finite fields works better, + # this will be the most performant + return f.roots(ring, multiplicities=False)[0] # The old version of `any_root()` allowed degree < 0 to indicate that the input polynomial # had a distinct degree factorisation, we pass this to any_irreducible_factor as a bool and @@ -2583,9 +2659,9 @@ cdef class Polynomial(CommutativePolynomial): degree = ZZ(degree) if degree < 0: from sage.misc.superseded import deprecation - deprecation(37170, "negative ``degree`` will be disallowed. Instead use the bool `assume_distinct_deg`.") + deprecation(37170, "negative ``degree`` will be disallowed. Instead use the bool `assume_equal_deg`.") degree = -degree - assume_distinct_deg = True + assume_equal_deg = True # If a certain degree is requested, then we find an irreducible factor of degree `degree` # use this to compute a field extension and return the generator as root of this polynomial @@ -2594,7 +2670,7 @@ cdef class Polynomial(CommutativePolynomial): try: f = self.any_irreducible_factor(degree=degree, assume_squarefree=assume_squarefree, - assume_distinct_deg=assume_distinct_deg) + assume_equal_deg=assume_equal_deg) except ValueError: raise ValueError(f"no irreducible factor of degree {degree} can be computed from {self}") @@ -2617,13 +2693,17 @@ cdef class Polynomial(CommutativePolynomial): # FiniteField type if the base field is a non-prime field, # so this slower option is chosen to ensure the root is # over explicitly a FiniteField type. - ring = self.base_ring().extension(f.degree(), names="a") - - # Now we look for a linear root of this irreducible polynomial of degree `degree` - # over the user supplied ring or the extension we just computed. If the user sent - # a bad ring here of course there may be no root found. - f = f.change_ring(ring) - return f.any_root() + ring = self.base_ring().extension(degree, names="a") + + # TODO: The proper thing to do here would be to call + # return f.change_ring(ring).any_root() + # but as we cannot work in the minimal extension (see above) working + # in the extension for f.change_ring(ring).any_root() is almost always + # much much much slower than using the call for roots() which uses + # C library bindings for all finite fields. + # Until the coercion system for finite fields works better, + # this will be the most performant + return f.roots(ring, multiplicities=False)[0] def __truediv__(left, right): r""" diff --git a/src/sage/rings/polynomial/polynomial_quotient_ring.py b/src/sage/rings/polynomial/polynomial_quotient_ring.py index 39f4e6ed521..9560ffc6ddc 100644 --- a/src/sage/rings/polynomial/polynomial_quotient_ring.py +++ b/src/sage/rings/polynomial/polynomial_quotient_ring.py @@ -2098,7 +2098,7 @@ def _isomorphic_ring(self): base_image = self.base_ring().modulus().change_ring(isomorphic_ring).any_root() base_to_isomorphic_ring = self.base_ring().hom([isomorphic_ring(base_image)]) modulus = self.modulus().map_coefficients(base_to_isomorphic_ring) - gen = modulus.any_root(assume_squarefree=True, degree=1, assume_distinct_deg=True) + gen = modulus.any_root(assume_squarefree=True, degree=1, assume_equal_deg=True) homspace = Hom(self, isomorphic_ring) to_isomorphic_ring = homspace.__make_element_class__(SetMorphism)(homspace, diff --git a/src/sage/rings/real_arb.pyx b/src/sage/rings/real_arb.pyx index c72d63e1a97..e1bc0c08f16 100644 --- a/src/sage/rings/real_arb.pyx +++ b/src/sage/rings/real_arb.pyx @@ -294,11 +294,7 @@ cdef int arb_to_mpfi(mpfi_t target, arb_t source, const long precision) except - EXAMPLES:: - sage: RIF(RBF(2)**(2**100)) # arb216 # indirect doctest - Traceback (most recent call last): - ... - ArithmeticError: Error converting arb to mpfi. Overflow? - sage: RIF(RBF(2)**(2**100)) # arb218 # indirect doctest + sage: RIF(RBF(2)**(2**100)) [5.8756537891115869e1388255822130839282 .. +infinity] # 64-bit [2.098... .. +infinity] # 32-bit @@ -1729,11 +1725,7 @@ cdef class RealBall(RingElement): :: sage: b = RBF(2)^(2^1000) - sage: b.mid() # arb216 - Traceback (most recent call last): - ... - RuntimeError: unable to convert to MPFR (exponent out of range?) - sage: b.mid() # arb218 + sage: b.mid() +infinity .. SEEALSO:: :meth:`rad`, :meth:`squash` diff --git a/src/sage/schemes/elliptic_curves/cm.py b/src/sage/schemes/elliptic_curves/cm.py index 1f0fd8bd34e..80cfd5f9b2a 100644 --- a/src/sage/schemes/elliptic_curves/cm.py +++ b/src/sage/schemes/elliptic_curves/cm.py @@ -233,6 +233,13 @@ def is_HCP(f, check_monic_irreducible=True): sage: all(not is_HCP(hilbert_class_polynomial(D) + 1) ....: for D in srange(-4,-100,-1) if D.is_discriminant()) True + + Ensure that :issue:`37471` is fixed:: + + sage: from sage.schemes.elliptic_curves.cm import is_HCP + sage: set_random_seed(297388353221545796156853787333338705098) + sage: is_HCP(hilbert_class_polynomial(-55)) + -55 """ zero = ZZ(0) # optional check that input is monic and irreducible @@ -264,14 +271,15 @@ def is_HCP(f, check_monic_irreducible=True): # Compute X^p-X mod fp z = fp.parent().gen() r = pow(z, p, fp) - z - d = r.gcd(fp).degree() # number of roots mod p + r = r.gcd(fp) + d = r.degree() # number of roots mod p if d == 0: continue - if not fp.is_squarefree(): + if not r.is_squarefree(): continue if d < h and d not in h2list: return zero - jp = fp.any_root(degree=1, assume_squarefree=True, assume_distinct_deg=True) + jp = r.any_root(degree=1, assume_squarefree=True, assume_equal_deg=True) E = EllipticCurve(j=jp) if E.is_supersingular(): continue diff --git a/src/sage/symbolic/ginac/useries-flint.h b/src/sage/symbolic/ginac/useries-flint.h index 7ecd4d50107..08847273e2e 100644 --- a/src/sage/symbolic/ginac/useries-flint.h +++ b/src/sage/symbolic/ginac/useries-flint.h @@ -27,9 +27,6 @@ #include "flint/fmpq_poly.h" #include "flint/fmpq.h" -extern "C" void fmpq_get_mpz_frac(mpz_t a, mpz_t b, fmpq_t c); -extern "C" void fmpq_init_set_mpz_frac_readonly(fmpq_t z, const mpz_t p, const mpz_t q); - #include diff --git a/src/sage/symbolic/ginac/useries.cpp b/src/sage/symbolic/ginac/useries.cpp index b9a8b867648..7649e36b49a 100644 --- a/src/sage/symbolic/ginac/useries.cpp +++ b/src/sage/symbolic/ginac/useries.cpp @@ -550,14 +550,16 @@ void power::useries(flint_series_t& fp, int order) const mpz_t cnum, cden; mpz_init(cnum); mpz_init(cden); - fmpq_get_mpz_frac(cnum, cden, c); + fmpz_get_mpz(cnum, fmpq_numref(c)); + fmpz_get_mpz(cden, fmpq_denref(c)); if (not mpz_perfect_square_p(cnum) or not mpz_perfect_square_p(cden)) throw flint_error(); mpz_sqrt(cnum, cnum); mpz_sqrt(cden, cden); fmpq_t cc; - fmpq_init_set_mpz_frac_readonly(cc, cnum, cden); + fmpz_init_set_readonly(fmpq_numref(cc), cnum); + fmpz_init_set_readonly(fmpq_denref(cc), cden); mpz_clear(cnum); mpz_clear(cden); diff --git a/src/sage/version.py b/src/sage/version.py index ff9dc5878fc..e92696ee471 100644 --- a/src/sage/version.py +++ b/src/sage/version.py @@ -1,5 +1,5 @@ # Sage version information for Python scripts # This file is auto-generated by the sage-update-version script, do not edit! -version = '10.3.rc0' -date = '2024-02-25' -banner = 'SageMath version 10.3.rc0, Release Date: 2024-02-25' +version = '10.3.rc2' +date = '2024-03-04' +banner = 'SageMath version 10.3.rc2, Release Date: 2024-03-04' diff --git a/src/sage_docbuild/ext/sage_autodoc.py b/src/sage_docbuild/ext/sage_autodoc.py index 346566a266a..4e9bcef39be 100644 --- a/src/sage_docbuild/ext/sage_autodoc.py +++ b/src/sage_docbuild/ext/sage_autodoc.py @@ -113,7 +113,7 @@ def identity(x: Any) -> Any: class _All: - """A special value for :*-members: that matches to any member.""" + """A special value for ``:*-members:`` that matches to any member.""" def __contains__(self, item: Any) -> bool: return True diff --git a/tox.ini b/tox.ini index 3673833724c..a9ff0423488 100644 --- a/tox.ini +++ b/tox.ini @@ -512,6 +512,9 @@ setenv = # brew caches downloaded files in ${HOME}/Library/Caches. We share it between different toxenvs. homebrew: SYSTEM=homebrew local-homebrew: HOMEBREW={envdir}/homebrew + # /opt/homebrew is the default install location on arm64 macOS + local-homebrew-opthomebrew: HOMEBREW=/opt/homebrew + # /usr/local is the default install location on x86_64 macOS local-{homebrew-usrlocal,nohomebrew}: HOMEBREW=/usr/local # local-macos-nohomebrew: "best effort" isolation to avoid using a homebrew installation in /usr/local # We use liblzma from the macOS system - which is available but its headers are not (neither is the xz executable). @@ -538,10 +541,11 @@ setenv = local-conda: PATH={env:CONDA_PREFIX}/bin:/usr/bin:/bin:/usr/sbin:/sbin local-conda: CONDA_PKGS_DIRS={env:SHARED_CACHE_DIR}/conda_pkgs local-conda: CONDA_OS=$(uname | sed 's/^Darwin/MacOSX/;') + local-conda: CONDA_ARCH=$(uname -m) local-conda-forge: CONDA_INSTALLER_URL_BASE=https://github.com/conda-forge/miniforge/releases/latest/download/ - local-conda-forge: CONDA_INSTALLER_FILE=Miniforge3-{env:CONDA_OS}-x86_64.sh + local-conda-forge: CONDA_INSTALLER_FILE=Miniforge3-{env:CONDA_OS}-{env:CONDA_ARCH}.sh local-conda-miniconda: CONDA_INSTALLER_URL_BASE=https://repo.anaconda.com/miniconda/ - local-conda-miniconda: CONDA_INSTALLER_FILE=Miniconda3-latest-{env:CONDA_OS}-x86_64.sh + local-conda-miniconda: CONDA_INSTALLER_FILE=Miniconda3-latest-{env:CONDA_OS}-{env:CONDA_ARCH}.sh local-conda: SETENV=. {env:CONDA_PREFIX}/bin/activate base local-conda-environment: CONDA_SAGE_ENVIRONMENT=sage-build local-conda-environment: CONDA_SAGE_ENVIRONMENT_DIR=