mirror of
https://github.com/logos-co/logos-tutorial.git
synced 2026-08-27 19:01:11 +00:00
Replaces the per-repo installer + cachix pair with the shared action, which
installs Nix with the Logos Attic cache (cache.nix.logos.co) preconfigured and
publishes what the job builds — master to the public cache, every other ref to
ci.
Each converted job also gains
environment: ${{ github.ref == 'refs/heads/master' && 'public-cache' || '' }}
because ATTIC_TOKEN_PUBLIC only exists inside that environment. Without it the
secret resolves empty on master and publishing is silently skipped — the job
still passes, so the omission would not surface as a failure.
The action installs Nix on every runner, macOS included. Sibling repos used
DeterminateSystems' installer there because cachix/install-nix-action collided
with the runner's pre-existing _nixbld users (eDSRecordAlreadyExists); that no
longer reproduces — logos-delivery-module converted the plain way and its
macos-latest leg passes — so the second installer is not carried over.
One property is deliberately not carried over: the old cachix step ran with
`continue-on-error: true` so a failed cache push could not fail a job whose
tests passed. The action exposes no equivalent, and adding one would also
swallow genuine setup failures now that the same step installs Nix rather than
only publishing at the end.
Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
214 lines
9.1 KiB
YAML
214 lines
9.1 KiB
YAML
name: Tutorial Tests
|
|
|
|
# ──────────────────────────────────────────────────────────────────────────────
|
|
# One-time setup required for the clickable report links to work:
|
|
#
|
|
# 1. Repo Settings → Pages → "Build and deployment" → Source: "Deploy from a
|
|
# branch", Branch: `gh-pages` / `(root)`. (The publish-report job creates
|
|
# the gh-pages branch on its first run.)
|
|
# 2. Nothing else — GITHUB_TOKEN already has the permissions granted below.
|
|
#
|
|
# Each run publishes the two-column HTML report to:
|
|
# https://<owner>.github.io/<repo>/pr-<N>/<os>/ (pull requests)
|
|
# https://<owner>.github.io/<repo>/main/<os>/ (pushes to main/master)
|
|
# and (for PRs) posts/updates a comment with the links.
|
|
#
|
|
# Note: pull requests opened from forks get a read-only GITHUB_TOKEN, so the
|
|
# Pages push and PR comment are skipped for them — the downloadable artifact is
|
|
# still produced. PRs from branches in this repo get the full clickable links.
|
|
# ──────────────────────────────────────────────────────────────────────────────
|
|
|
|
on:
|
|
pull_request:
|
|
branches: [master, main]
|
|
push:
|
|
branches: [master, main]
|
|
|
|
concurrency:
|
|
group: ci-${{ github.ref }}
|
|
cancel-in-progress: true
|
|
|
|
jobs:
|
|
tutorial-tests:
|
|
name: Tutorial Tests (${{ matrix.os }})
|
|
strategy:
|
|
fail-fast: false
|
|
matrix:
|
|
os: [ubuntu-latest, macos-latest]
|
|
# ATTIC_TOKEN_PUBLIC only exists in the public-cache environment; master
|
|
# jobs must opt into it to publish to the public cache.
|
|
environment: ${{ github.ref == 'refs/heads/master' && 'public-cache' || '' }}
|
|
|
|
runs-on: ${{ matrix.os }}
|
|
timeout-minutes: 90
|
|
|
|
steps:
|
|
- name: Checkout
|
|
uses: actions/checkout@v4
|
|
# The runner is the shared `doctest` CLI, invoked directly via its flake
|
|
|
|
- name: Setup Nix and Logos cache
|
|
uses: logos-co/setup-nix-cache-action@v1
|
|
with:
|
|
attic-token-ci: ${{ secrets.ATTIC_TOKEN_CI }}
|
|
attic-token-public: ${{ secrets.ATTIC_TOKEN_PUBLIC }}
|
|
# (github:logos-co/logos-doctest). The flake bundles Python + PyYAML
|
|
# (+ rich), so no pip install step is needed.
|
|
- name: Test - UI chain + Composing Modules + Dependency Interfaces (C Library tutorial pulled in via requires)
|
|
run: |
|
|
# Three leaves, run back-to-back into one report:
|
|
# - tutorial-cpp-ui-app: the Part 1 -> 2 -> 3 chain (requires:)
|
|
# - tutorial-composing-modules: the calc_aggregator core module
|
|
# (requires Part 1 only)
|
|
# - tutorial-interface-dependencies: the calc_via_interface module
|
|
# binding a calculator interface at runtime (requires Part 1 only)
|
|
# --continue-on-fail so the run walks every step and the published
|
|
# report is complete. The job still fails (non-zero exit) if any step
|
|
# failed; this only changes whether we stop early.
|
|
nix run github:logos-co/logos-doctest -- run \
|
|
tests/tutorial-cpp-ui-app.test.yaml \
|
|
tests/tutorial-composing-modules.test.yaml \
|
|
tests/tutorial-interface-dependencies.test.yaml \
|
|
--verbose \
|
|
--continue-on-fail \
|
|
--report "${{ runner.temp }}/tutorial-report.html"
|
|
|
|
- name: Stage report for upload
|
|
if: always()
|
|
shell: bash
|
|
run: |
|
|
mkdir -p report-out
|
|
# Name it index.html so the published directory URL renders directly.
|
|
if [ -f "${{ runner.temp }}/tutorial-report.html" ]; then
|
|
cp "${{ runner.temp }}/tutorial-report.html" report-out/index.html
|
|
else
|
|
echo "<h1>No report produced</h1>" > report-out/index.html
|
|
fi
|
|
|
|
- name: Upload tutorial execution report
|
|
if: always()
|
|
uses: actions/upload-artifact@v4
|
|
with:
|
|
name: tutorial-report-${{ matrix.os }}
|
|
path: report-out/index.html
|
|
if-no-files-found: warn
|
|
|
|
- name: Verify markdown generation
|
|
run: |
|
|
nix run github:logos-co/logos-doctest -- generate \
|
|
tests/tutorial-wrapping-c-library.test.yaml \
|
|
-o /tmp/gen-part1.md
|
|
nix run github:logos-co/logos-doctest -- generate \
|
|
tests/tutorial-qml-ui-app.test.yaml \
|
|
-o /tmp/gen-part2.md
|
|
nix run github:logos-co/logos-doctest -- generate \
|
|
tests/tutorial-composing-modules.test.yaml \
|
|
-o /tmp/gen-composing.md
|
|
nix run github:logos-co/logos-doctest -- generate \
|
|
tests/tutorial-interface-dependencies.test.yaml \
|
|
-o /tmp/gen-interface-deps.md
|
|
echo "Generated markdown successfully"
|
|
|
|
publish-report:
|
|
name: Publish report to GitHub Pages
|
|
needs: tutorial-tests
|
|
# Run even when tests fail — a failing run is exactly when you want to open
|
|
# the report. Skip on forks, where GITHUB_TOKEN can't push or comment.
|
|
if: ${{ always() && github.event.pull_request.head.repo.fork != true }}
|
|
runs-on: ubuntu-latest
|
|
|
|
permissions:
|
|
contents: write # push to the gh-pages branch
|
|
pull-requests: write # post/update the PR comment
|
|
|
|
# Serialize Pages pushes so two refs can't race on the gh-pages branch.
|
|
concurrency:
|
|
group: gh-pages-publish
|
|
cancel-in-progress: false
|
|
|
|
steps:
|
|
- name: Download all reports
|
|
uses: actions/download-artifact@v4
|
|
with:
|
|
path: artifacts
|
|
# No `name:` → downloads every artifact into artifacts/<name>/...
|
|
|
|
- name: Arrange site directory
|
|
id: arrange
|
|
shell: bash
|
|
run: |
|
|
set -euo pipefail
|
|
if [ "${{ github.event_name }}" = "pull_request" ]; then
|
|
BASE="pr-${{ github.event.pull_request.number }}"
|
|
else
|
|
BASE="main"
|
|
fi
|
|
echo "base=$BASE" >> "$GITHUB_OUTPUT"
|
|
|
|
mkdir -p "site/$BASE"
|
|
found=""
|
|
for os in ubuntu-latest macos-latest; do
|
|
src="artifacts/tutorial-report-$os/index.html"
|
|
if [ -f "$src" ]; then
|
|
mkdir -p "site/$BASE/$os"
|
|
cp "$src" "site/$BASE/$os/index.html"
|
|
found="$found $os"
|
|
fi
|
|
done
|
|
echo "found=$found" >> "$GITHUB_OUTPUT"
|
|
|
|
# Landing page for this ref linking to each OS report.
|
|
{
|
|
echo "<!doctype html><meta charset=utf-8>"
|
|
echo "<title>Tutorial reports — $BASE</title>"
|
|
echo "<style>body{font:16px system-ui;margin:40px;max-width:640px}a{color:#2563eb}</style>"
|
|
echo "<h1>Tutorial execution reports</h1>"
|
|
echo "<p><strong>$BASE</strong> · commit <code>${GITHUB_SHA::7}</code></p><ul>"
|
|
for os in ubuntu-latest macos-latest; do
|
|
if [ -d "site/$BASE/$os" ]; then
|
|
echo "<li><a href=\"./$os/\">$os</a></li>"
|
|
fi
|
|
done
|
|
echo "</ul>"
|
|
} > "site/$BASE/index.html"
|
|
|
|
- name: Deploy to gh-pages
|
|
if: steps.arrange.outputs.found != ''
|
|
uses: peaceiris/actions-gh-pages@v4
|
|
with:
|
|
github_token: ${{ secrets.GITHUB_TOKEN }}
|
|
publish_dir: ./site
|
|
keep_files: true # don't wipe other PRs' directories
|
|
commit_message: "Publish tutorial report for ${{ steps.arrange.outputs.base }} (${{ github.sha }})"
|
|
|
|
- name: Comment on PR with report links
|
|
if: ${{ github.event_name == 'pull_request' && steps.arrange.outputs.found != '' }}
|
|
uses: actions/github-script@v7
|
|
with:
|
|
script: |
|
|
const base = "${{ steps.arrange.outputs.base }}";
|
|
const owner = context.repo.owner;
|
|
const repo = context.repo.repo;
|
|
const root = `https://${owner}.github.io/${repo}/${base}`;
|
|
const oses = "${{ steps.arrange.outputs.found }}".trim().split(/\s+/).filter(Boolean);
|
|
|
|
const links = oses.map(os => `- [\`${os}\` report](${root}/${os}/)`).join("\n");
|
|
const marker = "<!-- tutorial-report-links -->";
|
|
const body =
|
|
`${marker}\n` +
|
|
`### 📊 Tutorial execution report\n\n` +
|
|
`Rendered tutorial alongside the commands actually run and their output ` +
|
|
`(updated each run, commit \`${context.sha.slice(0,7)}\`):\n\n` +
|
|
`${links}\n\n` +
|
|
`_Pages can take a minute to update after the run finishes._`;
|
|
|
|
const { data: comments } = await github.rest.issues.listComments({
|
|
owner, repo, issue_number: context.issue.number, per_page: 100,
|
|
});
|
|
const existing = comments.find(c => c.body && c.body.includes(marker));
|
|
if (existing) {
|
|
await github.rest.issues.updateComment({ owner, repo, comment_id: existing.id, body });
|
|
} else {
|
|
await github.rest.issues.createComment({ owner, repo, issue_number: context.issue.number, body });
|
|
}
|