diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 0000000..11d4bf6 --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,44 @@ +name: CI + +on: + push: + branches: [master, prerelease] + pull_request: + branches: [master, prerelease] + +jobs: + build-and-test: + name: Python ${{ matrix.python-version }} on ${{ matrix.os }} + runs-on: ${{ matrix.os }} + strategy: + fail-fast: false + matrix: + os: [ubuntu-latest] + python-version: ["3.10", "3.11", "3.12", "3.13", "3.14"] + + steps: + - uses: actions/checkout@v4 + with: + submodules: recursive + + - name: Set up conda (Miniforge) + uses: conda-incubator/setup-miniconda@v3 + with: + python-version: ${{ matrix.python-version }} + miniforge-variant: Miniforge3 + use-mamba: true + auto-activate-base: false + activate-environment: test-env + + - name: Install llvm-openmp (macOS) + if: runner.os == 'macOS' + shell: bash -el {0} + run: conda install -y -c conda-forge llvm-openmp + + - name: Install svmbir with dev dependencies + shell: bash -el {0} + run: pip install -e ".[dev]" + + - name: Run tests + shell: bash -el {0} + run: pytest svmbir/tests/ -v diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml new file mode 100644 index 0000000..065ddef --- /dev/null +++ b/.github/workflows/release.yml @@ -0,0 +1,40 @@ +name: Build and Release Wheels + +on: + push: + tags: + - "v*" + +jobs: + create-draft-release: + name: Create draft release + runs-on: ubuntu-latest + permissions: + contents: write + steps: + - uses: softprops/action-gh-release@v2 + with: + draft: true + generate_release_notes: true + + build-linux: + name: Linux wheels and sdist + runs-on: ubuntu-latest + needs: create-draft-release + permissions: + contents: write + steps: + - uses: actions/checkout@v4 + with: + submodules: recursive + + - name: Build wheels + uses: pypa/cibuildwheel@v2.22.0 + + - name: Build sdist + run: pipx run build --sdist + + - name: Upload to draft release + run: gh release upload ${{ github.ref_name }} wheelhouse/*.whl dist/*.tar.gz --clobber + env: + GH_TOKEN: ${{ github.token }} diff --git a/.gitignore b/.gitignore index 8d903b2..54bc4df 100644 --- a/.gitignore +++ b/.gitignore @@ -22,4 +22,6 @@ sftp-config.json *.DS_Store # Pycharm idea folder -.idea/ \ No newline at end of file +.idea/ +# cibuildwheel output +wheelhouse/ diff --git a/.travis.yml b/.travis.yml deleted file mode 100644 index 7766dfa..0000000 --- a/.travis.yml +++ /dev/null @@ -1,93 +0,0 @@ -os: linux - -language: python - -python: - - "3.6" - - "3.7" - - "3.8" - -#matrix: -# include: -# - os: osx -# osx_image: xcode12.2 -# language: generic -# env: -# - MATRIX_EVAL="brew install gcc@10 &&CC=gcc-10 && CXX=g++-10" -# - PYTHON=3.8 -# - SO_COMPILER=gcc -# -# - os: osx -# osx_image: xcode12.2 -# language: generic -# env: -# - MATRIX_EVAL="curl https://mac.r-project.org/openmp/openmp-11.0.1-darwin20-Release.tar.gz | tar -xz&& sudo cp usr/local/lib/* /usr/local/lib/ && sudo cp usr/local/include/* /usr/local/include/ &&CC=clang " -# - PYTHON=3.8 -# - SO_COMPILER=clang -notifications: - email: false - -addons: - apt: - packages: - - libfftw3-dev - - libatlas-dev - - libatlas-base-dev - - liblapack-dev - - libblas-dev - - gfortran - -before_install: - - export URLROOT=https://repo.continuum.io/miniconda/; - if [[ "$TRAVIS_OS_NAME" == "osx" ]]; then - brew update; - brew upgrade; - brew install fftw; - export MCINST=Miniconda3-latest-MacOSX-x86_64.sh; - else - if [[ "$TRAVIS_PYTHON_VERSION" == "2.7" ]]; then - export MCINST=Miniconda2-latest-Linux-x86_64.sh; - else - export MCINST=Miniconda3-latest-Linux-x86_64.sh; - fi - fi; - export MCURL=$URLROOT$MCINST; - wget $MCURL -O miniconda.sh - - bash miniconda.sh -b -p $HOME/miniconda - - export PATH="$HOME/miniconda/bin:$PATH" - - hash -r - - conda config --set always_yes yes --set changeps1 no - - conda update -q conda - - conda info -a -# - eval "${MATRIX_EVAL}" - -install: - - if [[ "$TRAVIS_OS_NAME" == "osx" ]]; then - export PYTHON_VERSION=3.8; - else - export PYTHON_VERSION=$TRAVIS_PYTHON_VERSION; - fi; - conda create --yes -q -n test-env python=$PYTHON_VERSION - - source activate test-env - - pip install -r requirements.txt - - if [[ "$TRAVIS_OS_NAME" != "osx" && "$TRAVIS_PYTHON_VERSION" == "3.8" ]]; then - conda install -c conda-forge codecov; - fi - - if [[ "$TRAVIS_OS_NAME" == "osx" ]]; then - if [[ "$SO_COMPILER" == "gcc" ]]; then - CC=gcc-10 python setup.py build_ext --inplace; - else - CC=clang python setup.py build_ext --inplace; - fi; - else - python setup.py build_ext --inplace; - fi; - - -script: - - pytest - -after_success: - - if [[ "$TRAVIS_OS_NAME" != "osx" && "$TRAVIS_PYTHON_VERSION" == "3.8" ]]; then - codecov; - fi diff --git a/dev_scripts/README.md b/dev_scripts/README.md new file mode 100644 index 0000000..f1a96bd --- /dev/null +++ b/dev_scripts/README.md @@ -0,0 +1,114 @@ +Considerations for package maintenance: +--------------------------------------- + +1. Package metadata (requires-python, dependencies in pyproject.toml) +This declares the minimum you need, not the maximum. The strong community consensus is: never put upper bounds on requires-python or on dependencies in published package metadata. Upper bounds are actively harmful — they prevent users from installing your package alongside other packages that have already moved forward. If numpy>=2.0 breaks something, the right fix is to fix the code, not to block numpy>=2.0. The only exception is when you have a known, tested incompatibility with a specific version. + +2. The CI test matrix +CI (Continuous Integration) is automated testing that runs in the cloud every time code is pushed to the repository or a pull request is opened. For svmbir, this means GitHub automatically installs the package and runs the test suite on Linux across all supported Python versions. The goal is to catch breakage early — before a bug reaches users — and to confirm that changes work correctly across the Python versions the package claims to support. Check https://github.com/cabouman/svmbir/actions for status. + +The CI matrix is the list of Python versions CI tests against. The current matrix is under jobs->matrix->python-version in `ci.yml`. When a new Python version comes out in October, add it to the list and see if anything breaks. This is the right place to discover problems early, before users file bugs. + +3. The dev environment (install_conda_environment.sh) +This should pin to a specific recent version for a reproducible daily-driver environment. The version you pick here doesn't limit what users can run — it's just what developers work in. Updating this once a year when a new Python ships is reasonable. + +4. The CI workflow (.github/workflows/ci.yml) +This is the configuration file that tells GitHub how to run CI. It specifies which Python versions to test, which operating system to use (Linux only — see section 5 for macOS), and what commands to run (install the package, run pytest). It fires automatically on every push to `master` or `prerelease`, and on every pull request targeting either branch — no manual action needed. + +Updating Python versions (do this ~once a year): +- **Add a new version**: Each October, Python ships a new release. Add it to the `python-version` list in `ci.yml`, and add the matching `cp3XX-*` entry to the `build` setting in `[tool.cibuildwheel]` in `pyproject.toml` so release wheels are built for it too. Note that Cython may lag a few months behind a new Python release, so if CI fails on the new version due to a Cython build error, simply remove it from both lists and try again after the next Cython release. +- **Drop an EOL version**: Python versions reach end-of-life roughly 3 years after release (schedule at python.org/downloads). Remove it from the `python-version` list in `ci.yml`, the `build` setting in `pyproject.toml`, and raise `requires-python` in `pyproject.toml` to match. + +Updating OS runners: +- CI runs Linux only (`ubuntu-latest`). macOS is no longer in the CI matrix — macOS compatibility is verified when building and test-installing the release wheels locally (see section 5 below). +- If a future Linux runner name change is needed, `ubuntu-latest` tracks GitHub's current default and rarely requires manual updates. + +5. The release workflow (.github/workflows/release.yml) and local macOS build +Linux wheels and the source distribution are built automatically by GitHub Actions. macOS arm64 wheels are built locally using `dev_scripts/build_mac_wheels.sh` and uploaded to the same draft release. Intel Mac (x86_64) is no longer supported. + +New releases: +------------- + +Each release is staged on the `prerelease` branch and stays as a non-public draft until you explicitly publish it, so you can verify everything before it goes live. The process has two steps: test first, then release. + +Both scripts are run from the `prerelease` branch. The typical sequence after your feature branch PR has been merged into `prerelease`: + + ``` + git checkout prerelease && git pull + ./test_release.sh # dry run — verify the pipeline works + ./cut_release.sh # real release + ``` + +`test_release.sh` warns (but does not exit) if you are not on `prerelease`, so it can also be used from a feature branch to debug a workflow problem. + +Note that the scripts need to be run as `./