Skip to content

Hot Reload Prebuilt SDK #138

Hot Reload Prebuilt SDK

Hot Reload Prebuilt SDK #138

name: Hot Reload Prebuilt SDK
# Manually-dispatched: builds the F#-hot-reload compiler + the F#-aware SDK on each OS, syncs the
# compiler into the SDK redist, and publishes the runnable SDK as a GitHub Release asset. Lives on
# the fork's default branch so it is dispatchable, but it builds the feature branches (hot-reload-v2
# and fsharp-hotreload-watch-v2) and never touches the review PR.
on:
workflow_dispatch:
inputs:
tag:
description: "Release tag to publish the binaries to"
required: true
default: "hot-reload-preview"
# Auto-refresh: on a schedule, rebuild ONLY if hot-reload-v2 or fsharp-hotreload-watch-v2 moved
# since the last publish. Cheap when nothing changed (just the `check` job runs, ~30s). Tune the
# cadence by editing the cron below.
schedule:
- cron: "0 */3 * * *"
# Instant trigger you can fire from anywhere (e.g. after pushing):
# gh api repos/NatElkins/fsharp/dispatches -f event_type=rebuild-binaries
repository_dispatch:
types: [rebuild-binaries]
permissions:
contents: write
jobs:
# Gate: build only when the source branches actually changed (always builds on manual/dispatch).
check:
runs-on: ubuntu-latest
outputs:
changed: ${{ steps.c.outputs.changed }}
combo: ${{ steps.c.outputs.combo }}
tag: ${{ steps.c.outputs.tag }}
steps:
- id: c
env:
GH_TOKEN: ${{ github.token }}
run: |
tag="${{ github.event.inputs.tag }}"
[ -z "$tag" ] && tag="hot-reload-preview"
fsha=$(git ls-remote https://github.com/NatElkins/fsharp.git hot-reload-v2 | cut -f1)
ssha=$(git ls-remote https://github.com/NatElkins/sdk.git fsharp-hotreload-watch-v2 | cut -f1)
combo="${fsha:0:12}-${ssha:0:12}"
last=$(gh release view "$tag" --repo "$GITHUB_REPOSITORY" --json body --jq '.body' 2>/dev/null \
| grep -oE 'built-from: [a-f0-9-]+' | awk '{print $2}' || true)
echo "tag=$tag" >> "$GITHUB_OUTPUT"
echo "combo=$combo" >> "$GITHUB_OUTPUT"
if [ "${{ github.event_name }}" = "schedule" ] && [ "$combo" = "$last" ]; then
echo "changed=false" >> "$GITHUB_OUTPUT"
echo "No source changes since last publish ($combo); skipping build."
else
echo "changed=true" >> "$GITHUB_OUTPUT"
echo "Building. combo=$combo last=${last:-none} event=${{ github.event_name }}"
fi
build:
needs: check
if: needs.check.outputs.changed == 'true'
strategy:
fail-fast: false
matrix:
include:
- { os: ubuntu-latest, rid: linux-x64 }
- { os: macos-latest, rid: osx-arm64 }
- { os: windows-latest, rid: win-x64 }
runs-on: ${{ matrix.os }}
steps:
- name: Free disk space (Linux)
if: runner.os == 'Linux'
run: |
sudo rm -rf /usr/share/dotnet /usr/local/lib/android /opt/ghc /opt/hostedtoolcache/CodeQL
df -h /
- name: Free disk space (macOS)
if: runner.os == 'macOS'
run: |
sudo rm -rf /Users/runner/hostedtoolcache || true
df -h /
- name: Enable git long paths (Windows)
if: runner.os == 'Windows'
shell: bash
run: git config --system core.longpaths true
- name: Checkout F# compiler (hot-reload-v2)
uses: actions/checkout@v4
with:
repository: NatElkins/fsharp
ref: hot-reload-v2
path: fsharp
- name: Checkout SDK (fsharp-hotreload-watch-v2)
uses: actions/checkout@v4
with:
repository: NatElkins/sdk
ref: fsharp-hotreload-watch-v2
path: sdk
- name: Guard against checked-in toolchain paths
shell: bash
# A dev-machine .dotnet symlink once got committed to hot-reload-v2 and broke the
# toolchain bootstrap on every CI checkout (dangling symlink, so writing
# .dotnet/dotnet-install.sh failed in a way curl reports as a network error).
# Fail fast with a clear message if that ever happens again.
run: |
set -euo pipefail
for repo in fsharp sdk; do
entry="$GITHUB_WORKSPACE/$repo/.dotnet"
if [ -L "$entry" ] || { [ -e "$entry" ] && [ ! -d "$entry" ]; }; then
echo "::error::$repo/.dotnet exists in the checkout as a non-directory (committed symlink or file). Remove it from the branch."
ls -la "$entry"
exit 1
fi
done
- name: Build F# compiler (unix)
if: runner.os != 'Windows'
shell: bash
working-directory: fsharp
run: ./build.sh -c Debug
- name: Build F# compiler (Windows)
if: runner.os == 'Windows'
shell: cmd
working-directory: fsharp
run: build.cmd -c Debug
- name: Prune F# artifacts to save disk (keep only the compiler service)
shell: bash
# The only F# outputs the redist needs are FSharp.Compiler.Service.dll + FSharp.Core.dll
# (synced in below). Drop everything else so the SDK build has room on the smaller runners.
run: |
cd "$GITHUB_WORKSPACE/fsharp/artifacts"
rm -rf obj || true
find bin -mindepth 1 -maxdepth 1 ! -name FSharp.Compiler.Service ! -name FSharp.Core -exec rm -rf {} + || true
df -h / 2>/dev/null | tail -1 || true
# The default build compiles the repo's test projects, several of which do not build on this
# merged branch (MSTEST0071, missing xUnit Fact, etc.). So: (1) run the full build to RESTORE
# the whole repo (its restore phase runs before the build phase, which we let fail at the
# broken tests); (2) then build ONLY the redist project, which compiles no test projects and
# now has its transitive deps restored.
- name: Build SDK redist (unix)
if: runner.os != 'Windows'
shell: bash
working-directory: sdk
run: |
./build.sh -c Debug /p:RunAnalyzers=false || true
./build.sh --build --projects "$(pwd)/src/Layout/redist/redist.csproj" -c Debug /p:RunAnalyzers=false
- name: Build SDK redist (Windows)
if: runner.os == 'Windows'
shell: cmd
working-directory: sdk
# eng/build.ps1 has no -projects switch, so after the full restore (build.cmd, which fails at
# the broken tests) build the redist directly with the bootstrapped dotnet.
run: |
call build.cmd -c Debug /p:RunAnalyzers=false
.dotnet\dotnet build src\Layout\redist\redist.csproj -c Debug /p:RunAnalyzers=false
- name: Sync hot-reload FCS into the redist
shell: bash
run: |
fcs="$GITHUB_WORKSPACE/fsharp/artifacts/bin/FSharp.Compiler.Service/Debug/netstandard2.0"
for d in "$GITHUB_WORKSPACE"/sdk/artifacts/bin/redist/Debug/dotnet/sdk/*/FSharp; do
cp -f "$fcs/FSharp.Compiler.Service.dll" "$d/"
cp -f "$fcs/FSharp.Core.dll" "$d/"
echo "synced FCS -> $d"
done
- name: Package redist
shell: bash
run: tar -czf "fsharp-hotreload-sdk-${{ matrix.rid }}.tar.gz" -C "$GITHUB_WORKSPACE/sdk/artifacts/bin/redist/Debug" dotnet
- name: Publish to GitHub Release
uses: softprops/action-gh-release@v2
with:
tag_name: ${{ needs.check.outputs.tag }}
files: fsharp-hotreload-sdk-${{ matrix.rid }}.tar.gz
finalize:
needs: [check, build]
runs-on: ubuntu-latest
steps:
# The release is refreshed in place on a fixed tag, so the page header keeps the
# original publish date; a visible build timestamp in the body is the freshness signal.
- name: Compute build timestamp
id: stamp
run: echo "date=$(date -u '+%Y-%m-%d %H:%M UTC')" >> "$GITHUB_OUTPUT"
- name: Stamp GitHub Release
uses: softprops/action-gh-release@v2
with:
tag_name: ${{ needs.check.outputs.tag }}
name: F# hot reload preview SDK
body: |
Prebuilt .NET SDK with F# hot reload (the F#-aware `dotnet watch`).
**Last built: ${{ steps.stamp.outputs.date }}** (the binaries below are replaced in place; ignore the release date in the header)
Unpack the archive for your platform and use the `dotnet` inside it:
tar -xzf fsharp-hotreload-sdk-<rid>.tar.gz
export PATH="$PWD/dotnet:$PATH" # Windows: add the dotnet folder to PATH
dotnet watch run # in an F# project that opts in
See [docs/hot-reload-quickstart.md](https://github.com/NatElkins/fsharp/blob/hot-reload-v2/docs/hot-reload-quickstart.md) (step 4 onward) for the opt-in flag and what to try editing.
built-from: ${{ needs.check.outputs.combo }}