diff --git a/.github/workflows/buildwheel.yml b/.github/workflows/buildwheel.yml index 6efc9781..3ddfedb4 100644 --- a/.github/workflows/buildwheel.yml +++ b/.github/workflows/buildwheel.yml @@ -10,7 +10,14 @@ jobs: strategy: fail-fast: false matrix: - os: [ubuntu-22.04, ubuntu-22.04-arm, windows-2022, macos-15-intel, macos-14] + os: [ + ubuntu-22.04, + ubuntu-22.04-arm, + windows-2022, + windows-11-arm, + macos-15-intel, + macos-14, + ] steps: - uses: actions/checkout@v6.0.2 @@ -22,7 +29,23 @@ jobs: - uses: msys2/setup-msys2@v2.30.0 with: msystem: mingw64 - if: ${{ startsWith( matrix.os , 'windows' ) }} + if: ${{ matrix.os == 'windows-2022' }} + + - uses: msys2/setup-msys2@v2.29.0 + with: + msystem: clangarm64 + if: ${{ matrix.os == 'windows-11-arm' }} + + - run: | + "C:\\msys64\\clangarm64\\bin" >> $env:GITHUB_PATH + "C:\\msys64\\usr\\bin" >> $env:GITHUB_PATH + "MSYSTEM=CLANGARM64" >> $env:GITHUB_ENV + "CC=clang" >> $env:GITHUB_ENV + "CXX=clang++" >> $env:GITHUB_ENV + "AR=llvm-ar" >> $env:GITHUB_ENV + "RANLIB=llvm-ranlib" >> $env:GITHUB_ENV + "STRIP=llvm-strip" >> $env:GITHUB_ENV + if: ${{ matrix.os == 'windows-11-arm' }} # Install pkgconfig on Windows from choco rather than from msys and # avoid using the Strawberry one. @@ -34,11 +57,32 @@ jobs: - run: echo "PKG_CONFIG_PATH=${{ github.workspace }}/.local/lib/pkgconfig" >> $env:GITHUB_ENV if: ${{ startsWith( matrix.os , 'windows' ) }} - - name: Build wheels + # We need to set these environment variables here rather than + # pyproject.toml so we can use different values for different + # architectures. For non-Windows this is just done with $(uname -m) + # don't know what that would do in msys2 on Windows when cibuildwheel + # parses it. + + - name: Build wheels for windows-2022 + uses: pypa/cibuildwheel@298ed2fb2c105540f5ed055e8a6ad78d82dd3a7e # v3.3.1 + if: ${{ matrix.os == 'windows-2022' }} + env: + CIBW_BEFORE_ALL_WINDOWS: msys2 -c bin/cibw_before_all_windows_amd64.sh + + - name: Build wheels for windows-11-arm uses: pypa/cibuildwheel@298ed2fb2c105540f5ed055e8a6ad78d82dd3a7e # v3.3.1 + if: ${{ matrix.os == 'windows-11-arm' }} env: - # override setting in pyproject.toml to use msys2 instead of msys64 bash - CIBW_BEFORE_ALL_WINDOWS: msys2 -c bin/cibw_before_all_windows.sh + CIBW_BEFORE_ALL_WINDOWS: msys2 -c bin/cibw_before_all_windows_arm64.sh + CIBW_BEFORE_BUILD_WINDOWS: python bin/cibw_before_build_windows_arm64.py && msys2 -c bin/cibw_before_build_windows_arm64.sh && pip install wheel delvewheel + PYTHONPATH: ${{ github.workspace }}\.local\python-site + + # After all the Windows-specific steps above this is what actually + # builds the wheels for every other OS: + + - name: Build wheels for any other OS + uses: pypa/cibuildwheel@298ed2fb2c105540f5ed055e8a6ad78d82dd3a7e # v3.3.1 + if: ${{ !startsWith( matrix.os , 'windows' ) }} - uses: actions/upload-artifact@v7 with: @@ -78,6 +122,7 @@ jobs: ubuntu-24.04-arm, windows-2022, windows-2025, + windows-11-arm, macos-15-intel, macos-14, macos-15, diff --git a/bin/build_dependencies_unix.sh b/bin/build_dependencies_unix.sh index f4805e85..83e953c0 100755 --- a/bin/build_dependencies_unix.sh +++ b/bin/build_dependencies_unix.sh @@ -18,6 +18,8 @@ set -o errexit SKIP_GMP=no SKIP_MPFR=no +FAT_GMP_ARG="--enable-fat" +PATCH_LDD=no USE_GMP=gmp PATCH_GMP_ARM64=no @@ -38,6 +40,8 @@ do echo " --host - set the host (target) for GMP build" echo " --skip-gmp - skip building GMP" echo " --skip-mpfr - skip building MPFR" + echo " --disable-fat - disable building fat binaries" + echo " --patch-ldd - patch flint shared linking for mingw on arm64" echo echo "Legacy options:" echo " --gmp gmp - build based on GMP (default)" @@ -84,6 +88,11 @@ do SKIP_MPFR=yes shift ;; + --disable-fat) + # Disable building fat binaries + FAT_GMP_ARG="--disable-assembly" + shift + ;; --patch-gmp-arm64) # Needed only for GMP 6.2.1 on OSX arm64 (Apple M1) hardware # As of GMP 6.3.0 this patch is no longer needed @@ -95,6 +104,11 @@ do PATCH_GMP_C23=yes shift ;; + --patch-ldd) + # Needed only for the FLINT shared build on mingw arm64. + PATCH_LDD=yes + shift + ;; --use-gmp-github-mirror) USE_GMP_GITHUB_MIRROR=yes shift @@ -191,7 +205,7 @@ if [ "$USE_GMP" = "gmp" ]; then ./configfsf.guess ./configure --prefix=$PREFIX\ - --enable-fat\ + $FAT_GMP_ARG\ --enable-shared=yes\ --enable-static=no\ --host=$HOST_ARG @@ -310,6 +324,15 @@ echo curl -O -L https://github.com/flintlib/flint/releases/download/v$FLINTVER/flint-$FLINTVER.tar.gz tar xf flint-$FLINTVER.tar.gz cd flint-$FLINTVER + + if [ $PATCH_LDD = "yes" ]; then + echo + echo -------------------------------------------- + echo " patching FLINT" + echo -------------------------------------------- + patch -N -Z -p1 < ../../../bin/patch-flint-windows-arm64-link.diff + fi + ./bootstrap.sh ./configure --prefix=$PREFIX\ --host=$HOST_ARG\ @@ -317,7 +340,7 @@ cd flint-$FLINTVER --with-mpfr=$PREFIX\ --disable-static\ --disable-debug - make -j6 + make -j4 make install cd .. diff --git a/bin/cibw_before_all_windows.sh b/bin/cibw_before_all_windows_amd64.sh similarity index 100% rename from bin/cibw_before_all_windows.sh rename to bin/cibw_before_all_windows_amd64.sh diff --git a/bin/cibw_before_all_windows_arm64.sh b/bin/cibw_before_all_windows_arm64.sh new file mode 100755 index 00000000..af8f037c --- /dev/null +++ b/bin/cibw_before_all_windows_arm64.sh @@ -0,0 +1,23 @@ +#!/bin/bash + +set -o errexit + +pacman -S --noconfirm \ + mingw-w64-clang-aarch64-toolchain\ + mingw-w64-clang-aarch64-tools-git\ + m4\ + make\ + base-devel\ + autoconf-wrapper\ + automake-wrapper\ + libtool\ + git\ + # + +bin/build_dependencies_unix.sh \ + --disable-fat\ + --use-gmp-github-mirror\ + --host aarch64-pc-windows-gnullvm\ + --patch-C23\ + --patch-ldd\ + # diff --git a/bin/cibw_before_build_windows_arm64.py b/bin/cibw_before_build_windows_arm64.py new file mode 100755 index 00000000..5cc800bd --- /dev/null +++ b/bin/cibw_before_build_windows_arm64.py @@ -0,0 +1,98 @@ +#!/usr/bin/env python3 + +from __future__ import annotations + +import shlex +import sys +import sysconfig +from pathlib import Path + + +def _normalize_path(path: Path | str) -> str: + return str(path).replace('\\', '/') + + +def _default_dll_name() -> str: + vernum = sysconfig.get_config_var('py_version_nodot') + if not vernum: + vernum = f"{sys.version_info.major}{sys.version_info.minor}" + is_freethreaded = bool(sysconfig.get_config_var('Py_GIL_DISABLED')) + if not is_freethreaded: + abiflags = sysconfig.get_config_var('ABIFLAGS') or getattr(sys, 'abiflags', '') or '' + is_freethreaded = 't' in abiflags + suffix = 't' if is_freethreaded else '' + return f'python{vernum}{suffix}.dll' + + +def _normalize_dll_name(name: str) -> str: + if name.endswith('.dll'): + return name + if name.startswith('lib') and name.endswith('.dll.a'): + return _default_dll_name() + return _default_dll_name() + + +def _find_dll(dll_name: str) -> Path: + candidates: list[Path] = [] + libdir = sysconfig.get_config_var('LIBDIR') + if libdir: + candidates.append(Path(libdir) / dll_name) + exe_dir = Path(sys.executable).resolve().parent + candidates.append(exe_dir / dll_name) + base_prefix = Path(getattr(sys, 'base_prefix', sys.prefix)) + candidates.append(base_prefix / dll_name) + candidates.append(base_prefix / 'DLLs' / dll_name) + candidates.append(base_prefix / 'libs' / dll_name) + + for candidate in candidates: + if candidate.exists(): + return candidate.resolve() + + paths = '\n'.join(_normalize_path(path) for path in candidates) + raise SystemExit(f'Could not find {dll_name} in:\n{paths}') + + +def main() -> None: + repo_root = Path.cwd().resolve() + lib_dir = repo_root / '.local' / 'lib' + pkgconfig_dir = lib_dir / 'pkgconfig' + lib_dir.mkdir(parents=True, exist_ok=True) + pkgconfig_dir.mkdir(parents=True, exist_ok=True) + + raw_name = sysconfig.get_config_var('DLLLIBRARY') or sysconfig.get_config_var('LDLIBRARY') or '' + dll_name = _normalize_dll_name(raw_name) + include_dir = sysconfig.get_config_var('INCLUDEPY') + if not include_dir: + raise SystemExit('Could not determine Python include dir') + pkg_version = sysconfig.get_config_var('LDVERSION') or sysconfig.get_python_version() + dll_path = _find_dll(dll_name) + + values = { + 'REPO_ROOT': _normalize_path(repo_root), + 'LIB_DIR': _normalize_path(lib_dir), + 'PKGCONFIG_DIR': _normalize_path(pkgconfig_dir), + 'DLL_NAME': dll_name, + 'DLL_PATH': _normalize_path(dll_path), + 'PKG_VERSION': pkg_version, + 'INCLUDE_DIR': _normalize_path(include_dir), + } + + env_path = repo_root / '.local' / 'cibw_before_build_windows_arm64.env' + env_text = ''.join(f'{key}={shlex.quote(value)}\n' for key, value in values.items()) + env_path.write_text(env_text, encoding='utf-8') + + python_site = repo_root / '.local' / 'python-site' + python_site.mkdir(parents=True, exist_ok=True) + sitecustomize = python_site / 'sitecustomize.py' + sitecustomize.write_text( + 'import sysconfig\n' + f"sysconfig.get_config_vars()['LIBPC'] = {values['PKGCONFIG_DIR']!r}\n", + encoding='utf-8', + ) + + print(f'Generated {env_path}') + print(f'Generated {sitecustomize}') + + +if __name__ == '__main__': + main() diff --git a/bin/cibw_before_build_windows_arm64.sh b/bin/cibw_before_build_windows_arm64.sh new file mode 100755 index 00000000..066a4818 --- /dev/null +++ b/bin/cibw_before_build_windows_arm64.sh @@ -0,0 +1,77 @@ +#!/bin/bash + +set -o errexit + +env_file=.local/cibw_before_build_windows_arm64.env +if [ ! -f "$env_file" ]; then + echo "Could not find $env_file" >&2 + exit 1 +fi + +# shellcheck disable=SC1090 +. "$env_file" + +lib_dir_msys="$LIB_DIR" +pkgconfig_dir_msys="$PKGCONFIG_DIR" +dll_path_msys="$DLL_PATH" +if command -v cygpath >/dev/null 2>&1; then + lib_dir_msys=$(cygpath -u "$LIB_DIR") + pkgconfig_dir_msys=$(cygpath -u "$PKGCONFIG_DIR") + dll_path_msys=$(cygpath -u "$DLL_PATH") +fi +mkdir -p "$pkgconfig_dir_msys" + +if ! command -v gendef >/dev/null 2>&1; then + echo "Could not find gendef on PATH" >&2 + exit 1 +fi + +dlltool= +if command -v cc >/dev/null 2>&1; then + dlltool=$(cc -print-prog-name=dlltool) +fi +if [ -n "${dlltool:-}" ] && [ -x "$dlltool" ]; then + : +elif [ -n "${dlltool:-}" ] && command -v "$dlltool" >/dev/null 2>&1; then + dlltool=$(command -v "$dlltool") +elif command -v dlltool >/dev/null 2>&1; then + dlltool=$(command -v dlltool) +elif command -v llvm-dlltool >/dev/null 2>&1; then + dlltool=$(command -v llvm-dlltool) +else + echo "Could not find dlltool or llvm-dlltool on PATH" >&2 + exit 1 +fi + +dll_stem=${DLL_NAME%.dll} +def_path_msys="$lib_dir_msys/$dll_stem.def" +import_lib_msys="$lib_dir_msys/lib$dll_stem.dll.a" +pc_path_msys="$pkgconfig_dir_msys/python-$PKG_VERSION.pc" + +rm -f "$def_path_msys" "$import_lib_msys" +( + cd "$lib_dir_msys" + gendef "$dll_path_msys" +) +echo "Generating $import_lib_msys with $dlltool" +"$dlltool" -d "$def_path_msys" -D "$DLL_NAME" -l "$import_lib_msys" +if [ ! -f "$import_lib_msys" ]; then + echo "Failed to create $import_lib_msys" >&2 + exit 1 +fi + +cat > "$pc_path_msys" <$(SHARED_LINK_RSP))$(foreach obj,$(LOBJS),$(file >>$(SHARED_LINK_RSP),$(obj))) ++ @$(CC) $(CFLAGS) -shared $(EXTRA_SHARED_FLAGS) @$(SHARED_LINK_RSP) -o $(FLINT_LIB_FULL) $(LDFLAGS) $(LIBS) + @$(RM_F) $(FLINT_LIB) + @$(RM_F) $(FLINT_LIB_MAJOR) + @$(LN_S) $(FLINT_LIB_FULL) $(FLINT_LIB) diff --git a/meson.build b/meson.build index 16860da3..6c61af96 100644 --- a/meson.build +++ b/meson.build @@ -18,7 +18,6 @@ cython_lower = '>=3.0.11' cython_upper = '<3.3' py = import('python').find_installation(pure: false) -dep_py = py.dependency() cc = meson.get_compiler('c') cy = meson.get_compiler('cython') @@ -76,7 +75,15 @@ else have_acb_theta = true endif -pyflint_deps = [dep_py, gmp_dep, mpfr_dep, flint_dep] +pyflint_deps = [gmp_dep, mpfr_dep, flint_dep] + +# Meson's Windows sysconfig dependency path can hand the clangarm64 +# toolchain the python DLL itself. Use pkg-config on Windows ARM64 so we can +# provide a MinGW import library instead. +if host_machine.system() == 'windows' and host_machine.cpu_family() in ['aarch64', 'arm64'] and cc.get_id() in ['gcc', 'clang'] + dep_py = py.dependency(method: 'pkg-config') + pyflint_deps = [dep_py] + pyflint_deps +endif add_project_arguments( '-X', 'embedsignature=True', diff --git a/pyproject.toml b/pyproject.toml index efa88bef..197084f8 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -154,7 +154,7 @@ repair-wheel-command = [ ] [tool.cibuildwheel.windows] -before-all = "C:\\msys64\\usr\\bin\\bash bin/cibw_before_all_windows.sh" +before-all = "C:\\msys64\\usr\\bin\\bash.exe bin/cibw_before_all_windows.sh" before-build = "pip install wheel delvewheel" repair-wheel-command = [ """python bin/cibw_repair_wheel_licenses.py {wheel} \