diff --git a/.github/workflows/ci-cmake_tests.yml b/.github/workflows/ci-cmake_tests.yml index 3e3956471..db4453852 100644 --- a/.github/workflows/ci-cmake_tests.yml +++ b/.github/workflows/ci-cmake_tests.yml @@ -1,8 +1,5 @@ name: Tests and Codecov on: - pull_request: - branches: - - master workflow_dispatch: jobs: BuildAndTest: diff --git a/.github/workflows/clang-format-check.yml b/.github/workflows/clang-format-check.yml index d37eaaa5f..2d4843eae 100644 --- a/.github/workflows/clang-format-check.yml +++ b/.github/workflows/clang-format-check.yml @@ -1,6 +1,5 @@ name: clang-format Check on: - pull_request: workflow_dispatch: jobs: formatting-check: diff --git a/.github/workflows/conda_build.yml b/.github/workflows/conda_build.yml index 2cb131d7b..b260c25b0 100644 --- a/.github/workflows/conda_build.yml +++ b/.github/workflows/conda_build.yml @@ -4,9 +4,6 @@ on: push: branches: - master - pull_request: - branches: - - master workflow_dispatch: jobs: diff --git a/.github/workflows/release_pypi.yml b/.github/workflows/release_pypi.yml new file mode 100644 index 000000000..8414d23f4 --- /dev/null +++ b/.github/workflows/release_pypi.yml @@ -0,0 +1,78 @@ +name: Release to PyPI + +on: + pull_request: + workflow_dispatch: + +jobs: + BuildWheel: + name: BuildWheel-${{ matrix.os }} + runs-on: ${{ matrix.os }} + strategy: + fail-fast: false + matrix: + os: [ubuntu-latest, macos-14, macos-15-intel] + # os: [ubuntu-latest] + defaults: + run: + shell: bash -l {0} + + steps: + - uses: actions/checkout@v4 + with: + fetch-depth: 0 + + # When re-running a PR job, GitHub Actions uses the merge commit created at + # the time of the original run, not a fresh merge with the latest target branch. + # This step ensures we always test against the most recent target branch. + - name: Merge with latest target branch (pull_request only) + if: github.event_name == 'pull_request' + run: | + git fetch origin ${{ github.event.pull_request.base.ref }} + git merge --no-edit origin/${{ github.event.pull_request.base.ref }} + + - name: Cache Ccache Directory (pull_request) + if: ${{ github.event_name == 'pull_request' }} + uses: actions/cache@v4 + with: + path: | + ~/.ccache + key: ccache-${{ runner.os }}-${{ github.event.pull_request.head.ref }}-${{ github.sha }} + restore-keys: | + ccache-${{ runner.os }}-${{ github.event.pull_request.head.ref }}- + ccache-${{ runner.os }}-${{ github.event.pull_request.base.ref }}- + + - name: Set Ccache Directory + run: | + echo "Start building---------------------------------" + export CCACHE_DIR="${HOME}/.ccache" + + - name: Setup macOS + if: matrix.os == 'macos-15-intel' || matrix.os == 'macos-14' + run: | + if [[ ${{ matrix.os }} == 'macos-15-intel' ]]; then + echo "MACOSX_DEPLOYMENT_TARGET=15.0" >> "$GITHUB_ENV" + elif [[ ${{ matrix.os }} == 'macos-14' ]]; then + echo "MACOSX_DEPLOYMENT_TARGET=14.0" >> "$GITHUB_ENV" + fi + + - name: Build Wheels + uses: pypa/cibuildwheel@v3.3.0 + env: + CMAKE_C_COMPILER_LAUNCHER: ccache + CMAKE_CXX_COMPILER_LAUNCHER: ccache + CMAKE_CUDA_COMPILER_LAUNCHER: ccache + # DO NOT enable this line because the script for conda build is not + # executed in a interactive shell, and "~" will not be expanded to the + # home directory. Export CCACHE_DIR in the run command instead. + # CCACHE_DIR: ~/.ccache + CCACHE_MAXSIZE: 1G # The limit of actions/cache is 10GB + # Pass environment variables into cibuildwheel build environments + # This is only needed on Linux becuase the wheels are built in docker + # images only on Linux. + CIBW_ENVIRONMENT_PASS_LINUX: CMAKE_C_COMPILER_LAUNCHER CMAKE_CXX_COMPILER_LAUNCHER CMAKE_CUDA_COMPILER_LAUNCHER CCACHE_MAXSIZE + + - uses: actions/upload-artifact@v4 + with: + name: cibw-wheels-${{ matrix.os }}-${{ strategy.job-index }} + path: ./wheelhouse/*.whl diff --git a/CMakeLists.txt b/CMakeLists.txt index 4d91a9d3b..24e9040dc 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -191,6 +191,14 @@ if(USE_CUDA) set(CMAKE_CUDA_USE_RESPONSE_FILE_FOR_OBJECTS 0) endif() + +# Print all environment variables for debugging +message(STATUS "==================== Environment Variables ====================") +execute_process(COMMAND ${CMAKE_COMMAND} -E environment + OUTPUT_VARIABLE ENV_OUTPUT) +message(STATUS "${ENV_OUTPUT}") +message(STATUS "================================================================") + # C++ uses link-time optimization anyway; this enables additionally -flto=auto, # for parallel compilation # It cannot enable on MacOS since it causes building errors when linking the library libcytnx.a. @@ -243,7 +251,7 @@ endif() # ##################################################################### # ## Boost -find_package(Boost CONFIG REQUIRED) +find_package(Boost REQUIRED) target_include_directories(cytnx SYSTEM PUBLIC ${Boost_INCLUDE_DIRS} diff --git a/include/cytnx_error.hpp b/include/cytnx_error.hpp index 109d2cff1..34135276c 100644 --- a/include/cytnx_error.hpp +++ b/include/cytnx_error.hpp @@ -9,7 +9,12 @@ #include #include +#if defined(__GLIBC__) #include +#define HAS_EXECINFO 1 +#else +#define HAS_EXECINFO 0 +#endif #ifdef _MSC_VER #define __PRETTY_FUNCTION__ __FUNCTION__ @@ -34,6 +39,7 @@ static inline void error_msg(char const *const func, const char *const file, int va_end(args); // std::cerr << output_str << std::endl; std::cerr << output_str << std::endl; + #if HAS_EXECINFO std::cerr << "Stack trace:" << std::endl; void *array[10]; size_t size; @@ -43,6 +49,7 @@ static inline void error_msg(char const *const func, const char *const file, int std::cerr << strings[i] << std::endl; } free(strings); +#endif throw std::logic_error(output_str); } // } catch (const char *output_msg) { diff --git a/pyproject.toml b/pyproject.toml index a425c80e3..b084823a5 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,5 +1,5 @@ [build-system] -requires = ["scikit-build-core >=0.11", "pybind11 >=3.0"] +requires = ["scikit-build-core >=0.11", "pybind11 >=3.0", "setuptools"] build-backend = "scikit_build_core.build" [project] @@ -43,6 +43,10 @@ cmake.args = [ # conflicts against the build process of HPTT. "-G Unix Makefiles", ] +# Force the build directory to be "build" instead of a random temp folder. This helps +# ccache caches the compiler outputs successfully. +build-dir = "build/{wheel_tag}" +# cp39-cp39-macosx_14_0_arm64 [tool.scikit-build.metadata.version] provider = "scikit_build_core.metadata.regex" @@ -53,3 +57,31 @@ set\(\w+?VERSION_MINOR\s+(?P\d+)\) set\(\w+?VERSION_PATCH\s+(?P\d+)\) ''' result = "{major}.{minor}.{patch}" + +[tool.cibuildwheel] +# The image used to build wheels for x86_64 on Linux is manylinux_2_28, which supports +# older version of Linux than manylinux_2_34. However, manylinux_2_28 is AlmaLinux 8 +# based and AlmaLinux 8 ships Boost v1.66. The Boost's CMake configuration files are only +# available since v1.70, so AlmaLinux 9 based manylinux_2_34 is picked here. +# There is a caveat for manylinux_2_34. manylinux_2_34 assume the machines use x86-64-v2 +# architecture which is an extension of x86_64, so the wheels built with manylinux_2_34 +# may not support older machines. See https://github.com/pypa/manylinux?tab=readme-ov-file#manylinux_2_34-almalinux-9-based---alpha +# for more information. +manylinux-x86_64-image = "manylinux_2_28" +before-build = "python ./tools/cibuildwheel_before_build.py" + +[tool.cibuildwheel.linux] +# before-all = "apt-get install -y arpack boost ccache libomp openblas" +before-all = "bash ./tools/cibuildwheel_before_all.sh" +# To include LAPACKE header. +environment = { CMAKE_PREFIX_PATH = "/usr/include/openblas", CPATH = "/usr/include/openblas", CCACHE_CONFIGPATH = "~/ccache.conf", CMAKE_ARGS = "-DBoost_NO_BOOST_CMAKE=ON" } + +[tool.cibuildwheel.macos] +before-all = "brew update; brew install arpack boost ccache libomp openblas" +environment = { CMAKE_PREFIX_PATH = "$(brew --prefix arpack):$(brew --prefix boost):$(brew --prefix libomp):$(brew --prefix openblas)", PATH = "$(brew --prefix ccache)/libexec:$PATH", CCACHE_CONFIGPATH = "~/ccache.conf" } + +# [tool.cibuildwheel] +# before-build = "bash {project}/tools/cibuildwheel_before_build.sh" +# # environment = { CMAKE_PREFIX_PATH = "$(brew --prefix openblas)", PATH = "$(brew --prefix ccache)/libexec:$PATH" } +# # Alternative static configuration for macOS: +# environment = { CMAKE_PREFIX_PATH = "/opt/homebrew/opt/openblas", PATH = "/opt/homebrew/opt/ccache/libexec:$PATH" } diff --git a/tools/cibuildwheel_before_all.sh b/tools/cibuildwheel_before_all.sh new file mode 100644 index 000000000..d70fac68b --- /dev/null +++ b/tools/cibuildwheel_before_all.sh @@ -0,0 +1,24 @@ +set -xe + +# Install required packages based on distro +if command -v apk &> /dev/null; then + # musllinux (Alpine) + apk add --no-cache boost-dev openblas-dev arpack-dev ccache +elif command -v dnf &> /dev/null; then + # manylinux_2_28+ (AlmaLinux/RHEL) + dnf install -y boost-devel openblas-devel arpack-devel ccache +elif command -v yum &> /dev/null; then + # manylinux2014 (CentOS) + yum install -y boost-devel openblas-devel arpack-devel ccache +else + echo "WARNING: No package manager found" +fi + +# Create symlinks for OpenBLAS headers if available +if [ -d /usr/include/openblas ]; then + ln -sf /usr/include/openblas/lapacke.h /usr/include/lapacke.h + ln -sf /usr/include/openblas/lapack.h /usr/include/lapack.h + ln -sf /usr/include/openblas/lapacke_mangling.h /usr/include/lapacke_mangling.h + ln -sf /usr/include/openblas/cblas.h /usr/include/cblas.h + ln -sf /usr/include/openblas/openblas_config.h /usr/include/openblas_config.h +fi diff --git a/tools/cibuildwheel_before_build.py b/tools/cibuildwheel_before_build.py new file mode 100644 index 000000000..bf0bac717 --- /dev/null +++ b/tools/cibuildwheel_before_build.py @@ -0,0 +1,25 @@ +import os +import pathlib +import sys + +import packaging.tags +tag = f'cp{sys.version_info.major}{sys.version_info.minor}' + +ccache_config_path = os.getenv('CCACHE_CONFIGPATH') + +# Expand ~ to actual home directory +ccache_config_path = os.path.expanduser(ccache_config_path) +if not ccache_config_path: + raise RuntimeError('The CCACHE_CONFIGPATH environment variable must be set.') + +print("ccache_config_path:", ccache_config_path) +print("ccache_config_path:", pathlib.Path(ccache_config_path).absolute()) + + +wheel_tag = str(next(packaging.tags.sys_tags())) +print("wheel_tag:", wheel_tag) + +with open(ccache_config_path, 'w') as f: + f.writelines([ + f'base_dir = /project/build/{wheel_tag}' + ]) diff --git a/tools/cibuildwheel_before_build.sh b/tools/cibuildwheel_before_build.sh new file mode 100644 index 000000000..e39ef590a --- /dev/null +++ b/tools/cibuildwheel_before_build.sh @@ -0,0 +1,55 @@ +set -xe + +# Install OpenBLAS +# python -m pip install scipy-openblas64 +# blas_lib_dir=$(python -c "import scipy_openblas64; print(scipy_openblas64.get_lib_dir())") +# blas_lib_name=$(python -c "import scipy_openblas64; print(scipy_openblas64.get_library())") +# # This handles different extensions (.so on Linux, .dylib on macOS) +# blas_lib_file=$(find "$blas_lib_dir" -name "*${blas_lib_name}.*" -type f | head -n 1) +# export BLAS_LIBRARIES="$blas_lib_file" + +# # Verify BLAS_LIBRARIES points to an existing file +# if [ -f "$BLAS_LIBRARIES" ]; then +# echo "✓ BLAS_LIBRARIES points to existing file: $BLAS_LIBRARIES" +# else +# echo "✗ ERROR: BLAS_LIBRARIES does not point to a valid file: $BLAS_LIBRARIES" +# exit 1 +# fi + +# BLA_SIZEOF_INTEGER=8 matches the ILP64 openblas64_ interface +# that scipy-openblas64 provides; this avoids CMake thinking +# it found a 32-bit BLAS. +# export BLA_SIZEOF_INTEGER=8 + + +uname -s +# Set up Homebrew for Linux +# Homebrew was removed from PATH on Linux: +# https://github.com/actions/runner-images/issues/6283 +if [[ "$(uname -s)" == "Linux" ]]; then + # Find linuxbrew from root folder recursively + # Common locations: /home/linuxbrew/.linuxbrew, /home/*/linuxbrew/.linuxbrew + # brew_find=$(find / -name "*linuxbrew*" -type d 2>/dev/null) + # linuxbrew_bin=$(echo $brew_find | head -n 1) + # if [ -n "$linuxbrew_bin" ] && [ -d "$linuxbrew_bin/bin" ]; then + # echo "Found linuxbrew at: $linuxbrew_bin" + # export PATH="$linuxbrew_bin/bin:$PATH" + # else + # echo "Warning: linuxbrew not found, trying default path" + # export PATH="/home/linuxbrew/.linuxbrew/bin:$PATH" + # fi + export PATH="/host/home/linuxbrew/.linuxbrew/bin:$PATH" +fi + +# Install boost, ccache and arpack-ng +# All Ubuntu and macOS GitHub action runners have Homebrew installed. +brew install boost ccache arpack openblas + +brew --prefix openblas +ls $(brew --prefix openblas) +brew --prefix ccache +ls $(brew --prefix ccache) +brew --prefix arpack +ls $(brew --prefix arpack) +brew --prefix boost +ls $(brew --prefix boost)