From 12ba4d14a8aef254d95ee6f120f5e40ed1ecc6d4 Mon Sep 17 00:00:00 2001 From: Derek Tucker Date: Sun, 26 May 2024 17:48:49 -0600 Subject: [PATCH] Cibuildwheel (#54) * started cibuildwheel support * testing stage * fix path * add mac, though will not build * update mac openblas * mac fix * windows fix * expand os * bugfix * bugfix * upgrade version * update to v4 * changes to openblas * fix rpath * Revert "changes to openblas" This reverts commit bb07fe91f1e320bbfd6c5ddd309650249e4e9866. * bugfix * bugfix * bugfix * bugfix * bugfix * bugfix * windows fix * windows fix * bugfix * add mkl * turn off repair * remove openblas * fix for new upload-artificat * fix pypi upload * finished turn on by tag --- .github/workflows/python-package.yml | 41 +-- bin/cibw_before_build.sh | 46 --- bin/cibw_before_build_linux.sh | 24 ++ bin/cibw_before_build_macos.sh | 89 ++++++ bin/cibw_before_build_win.sh | 2 + bin/openblas_support.py | 434 +++++++++++++++++++++++++++ bin/repair_windows.sh | 32 ++ fdasrsf/_distributor_init.py | 4 + pyproject.toml | 27 +- 9 files changed, 623 insertions(+), 76 deletions(-) delete mode 100644 bin/cibw_before_build.sh create mode 100644 bin/cibw_before_build_linux.sh create mode 100644 bin/cibw_before_build_macos.sh create mode 100644 bin/cibw_before_build_win.sh create mode 100644 bin/openblas_support.py create mode 100644 bin/repair_windows.sh create mode 100644 fdasrsf/_distributor_init.py diff --git a/.github/workflows/python-package.yml b/.github/workflows/python-package.yml index 88273ae..916bc47 100644 --- a/.github/workflows/python-package.yml +++ b/.github/workflows/python-package.yml @@ -62,24 +62,25 @@ jobs: - name: Upload Coverage to Codecov uses: codecov/codecov-action@v3 - # build_wheels: - # needs: [build] - # if: startsWith( github.ref, 'refs/tags/') - # name: Build wheels on ${{ matrix.os }} - # runs-on: ${{ matrix.os }} - # strategy: - # matrix: - # os: [ubuntu-22.04] + build_wheels: + needs: [build] + if: startsWith( github.ref, 'refs/tags/') + name: Build wheels on ${{ matrix.os }} + runs-on: ${{ matrix.os }} + strategy: + matrix: + os: [ubuntu-latest, windows-latest, macos-13, macos-14] - # steps: - # - uses: actions/checkout@v4 + steps: + - uses: actions/checkout@v4 - # - name: Build wheels - # uses: pypa/cibuildwheel@v2.16.2 + - name: Build wheels + uses: pypa/cibuildwheel@v2.16.5 - # - uses: actions/upload-artifact@v3 - # with: - # path: ./wheelhouse/*.whl + - uses: actions/upload-artifact@v4 + with: + name: cibw-wheels-${{ matrix.os }}-${{ strategy.job-index }} + path: ./wheelhouse/*.whl build_sdist: needs: [build] @@ -92,24 +93,26 @@ jobs: - name: Build sdist run: pipx run build --sdist - - uses: actions/upload-artifact@v3 + - uses: actions/upload-artifact@v4 with: + name: cibw-sdist path: dist/*.tar.gz upload_pypi: - needs: [build_sdist] + needs: [build_sdist, build_wheels] runs-on: ubuntu-latest environment: pypi permissions: id-token: write if: github.event_name == 'push' && startsWith(github.ref, 'refs/tags/') steps: - - uses: actions/download-artifact@v3 + - uses: actions/download-artifact@v4 with: # unpacks default artifact into dist/ # if `name: artifact` is omitted, the action will create extra parent dir - name: artifact + pattern: cibw-* path: dist + merge-multiple: true - uses: pypa/gh-action-pypi-publish@release/v1 with: diff --git a/bin/cibw_before_build.sh b/bin/cibw_before_build.sh deleted file mode 100644 index e8cdb91..0000000 --- a/bin/cibw_before_build.sh +++ /dev/null @@ -1,46 +0,0 @@ -set -xe - -PROJECT_DIR="$1" -PLATFORM=$(PYTHONPATH=bin python -c "import openblas_support; print(openblas_support.get_plat())") -VERSION=$(PYTHONPATH=bin python -c "import sys; print(sys.version.split()[0][0:3])") - - # Install Openblas -if [[ $RUNNER_OS == "Linux" || $RUNNER_OS == "macOS" ]] ; then - basedir=$(python bin/openblas_support.py --use-ilp64) - if [[ $RUNNER_OS == "macOS" && $PLATFORM == "macosx-arm64" ]]; then - # /usr/local/lib doesn't exist on cirrus-ci runners - sudo mkdir -p /usr/local/lib /usr/local/include /usr/local/lib/cmake/openblas - sudo mkdir -p /opt/arm64-builds/lib /opt/arm64-builds/include - sudo chown -R $USER /opt/arm64-builds - cp -r $basedir/lib/* /opt/arm64-builds/lib/ - cp $basedir/include/* /opt/arm64-builds/include/ - sudo cp -r $basedir/lib/* /usr/local/lib/ - sudo cp $basedir/include/* /usr/local/include/ - else - cp -r $basedir/lib/* /usr/local/lib/ - cp $basedir/include/* /usr/local/include/ - fi -elif [[ $RUNNER_OS == "Windows" ]]; then - # delvewheel is the equivalent of delocate/auditwheel for windows. - python -m pip install delvewheel - - if [[ $PLATFORM == 'win-32' ]]; then - echo "No BLAS used for 32-bit wheels" - else - # Note: DLLs are copied from /c/opt/64/bin by delvewheel, see - # tools/wheels/repair_windows.sh - mkdir -p /c/opt/64/lib/pkgconfig - target=$(python -c "import bin.openblas_support as obs; plat=obs.get_plat(); target=f'openblas_{plat}.zip'; obs.download_openblas(target, plat, libsuffix='64_');print(target)") - unzip -o -d /c/opt/ $target - fi -fi - -if [[ $RUNNER_OS == "macOS" ]]; then - # Install same version of gfortran as the openblas-libs builds - if [[ $PLATFORM == "macosx-arm64" ]]; then - PLAT="arm64" - fi - source $PROJECT_DIR/bin/gfortran_utils.sh - install_gfortran - pip install "delocate==0.10.4" -fi \ No newline at end of file diff --git a/bin/cibw_before_build_linux.sh b/bin/cibw_before_build_linux.sh new file mode 100644 index 0000000..51c9d32 --- /dev/null +++ b/bin/cibw_before_build_linux.sh @@ -0,0 +1,24 @@ +set -xe + + +NIGHTLY_FLAG="" + +if [ "$#" -eq 1 ]; then + PROJECT_DIR="$1" +elif [ "$#" -eq 2 ] && [ "$1" = "--nightly" ]; then + NIGHTLY_FLAG="--nightly" + PROJECT_DIR="$2" +else + echo "Usage: $0 [--nightly] " + exit 1 +fi + +PLATFORM=$(PYTHONPATH=bin python -c "import openblas_support; print(openblas_support.get_plat())") + +printenv +# Update license + +# Install Openblas +basedir=$(python bin/openblas_support.py $NIGHTLY_FLAG) +cp -r $basedir/lib/* /usr/local/lib +cp $basedir/include/* /usr/local/include \ No newline at end of file diff --git a/bin/cibw_before_build_macos.sh b/bin/cibw_before_build_macos.sh new file mode 100644 index 0000000..73d1acd --- /dev/null +++ b/bin/cibw_before_build_macos.sh @@ -0,0 +1,89 @@ +set -xe + +PROJECT_DIR="$1" +PLATFORM=$(PYTHONPATH=bin python -c "import openblas_support; print(openblas_support.get_plat())") +echo $PLATFORM + + +######################################################################################### +# Install GFortran + OpenBLAS + +if [[ $PLATFORM == "macosx-x86_64" ]]; then + # Openblas + basedir=$(python bin/openblas_support.py) + + # copy over the OpenBLAS library stuff first + cp -r $basedir/lib/* /usr/local/lib + cp $basedir/include/* /usr/local/include + + #GFORTRAN=$(type -p gfortran-9) + #sudo ln -s $GFORTRAN /usr/local/bin/gfortran + # same version of gfortran as the openblas-libs and scipy-wheel builds + curl -L https://github.com/isuruf/gcc/releases/download/gcc-11.3.0-2/gfortran-darwin-x86_64-native.tar.gz -o gfortran.tar.gz + + GFORTRAN_SHA256=$(shasum -a 256 gfortran.tar.gz) + KNOWN_SHA256="981367dd0ad4335613e91bbee453d60b6669f5d7e976d18c7bdb7f1966f26ae4 gfortran.tar.gz" + if [ "$GFORTRAN_SHA256" != "$KNOWN_SHA256" ]; then + echo sha256 mismatch + exit 1 + fi + + sudo mkdir -p /opt/ + # places gfortran in /opt/gfortran-darwin-x86_64-native. There's then + # bin, lib, include, libexec underneath that. + sudo tar -xv -C /opt -f gfortran.tar.gz + + # Link these into /usr/local so that there's no need to add rpath or -L + for f in libgfortran.dylib libgfortran.5.dylib libgcc_s.1.dylib libgcc_s.1.1.dylib libquadmath.dylib libquadmath.0.dylib; do + ln -sf /opt/gfortran-darwin-x86_64-native/lib/$f /usr/local/lib/$f + done + ln -sf /opt/gfortran-darwin-x86_64-native/bin/gfortran /usr/local/bin/gfortran + + # Set SDKROOT env variable if not set + # This step is required whenever the gfortran compilers sourced from + # conda-forge (built by isuru fernando) are used outside of a conda-forge + # environment (so it mirrors what is done in the conda-forge compiler + # activation scripts) + export SDKROOT=${SDKROOT:-$(xcrun --show-sdk-path)} +fi + +if [[ $PLATFORM == "macosx-arm64" ]]; then + # OpenBLAS + # need a version of OpenBLAS that is suited for gcc >= 11 + basedir=$(python bin/openblas_support.py) + + # use /opt/arm64-builds as a prefix, because that's what the multibuild + # OpenBLAS pkgconfig files state + sudo mkdir -p /opt/arm64-builds/lib + sudo mkdir -p /opt/arm64-builds/include + sudo mkdir -p /usr/local/lib + sudo mkdir -p /usr/local/include + sudo cp -r $basedir/lib/* /opt/arm64-builds/lib + sudo cp $basedir/include/* /opt/arm64-builds/include + + # we want to force a dynamic linking + sudo rm /opt/arm64-builds/lib/*.a + + sudo cp -r /opt/arm64-builds/lib/* /usr/local/lib + sudo cp /opt/arm64-builds/include/* /usr/local/include + + curl -L https://github.com/fxcoudert/gfortran-for-macOS/releases/download/12.1-monterey/gfortran-ARM-12.1-Monterey.dmg -o gfortran.dmg + GFORTRAN_SHA256=$(shasum -a 256 gfortran.dmg) + KNOWN_SHA256="e2e32f491303a00092921baebac7ffb7ae98de4ca82ebbe9e6a866dd8501acdf gfortran.dmg" + + if [ "$GFORTRAN_SHA256" != "$KNOWN_SHA256" ]; then + echo sha256 mismatch + exit 1 + fi + + hdiutil attach -mountpoint /Volumes/gfortran gfortran.dmg + sudo installer -pkg /Volumes/gfortran/gfortran.pkg -target / + type -p gfortran + + # Link these into /usr/local so that there's no need to add rpath or -L + for f in libgfortran.dylib libgfortran.5.dylib libgcc_s.1.1.dylib libquadmath.dylib libquadmath.0.dylib; do + sudo cp /usr/local/gfortran/lib/$f /opt/arm64-builds/lib/$f + done + sudo ln -sf /usr/local/gfortran//bin/gfortran /usr/local/bin/gfortran + +fi \ No newline at end of file diff --git a/bin/cibw_before_build_win.sh b/bin/cibw_before_build_win.sh new file mode 100644 index 0000000..2726f65 --- /dev/null +++ b/bin/cibw_before_build_win.sh @@ -0,0 +1,2 @@ + +python -m pip install mkl-devel diff --git a/bin/openblas_support.py b/bin/openblas_support.py new file mode 100644 index 0000000..9bc2b5b --- /dev/null +++ b/bin/openblas_support.py @@ -0,0 +1,434 @@ +import glob +import os +import platform +import sysconfig +import sys +import shutil +import tarfile +import textwrap +import time +import zipfile + +from tempfile import mkstemp, gettempdir +from urllib.request import urlopen, Request +from urllib.error import HTTPError + +OPENBLAS_V = "0.3.27" +OPENBLAS_LONG = "v0.3.27" +BASE_LOC = "https://anaconda.org/multibuild-wheels-staging/openblas-libs" +NIGHTLY_BASE_LOC = "https://anaconda.org/scientific-python-nightly-wheels/openblas-libs" + +SUPPORTED_PLATFORMS = [ + "linux-aarch64", + "linux-x86_64", + "musllinux-x86_64", + "linux-i686", + "linux-ppc64le", + "linux-s390x", + "win-amd64", + "win-32", + "macosx-x86_64", + "macosx-arm64", +] +IS_32BIT = sys.maxsize < 2**32 + + +def get_plat(): + plat = sysconfig.get_platform() + plat_split = plat.split("-") + + arch = plat_split[-1] + if arch == "win32": + plat = "win-32" + elif arch in ["universal2", "intel"]: + plat = f"macosx-{platform.uname().machine}" + elif len(plat_split) > 2: + plat = f"{plat_split[0]}-{arch}" + assert plat in SUPPORTED_PLATFORMS, f"invalid platform {plat}" + return plat + + +def get_ilp64(): + if os.environ.get("NPY_USE_BLAS_ILP64", "0") == "0": + return None + if IS_32BIT: + raise RuntimeError("NPY_USE_BLAS_ILP64 set on 32-bit arch") + return "64_" + + +def get_manylinux(arch): + default = "2014" + ml_ver = os.environ.get("MB_ML_VER", default) + # XXX For PEP 600 this can be a glibc version + assert ml_ver in ("2010", "2014", "_2_24"), f"invalid MB_ML_VER {ml_ver}" + suffix = f"manylinux{ml_ver}_{arch}.tar.gz" + return suffix + + +def get_musllinux(arch): + musl_ver = "1_1" + suffix = f"musllinux_{musl_ver}_{arch}.tar.gz" + return suffix + + +def get_linux(arch): + # best way of figuring out whether manylinux or musllinux is to look + # at the packaging tags. If packaging isn't installed (it's not by default) + # fallback to sysconfig (which may be flakier) + try: + from packaging.tags import sys_tags + + tags = list(sys_tags()) + plat = tags[0].platform + except ImportError: + # fallback to sysconfig for figuring out if you're using musl + plat = "manylinux" + # value could be None + v = sysconfig.get_config_var("HOST_GNU_TYPE") or "" + if "musl" in v: + plat = "musllinux" + + if "manylinux" in plat: + return get_manylinux(arch) + elif "musllinux" in plat: + return get_musllinux(arch) + + +def download_openblas( + target, plat, ilp64, *, openblas_version=OPENBLAS_LONG, base_loc=BASE_LOC +): + osname, arch = plat.split("-") + fnsuffix = {None: "", "64_": "64_"}[ilp64] + filename = "" + headers = { + "User-Agent": ( + "Mozilla/5.0 (Windows NT 6.1) AppleWebKit/537.36 ; " + "(KHTML, like Gecko) Chrome/41.0.2228.0 Safari/537.3" + ) + } + suffix = None + if osname == "linux": + suffix = get_linux(arch) + typ = "tar.gz" + elif plat == "macosx-x86_64": + suffix = "macosx_10_9_x86_64-gf_c469a42.tar.gz" + typ = "tar.gz" + elif plat == "macosx-arm64": + suffix = "macosx_11_0_arm64-gf_5272328.tar.gz" + typ = "tar.gz" + elif osname == "win": + if plat == "win-32": + suffix = "win32-gcc_8_3_0.zip" + else: + suffix = "win_amd64-gcc_10_3_0.zip" + typ = "zip" + + if not suffix: + return None + BASEURL = f"{base_loc}/{openblas_version}/download" + filename = f"{BASEURL}/openblas{fnsuffix}-{openblas_version}-{suffix}" + req = Request(url=filename, headers=headers) + + for _ in range(3): + try: + time.sleep(1) + response = urlopen(req) + break + except HTTPError: + print(f'Could not download "{filename}"', file=sys.stderr) + raise + + length = response.getheader("content-length") + if response.status != 200: + print(f'Could not download "{filename}"', file=sys.stderr) + return None + print(f"Downloading {length} from {filename}", file=sys.stderr) + data = response.read() + print("Saving to file", file=sys.stderr) + with open(target, "wb") as fid: + fid.write(data) + return typ + + +def setup_openblas(plat=get_plat(), ilp64=get_ilp64(), nightly=False): + """ + Download and setup an openblas library for building. If successful, + the configuration script will find it automatically. + + Returns + ------- + msg : str + path to extracted files on success, otherwise indicates what went wrong + To determine success, do ``os.path.exists(msg)`` + """ + fd, tmp = mkstemp() + os.close(fd) + if not plat: + raise ValueError("unknown platform") + openblas_version = "HEAD" if nightly else OPENBLAS_LONG + base_loc = NIGHTLY_BASE_LOC if nightly else BASE_LOC + typ = download_openblas( + tmp, plat, ilp64, openblas_version=openblas_version, base_loc=base_loc + ) + if not typ: + return "" + osname, arch = plat.split("-") + if osname == "win": + if not typ == "zip": + return f"expecting to download zipfile on windows, not {typ}" + return unpack_windows_zip(tmp) + else: + if not typ == "tar.gz": + return "expecting to download tar.gz, not %s" % str(typ) + return unpack_targz(tmp) + + +def unpack_windows_zip(fname): + with zipfile.ZipFile(fname, "r") as zf: + # Get the openblas.a file, but not openblas.dll.a nor openblas.dev.a + lib = [ + x + for x in zf.namelist() + if OPENBLAS_LONG in x + and x.endswith("a") + and not x.endswith("dll.a") + and not x.endswith("dev.a") + ] + if not lib: + return ( + "could not find libopenblas_%s*.a " + "in downloaded zipfile" % OPENBLAS_LONG + ) + if get_ilp64() is None: + target = os.path.join(gettempdir(), "openblas.a") + else: + target = os.path.join(gettempdir(), "openblas64_.a") + with open(target, "wb") as fid: + fid.write(zf.read(lib[0])) + return target + + +def unpack_targz(fname): + target = os.path.join(gettempdir(), "openblas") + if not os.path.exists(target): + os.mkdir(target) + with tarfile.open(fname, "r") as zf: + # Strip common prefix from paths when unpacking + prefix = os.path.commonpath(zf.getnames()) + extract_tarfile_to(zf, target, prefix) + return target + + +def extract_tarfile_to(tarfileobj, target_path, archive_path): + """Extract TarFile contents under archive_path/ to target_path/""" + + target_path = os.path.abspath(target_path) + + def get_members(): + for member in tarfileobj.getmembers(): + if archive_path: + norm_path = os.path.normpath(member.name) + if norm_path.startswith(archive_path + os.path.sep): + member.name = norm_path[len(archive_path) + 1 :] + else: + continue + + dst_path = os.path.abspath(os.path.join(target_path, member.name)) + if os.path.commonpath([target_path, dst_path]) != target_path: + # Path not under target_path, probably contains ../ + continue + + yield member + + tarfileobj.extractall(target_path, members=get_members()) + reformat_pkg_file(target_path=target_path) + + +def reformat_pkg_file(target_path): + # attempt to deal with: + # https://github.com/scipy/scipy/pull/20362#issuecomment-2028517797 + for root, dirs, files in os.walk(target_path): + for name in files: + if name.endswith(".pc") and "openblas" in name: + pkg_path = os.path.join(root, name) + new_pkg_lines = [] + with open(pkg_path) as pkg_orig: + for line in pkg_orig: + if line.startswith("Libs:"): + new_line = line.replace("$(libprefix}", "${libprefix}") + new_pkg_lines.append(new_line) + else: + new_pkg_lines.append(line) + with open(pkg_path, "w") as new_pkg: + new_pkg.writelines(new_pkg_lines) + + +def make_init(dirname): + """ + Create a _distributor_init.py file for OpenBlas + """ + with open(os.path.join(dirname, "_distributor_init.py"), "w") as fid: + fid.write( + textwrap.dedent( + """ + ''' + Helper to preload windows dlls to prevent dll not found errors. + Once a DLL is preloaded, its namespace is made available to any + subsequent DLL. This file originated in the numpy-wheels repo, + and is created as part of the scripts that build the wheel. + ''' + import os + import glob + if os.name == 'nt': + # convention for storing / loading the DLL from + # numpy/.libs/, if present + try: + from ctypes import WinDLL + basedir = os.path.dirname(__file__) + except: + pass + else: + libs_dir = os.path.abspath(os.path.join(basedir, '.libs')) + DLL_filenames = [] + if os.path.isdir(libs_dir): + for filename in glob.glob(os.path.join(libs_dir, + '*openblas*dll')): + # NOTE: would it change behavior to load ALL + # DLLs at this path vs. the name restriction? + WinDLL(os.path.abspath(filename)) + DLL_filenames.append(filename) + if len(DLL_filenames) > 1: + import warnings + warnings.warn("loaded more than 1 DLL from .libs:" + "\\n%s" % "\\n".join(DLL_filenames), + stacklevel=1) + """ + ) + ) + + +def test_setup(plats): + """ + Make sure all the downloadable files exist and can be opened + """ + + def items(): + """yields all combinations of arch, ilp64""" + for plat in plats: + yield plat, None + osname, arch = plat.split("-") + if arch not in ("i686", "arm64", "32"): + yield plat, "64_" + if osname == "linux" and arch in ("i686", "x86_64"): + oldval = os.environ.get("MB_ML_VER", None) + os.environ["MB_ML_VER"] = "1" + yield plat, None + # Once we create x86_64 and i686 manylinux2014 wheels... + # os.environ['MB_ML_VER'] = '2014' + # yield arch, None, False + if oldval: + os.environ["MB_ML_VER"] = oldval + else: + os.environ.pop("MB_ML_VER") + + errs = [] + for plat, ilp64 in items(): + osname, _ = plat.split("-") + if plat not in plats: + continue + target = None + try: + try: + target = setup_openblas(plat, ilp64) + except Exception as e: + print(f"Could not setup {plat} with ilp64 {ilp64}, ") + print(e) + errs.append(e) + continue + if not target: + raise RuntimeError(f"Could not setup {plat}") + print(target) + if osname == "win": + if not target.endswith(".a"): + raise RuntimeError("Not .a extracted!") + else: + files = glob.glob(os.path.join(target, "lib", "*.a")) + if not files: + raise RuntimeError("No lib/*.a unpacked!") + finally: + if target is not None: + if os.path.isfile(target): + os.unlink(target) + else: + shutil.rmtree(target) + if errs: + raise errs[0] + + +def test_version(expected_version, ilp64=get_ilp64()): + """ + Assert that expected OpenBLAS version is + actually available via SciPy + """ + import scipy + import scipy.linalg + import ctypes + + dll = ctypes.CDLL(scipy.linalg.cython_blas.__file__) + if ilp64 == "64_": + get_config = dll.openblas_get_config64_ + else: + get_config = dll.openblas_get_config + get_config.restype = ctypes.c_char_p + res = get_config() + print("OpenBLAS get_config returned", str(res)) + if not expected_version: + expected_version = OPENBLAS_V + check_str = b"OpenBLAS %s" % expected_version.encode() + print(check_str) + assert check_str in res, f"{expected_version} not found in {res}" + if ilp64: + assert b"USE64BITINT" in res + else: + assert b"USE64BITINT" not in res + + +if __name__ == "__main__": + import argparse + + parser = argparse.ArgumentParser( + description="Download and expand an OpenBLAS archive for this " "architecture" + ) + parser.add_argument( + "--test", + nargs="*", + default=None, + help='Test different architectures. "all", or any of ' f"{SUPPORTED_PLATFORMS}", + ) + parser.add_argument( + "--write-init", + nargs=1, + metavar="OUT_SCIPY_DIR", + help="Write distribution init to named dir", + ) + parser.add_argument( + "--check_version", + nargs="?", + default="", + help="Check provided OpenBLAS version string " "against available OpenBLAS", + ) + parser.add_argument( + "--nightly", action="store_true", help="If set, use nightly OpenBLAS build." + ) + args = parser.parse_args() + if args.check_version != "": + test_version(args.check_version) + elif args.write_init: + make_init(args.write_init[0]) + elif args.test is None: + print(setup_openblas(nightly=args.nightly)) + else: + if len(args.test) == 0 or "all" in args.test: + test_setup(SUPPORTED_PLATFORMS) + else: + test_setup(args.test) diff --git a/bin/repair_windows.sh b/bin/repair_windows.sh new file mode 100644 index 0000000..05a5cf4 --- /dev/null +++ b/bin/repair_windows.sh @@ -0,0 +1,32 @@ +set -xe + +WHEEL="$1" +DEST_DIR="$2" + +# create a temporary directory in the destination folder and unpack the wheel +# into there +pushd $DEST_DIR +mkdir -p tmp +pushd tmp +wheel unpack $WHEEL +pushd fdsasrsf* + +# To avoid DLL hell, the file name of libopenblas that's being vendored with +# the wheel has to be name-mangled. delvewheel is unable to name-mangle PYD +# containing extra data at the end of the binary, which frequently occurs when +# building with mingw. +# We therefore find each PYD in the directory structure and strip them. + +for f in $(find ./fdasrsf* -name '*.pyd'); do strip $f; done + + +# now repack the wheel and overwrite the original +wheel pack . +mv -fv *.whl $WHEEL + +cd $DEST_DIR +rm -rf tmp + +# the libopenblas.dll is placed into this directory in the cibw_before_build +# script. +delvewheel repair --add-path /c/opt/openblas/openblas_dll --no-dll libsf_error_state.dll -w $DEST_DIR $WHEEL \ No newline at end of file diff --git a/fdasrsf/_distributor_init.py b/fdasrsf/_distributor_init.py new file mode 100644 index 0000000..4c90e1c --- /dev/null +++ b/fdasrsf/_distributor_init.py @@ -0,0 +1,4 @@ +try: + from . import _distributor_init_local # noqa: F401 +except ImportError: + pass \ No newline at end of file diff --git a/pyproject.toml b/pyproject.toml index 481f1c3..c963edc 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -44,33 +44,38 @@ documentation = "https://fdasrsf-python.readthedocs.io/en/latest/" # Note: the below skip command doesn't do much currently, the platforms to # build wheels for in CI are controlled in `.github/workflows/wheels.yml` and # `tools/ci/cirrus_wheels.yml`. -skip = "cp36-* cp37-* cp38-* cp312-* pp* *-manylinux_i686 *_ppc64le *_s390x *-win32 *_i686" +skip = "cp36-* cp37-* cp38-* pp* *_i686 *_ppc64le *_s390x *-win32" build-verbosity = "3" -environment = {BLAS="/usr/local/lib",BLAS_SRC="/usr/local/include"} -before-build = "bash {project}/bin/cibw_before_build.sh {project}" [tool.cibuildwheel.linux] manylinux-x86_64-image = "manylinux2014" manylinux-aarch64-image = "manylinux2014" +before-build = "bash {project}/bin/cibw_before_build_linux.sh {project}" environment = {RUNNER_OS="Linux"} [tool.cibuildwheel.macos] -# For universal2 wheels, we will need to fuse them manually -# instead of going through cibuildwheel -# This is because cibuildwheel tries to make a fat wheel -# https://github.com/multi-build/multibuild/blame/devel/README.rst#L541-L565 -# for more info -archs = "x86_64 arm64" -test-skip = "*_universal2:arm64" +before-build = "bash {project}/bin/cibw_before_build_macos.sh {project}" environment = {RUNNER_OS="macOS"} [tool.cibuildwheel.windows] -environment = {PKG_CONFIG_PATH="C:/opt/64/lib/pkgconfig"} +before-build = "bash {project}/bin/cibw_before_build_win.sh {project}" +#repair-wheel-command = "bash ./bin/repair_windows.sh {wheel} {dest_dir}" +environment = {RUNNER_OS="Windows"} [[tool.cibuildwheel.overrides]] select = "*-win32" environment = {PKG_CONFIG_PATH="/opt/32/lib/pkgconfig"} +[[tool.cibuildwheel.overrides]] +select = "*-win_amd64" +# can use pkg-config detection for win_amd64 because the installed rtools +# provide a working pkg-config. +# An alternative is to set CMAKE_PREFIX_PATH="c:/opt/openblas/if_32/32" +# Don't use double backslash for path separators, they don't get passed +# to the build correctly +# environment = { CMAKE_PREFIX_PATH="c:/opt/64" } +environment = { PKG_CONFIG_PATH = "c:/opt/64/lib/pkgconfig" } + [build-system] requires = ["setuptools>=46.0", "wheel", "cffi>=1.0.0", "Cython", "findblas", "oldest-supported-numpy"] # PEP 518 - what is required to build build-backend = "setuptools.build_meta"