From 98b45ab5fadcee92e04f64b002758844c0dedf4e Mon Sep 17 00:00:00 2001 From: Agriya Khetarpal <74401230+agriyakhetarpal@users.noreply.github.com> Date: Sat, 25 May 2024 00:59:00 +0530 Subject: [PATCH] Revert "Use wider-compatible LLVM-OpenMP, bundle `libgfortran` dylibs for macOS wheels (#4092)" (#4109) This reverts commit becb1a4a4b137452d7e490b314fcd5801e3a9137. --- .github/workflows/publish_pypi.yml | 70 ++++-------- .gitignore | 4 - .lycheeignore | 1 - .../installation/install-from-source.rst | 17 ++- scripts/install_KLU_Sundials.py | 101 ++++-------------- 5 files changed, 50 insertions(+), 143 deletions(-) diff --git a/.github/workflows/publish_pypi.yml b/.github/workflows/publish_pypi.yml index 7154882536..c51aa8f0e6 100644 --- a/.github/workflows/publish_pypi.yml +++ b/.github/workflows/publish_pypi.yml @@ -100,7 +100,7 @@ jobs: strategy: fail-fast: false matrix: - os: [ubuntu-latest, macos-13] + os: [ubuntu-latest, macos-12] steps: - uses: actions/checkout@v4 - uses: actions/setup-python@v5 @@ -110,6 +110,15 @@ jobs: - name: Clone pybind11 repo (no history) run: git clone --depth 1 --branch v2.11.1 https://github.com/pybind/pybind11.git + # sometimes gfortran cannot be found, so reinstall gcc just to be sure + - name: Install SuiteSparse and SUNDIALS on macOS + if: matrix.os == 'macos-12' + run: | + brew install graphviz libomp + brew reinstall gcc + python -m pip install cmake wget + python scripts/install_KLU_Sundials.py + - name: Build wheels on Linux run: pipx run cibuildwheel --output-dir wheelhouse if: matrix.os == 'ubuntu-latest' @@ -145,57 +154,18 @@ jobs: - name: Clone pybind11 repo (no history) run: git clone --depth 1 --branch v2.11.1 https://github.com/pybind/pybind11.git - - name: Build wheels on macOS arm64 + - name: Install SuiteSparse and SUNDIALS on macOS run: | - python -m pip install cibuildwheel - python -m cibuildwheel --output-dir wheelhouse - env: - MACOSX_DEPLOYMENT_TARGET: 11.0 - # Sourced from - # License: BSD-3-Clause - # https://github.com/scipy/scipy/blob/f2d4775e7762fad984f8f0acd8227c725ff21630/tools/wheels/cibw_before_build_macos.sh#L23-L49 - CIBW_BEFORE_ALL_MACOS: | - set -e -x - - # download gfortran with proper macos minimum version (11.0) - curl -L https://github.com/isuruf/gcc/releases/download/gcc-11.3.0-2/gfortran-darwin-arm64-native.tar.gz -o gfortran.tar.gz - - GFORTRAN_SHA256=$(shasum --algorithm 256 gfortran.tar.gz) - KNOWN_SHA256="84364eee32ba843d883fb8124867e2bf61a0cd73b6416d9897ceff7b85a24604 gfortran.tar.gz" - if [ "$GFORTRAN_SHA256" != "$KNOWN_SHA256" ]; then - echo "SHA256 mismatch for gfortran.tar.gz" - echo "expected: $KNOWN_SHA256" - echo "got: $GFORTRAN_SHA256" - exit 1 - fi - - mkdir -p gfortran_installed/ - tar -xv -C gfortran_installed/ -f gfortran.tar.gz + brew install graphviz libomp + brew reinstall gcc + python -m pip install cmake pipx + python scripts/install_KLU_Sundials.py - export FC=$(pwd)/gfortran_installed/gfortran-darwin-arm64-native/bin/gfortran - export PATH=$(pwd)/gfortran_installed/gfortran-darwin-arm64-native/bin:$PATH - - # link libgfortran.5.dylib, libgfortran.dylib, libquadmath.0.dylib, libquadmath.dylib, libgcc_s.1.1.dylib - # and place them in $HOME/.local/lib, and then change rpath - # note: libgcc_s.1.dylib not present for arm64, skip for now - # to $HOME/.local/lib - - mkdir -p $HOME/.local/lib - lib_dir=$(pwd)/gfortran_installed/gfortran-darwin-arm64-native/lib - for lib in libgfortran.5.dylib libgfortran.dylib libquadmath.0.dylib libquadmath.dylib libgcc_s.1.1.dylib; do - cp $lib_dir/$lib $HOME/.local/lib/ - install_name_tool -id $HOME/.local/lib/$lib $HOME/.local/lib/$lib - done - - export SDKROOT=${SDKROOT:-$(xcrun --show-sdk-path)} - - python -m pip install cmake wget - python scripts/install_KLU_Sundials.py - CIBW_BEFORE_BUILD_MACOS: python -m pip install --upgrade pip cmake casadi setuptools wheel delocate - # Use higher macOS target for now: https://github.com/casadi/casadi/issues/3698 - CIBW_REPAIR_WHEEL_COMMAND_MACOS: | - delocate-listdeps {wheel} && delocate-wheel -v -w {dest_dir} {wheel} --require-target-macos-version 11.1 - for file in {dest_dir}/*.whl; do mv "$file" "${file//macosx_11_1/macosx_11_0}"; done + - name: Build wheels on macOS arm64 + run: python -m pipx run cibuildwheel --output-dir wheelhouse + env: + CIBW_BEFORE_BUILD: python -m pip install cmake casadi setuptools wheel delocate + CIBW_REPAIR_WHEEL_COMMAND: delocate-listdeps {wheel} && delocate-wheel -v -w {dest_dir} {wheel} CIBW_TEST_COMMAND: python -c "import pybamm; pybamm.IDAKLUSolver()" - name: Upload wheels for macOS arm64 diff --git a/.gitignore b/.gitignore index 6f1f201617..03750e18b2 100644 --- a/.gitignore +++ b/.gitignore @@ -137,7 +137,3 @@ results/ # tests test_callback.log - -# openmp downloads -install_KLU_Sundials/openmp-* -install_KLU_Sundials/usr/ diff --git a/.lycheeignore b/.lycheeignore index 1543b69f03..338303b4ba 100644 --- a/.lycheeignore +++ b/.lycheeignore @@ -1,7 +1,6 @@ # a list of links/files to be ignored by lychee link checker (see workflow file) https://github.com/DrTimothyAldenDavis/SuiteSparse/archive/v%7BSUITESPARSE_VERSION%7D.tar.gz https://github.com/LLNL/sundials/releases/download/v%7BSUNDIALS_VERSION%7D/sundials-%7BSUNDIALS_VERSION%7D.tar.gz -https://mac.r-project.org/openmp/openmp-%7BOPENMP_VERSION%7D-darwin20-Release.tar.gz # Errors in docs/source/user_guide/getting_started.md file:///home/runner/work/PyBaMM/PyBaMM/docs/source/user_guide/api_docs diff --git a/docs/source/user_guide/installation/install-from-source.rst b/docs/source/user_guide/installation/install-from-source.rst index a2a7637c9e..ea664b4a5b 100644 --- a/docs/source/user_guide/installation/install-from-source.rst +++ b/docs/source/user_guide/installation/install-from-source.rst @@ -43,11 +43,11 @@ You can install the above with Where ``X`` is the version sub-number. -.. tab:: macOS +.. tab:: MacOS .. code:: bash - brew install python openblas gcc gfortran graphviz cmake pandoc + brew install python openblas gcc gfortran graphviz libomp cmake pandoc .. note:: @@ -82,9 +82,8 @@ If you are running windows, you can simply skip this section and jump to :ref:`p # in the PyBaMM/ directory nox -s pybamm-requires -This will download, compile and install the SuiteSparse and SUNDIALS (with OpenMP) libraries -and the ``pybind11`` headers. -SuiteSparse and SUNDIALS are installed in ``~/.local`` by default. +This will download, compile and install the SuiteSparse and SUNDIALS libraries. +Both libraries are installed in ``~/.local``. For users requiring more control over the installation process, the ``pybamm-requires`` session supports additional command-line arguments: @@ -111,7 +110,7 @@ If you'd rather do things yourself, 1. Make sure you have CMake installed 2. Compile and install SuiteSparse (PyBaMM only requires the ``KLU`` component). -3. Compile and install SUNDIALS with `OpenMP support `_. +3. Compile and install SUNDIALS. 4. Clone the pybind11 repository in the ``PyBaMM/`` directory (make sure the directory is named ``pybind11``). @@ -316,12 +315,12 @@ source files to your current directory. ``ValueError: Integrator name ida does not exist``, or ``ValueError: Integrator name cvode does not exist``. -**Solution:** This could mean that you have not linked to -SUNDIALS correctly, check the instructions given above and make +**Solution:** This could mean that you have not installed +``scikits.odes`` correctly, check the instructions given above and make sure each command was successful. One possibility is that you have not set your ``LD_LIBRARY_PATH`` to -point to the SUNDIALS library, type ``echo $LD_LIBRARY_PATH`` and make +point to the sundials library, type ``echo $LD_LIBRARY_PATH`` and make sure one of the directories printed out corresponds to where the SUNDIALS libraries are located. diff --git a/scripts/install_KLU_Sundials.py b/scripts/install_KLU_Sundials.py index 983f07b4bb..14d2a47fd4 100755 --- a/scripts/install_KLU_Sundials.py +++ b/scripts/install_KLU_Sundials.py @@ -7,35 +7,18 @@ SUITESPARSE_VERSION = "6.0.3" SUNDIALS_VERSION = "6.5.0" - SUITESPARSE_URL = f"https://github.com/DrTimothyAldenDavis/SuiteSparse/archive/v{SUITESPARSE_VERSION}.tar.gz" SUNDIALS_URL = f"https://github.com/LLNL/sundials/releases/download/v{SUNDIALS_VERSION}/sundials-{SUNDIALS_VERSION}.tar.gz" - SUITESPARSE_CHECKSUM = ( "7111b505c1207f6f4bd0be9740d0b2897e1146b845d73787df07901b4f5c1fb7" ) SUNDIALS_CHECKSUM = "4e0b998dff292a2617e179609b539b511eb80836f5faacf800e688a886288502" - -# universal binaries for macOS 11.0 and later; sourced from https://mac.r-project.org/openmp/ -OPENMP_VERSION = "16.0.4" -OPENMP_URL = ( - f"https://mac.r-project.org/openmp/openmp-{OPENMP_VERSION}-darwin20-Release.tar.gz" -) -OPENMP_CHECKSUM = "a763f0bdc9115c4f4933accc81f514f3087d56d6528778f38419c2a0d2231972" - - DEFAULT_INSTALL_DIR = os.path.join(os.getenv("HOME"), ".local") def safe_remove_dir(path): - """Remove a directory or file if it exists.""" - try: - if os.path.isfile(path): - os.remove(path) - elif os.path.isdir(path): - shutil.rmtree(path) - except Exception as e: - print(f"Error while removing {path}: {e}") + if os.path.exists(path): + shutil.rmtree(path) def install_suitesparse(download_dir): @@ -61,11 +44,11 @@ def install_suitesparse(download_dir): env = os.environ.copy() for libdir in ["SuiteSparse_config", "AMD", "COLAMD", "BTF", "KLU"]: build_dir = os.path.join(suitesparse_src, libdir) - # Set an RPATH in order for libsuitesparseconfig.dylib to find libomp.dylib + # We want to ensure that libsuitesparseconfig.dylib is not repeated in + # multiple paths at the time of wheel repair. Therefore, it should not be + # built with an RPATH since it is copied to the install prefix. if libdir == "SuiteSparse_config": - env["CMAKE_OPTIONS"] = ( - f"-DCMAKE_INSTALL_PREFIX={install_dir} -DCMAKE_INSTALL_RPATH={install_dir}/lib" - ) + env["CMAKE_OPTIONS"] = f"-DCMAKE_INSTALL_PREFIX={install_dir}" else: # For AMD, COLAMD, BTF and KLU; do not set a BUILD RPATH but use an # INSTALL RPATH in order to ensure that the dynamic libraries are found @@ -103,9 +86,20 @@ def install_sundials(download_dir, install_dir): # try to find OpenMP on mac if platform.system() == "Darwin": # flags to find OpenMP on mac - OpenMP_C_FLAGS = f"-Xpreprocessor -fopenmp -lomp -L{os.path.join(KLU_LIBRARY_DIR)} -I{os.path.join(KLU_INCLUDE_DIR)}" - OpenMP_C_LIB_NAMES = "omp" - OpenMP_omp_LIBRARY = os.path.join(KLU_LIBRARY_DIR, "libomp.dylib") + if platform.processor() == "arm": + OpenMP_C_FLAGS = ( + "-Xpreprocessor -fopenmp -I/opt/homebrew/opt/libomp/include" + ) + OpenMP_C_LIB_NAMES = "omp" + OpenMP_omp_LIBRARY = "/opt/homebrew/opt/libomp/lib/libomp.dylib" + elif platform.processor() == "i386": + OpenMP_C_FLAGS = "-Xpreprocessor -fopenmp -I/usr/local/opt/libomp/include" + OpenMP_C_LIB_NAMES = "omp" + OpenMP_omp_LIBRARY = "/usr/local/opt/libomp/lib/libomp.dylib" + else: + raise NotImplementedError( + f"Unsupported processor architecture: {platform.processor()}. Only 'arm' and 'i386' architectures are supported." + ) cmake_args += [ "-DOpenMP_C_FLAGS=" + OpenMP_C_FLAGS, @@ -129,47 +123,6 @@ def install_sundials(download_dir, install_dir): subprocess.run(make_cmd, cwd=build_dir, check=True) -# Relevant for macOS only because recent Xcode Clang versions do not include OpenMP headers. -# Other compilers (e.g. GCC) include the OpenMP specification by default. -def set_up_openmp(download_dir, install_dir): - print("-" * 10, "Extracting OpenMP archive", "-" * 40) - - openmp_dir = f"openmp-{OPENMP_VERSION}" - openmp_src = os.path.join(download_dir, openmp_dir) - - # extract OpenMP archive - with tarfile.open( - os.path.join(download_dir, f"{openmp_dir}-darwin20-Release.tar.gz") - ) as tar: - tar.extractall(openmp_src) - - # create directories - os.makedirs(os.path.join(install_dir, "lib"), exist_ok=True) - os.makedirs(os.path.join(install_dir, "include"), exist_ok=True) - - # copy files - shutil.copy( - os.path.join(openmp_src, "usr", "local", "lib", "libomp.dylib"), - os.path.join(install_dir, "lib"), - ) - for file in os.listdir(os.path.join(openmp_src, "usr", "local", "include")): - shutil.copy( - os.path.join(openmp_src, "usr", "local", "include", file), - os.path.join(install_dir, "include"), - ) - - # fix rpath; for some reason the downloaded dylib has an absolute path - # to /usr/local/lib/, so use self-referential rpath - subprocess.check_call( - [ - "install_name_tool", - "-id", - "@rpath/libomp.dylib", - f"{os.path.join(install_dir, 'lib', 'libomp.dylib')}", - ] - ) - - def check_libraries_installed(install_dir): # Define the directories to check for SUNDIALS and SuiteSparse libraries lib_dirs = [install_dir] @@ -217,7 +170,7 @@ def check_libraries_installed(install_dir): suitesparse_files = [file + ".dylib" for file in suitesparse_files] else: raise NotImplementedError( - f"Unsupported operating system: {platform.system()}. This script supports only Linux and macOS." + f"Unsupported operating system: {platform.system()}. This script currently supports only Linux and macOS." ) suitesparse_lib_found = True @@ -239,16 +192,6 @@ def check_libraries_installed(install_dir): return sundials_lib_found, suitesparse_lib_found -def check_openmp_installed_on_macos(install_dir): - openmp_lib_found = isfile(join(install_dir, "lib", "libomp.dylib")) - openmp_headers_found = isfile(join(install_dir, "include", "omp.h")) - if not openmp_lib_found or not openmp_headers_found: - print("libomp.dylib or omp.h not found. Proceeding with OpenMP installation.") - else: - print(f"libomp.dylib and omp.h found in {install_dir}.") - return openmp_lib_found - - def calculate_sha256(file_path): sha256_hash = hashlib.sha256() with open(file_path, "rb") as f: @@ -324,7 +267,7 @@ def parallel_download(urls, download_dir): # Get installation location parser = argparse.ArgumentParser( - description="Download, compile and install SUNDIALS and SuiteSparse." + description="Download, compile and install Sundials and SuiteSparse." ) parser.add_argument( "--force",