diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index f8e2576c..23f85229 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -39,7 +39,8 @@ jobs: strategy: matrix: os: [ubuntu-latest, macos-latest, windows-latest] - python-version: [3.7, 3.8, 3.9, "3.10"] + python-version: [3.7, 3.8, 3.9, "3.10.6"] + # more specific version of 3.10 due to https://github.com/python/mypy/issues/13627 env: GITHUB_PAT: ${{ secrets.GITHUB_TOKEN }} steps: diff --git a/cmdstanpy/install_cmdstan.py b/cmdstanpy/install_cmdstan.py index 69afe501..6f6a6b23 100644 --- a/cmdstanpy/install_cmdstan.py +++ b/cmdstanpy/install_cmdstan.py @@ -41,6 +41,7 @@ validate_dir, wrap_url_progress_hook, ) +from cmdstanpy.utils.cmdstan import get_download_url from . import progress as progbar @@ -426,10 +427,7 @@ def install_version( def is_version_available(version: str) -> bool: is_available = True - url = ( - 'https://github.com/stan-dev/cmdstan/releases/download/' - 'v{0}/cmdstan-{0}.tar.gz'.format(version) - ) + url = get_download_url(version) for i in range(6): try: urllib.request.urlopen(url) @@ -458,10 +456,7 @@ def retrieve_version(version: str, progress: bool = True) -> None: if version is None or version == '': raise ValueError('Argument "version" unspecified.') print('Downloading CmdStan version {}'.format(version)) - url = ( - 'https://github.com/stan-dev/cmdstan/releases/download/' - 'v{0}/cmdstan-{0}.tar.gz'.format(version) - ) + url = get_download_url(version) for i in range(6): # always retry to allow for transient URLErrors try: if progress and progbar.allow_show_progress(): diff --git a/cmdstanpy/utils/cmdstan.py b/cmdstanpy/utils/cmdstan.py index 45bd7e57..273e0d27 100644 --- a/cmdstanpy/utils/cmdstan.py +++ b/cmdstanpy/utils/cmdstan.py @@ -3,6 +3,7 @@ """ import os import platform +import subprocess import sys from collections import OrderedDict from typing import Callable, Dict, Optional, Tuple, Union @@ -17,6 +18,46 @@ EXTENSION = '.exe' if platform.system() == 'Windows' else '' +def determine_linux_arch() -> str: + machine = platform.machine() + arch = "" + if machine == "aarch64": + arch = "arm64" + elif machine == "armv7l": + # Telling armel and armhf apart is nontrivial + # c.f. https://forums.raspberrypi.com/viewtopic.php?t=20873 + readelf = subprocess.run( + ["readelf", "-A", "/proc/self/exe"], + check=True, + stdout=subprocess.PIPE, + text=True, + ) + if "Tag_ABI_VFP_args" in readelf.stdout: + arch = "armel" + else: + arch = "armhf" + elif machine == "mips64": + arch = "mips64el" + elif machine == "ppc64el": + arch = "ppc64le" + elif machine == "s390x": + arch = "s390x" + return arch + + +def get_download_url(version: str) -> str: + arch = os.environ.get("CMDSTAN_ARCH", "") + if not arch and platform.system() == "Linux": + arch = determine_linux_arch() + + if arch and arch.lower() != "false": + url_end = f'v{version}/cmdstan-{version}-linux-{arch}.tar.gz' + else: + url_end = f'v{version}/cmdstan-{version}.tar.gz' + + return f'https://github.com/stan-dev/cmdstan/releases/download/{url_end}' + + def validate_dir(install_dir: str) -> None: """Check that specified install directory exists, can write.""" if not os.path.exists(install_dir): diff --git a/docsrc/installation.rst b/docsrc/installation.rst index 9059edc7..54e5c566 100644 --- a/docsrc/installation.rst +++ b/docsrc/installation.rst @@ -209,6 +209,17 @@ can be used to override these defaults: install_cmdstan -d my_local_cmdstan -v 2.27.0 ls -F my_local_cmdstan +Alternate Linux Architectures +............................. + +CmdStan can be installed on Linux for the following non-x86 architectures: +``arm64``, ``armel``, ``armhf``, ``mips64el``, ``ppc64el`` and ``s390x``. + +CmdStanPy will do its best to determine which of these is applicable for your +machine when running ``install_cmdstan``. If the wrong choice is made, or if you +need to manually override this, you can set the ``CMDSTAN_ARCH`` environment variable +to one of the above options, or to "false" to use the standard x86 download. + DIY Installation ^^^^^^^^^^^^^^^^