Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
79 changes: 79 additions & 0 deletions .github/workflows/publish_pypi.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
name: Publish the Python package to PyPI

# Publishes the app's own Python code (the package in src/) on PyPI at each release, with the version of the release: the same
# moment and the same version as the installers (release_constructor.yml).
#
# OFF BY DEFAULT: both jobs are skipped unless the repository variable PYPI_PUBLISH is
# true -> publish on PyPI (pypi.org)
# testpypi -> publish on TestPyPI (test.pypi.org), a trial
# No secret or token: PyPI trusted publishing. One-time setup (a pending trusted publisher on pypi.org for this workflow file and the
# environment "pypi", then the variable): .tools/docs/external_code_upload.md. To turn it off, delete the variable.

on:
release:
types:
- published

permissions:
contents: read

jobs:
build:
name: Build the package of src/
if: vars.PYPI_PUBLISH == 'true' || vars.PYPI_PUBLISH == 'testpypi'
runs-on: ubuntu-latest
outputs:
url: ${{ steps.build.outputs.url }}
steps:
- name: Checkout repository
uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4
with:
persist-credentials: false

- name: Set up Python
uses: actions/setup-python@7f4fc3e22c37d6ff65e88745f38bd3157c663f7c # v4
with:
python-version: '3.11'

- name: Install requirements
run: pip install -r .tools/requirements.txt build==1.3.0

# The tag (v1.2.3 or 1.2.3) without the v. The checkout is the tagged commit, before the "Update version" commit of
# release_constructor.yml: pypi_package.py writes this version into setup.py itself (not committed).
- name: Build and check the package (nothing is uploaded yet)
id: build
env:
TAG: ${{ github.event.release.tag_name }}
PYPI_PUBLISH: ${{ vars.PYPI_PUBLISH }}
run: python .tools/python/pypi_package.py --version "${TAG#v}" --index "$PYPI_PUBLISH" --outdir dist --github-output "$GITHUB_OUTPUT"

- name: Store the distributions
uses: actions/upload-artifact@cf430e030ddbb5b0abf93d22962f4752f3646cd9 # v7
with:
name: python-package-distributions
path: dist/
if-no-files-found: error

publish:
name: Upload to ${{ vars.PYPI_PUBLISH == 'testpypi' && 'TestPyPI' || 'PyPI' }}
needs: build
if: vars.PYPI_PUBLISH == 'true' || vars.PYPI_PUBLISH == 'testpypi'
runs-on: ubuntu-latest
# The environment named in the trusted publisher on pypi.org (and test.pypi.org). GitHub creates it on the first run; protection
# rules added to it (Settings > Environments > pypi), such as required reviewers, then apply to every upload.
environment:
name: pypi
url: ${{ needs.build.outputs.url }}
permissions:
id-token: write # trusted publishing: PyPI accepts a short-lived token that GitHub signs for this workflow and environment
steps:
- name: Download the distributions
uses: actions/download-artifact@9000827ccba6bdab643e8b6fd33ac0654aef8333 # v8.0.2
with:
name: python-package-distributions
path: dist/

- name: Upload
uses: pypa/gh-action-pypi-publish@7245906fb61fa33a13554f9d5fb856fe61758086 # v1.14.3
with:
repository-url: ${{ vars.PYPI_PUBLISH == 'testpypi' && 'https://test.pypi.org/legacy/' || 'https://upload.pypi.org/legacy/' }}
49 changes: 38 additions & 11 deletions .github/workflows/release_constructor.yml
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,8 @@ jobs:
run: |
set -euxo pipefail
RAW="${{ github.event.release.tag_name }}"
# The tag as typed (v1.0.0 or 1.0.0): bump_version.py points the download links to it.
echo "RELEASE_TAG=$RAW" >> "$GITHUB_ENV"
RAW="${RAW#v}"
echo "release_version=$RAW" >> "$GITHUB_OUTPUT"
echo "RELEASE_VERSION=$RAW" >> "$GITHUB_ENV"
Expand Down Expand Up @@ -61,6 +63,11 @@ jobs:
"bump_Welcome_notebook.py" \
.tools/python/bump_Welcome_notebook.py \
python .tools/python/bump_Welcome_notebook.py "${{ github.repository_owner }}" "${{ github.event.repository.name }}"
bash .tools/bash/run_with_context.sh \
artifacts/error_log_document.log \
"check_placeholders.py" \
.tools/python/check_placeholders.py \
python .tools/python/check_placeholders.py


- name: Commit updated versioned files
Expand All @@ -78,22 +85,35 @@ jobs:
if-no-files-found: ignore
archive: false

platforms:
name: Installers to build
runs-on: ubuntu-latest
outputs:
matrix: ${{ steps.matrix.outputs.matrix }}
steps:
- name: Checkout repository
uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4

- name: Set up Python
uses: actions/setup-python@7f4fc3e22c37d6ff65e88745f38bd3157c663f7c # v4
with:
python-version: '3.11'

- name: Install requirements
run: pip install -r .tools/requirements.txt

- name: Read distribution.platforms of labconstrictor-app.yaml
id: matrix
run: python .tools/python/release_platforms.py --github-output "$GITHUB_OUTPUT"

build-installers:
name: Build installers (${{ matrix.os_name }})
runs-on: ${{ matrix.runner }}
needs: bump-version
needs: [bump-version, platforms]
strategy:
fail-fast: false
matrix:
include:
- os_name: linux
runner: ubuntu-latest
- os_name: macos-arm64
runner: macos-15
- os_name: macos-intel
runner: macos-15-intel
- os_name: windows
runner: windows-latest
# One build per system of distribution.platforms in labconstrictor-app.yaml (every system without it), from the platforms job.
matrix: ${{ fromJSON(needs.platforms.outputs.matrix) }}
defaults:
run:
shell: bash -l {0}
Expand All @@ -119,6 +139,8 @@ jobs:
exit 1
fi

# The tag as typed (v1.0.0 or 1.0.0): bump_version.py points the download links to it.
echo "RELEASE_TAG=$RAW" >> "$GITHUB_ENV"
RAW="${RAW#v}"
echo "release_version=$RAW" >> "$GITHUB_OUTPUT"
echo "RELEASE_VERSION=$RAW" >> "$GITHUB_ENV"
Expand Down Expand Up @@ -148,6 +170,11 @@ jobs:
"bump_Welcome_notebook.py" \
.tools/python/bump_Welcome_notebook.py \
python .tools/python/bump_Welcome_notebook.py "${{ github.repository_owner }}" "${{ github.event.repository.name }}"
bash .tools/bash/run_with_context.sh \
"artifacts/error_log_document.log" \
"check_placeholders.py" \
.tools/python/check_placeholders.py \
python .tools/python/check_placeholders.py

- name: Set up Miniconda
uses: conda-incubator/setup-miniconda@fc2d68f6413eb2d87b895e92f8584b5b94a10167 # v3
Expand Down
75 changes: 66 additions & 9 deletions .github/workflows/update_on_notebook_change.yml
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,9 @@ on:
- 'notebooks/**/requirements.yaml'
workflow_dispatch:

# This enables to run a single workflow at a time
# One run of this workflow at a time per branch. The src workflow has its own group: the two can run at the same time,
# because each one rebases its commit onto the latest branch before it pushes (a shared group kept only one waiting run, so a
# third push cancelled the waiting run of the other workflow).
concurrency:
group: notebooks-global-lock-${{ github.ref }}
cancel-in-progress: false
Expand Down Expand Up @@ -98,6 +100,12 @@ jobs:
.tools/python/merge_requirements.py \
python .tools/python/merge_requirements.py --source-dir notebooks --output requirements.txt

# requirements*.txt -> requirements*.lock.txt: every package the installer installs with pip, with its exact version (uv, valid on
# Windows, macOS and Linux). A lock file is rewritten only when its requirements file, Python or JupyterLab version changed.
- name: Lock requirements
run: |
bash .tools/bash/run_with_context.sh artifacts/error_log_document.log "lock_requirements.py" .tools/python/lock_requirements.py python .tools/python/lock_requirements.py

- name: Check for changes
id: check-changes
run: |
Expand Down Expand Up @@ -138,17 +146,66 @@ jobs:
- name: Commit updated files
id: auto-commit
if: steps.check-changes.outputs.has_changes == 'true' && steps.test-conda-env.outcome == 'success'
uses: stefanzweifel/git-auto-commit-action@b863ae1933cb653a53c021fe36dbb774e1fb9403 # v5
with:
commit_message: GitHub Action - Updated files because of notebook and/or requirements changes
file_pattern: requirements*.txt environment.yaml notebooks/* setup.py
env:
BRANCH: ${{ github.ref_name }}
COMMIT_MESSAGE: GitHub Action - Updated files because of notebook and/or requirements changes
# The run checked out the commit that triggered it (the scripts above compare it with its parent); the branch may have moved
# since (another workflow committed, or a new push). A rejected push is therefore retried after a rebase onto the latest branch.
run: |
git config user.name "github-actions[bot]"
git config user.email "41898282+github-actions[bot]@users.noreply.github.com"
git ls-files -z --modified --deleted --others --exclude-standard -- 'requirements*.txt' environment.yaml 'notebooks/*' setup.py | xargs -0 -r git add -A --
if git diff --cached --quiet; then
echo "No changes to commit."
exit 0
fi
git commit -m "$COMMIT_MESSAGE"
for attempt in 1 2 3 4 5; do
if git push origin "HEAD:refs/heads/$BRANCH"; then
exit 0
fi
echo "The push was rejected (attempt $attempt of 5): rebasing onto the latest $BRANCH."
sleep $((attempt * 5))
git fetch origin "$BRANCH"
if ! git rebase --autostash "origin/$BRANCH"; then
git rebase --abort || true
echo "::error::The commit of this run conflicts with the latest $BRANCH; run this workflow again (Actions > Run workflow)."
exit 1
fi
done
echo "::error::The push was rejected 5 times; run this workflow again (Actions > Run workflow)."
exit 1

# Only when the merged requirements could not be installed: a failure of any other step (a script, the push) never reverts a commit.
- name: Revert user commit that broke requirements
if: failure() && github.event_name == 'push'
if: failure() && github.event_name == 'push' && steps.test-conda-env.outcome == 'failure'
env:
BRANCH: ${{ github.ref_name }}
BAD_COMMIT: ${{ github.sha }}
run: |
git config user.name "github-actions[bot]"
git config user.email "github-actions[bot]@users.noreply.github.com"
git config user.email "41898282+github-actions[bot]@users.noreply.github.com"
git reset --hard
git clean -fd
git revert "${{ github.sha }}" -m 1 --no-edit
git push origin HEAD:${{ github.ref_name }}
for attempt in 1 2 3 4 5; do
git fetch origin "$BRANCH"
git checkout -B "$BRANCH" "origin/$BRANCH"
# A merge commit is reverted against its first parent (-m 1); any other commit is reverted as it is.
if [ "$(git rev-list --parents -n 1 "$BAD_COMMIT" | wc -w)" -gt 2 ]; then
REVERT_ARGS=(-m 1)
else
REVERT_ARGS=()
fi
if ! git revert --no-edit "${REVERT_ARGS[@]}" "$BAD_COMMIT"; then
git revert --abort || true
echo "::error::$BAD_COMMIT broke the requirements but could not be reverted automatically (later commits change the same lines): revert it by hand."
exit 1
fi
if git push origin "HEAD:refs/heads/$BRANCH"; then
exit 0
fi
echo "The push of the revert was rejected (attempt $attempt of 5): retrying on the latest $BRANCH."
sleep $((attempt * 5))
done
echo "::error::The revert of $BAD_COMMIT could not be pushed: revert it by hand."
exit 1
45 changes: 35 additions & 10 deletions .github/workflows/update_on_src_change.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,11 @@ on:
- 'src/**'
workflow_dispatch:

# This enables to run a single workflow at a time
# One run of this workflow at a time per branch. The notebook workflow has its own group: the two can run at the same time,
# because each one rebases its commit onto the latest branch before it pushes (a shared group kept only one waiting run, so a
# third push cancelled the waiting run of the other workflow).
concurrency:
group: notebooks-global-lock-${{ github.ref }}
group: src-change-lock-${{ github.ref }}
cancel-in-progress: false

jobs:
Expand All @@ -18,13 +20,13 @@ jobs:
runs-on: ubuntu-latest
permissions:
contents: write

steps:
- name: Checkout repository
uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4
with:
fetch-depth: 0 # Fetch all history for all branches and tags

- name: Update src change file
run: |
cat > .tools/meta/src_change.yaml <<EOF
Expand All @@ -33,9 +35,32 @@ jobs:

- name: Commit updated files
id: auto-commit
uses: stefanzweifel/git-auto-commit-action@b863ae1933cb653a53c021fe36dbb774e1fb9403 # v5
with:
commit_message: GitHub Action - Updated src change file

file_pattern: .tools/meta/src_change.yaml

env:
BRANCH: ${{ github.ref_name }}
COMMIT_MESSAGE: GitHub Action - Updated src change file
# The run checked out the commit that triggered it; the branch may have moved since (another workflow committed, or a new push).
# A rejected push is therefore retried after a rebase onto the latest branch.
run: |
git config user.name "github-actions[bot]"
git config user.email "41898282+github-actions[bot]@users.noreply.github.com"
git add -A -- .tools/meta/src_change.yaml
if git diff --cached --quiet; then
echo "No changes to commit."
exit 0
fi
git commit -m "$COMMIT_MESSAGE"
for attempt in 1 2 3 4 5; do
if git push origin "HEAD:refs/heads/$BRANCH"; then
exit 0
fi
echo "The push was rejected (attempt $attempt of 5): rebasing onto the latest $BRANCH."
sleep $((attempt * 5))
git fetch origin "$BRANCH"
if ! git rebase --autostash "origin/$BRANCH"; then
git rebase --abort || true
echo "::error::The commit of this run conflicts with the latest $BRANCH; run this workflow again (Actions > Run workflow)."
exit 1
fi
done
echo "::error::The push was rejected 5 times; run this workflow again (Actions > Run workflow)."
exit 1
29 changes: 28 additions & 1 deletion .template_sync/manifest.yaml
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
template_version: 0.1.19
template_version: 0.1.28
migrations:
- from: 0.0.0
to: 0.0.1
Expand Down Expand Up @@ -63,3 +63,30 @@ migrations:
- from: 0.1.18
to: 0.1.19
script: migrations/v0_1_18_to_v0_1_19.py
- from: 0.1.19
to: 0.1.20
script: migrations/v0_1_19_to_v0_1_20.py
- from: 0.1.20
to: 0.1.21
script: migrations/v0_1_20_to_v0_1_21.py
- from: 0.1.21
to: 0.1.22
script: migrations/v0_1_21_to_v0_1_22.py
- from: 0.1.22
to: 0.1.23
script: migrations/v0_1_22_to_v0_1_23.py
- from: 0.1.23
to: 0.1.24
script: migrations/v0_1_23_to_v0_1_24.py
- from: 0.1.24
to: 0.1.25
script: migrations/v0_1_24_to_v0_1_25.py
- from: 0.1.25
to: 0.1.26
script: migrations/v0_1_25_to_v0_1_26.py
- from: 0.1.26
to: 0.1.27
script: migrations/v0_1_26_to_v0_1_27.py
- from: 0.1.27
to: 0.1.28
script: migrations/v0_1_27_to_v0_1_28.py
Loading
Loading