From 271095360bf262a56ff155f24a521ff8cd146c9e Mon Sep 17 00:00:00 2001 From: Pete Bachant Date: Tue, 2 Jun 2026 17:02:07 -0700 Subject: [PATCH 01/13] Automatically run setup steps based on environment kinds --- README.md | 8 +-- action.yml | 195 +++++++++++++++++++++++++++++++++++++++++++++++++++- example.yml | 5 +- 3 files changed, 197 insertions(+), 11 deletions(-) diff --git a/README.md b/README.md index cbe901f..6255e63 100644 --- a/README.md +++ b/README.md @@ -7,6 +7,9 @@ A GitHub Action to run a Calkit project's pipeline and optionally save results The example workflow below shows how to run a Calkit project, saving results. Note the permissions, concurrency, and checkout options. +The action detects required environment kinds from `calkit.yaml` and sets up +needed tooling (for example Calkit via `uv`, Julia, Pixi, Conda, R, MATLAB, +and Docker Buildx) before running. @@ -44,10 +47,7 @@ jobs: run: | git config user.name github-actions[bot] git config user.email 41898282+github-actions[bot]@users.noreply.github.com - - name: Setup uv - uses: astral-sh/setup-uv@v5 - - name: Install Calkit - run: uv tool install calkit-python + # Calkit automatically runs necessary setup actions based on environments - name: Run Calkit uses: calkit/run-action@v2 ``` diff --git a/action.yml b/action.yml index c4617c3..3375fd3 100644 --- a/action.yml +++ b/action.yml @@ -10,7 +10,8 @@ inputs: description: Whether or not to save (commit and push) results to Git and DVC remotes. default: "true" dvc_token: - description: DVC token for pushing results. If not provided, will be obtained + description: + DVC token for pushing results. If not provided, will be obtained via GitHub OIDC. required: false pull_dvc: @@ -29,17 +30,201 @@ outputs: {} runs: using: composite steps: + - name: Detect setup requirements + id: detect + shell: bash + run: | + set -euo pipefail + + has_kind() { + local kind="$1" + grep -Eiq "^[[:space:]]*kind:[[:space:]]*[\"']?${kind}([\"']?[[:space:]#]|$)" calkit.yaml + } + + if command -v calkit >/dev/null 2>&1; then + calkit_missing=false + else + calkit_missing=true + fi + echo "calkit_missing=${calkit_missing}" >> "$GITHUB_OUTPUT" + + if command -v uv >/dev/null 2>&1; then + uv_missing=false + else + uv_missing=true + fi + echo "uv_missing=${uv_missing}" >> "$GITHUB_OUTPUT" + + if command -v python >/dev/null 2>&1 || command -v python3 >/dev/null 2>&1; then + python_missing=false + else + python_missing=true + fi + + if command -v conda >/dev/null 2>&1; then + conda_missing=false + else + conda_missing=true + fi + + if command -v pixi >/dev/null 2>&1; then + pixi_missing=false + else + pixi_missing=true + fi + + if command -v julia >/dev/null 2>&1; then + julia_missing=false + else + julia_missing=true + fi + + if command -v Rscript >/dev/null 2>&1 || command -v R >/dev/null 2>&1; then + r_missing=false + else + r_missing=true + fi + + if command -v matlab >/dev/null 2>&1; then + matlab_missing=false + else + matlab_missing=true + fi + + if command -v docker >/dev/null 2>&1 && docker buildx version >/dev/null 2>&1; then + docker_buildx_missing=false + else + docker_buildx_missing=true + fi + + needs_python=false + needs_conda=false + needs_pixi=false + needs_julia=false + needs_r=false + needs_matlab=false + needs_docker=false + unsupported_kinds="" + + if [ -f calkit.yaml ]; then + if has_kind "uv-venv" || has_kind "venv"; then + if [ "$python_missing" = true ]; then + needs_python=true + fi + fi + if has_kind "conda"; then + if [ "$conda_missing" = true ]; then + needs_conda=true + fi + if [ "$python_missing" = true ]; then + needs_python=true + fi + fi + if has_kind "pixi"; then + if [ "$pixi_missing" = true ]; then + needs_pixi=true + fi + fi + if has_kind "julia"; then + if [ "$julia_missing" = true ]; then + needs_julia=true + fi + fi + if has_kind "renv"; then + if [ "$r_missing" = true ]; then + needs_r=true + fi + fi + if has_kind "matlab"; then + if [ "$matlab_missing" = true ]; then + needs_matlab=true + fi + fi + if has_kind "docker"; then + if [ "$docker_buildx_missing" = true ]; then + needs_docker=true + fi + fi + + # These kinds do not have a direct setup action in this composite action. + if has_kind "ssh"; then unsupported_kinds="${unsupported_kinds}ssh,"; fi + if has_kind "slurm"; then unsupported_kinds="${unsupported_kinds}slurm,"; fi + unsupported_kinds="${unsupported_kinds%,}" + fi + + echo "needs_python=${needs_python}" >> "$GITHUB_OUTPUT" + echo "needs_conda=${needs_conda}" >> "$GITHUB_OUTPUT" + echo "needs_pixi=${needs_pixi}" >> "$GITHUB_OUTPUT" + echo "needs_julia=${needs_julia}" >> "$GITHUB_OUTPUT" + echo "needs_r=${needs_r}" >> "$GITHUB_OUTPUT" + echo "needs_matlab=${needs_matlab}" >> "$GITHUB_OUTPUT" + echo "needs_docker=${needs_docker}" >> "$GITHUB_OUTPUT" + echo "unsupported_kinds=${unsupported_kinds}" >> "$GITHUB_OUTPUT" + + - name: Setup uv + if: ${{ steps.detect.outputs.calkit_missing == 'true' && steps.detect.outputs.uv_missing == 'true' }} + uses: astral-sh/setup-uv@v5 + + - name: Install Calkit + if: ${{ steps.detect.outputs.calkit_missing == 'true' }} + shell: bash + run: | + uv tool install calkit-python + echo "$HOME/.local/bin" >> "$GITHUB_PATH" + + - name: Setup Python + if: ${{ steps.detect.outputs.needs_python == 'true' }} + uses: actions/setup-python@v5 + with: + python-version: "3.x" + + - name: Setup Miniconda + if: ${{ steps.detect.outputs.needs_conda == 'true' }} + uses: conda-incubator/setup-miniconda@v3 + with: + auto-activate-base: true + + - name: Setup Pixi + if: ${{ steps.detect.outputs.needs_pixi == 'true' }} + uses: prefix-dev/setup-pixi@v0 + + - name: Setup Julia + if: ${{ steps.detect.outputs.needs_julia == 'true' }} + uses: julia-actions/setup-julia@v2 + with: + version: "1" + + - name: Setup R + if: ${{ steps.detect.outputs.needs_r == 'true' }} + uses: r-lib/actions/setup-r@v2 + + - name: Setup MATLAB + if: ${{ steps.detect.outputs.needs_matlab == 'true' }} + uses: matlab-actions/setup-matlab@v2 + + - name: Setup Docker Buildx + if: ${{ steps.detect.outputs.needs_docker == 'true' }} + uses: docker/setup-buildx-action@v3 + + - name: Report unsupported environment kinds + if: ${{ steps.detect.outputs.unsupported_kinds != '' }} + shell: bash + run: | + echo "::notice::Detected environment kinds without explicit setup action: ${{ steps.detect.outputs.unsupported_kinds }}" + - name: Restore DVC cache for current branch if: ${{ inputs.cache_dvc == 'true' }} id: cache-dvc-restore uses: actions/cache/restore@v5 with: path: .dvc/cache - key: ${{ runner.os }}-dvc-cache-${{ github.head_ref || github.ref_name }}-${{ + key: + ${{ runner.os }}-dvc-cache-${{ github.head_ref || github.ref_name }}-${{ github.sha }} restore-keys: | ${{ runner.os }}-dvc-cache-${{ github.head_ref || github.ref_name }}- ${{ runner.os }}-dvc-cache-${{ github.event.repository.default_branch }}- + - name: Get GitHub OIDC token if: ${{ !inputs.dvc_token && (inputs.save == 'true' || inputs.pull_dvc == 'true') }} @@ -53,6 +238,7 @@ runs: fi echo "::add-mask::$OIDC_TOKEN" echo "oidc-token=$OIDC_TOKEN" >> $GITHUB_OUTPUT + - name: Exchange OIDC token for Calkit token if: ${{ !inputs.dvc_token && (inputs.save == 'true' || inputs.pull_dvc == 'true') }} @@ -68,6 +254,7 @@ runs: fi echo "::add-mask::$CALKIT_TOKEN" echo "calkit-token=$CALKIT_TOKEN" >> $GITHUB_OUTPUT + - run: calkit config remote-auth if: ${{ inputs.save == 'true' || inputs.pull_dvc == 'true' }} shell: bash @@ -86,11 +273,13 @@ runs: env: CALKIT_TOKEN: ${{ inputs.dvc_token || steps.get-calkit-token.outputs.calkit-token }} CALKIT_DVC_TOKEN: ${{ inputs.dvc_token || steps.get-calkit-token.outputs.calkit-token }} + - name: Save DVC cache if: ${{ inputs.cache_dvc == 'true' }} id: cache-dvc-save uses: actions/cache/save@v5 with: path: .dvc/cache - key: ${{ runner.os }}-dvc-cache-${{ github.head_ref || github.ref_name }}-${{ + key: + ${{ runner.os }}-dvc-cache-${{ github.head_ref || github.ref_name }}-${{ github.sha }} diff --git a/example.yml b/example.yml index 8e789de..9a55f0e 100644 --- a/example.yml +++ b/example.yml @@ -31,9 +31,6 @@ jobs: run: | git config user.name github-actions[bot] git config user.email 41898282+github-actions[bot]@users.noreply.github.com - - name: Setup uv - uses: astral-sh/setup-uv@v5 - - name: Install Calkit - run: uv tool install calkit-python + # Calkit automatically runs necessary setup actions based on environments - name: Run Calkit uses: calkit/run-action@v2 From 4e3a073f9b7ca7ac398085a41c11fe5de95457a2 Mon Sep 17 00:00:00 2001 From: Pete Bachant Date: Tue, 2 Jun 2026 17:09:24 -0700 Subject: [PATCH 02/13] Potential fix for pull request finding Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> --- action.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/action.yml b/action.yml index 3375fd3..e6b3e83 100644 --- a/action.yml +++ b/action.yml @@ -162,7 +162,7 @@ runs: echo "unsupported_kinds=${unsupported_kinds}" >> "$GITHUB_OUTPUT" - name: Setup uv - if: ${{ steps.detect.outputs.calkit_missing == 'true' && steps.detect.outputs.uv_missing == 'true' }} + if: ${{ steps.detect.outputs.uv_missing == 'true' }} uses: astral-sh/setup-uv@v5 - name: Install Calkit From 42b0b3abf0b1b2cb6e021818fc670b4b6ce2b464 Mon Sep 17 00:00:00 2001 From: Pete Bachant Date: Tue, 2 Jun 2026 17:10:50 -0700 Subject: [PATCH 03/13] Potential fix for pull request finding Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> --- action.yml | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/action.yml b/action.yml index e6b3e83..667113a 100644 --- a/action.yml +++ b/action.yml @@ -218,9 +218,8 @@ runs: uses: actions/cache/restore@v5 with: path: .dvc/cache - key: - ${{ runner.os }}-dvc-cache-${{ github.head_ref || github.ref_name }}-${{ - github.sha }} + key: >- + ${{ runner.os }}-dvc-cache-${{ github.head_ref || github.ref_name }}-${{ github.sha }} restore-keys: | ${{ runner.os }}-dvc-cache-${{ github.head_ref || github.ref_name }}- ${{ runner.os }}-dvc-cache-${{ github.event.repository.default_branch }}- From e539d822c427b3e14c357fa7271a99f95f3367a8 Mon Sep 17 00:00:00 2001 From: Pete Bachant Date: Tue, 2 Jun 2026 17:11:39 -0700 Subject: [PATCH 04/13] Potential fix for pull request finding Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> --- action.yml | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/action.yml b/action.yml index 667113a..ac51519 100644 --- a/action.yml +++ b/action.yml @@ -279,6 +279,5 @@ runs: uses: actions/cache/save@v5 with: path: .dvc/cache - key: - ${{ runner.os }}-dvc-cache-${{ github.head_ref || github.ref_name }}-${{ - github.sha }} + key: >- + ${{ runner.os }}-dvc-cache-${{ github.head_ref || github.ref_name }}-${{ github.sha }} From f858ab8e3de7bcf37e384bcf9a2dc283e5601f26 Mon Sep 17 00:00:00 2001 From: Pete Bachant Date: Tue, 2 Jun 2026 17:12:10 -0700 Subject: [PATCH 05/13] Potential fix for pull request finding Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> --- example.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/example.yml b/example.yml index 9a55f0e..6e26cdc 100644 --- a/example.yml +++ b/example.yml @@ -31,6 +31,6 @@ jobs: run: | git config user.name github-actions[bot] git config user.email 41898282+github-actions[bot]@users.noreply.github.com - # Calkit automatically runs necessary setup actions based on environments + # This action automatically runs necessary setup steps based on environments - name: Run Calkit uses: calkit/run-action@v2 From c5f3652a334371a3c116eba633ef379cc9e9864f Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Wed, 3 Jun 2026 00:12:19 +0000 Subject: [PATCH 06/13] Sync README snippet from example.yml --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 6255e63..6a78158 100644 --- a/README.md +++ b/README.md @@ -47,7 +47,7 @@ jobs: run: | git config user.name github-actions[bot] git config user.email 41898282+github-actions[bot]@users.noreply.github.com - # Calkit automatically runs necessary setup actions based on environments + # This action automatically runs necessary setup steps based on environments - name: Run Calkit uses: calkit/run-action@v2 ``` From a9f57667c61d4aa0075f64a8da7b12825a254295 Mon Sep 17 00:00:00 2001 From: Pete Bachant Date: Tue, 2 Jun 2026 17:13:02 -0700 Subject: [PATCH 07/13] Potential fix for pull request finding Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> --- action.yml | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/action.yml b/action.yml index ac51519..4de2ac8 100644 --- a/action.yml +++ b/action.yml @@ -141,8 +141,12 @@ runs: fi fi if has_kind "docker"; then - if [ "$docker_buildx_missing" = true ]; then - needs_docker=true + if command -v docker >/dev/null 2>&1; then + if [ "$docker_buildx_missing" = true ]; then + needs_docker=true + fi + else + unsupported_kinds="${unsupported_kinds}docker," fi fi From 6da1b4169a7fc6f6170a6ba458bb4fdadb15981e Mon Sep 17 00:00:00 2001 From: Pete Bachant Date: Tue, 2 Jun 2026 18:16:55 -0700 Subject: [PATCH 08/13] Address PR comments --- action.yml | 21 +++++++++++++++++++-- 1 file changed, 19 insertions(+), 2 deletions(-) diff --git a/action.yml b/action.yml index 4de2ac8..5dedb15 100644 --- a/action.yml +++ b/action.yml @@ -98,20 +98,29 @@ runs: fi needs_python=false + needs_uv=false needs_conda=false needs_pixi=false needs_julia=false needs_r=false needs_matlab=false needs_docker=false + docker_cli_missing=false unsupported_kinds="" + if [ "$calkit_missing" = true ]; then + needs_uv=true + fi + if [ -f calkit.yaml ]; then if has_kind "uv-venv" || has_kind "venv"; then if [ "$python_missing" = true ]; then needs_python=true fi fi + if has_kind "uv-venv"; then + needs_uv=true + fi if has_kind "conda"; then if [ "$conda_missing" = true ]; then needs_conda=true @@ -146,7 +155,7 @@ runs: needs_docker=true fi else - unsupported_kinds="${unsupported_kinds}docker," + docker_cli_missing=true fi fi @@ -157,16 +166,18 @@ runs: fi echo "needs_python=${needs_python}" >> "$GITHUB_OUTPUT" + echo "needs_uv=${needs_uv}" >> "$GITHUB_OUTPUT" echo "needs_conda=${needs_conda}" >> "$GITHUB_OUTPUT" echo "needs_pixi=${needs_pixi}" >> "$GITHUB_OUTPUT" echo "needs_julia=${needs_julia}" >> "$GITHUB_OUTPUT" echo "needs_r=${needs_r}" >> "$GITHUB_OUTPUT" echo "needs_matlab=${needs_matlab}" >> "$GITHUB_OUTPUT" echo "needs_docker=${needs_docker}" >> "$GITHUB_OUTPUT" + echo "docker_cli_missing=${docker_cli_missing}" >> "$GITHUB_OUTPUT" echo "unsupported_kinds=${unsupported_kinds}" >> "$GITHUB_OUTPUT" - name: Setup uv - if: ${{ steps.detect.outputs.uv_missing == 'true' }} + if: ${{ steps.detect.outputs.needs_uv == 'true' && steps.detect.outputs.uv_missing == 'true' }} uses: astral-sh/setup-uv@v5 - name: Install Calkit @@ -216,6 +227,12 @@ runs: run: | echo "::notice::Detected environment kinds without explicit setup action: ${{ steps.detect.outputs.unsupported_kinds }}" + - name: Report missing Docker CLI for docker environments + if: ${{ steps.detect.outputs.docker_cli_missing == 'true' }} + shell: bash + run: | + echo "::notice::Detected kind 'docker' in calkit.yaml, but Docker CLI is not available on this runner. Install Docker on the runner to use docker environments." + - name: Restore DVC cache for current branch if: ${{ inputs.cache_dvc == 'true' }} id: cache-dvc-restore From c98e3fd0b1f60d8ee3f12dccc399adfdc1356028 Mon Sep 17 00:00:00 2001 From: Pete Bachant Date: Tue, 2 Jun 2026 19:18:14 -0700 Subject: [PATCH 09/13] Refine --- action.yml | 162 +++++++++++++++++++++++++++++++---------------------- 1 file changed, 95 insertions(+), 67 deletions(-) diff --git a/action.yml b/action.yml index 5dedb15..9be7282 100644 --- a/action.yml +++ b/action.yml @@ -30,36 +30,67 @@ outputs: {} runs: using: composite steps: + - name: Check for uv and Calkit + id: tools + shell: bash + run: | + set -euo pipefail + if command -v uv >/dev/null 2>&1; then + echo "uv_missing=false" >> "$GITHUB_OUTPUT" + else + echo "uv_missing=true" >> "$GITHUB_OUTPUT" + fi + if command -v calkit >/dev/null 2>&1; then + echo "calkit_missing=false" >> "$GITHUB_OUTPUT" + else + echo "calkit_missing=true" >> "$GITHUB_OUTPUT" + fi + + # uv is only needed up front to install Calkit; uv-venv projects that + # need uv but already have Calkit are handled by the uv step after + # detection. + - name: Setup uv + if: ${{ steps.tools.outputs.uv_missing == 'true' && steps.tools.outputs.calkit_missing == 'true' }} + uses: astral-sh/setup-uv@v5 + + - name: Install Calkit + if: ${{ steps.tools.outputs.calkit_missing == 'true' }} + shell: bash + run: | + uv tool install calkit-python + echo "$HOME/.local/bin" >> "$GITHUB_PATH" + - name: Detect setup requirements id: detect shell: bash run: | set -euo pipefail + # Let Calkit parse its own config so we only look at the + # `environments` section (a real YAML parse that also resolves + # includes), rather than grepping every `kind:` in the file -- + # pipeline stages, dependencies, datasets, etc. all use `kind:` too. + kinds=$(calkit list environments 2>/dev/null \ + | grep -E '^[[:space:]]+kind:' \ + | sed -E 's/^[[:space:]]+kind:[[:space:]]*//; s/[[:space:]]+#.*$//' \ + | tr -d "\"'" | sort -u || true) + echo "Detected environment kinds: $(echo "$kinds" | paste -sd, -)" + has_kind() { - local kind="$1" - grep -Eiq "^[[:space:]]*kind:[[:space:]]*[\"']?${kind}([\"']?[[:space:]#]|$)" calkit.yaml + printf '%s\n' "$kinds" | grep -qx "$1" } - if command -v calkit >/dev/null 2>&1; then - calkit_missing=false + if command -v python >/dev/null 2>&1 || command -v python3 >/dev/null 2>&1; then + python_missing=false else - calkit_missing=true + python_missing=true fi - echo "calkit_missing=${calkit_missing}" >> "$GITHUB_OUTPUT" if command -v uv >/dev/null 2>&1; then uv_missing=false else uv_missing=true fi - echo "uv_missing=${uv_missing}" >> "$GITHUB_OUTPUT" - - if command -v python >/dev/null 2>&1 || command -v python3 >/dev/null 2>&1; then - python_missing=false - else - python_missing=true - fi if command -v conda >/dev/null 2>&1; then conda_missing=false @@ -108,63 +139,64 @@ runs: docker_cli_missing=false unsupported_kinds="" - if [ "$calkit_missing" = true ]; then - needs_uv=true - fi - - if [ -f calkit.yaml ]; then - if has_kind "uv-venv" || has_kind "venv"; then - if [ "$python_missing" = true ]; then - needs_python=true - fi + if has_kind "uv-venv" || has_kind "venv" || has_kind "uv"; then + if [ "$python_missing" = true ]; then + needs_python=true fi - if has_kind "uv-venv"; then + fi + # uv is installed up front to install Calkit, so it is normally + # already present here; this covers the case where Calkit was + # pre-installed (so we skipped the initial uv setup) but uv is not. + if has_kind "uv-venv" || has_kind "uv"; then + if [ "$uv_missing" = true ]; then needs_uv=true fi - if has_kind "conda"; then - if [ "$conda_missing" = true ]; then - needs_conda=true - fi - if [ "$python_missing" = true ]; then - needs_python=true - fi + fi + if has_kind "conda"; then + if [ "$conda_missing" = true ]; then + needs_conda=true fi - if has_kind "pixi"; then - if [ "$pixi_missing" = true ]; then - needs_pixi=true - fi + if [ "$python_missing" = true ]; then + needs_python=true fi - if has_kind "julia"; then - if [ "$julia_missing" = true ]; then - needs_julia=true - fi + fi + if has_kind "pixi"; then + if [ "$pixi_missing" = true ]; then + needs_pixi=true fi - if has_kind "renv"; then - if [ "$r_missing" = true ]; then - needs_r=true - fi + fi + if has_kind "julia"; then + if [ "$julia_missing" = true ]; then + needs_julia=true fi - if has_kind "matlab"; then - if [ "$matlab_missing" = true ]; then - needs_matlab=true - fi + fi + if has_kind "renv"; then + if [ "$r_missing" = true ]; then + needs_r=true fi - if has_kind "docker"; then - if command -v docker >/dev/null 2>&1; then - if [ "$docker_buildx_missing" = true ]; then - needs_docker=true - fi - else - docker_cli_missing=true + fi + if has_kind "matlab"; then + if [ "$matlab_missing" = true ]; then + needs_matlab=true + fi + fi + if has_kind "docker"; then + if command -v docker >/dev/null 2>&1; then + if [ "$docker_buildx_missing" = true ]; then + needs_docker=true fi + else + docker_cli_missing=true fi - - # These kinds do not have a direct setup action in this composite action. - if has_kind "ssh"; then unsupported_kinds="${unsupported_kinds}ssh,"; fi - if has_kind "slurm"; then unsupported_kinds="${unsupported_kinds}slurm,"; fi - unsupported_kinds="${unsupported_kinds%,}" fi + # These kinds do not have a direct setup action in this composite action. + if has_kind "ssh"; then unsupported_kinds="${unsupported_kinds}ssh,"; fi + if has_kind "slurm"; then unsupported_kinds="${unsupported_kinds}slurm,"; fi + if has_kind "pbs"; then unsupported_kinds="${unsupported_kinds}pbs,"; fi + if has_kind "nix"; then unsupported_kinds="${unsupported_kinds}nix,"; fi + unsupported_kinds="${unsupported_kinds%,}" + echo "needs_python=${needs_python}" >> "$GITHUB_OUTPUT" echo "needs_uv=${needs_uv}" >> "$GITHUB_OUTPUT" echo "needs_conda=${needs_conda}" >> "$GITHUB_OUTPUT" @@ -176,17 +208,10 @@ runs: echo "docker_cli_missing=${docker_cli_missing}" >> "$GITHUB_OUTPUT" echo "unsupported_kinds=${unsupported_kinds}" >> "$GITHUB_OUTPUT" - - name: Setup uv - if: ${{ steps.detect.outputs.needs_uv == 'true' && steps.detect.outputs.uv_missing == 'true' }} + - name: Setup uv for uv and uv-venv environments + if: ${{ steps.detect.outputs.needs_uv == 'true' }} uses: astral-sh/setup-uv@v5 - - name: Install Calkit - if: ${{ steps.detect.outputs.calkit_missing == 'true' }} - shell: bash - run: | - uv tool install calkit-python - echo "$HOME/.local/bin" >> "$GITHUB_PATH" - - name: Setup Python if: ${{ steps.detect.outputs.needs_python == 'true' }} uses: actions/setup-python@v5 @@ -281,12 +306,15 @@ runs: env: CALKIT_TOKEN: ${{ inputs.dvc_token || steps.get-calkit-token.outputs.calkit-token }} CALKIT_DVC_TOKEN: ${{ inputs.dvc_token || steps.get-calkit-token.outputs.calkit-token }} + - run: calkit dvc pull if: ${{ inputs.pull_dvc == 'true' }} shell: bash continue-on-error: true + - run: calkit run ${{ inputs.run_args }} shell: bash + - run: calkit save -am "Run pipeline" if: ${{ inputs.save == 'true' }} shell: bash From 5b1be6c37da7758770def9dd31ffa1a461b8c70f Mon Sep 17 00:00:00 2001 From: Pete Bachant Date: Tue, 2 Jun 2026 19:41:06 -0700 Subject: [PATCH 10/13] Add test --- .github/workflows/test.yml | 56 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 56 insertions(+) create mode 100644 .github/workflows/test.yml diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml new file mode 100644 index 0000000..81b5795 --- /dev/null +++ b/.github/workflows/test.yml @@ -0,0 +1,56 @@ +name: Test action + +on: + push: + pull_request: + workflow_dispatch: + +# Saving/pulling is disabled below, so no Calkit/DVC token (and no OIDC) is +# needed and the default read-only token is sufficient. +permissions: + contents: read + +jobs: + run-examples: + name: ${{ matrix.example.repo }} + runs-on: ubuntu-latest + strategy: + fail-fast: false + matrix: + # Each entry clones a real Calkit example project and runs a single + # lightweight stage so the action's environment setup is exercised + # without building the heavy `tex` (texlive/texlive Docker) env that + # these projects use for their paper-build stages. `target` must be a + # stage whose environment is the one we want to verify got installed. + # To cover another kind, add an example repo + a cheap target here. + example: + - repo: example-basic + target: collect-data # uses the `py` (uv-venv) environment + verify: uv --version + - repo: example-julia + target: run-script # uses the `main` (julia) environment + verify: julia --version + steps: + # The composite action's steps run in $GITHUB_WORKSPACE, and it reads + # ./calkit.yaml from there, so the example project must be the workspace + # root. The workspace is empty at job start, so cloning into "." works; + # we then check this action out into a subdirectory and reference it + # locally via `uses: ./_action`. + - name: Clone example project + run: git clone --depth 1 "https://github.com/calkit/${{ matrix.example.repo }}.git" . + + - name: Check out this action + uses: actions/checkout@v4 + with: + path: _action + + - name: Run Calkit action + uses: ./_action + with: + save: "false" + pull_dvc: "false" + cache_dvc: "false" + run_args: ${{ matrix.example.target }} + + - name: Verify expected tooling was installed + run: ${{ matrix.example.verify }} From 7f55a85fd2abbfa2fbe2b9a40467f3e3e70dea82 Mon Sep 17 00:00:00 2001 From: Pete Bachant Date: Tue, 2 Jun 2026 19:57:40 -0700 Subject: [PATCH 11/13] Install juliaup --- action.yml | 20 ++++++++++++++++---- 1 file changed, 16 insertions(+), 4 deletions(-) diff --git a/action.yml b/action.yml index 9be7282..9dc5546 100644 --- a/action.yml +++ b/action.yml @@ -104,6 +104,15 @@ runs: pixi_missing=true fi + # Calkit manages Julia versions via juliaup (e.g. `juliaup add + # 1.11.7` for a `julia: "1.11.7"` environment). We install juliaup + # only when the runner has no Julia tooling at all; if the user + # already set up julia or juliaup themselves, we leave it alone. + if command -v juliaup >/dev/null 2>&1; then + juliaup_missing=false + else + juliaup_missing=true + fi if command -v julia >/dev/null 2>&1; then julia_missing=false else @@ -166,7 +175,7 @@ runs: fi fi if has_kind "julia"; then - if [ "$julia_missing" = true ]; then + if [ "$juliaup_missing" = true ] && [ "$julia_missing" = true ]; then needs_julia=true fi fi @@ -228,11 +237,14 @@ runs: if: ${{ steps.detect.outputs.needs_pixi == 'true' }} uses: prefix-dev/setup-pixi@v0 - - name: Setup Julia + # Install juliaup (not a fixed Julia) so Calkit can install and select + # the exact Julia version(s) declared in each environment via + # `juliaup add`. The channel here is just a default bootstrap install. + - name: Setup juliaup if: ${{ steps.detect.outputs.needs_julia == 'true' }} - uses: julia-actions/setup-julia@v2 + uses: julia-actions/install-juliaup@v2 with: - version: "1" + channel: "1" - name: Setup R if: ${{ steps.detect.outputs.needs_r == 'true' }} From 022f9359a0dab0c6a874837d4434aff14f2edcc0 Mon Sep 17 00:00:00 2001 From: Pete Bachant Date: Tue, 2 Jun 2026 20:05:13 -0700 Subject: [PATCH 12/13] Require juliaup --- .github/workflows/test.yml | 2 ++ action.yml | 17 ++++++++--------- 2 files changed, 10 insertions(+), 9 deletions(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 81b5795..9bac880 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -2,6 +2,8 @@ name: Test action on: push: + branches: + - main pull_request: workflow_dispatch: diff --git a/action.yml b/action.yml index 9dc5546..53bcd0e 100644 --- a/action.yml +++ b/action.yml @@ -105,19 +105,18 @@ runs: fi # Calkit manages Julia versions via juliaup (e.g. `juliaup add - # 1.11.7` for a `julia: "1.11.7"` environment). We install juliaup - # only when the runner has no Julia tooling at all; if the user - # already set up julia or juliaup themselves, we leave it alone. + # 1.11.7` for a `julia: "1.11.7"` environment), so juliaup -- not a + # fixed Julia -- is what must be present. We deliberately gate only on + # juliaup and ignore any plain `julia` on the runner: hosted runner + # images ship a pinned Julia that usually won't match the project's + # required version, and without juliaup Calkit can't install the right + # one. Users who manage Julia themselves should install juliaup, which + # this then detects and leaves alone. if command -v juliaup >/dev/null 2>&1; then juliaup_missing=false else juliaup_missing=true fi - if command -v julia >/dev/null 2>&1; then - julia_missing=false - else - julia_missing=true - fi if command -v Rscript >/dev/null 2>&1 || command -v R >/dev/null 2>&1; then r_missing=false @@ -175,7 +174,7 @@ runs: fi fi if has_kind "julia"; then - if [ "$juliaup_missing" = true ] && [ "$julia_missing" = true ]; then + if [ "$juliaup_missing" = true ]; then needs_julia=true fi fi From ee89fc5f8e37dfe000c6719509ac6a5b4c658012 Mon Sep 17 00:00:00 2001 From: Pete Bachant Date: Tue, 2 Jun 2026 20:13:41 -0700 Subject: [PATCH 13/13] Add workflow to rotate tags --- .github/workflows/release.yml | 41 +++++++++++++++++++++++++++++++++++ 1 file changed, 41 insertions(+) create mode 100644 .github/workflows/release.yml diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml new file mode 100644 index 0000000..9c284dd --- /dev/null +++ b/.github/workflows/release.yml @@ -0,0 +1,41 @@ +name: Move floating version tags + +# When a release is published with a full semver tag (e.g. v2.3.1), move the +# floating major (v2) and minor (v2.3) tags to point at it, so consumers +# pinned to `@v2` or `@v2.3` get the new release. Prereleases (e.g. +# v2.4.0-rc.1) are skipped, so you can publish them without shipping to +# everyone on `@v2`. + +on: + release: + types: [published] + +permissions: + contents: write + +jobs: + retag: + runs-on: ubuntu-latest + if: ${{ !github.event.release.prerelease }} + steps: + - uses: actions/checkout@v4 + with: + fetch-depth: 0 + + - name: Update major and minor tags + run: | + set -euo pipefail + TAG="${GITHUB_REF_NAME}" # e.g. v2.3.1 + # Only move tags for plain vMAJOR.MINOR.PATCH releases. + if [[ ! "$TAG" =~ ^v[0-9]+\.[0-9]+\.[0-9]+$ ]]; then + echo "::notice::Tag '$TAG' is not a vX.Y.Z release; not moving floating tags." + exit 0 + fi + MAJOR="${TAG%%.*}" # v2 + MINOR="${TAG%.*}" # v2.3 + git config user.name "github-actions[bot]" + git config user.email "41898282+github-actions[bot]@users.noreply.github.com" + git tag -f "$MAJOR" "$TAG" + git tag -f "$MINOR" "$TAG" + git push -f origin "$MAJOR" "$MINOR" + echo "Moved $MAJOR and $MINOR -> $TAG"