From e99e3cd0f3e8d76c0e536d2020b3833c381bc36f Mon Sep 17 00:00:00 2001 From: Tobias Diez Date: Sat, 29 Nov 2025 22:47:18 +0100 Subject: [PATCH 01/10] Add Mingw build to meson-ci --- .github/workflows/ci.yml | 91 +++++++++++++++++++++++++++++++++++----- 1 file changed, 81 insertions(+), 10 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 14ed18ffc1c..d95a97ae5dc 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -3,18 +3,18 @@ name: CI Linux on: push: tags: - - '*' + - "*" pull_request: paths: - - '**.build' - - 'uv.lock' - - 'subprojects/**' - - '.github/workflows/ci.yml' + - "**.build" + - "uv.lock" + - "subprojects/**" + - ".github/workflows/ci.yml" workflow_dispatch: # Allow to run manually jobs: - build: + linux: name: Build and Test runs-on: ubuntu-latest strategy: @@ -107,10 +107,10 @@ jobs: id: clear-container-name if: failure() || success() run: | - # Output the container name, but with ":" and "/" replaced by "-" - # This is needed to avoid issues with the upload-artifact action - CONTAINER_NAME=$(echo "${{ matrix.container }}" | tr ':/' '--') - echo "CONTAINER_NAME=$CONTAINER_NAME" >> $GITHUB_OUTPUT + # Output the container name, but with ":" and "/" replaced by "-" + # This is needed to avoid issues with the upload-artifact action + CONTAINER_NAME=$(echo "${{ matrix.container }}" | tr ':/' '--') + echo "CONTAINER_NAME=$CONTAINER_NAME" >> $GITHUB_OUTPUT - name: Upload log uses: actions/upload-artifact@v4.5.0 @@ -120,3 +120,74 @@ jobs: path: | build/cp313/meson-logs/ build/cp313/meson-info/ + + mingw: + name: Build and Test (Mingw) + runs-on: windows-latest + steps: + - name: Checkout code + uses: actions/checkout@v4 + + - name: Install uv + uses: astral-sh/setup-uv@v6.7.0 + + - name: Set up Python + run: | + uv python install + uv venv + source .venv/Scripts/activate + echo PATH=$PATH >> $GITHUB_ENV + + - name: Install dependencies + uses: msys2/setup-msys2@v2 + with: + msystem: UCRT64 + update: true + install: >- + base-devel + mingw-w64-ucrt-x86_64-toolchain + mingw-w64-ucrt-x86_64-ninja + mingw-w64-ucrt-x86_64-gmp + mingw-w64-ucrt-x86_64-openblas + mingw-w64-ucrt-x86_64-m4ri + mingw-w64-ucrt-x86_64-flint + mingw-w64-ucrt-x86_64-mpfi + mingw-w64-ucrt-x86_64-gsl + mingw-w64-ucrt-x86_64-libgd + mingw-w64-ucrt-x86_64-gsl + mingw-w64-ucrt-x86_64-libgd + mingw-w64-ucrt-x86_64-ntl + mingw-w64-ucrt-x86_64-boost + mingw-w64-ucrt-x86_64-nauty + mingw-w64-ucrt-x86_64-libhomfly + mingw-w64-ucrt-x86_64-symmetrica + mingw-w64-ucrt-x86_64-glpk + mingw-w64-ucrt-x86_64-gc + path-type: inherit + + - name: Build + run: | + # Install build dependencies manually as workaround for https://github.com/astral-sh/uv/issues/1516 + uv pip install \ + meson-python \ + "cysignals >=1.11.2, != 1.12.0" \ + "cython >=3.0, != 3.0.3, < 3.1.0" \ + "gmpy2 >=2.1.5" \ + memory_allocator \ + "numpy >=1.25" \ + jinja2 \ + setuptools + uv sync --frozen --inexact --no-build-isolation -v --no-editable --config-settings=builddir=builddir + + - name: Test + run: | + uv run --frozen ./sage -t --all -p4 || true + + - name: Upload log + uses: actions/upload-artifact@v4.5.0 + if: failure() || success() + with: + name: mingw-log + path: | + build/cp313/meson-logs/ + build/cp313/meson-info/ From 12d55fdd36499b79a173ac7dc45dd8feb6dff7dc Mon Sep 17 00:00:00 2001 From: Tobias Diez Date: Sat, 29 Nov 2025 22:57:03 +0100 Subject: [PATCH 02/10] Fix venv activation --- .github/workflows/ci.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index d95a97ae5dc..1c236c4b82c 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -135,7 +135,7 @@ jobs: run: | uv python install uv venv - source .venv/Scripts/activate + .venv\Scripts\activate echo PATH=$PATH >> $GITHUB_ENV - name: Install dependencies From 85e4cc8d8ffc8cd9291874a5cc4b630c935ec68f Mon Sep 17 00:00:00 2001 From: Tobias Diez Date: Sat, 29 Nov 2025 23:17:22 +0100 Subject: [PATCH 03/10] Fix shell specification for Mingw --- .github/workflows/ci.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 1c236c4b82c..072f747a47c 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -166,6 +166,7 @@ jobs: path-type: inherit - name: Build + shell: msys2 {0} run: | # Install build dependencies manually as workaround for https://github.com/astral-sh/uv/issues/1516 uv pip install \ @@ -180,6 +181,7 @@ jobs: uv sync --frozen --inexact --no-build-isolation -v --no-editable --config-settings=builddir=builddir - name: Test + shell: msys2 {0} run: | uv run --frozen ./sage -t --all -p4 || true From 11091fdb64f0dcc4df793c0b8bdadd4f1a499552 Mon Sep 17 00:00:00 2001 From: Tobias Diez Date: Sat, 29 Nov 2025 23:43:28 +0100 Subject: [PATCH 04/10] Set SIZEOF_LONG in singular subproject --- subprojects/packagefiles/singular/factory/meson.build | 1 + 1 file changed, 1 insertion(+) diff --git a/subprojects/packagefiles/singular/factory/meson.build b/subprojects/packagefiles/singular/factory/meson.build index a2b1961253e..3a28d85a05e 100644 --- a/subprojects/packagefiles/singular/factory/meson.build +++ b/subprojects/packagefiles/singular/factory/meson.build @@ -12,6 +12,7 @@ has_iostream_h = cpp.has_header('iostream.h') conf.set('NOSTREAMIO', not has_iostream_h)#, 'strstream.h', 'fstream.h', 'iostream', 'string', 'fstream', 'ctype.h')) conf.set('HAVE_IOSTREAM_H', has_iostream_h) conf.set('FACTORYVERSION', meson.project_version()) +conf.set('SIZEOF_LONG', cpp.sizeof('long')) factory_configuration = '@0@ in @1@'.format(meson.project_version(), meson.project_source_root()) conf.set_quoted('FACTORYCONFIGURATION', factory_configuration) From c71bd71e47b960b0c7cd02c5c05c5f04406620a2 Mon Sep 17 00:00:00 2001 From: Tobias Diez Date: Sun, 30 Nov 2025 00:45:48 +0100 Subject: [PATCH 05/10] Fix data type for graph degree in static_dense_graph Fix for src/sage/graphs/base/static_dense_graph.cp313-win_amd64.pyd.p/src/sage/graphs/base/static_dense_graph.pyx.c: 2025-11-29T22:55:09.9262575Z In function 2025-11-29T22:55:09.9262723Z '__pyx_gb_4sage_6graphs_4base_18static_dense_graph_13generator1': 2025-11-29T22:55:09.9263047Z src/sage/graphs/base/static_dense_graph.cp313-win_amd64.pyd.p/src/sage/graphs/base/static_dense_graph.pyx.c:19862:88: 2025-11-29T22:55:09.9263213Z error: passing argument 1 of '_bitset_len' from incompatible pointer 2025-11-29T22:55:09.9263401Z type [-Wincompatible-pointer-types] 2025-11-29T22:55:09.9263473Z 19862 | __pyx_t_9 = 2025-11-29T22:55:09.9263601Z PyInt_FromSsize_t((__pyx_cur_scope->__pyx_v_num_edges 2025-11-29T22:55:09.9263701Z + _bitset_len((&__pyx_cur_scope->__pyx_v_c), 1))); if 2025-11-29T22:55:09.9263813Z (unlikely(!__pyx_t_9)) __PYX_ERR(0, 688, __pyx_L1_error) 2025-11-29T22:55:09.9263872Z | 2025-11-29T22:55:09.9263941Z ~^~~~~~~~~~~~~~~~~~~~~~~~~~~~ 2025-11-29T22:55:09.9263992Z | 2025-11-29T22:55:09.9264050Z | 2025-11-29T22:55:09.9264101Z | 2025-11-29T22:55:09.9264192Z mp_bitcnt_t * {aka long unsigned int *} 2025-11-29T22:55:09.9264262Z In file included from 2025-11-29T22:55:09.9264587Z src/sage/graphs/base/static_dense_graph.cp313-win_amd64.pyd.p/src/sage/graphs/base/static_dense_graph.pyx.c:1228: 2025-11-29T22:55:09.9264748Z ..\src\sage\data_structures/bitset_intrinsics.h:272:43: note: expected 2025-11-29T22:55:09.9264889Z 'mp_limb_t *' {aka 'long long unsigned int *'} but argument is of type 2025-11-29T22:55:09.9264978Z 'mp_bitcnt_t *' {aka 'long unsigned int *'} 2025-11-29T22:55:09.9265124Z 272 | static inline long _bitset_len(mp_limb_t* bits, mp_bitcnt_t 2025-11-29T22:55:09.9265178Z limbs){ 2025-11-29T22:55:09.9265261Z | ~~~~~~~~~~~^~~~ 2025-11-29T22:55:09.9265560Z src/sage/graphs/base/static_dense_graph.cp313-win_amd64.pyd.p/src/sage/graphs/base/static_dense_graph.pyx.c: 2025-11-29T22:55:09.9265649Z In function '__Pyx_ImportType_3_0_12': 2025-11-29T22:55:09.9265978Z src/sage/graphs/base/static_dense_graph.cp313-win_amd64.pyd.p/src/sage/graphs/base/static_dense_graph.pyx.c:28401:13: 2025-11-29T22:55:09.9266139Z warning: unknown conversion type character 'z' in format [-Wformat=] 2025-11-29T22:55:09.9266246Z 28401 | "%s.%s size changed, may indicate binary 2025-11-29T22:55:09.9266322Z incompatibility. " 2025-11-29T22:55:09.9266375Z | 2025-11-29T22:55:09.9266467Z ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 2025-11-29T22:55:09.9266793Z src/sage/graphs/base/static_dense_graph.cp313-win_amd64.pyd.p/src/sage/graphs/base/static_dense_graph.pyx.c:28402:24: 2025-11-29T22:55:09.9266871Z note: format string is defined here 2025-11-29T22:55:09.9267004Z 28402 | "Expected %zd from C header, got %zd from PyObject", 2025-11-29T22:55:09.9267151Z | ^ 2025-11-29T22:55:09.9267469Z src/sage/graphs/base/static_dense_graph.cp313-win_amd64.pyd.p/src/sage/graphs/base/static_dense_graph.pyx.c:28401:13: 2025-11-29T22:55:09.9267629Z warning: unknown conversion type character 'z' in format [-Wformat=] 2025-11-29T22:55:09.9267730Z 28401 | "%s.%s size changed, may indicate binary 2025-11-29T22:55:09.9267803Z incompatibility. " 2025-11-29T22:55:09.9267855Z | 2025-11-29T22:55:09.9267938Z ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 2025-11-29T22:55:09.9268266Z src/sage/graphs/base/static_dense_graph.cp313-win_amd64.pyd.p/src/sage/graphs/base/static_dense_graph.pyx.c:28402:47: 2025-11-29T22:55:09.9268344Z note: format string is defined here 2025-11-29T22:55:09.9268464Z 28402 | "Expected %zd from C header, got %zd from PyObject", 2025-11-29T22:55:09.9268549Z | ^ 2025-11-29T22:55:09.9268861Z src/sage/graphs/base/static_dense_graph.cp313-win_amd64.pyd.p/src/sage/graphs/base/static_dense_graph.pyx.c:28401:13: 2025-11-29T22:55:09.9269001Z warning: too many arguments for format [-Wformat-extra-args] 2025-11-29T22:55:09.9269096Z 28401 | "%s.%s size changed, may indicate binary 2025-11-29T22:55:09.9269161Z incompatibility. " 2025-11-29T22:55:09.9269215Z | 2025-11-29T22:55:09.9269366Z ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ --- src/sage/graphs/base/static_dense_graph.pyx | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/src/sage/graphs/base/static_dense_graph.pyx b/src/sage/graphs/base/static_dense_graph.pyx index f417b7e666c..b54a53c9545 100644 --- a/src/sage/graphs/base/static_dense_graph.pyx +++ b/src/sage/graphs/base/static_dense_graph.pyx @@ -612,7 +612,7 @@ def connected_full_subgraphs(G, edges_only=False, labels=False, raise ValueError("the input (di)graph is not connected") for d in G.degree(): - if d >= 8 * sizeof(unsigned long) - 1: + if d >= 8 * sizeof(mp_limb_t) - 1: raise ValueError("the degree of the graph is too large for this method") cdef Py_ssize_t n = G.order() @@ -640,7 +640,7 @@ def connected_full_subgraphs(G, edges_only=False, labels=False, cdef MemoryAllocator mem = MemoryAllocator() cdef int * order = mem.calloc(n, sizeof(int)) - cdef mp_bitcnt_t * n_cpt = mem.calloc(n, sizeof(mp_bitcnt_t)) + cdef mp_limb_t * n_cpt = mem.calloc(n, sizeof(mp_limb_t)) # We use several bitsets to store the current boundary and active neighbors. # We also need another bitset that we create at the same time @@ -659,10 +659,10 @@ def connected_full_subgraphs(G, edges_only=False, labels=False, bitset_complement(active, active) bitset_discard(active, 0) bitset_copy(neighborhoods.rows[0], DG.rows[0]) - n_cpt[0] = 1 << bitset_len(DG.rows[0]) + n_cpt[0] = (1) << bitset_len(DG.rows[0]) cdef long u, v, j - cdef mp_bitcnt_t c + cdef mp_limb_t c cdef Py_ssize_t num_edges = 0 cdef list E = [] cdef list edges @@ -734,7 +734,10 @@ def connected_full_subgraphs(G, edges_only=False, labels=False, # prepare neighborhood of u bitset_and(neighborhoods.rows[i], active, DG.rows[u]) j = bitset_len(neighborhoods.rows[i]) - n_cpt[i] = bool(j) << j # 0 if not j else 2^j + if j: + n_cpt[i] = (1) << j # 2^j possible neighborhoods + else: + n_cpt[i] = 0 sig_on() binary_matrix_free(boundaries) From 5cf5e3e43f7dd9abd49bbdeae2dcd76dd37cb944 Mon Sep 17 00:00:00 2001 From: Tobias Diez Date: Sun, 30 Nov 2025 01:15:30 +0100 Subject: [PATCH 06/10] Don't use M_PI from libc.math It's not part of the C standard, and not provided on Windows. Fix for src/sage/plot/plot3d/shapes.cp311-win_amd64.pyd.p/src/sage/plot/plot3d/shapes.pyx.c:12624:35: error: 'M_PI' undeclared (first use in this function) 12624 | __pyx_t_3 = PyLong_FromDouble(((M_PI * __pyx_v_self->radius) / __pyx_v_ds)); if (unlikely(!__pyx_t_3)) __PYX_ERR(0, 959, __pyx_L1_error) --- src/sage/plot/plot3d/shapes.pyx | 4 ++-- src/sage/stats/hmm/chmm.pyx | 3 ++- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/src/sage/plot/plot3d/shapes.pyx b/src/sage/plot/plot3d/shapes.pyx index 4acab1a14eb..66806533e7e 100644 --- a/src/sage/plot/plot3d/shapes.pyx +++ b/src/sage/plot/plot3d/shapes.pyx @@ -51,14 +51,14 @@ EXAMPLES:: # https://www.gnu.org/licenses/ # **************************************************************************** -from libc.math cimport sqrt, sin, cos, acos, M_PI +from libc.math cimport sqrt, sin, cos, acos from sage.rings.real_double import RDF from sage.modules.free_module_element import vector from sage.misc.decorators import rename_keyword from sage.plot.plot3d.base import Graphics3dGroup from sage.plot.plot3d.index_face_set cimport IndexFaceSet, PrimitiveObject from sage.plot.plot3d.transform cimport point_c - +from sage.arith.constants cimport M_PI # Helper function to check that Box input is right def validate_frame_size(size): diff --git a/src/sage/stats/hmm/chmm.pyx b/src/sage/stats/hmm/chmm.pyx index e1ed80da585..6337376a1f1 100644 --- a/src/sage/stats/hmm/chmm.pyx +++ b/src/sage/stats/hmm/chmm.pyx @@ -18,7 +18,8 @@ AUTHOR: # *************************************************************************** from cpython.object cimport PyObject_RichCompare -from libc.math cimport log, sqrt, exp, isnormal, isfinite, M_PI +from libc.math cimport log, sqrt, exp, isnormal, isfinite +from sage.arith.constants cimport M_PI cdef double sqrt2pi = sqrt(2*M_PI) from cysignals.signals cimport sig_on, sig_off From bac60343262a2416f97882923b671d908f6720df Mon Sep 17 00:00:00 2001 From: Tobias Diez Date: Sun, 30 Nov 2025 01:20:11 +0100 Subject: [PATCH 07/10] Add mpfr dependency for real_mpfi in meson.build Fix for [1/775] Linking target src/sage/rings/real_mpfi.cp311-win_amd64.pyd FAILED: [code=1] src/sage/rings/real_mpfi.cp311-win_amd64.pyd "cc" -o src/sage/rings/real_mpfi.cp311-win_amd64.pyd src/sage/rings/real_mpfi.cp311-win_amd64.pyd.p/meson-generated_src_sage_rings_real_mpfi.pyx.c.obj "-Wl,--allow-shlib-undefined" "-Wl,-O1" "-shared" "-Wl,--start-group" "-Wl,--out-implib=src\sage\rings\real_mpfi.cp311-win_amd64.dll.a" "C:\Users\Tobia\AppData\Local\Programs\Python\Python311\python311.dll" "C:/rtools45/ucrt64/lib/libgmp.dll.a" "-lmpfi" "-lkernel32" "-luser32" "-lgdi32" "-lwinspool" "-lshell32" "-lole32" "-loleaut32" "-luuid" "-lcomdlg32" "-ladvapi32" "-Wl,--end-group" C:/rtools45/ucrt64/bin/../lib/gcc/x86_64-w64-mingw32/15.2.0/../../../../x86_64-w64-mingw32/bin/ld.exe: src/sage/rings/real_mpfi.cp311-win_amd64.pyd.p/meson-generated_src_sage_rings_real_mpfi.pyx.c.obj:real_mpfi.pyx.:(.text+0x471b): undefined reference to `mpfr_equal_p --- src/sage/rings/meson.build | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/sage/rings/meson.build b/src/sage/rings/meson.build index 743c223dcc7..e628f5ee342 100644 --- a/src/sage/rings/meson.build +++ b/src/sage/rings/meson.build @@ -191,7 +191,7 @@ foreach name, pyx : extension_data elif name == 'real_double_element_gsl' deps += [gsl] elif name == 'real_mpfi' - deps += [mpfi] + deps += [mpfi, mpfr] elif name == 'real_mpfr' deps += [gmpy2, mpfr] elif name == 'tate_algebra_element' From 0751f4c0968ce3bdfaf51cbc536c8c6b9be528b2 Mon Sep 17 00:00:00 2001 From: Tobias Diez Date: Sun, 30 Nov 2025 16:42:28 +0100 Subject: [PATCH 08/10] Fix Nauty path on Windows Fix for SAGE_NAUTY_BINS_PREFIX = "D:\a\_temp\msys64\ucrt64\bin\.EXE" ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ SyntaxError: (unicode error) 'unicodeescape' codec can't decode bytes in position 17-19: truncated \uXXXX escape --- src/sage/config.py.in | 4 ++-- src/sage/meson.build | 7 ++++++- 2 files changed, 8 insertions(+), 3 deletions(-) diff --git a/src/sage/config.py.in b/src/sage/config.py.in index d7ae0db56d4..d38c1a5c828 100644 --- a/src/sage/config.py.in +++ b/src/sage/config.py.in @@ -34,8 +34,8 @@ NTL_LIBDIR = "@NTL_LIBDIR@" # Path to the ecl-config script ECL_CONFIG = "@SAGE_ECL_CONFIG@".replace("${prefix}", SAGE_LOCAL) -# Path to the nauty binaries; of the form "/path/to/" or "/path/to/nauty-" -SAGE_NAUTY_BINS_PREFIX = "@SAGE_NAUTY_BINS_PREFIX@" +# Path to the nauty binaries; of the form "/path/to" or "/path/to/nauty-" +SAGE_NAUTY_BINS_PREFIX = r"@SAGE_NAUTY_BINS_PREFIX@" SAGE_ECMBIN = "@SAGE_ECMBIN@" diff --git a/src/sage/meson.build b/src/sage/meson.build index d43e636a4e8..248c5668a4a 100644 --- a/src/sage/meson.build +++ b/src/sage/meson.build @@ -79,7 +79,12 @@ foreach prog : nauty_progs ) endif if nauty_check.found() - nauty_bins_prefix = nauty_check.full_path().replace(prog, '') + nauty_bins_prefix = nauty_check.full_path().replace(prog + '.EXE', '').replace( + prog, + '', + ).strip( + '\\/', + ) endif endforeach conf_data.set('SAGE_NAUTY_BINS_PREFIX', nauty_bins_prefix) From 308f79ae9ce6d5380dc68e8d514e5ad7bdcbe9be Mon Sep 17 00:00:00 2001 From: Tobias Diez Date: Mon, 1 Dec 2025 16:11:03 +0100 Subject: [PATCH 09/10] Allow `Path` as argument for `Executable` feature to make it easier to specify the absolute path of the nauty exe --- src/sage/features/__init__.py | 45 ++++++++++++++++++++++------------- src/sage/features/nauty.py | 10 +++++++- src/sage/meson.build | 2 +- 3 files changed, 38 insertions(+), 19 deletions(-) diff --git a/src/sage/features/__init__.py b/src/sage/features/__init__.py index df9bdf6d892..e8a3a96fc05 100644 --- a/src/sage/features/__init__.py +++ b/src/sage/features/__init__.py @@ -657,7 +657,7 @@ def absolute_filename(self) -> str: class Executable(FileFeature): r""" - A feature describing an executable in the ``PATH``. + A feature describing an executable. In an installation of Sage with ``SAGE_LOCAL`` different from ``SAGE_VENV``, the executable is searched first in ``SAGE_VENV/bin``, then in ``SAGE_LOCAL/bin``, @@ -680,7 +680,9 @@ class Executable(FileFeature): sage: Executable(name='does-not-exist', executable='does-not-exist-xxxxyxyyxyy').is_present() FeatureTestResult('does-not-exist', False) """ - def __init__(self, name, executable, **kwds): + executable: Path + + def __init__(self, name: str, executable: Path | str, **kwds) -> None: r""" TESTS:: @@ -689,9 +691,9 @@ def __init__(self, name, executable, **kwds): True """ Feature.__init__(self, name, **kwds) - self.executable = executable + self.executable = Path(executable) - def _is_present(self): + def _is_present(self) -> FeatureTestResult: r""" Test whether the executable is on the current PATH and functional. @@ -708,7 +710,7 @@ def _is_present(self): return result return self.is_functional() - def is_functional(self): + def is_functional(self) -> FeatureTestResult: r""" Return whether an executable in the path is functional. @@ -744,22 +746,31 @@ def absolute_filename(self) -> str: sage.features.FeatureNotPresentError: does-not-exist is not available. Executable 'does-not-exist-xxxxyxyyxyy' not found on PATH. """ - if SAGE_LOCAL: - if Path(SAGE_VENV).resolve() != Path(SAGE_LOCAL).resolve(): - # As sage.env currently gives SAGE_LOCAL a fallback value from SAGE_VENV, - # SAGE_LOCAL is never unset. So we only use it if it differs from SAGE_VENV. - search_path = ':'.join([os.path.join(SAGE_VENV, 'bin'), - os.path.join(SAGE_LOCAL, 'bin')]) - path = shutil.which(self.executable, path=search_path) - if path is not None: - return path + if self.executable.is_absolute() and self.executable.exists(): + return str(self.executable.resolve()) + + if ( + SAGE_LOCAL + and SAGE_VENV + and Path(SAGE_VENV).resolve() != Path(SAGE_LOCAL).resolve() + ): + # As sage.env currently gives SAGE_LOCAL a fallback value from SAGE_VENV, + # SAGE_LOCAL is never unset. So we only use it if it differs from SAGE_VENV. + search_paths = os.pathsep.join( + [os.path.join(SAGE_VENV, "bin"), os.path.join(SAGE_LOCAL, "bin")] + ) + path = shutil.which(self.executable, path=search_paths) + if path is not None: + return path # Now look up in the regular PATH. path = shutil.which(self.executable) if path is not None: return path - raise FeatureNotPresentError(self, - reason="Executable {executable!r} not found on PATH.".format(executable=self.executable), - resolution=self.resolution()) + raise FeatureNotPresentError( + self, + reason=f"Executable '{str(self.executable)}' not found on PATH.", + resolution=self.resolution(), + ) class StaticFile(FileFeature): diff --git a/src/sage/features/nauty.py b/src/sage/features/nauty.py index 207c177971d..0b07fb2c3ed 100644 --- a/src/sage/features/nauty.py +++ b/src/sage/features/nauty.py @@ -11,6 +11,8 @@ # https://www.gnu.org/licenses/ # ***************************************************************************** +from pathlib import Path + from sage.env import SAGE_NAUTY_BINS_PREFIX from . import Executable @@ -36,10 +38,16 @@ def __init__(self, name): sage: isinstance(NautyExecutable('geng'), NautyExecutable) True """ + if SAGE_NAUTY_BINS_PREFIX is None: + raise ValueError("SAGE_NAUTY_BINS_PREFIX is not set.") + if SAGE_NAUTY_BINS_PREFIX.endswith("-"): + executable = f"{SAGE_NAUTY_BINS_PREFIX}{name}" + else: + executable = Path(SAGE_NAUTY_BINS_PREFIX) / name Executable.__init__( self, name=f"nauty_{name}", - executable=f"{SAGE_NAUTY_BINS_PREFIX}{name}", + executable=executable, spkg="nauty", type="standard", ) diff --git a/src/sage/meson.build b/src/sage/meson.build index 248c5668a4a..3b356cf1fdc 100644 --- a/src/sage/meson.build +++ b/src/sage/meson.build @@ -83,7 +83,7 @@ foreach prog : nauty_progs prog, '', ).strip( - '\\/', + '\\', ) endif endforeach From 9b359cfc23fd143d1c214398b1fe38f217419133 Mon Sep 17 00:00:00 2001 From: Tobias Diez Date: Wed, 3 Dec 2025 10:54:27 +0100 Subject: [PATCH 10/10] Add cysignals to uv pip install --- src/doc/en/installation/source.rst | 1 + 1 file changed, 1 insertion(+) diff --git a/src/doc/en/installation/source.rst b/src/doc/en/installation/source.rst index e049edd0bde..a0bff433779 100644 --- a/src/doc/en/installation/source.rst +++ b/src/doc/en/installation/source.rst @@ -172,6 +172,7 @@ To compile and install Sage in editable install, then just use: $ uv pip install \ meson-python \ "cypari2 >=2.2.1" \ + "cysignals >=1.11.2, != 1.12.0" \ "cython >=3.0, != 3.0.3, != 3.1.0" \ "gmpy2 >=2.1.5" \ memory_allocator \