Skip to content

Reduce jl4-service memory: share compilation across exports #175

Reduce jl4-service memory: share compilation across exports

Reduce jl4-service memory: share compilation across exports #175

Workflow file for this run

name: PR Checks
on:
pull_request:
branches: [main]
# Only run one check per PR to save resources
concurrency:
group: pr-checks-${{ github.ref }}
cancel-in-progress: true
jobs:
# ============================================
# Detect which files changed to skip unnecessary jobs
# ============================================
changes:
name: Detect Changes
runs-on: ubuntu-latest
permissions:
pull-requests: read
outputs:
haskell: ${{ steps.filter.outputs.haskell }}
typescript: ${{ steps.filter.outputs.typescript }}
docs: ${{ steps.filter.outputs.docs }}
steps:
- uses: dorny/paths-filter@v3
id: filter
with:
filters: |
haskell:
- '**/*.hs'
- '**/*.cabal'
- 'cabal.project'
- 'cabal.project.local'
- 'jl4-core/libraries/**'
typescript:
- '**/*.ts'
- '**/*.tsx'
- '**/*.js'
- '**/*.svelte'
- 'package.json'
- 'package-lock.json'
- 'ts-apps/**'
- 'ts-shared/**'
docs:
- 'doc/**'
# ============================================
# TypeScript: Lint, Format, Build & Test (combined)
# ============================================
typescript:
name: TypeScript Checks
needs: changes
if: needs.changes.outputs.typescript == 'true'
runs-on: ubuntu-latest
container:
image: ubuntu:24.04
steps:
- name: Update repositories
run: apt update
- name: Install deps
run: apt install -y git sudo software-properties-common curl xvfb
- name: Update git config
run: git config --global --add safe.directory $GITHUB_WORKSPACE
- uses: actions/checkout@v4
- name: Use Node.js
uses: actions/setup-node@v4
with:
cache-dependency-path: package-lock.json
node-version: "20.x"
cache: npm
- run: npm ci
- run: npm run build
- run: npm run lint
- run: npm run format:check
- run: npm run check
- run: xvfb-run -a npm run test
# ============================================
# Haskell: Build & Tests
# ============================================
haskell:
name: Haskell Build & Test
needs: changes
if: needs.changes.outputs.haskell == 'true' || needs.changes.outputs.docs == 'true'
runs-on: ubuntu-latest
container:
image: ubuntu:24.04
env:
LC_ALL: C.UTF-8
LANG: C.UTF-8
steps:
- name: Update repositories
run: apt update
- name: Install deps
run: >
apt install -y
git
sudo
software-properties-common
build-essential
curl
libffi-dev
libffi8
libgmp-dev
libgmp10
libncurses-dev
pkg-config
zlib1g-dev
liblzma-dev
xz-utils
- name: Update git config
run: git config --global --add safe.directory $GITHUB_WORKSPACE
- uses: actions/checkout@v4
- name: Update git submodules
run: git submodule update --init
- name: Set up GHC
uses: haskell-actions/setup@v2
id: setup
with:
ghc-version: "9.10.2"
cabal-version: "latest"
cabal-update: true
# Restore caches BEFORE configure so cabal can plan incrementally
# on top of existing build state rather than starting fresh.
- name: Restore cached build artifacts
uses: actions/cache/restore@v4
id: build-cache
with:
path: dist-newstyle
key: ${{ runner.os }}-ghc-${{ steps.setup.outputs.ghc-version }}-dist-${{ hashFiles('**/*.hs', '**/*.cabal') }}
restore-keys: |
${{ runner.os }}-ghc-${{ steps.setup.outputs.ghc-version }}-dist-
- name: Restore cached dependencies
uses: actions/cache/restore@v4
id: deps-cache
env:
key: ${{ runner.os }}-ghc-${{ steps.setup.outputs.ghc-version }}-cabal-${{ steps.setup.outputs.cabal-version }}
with:
path: ${{ steps.setup.outputs.cabal-store }}
key: ${{ env.key }}-plan-${{ hashFiles('**/*.cabal', 'cabal.project') }}
restore-keys: ${{ env.key }}-
- name: Configure the build
run: |
cabal update
cabal configure --enable-tests --enable-benchmarks --disable-documentation
cabal build all --dry-run
- name: Install dependencies
if: steps.deps-cache.outputs.cache-hit != 'true'
run: cabal build all --only-dependencies
- name: Save cached dependencies
uses: actions/cache/save@v4
if: steps.deps-cache.outputs.cache-hit != 'true'
with:
path: ${{ steps.setup.outputs.cabal-store }}
key: ${{ steps.deps-cache.outputs.cache-primary-key }}
- name: Build
run: cabal build all
- name: Save cached build artifacts
uses: actions/cache/save@v4
if: success()
with:
path: dist-newstyle
key: ${{ runner.os }}-ghc-${{ steps.setup.outputs.ghc-version }}-dist-${{ hashFiles('**/*.hs', '**/*.cabal') }}
- name: Run tests
run: cabal test all
env:
JL4_LIBRARY_PATH: ${{ github.workspace }}/jl4-core/libraries
- name: Run documentation tests
run: ./doc/test-docs.sh
# ============================================
# WASM Build & Test (only when Haskell changes)
# ============================================
wasm-build:
name: WASM Build & Test
needs: changes
if: needs.changes.outputs.haskell == 'true'
runs-on: ubuntu-latest
env:
GHC_WASM_VERSION: "9.10"
steps:
- name: Checkout code
uses: actions/checkout@v4
with:
submodules: true
- name: Install system dependencies
run: |
sudo apt-get update
sudo apt-get install -y curl xz-utils binaryen
- name: Cache GHC WASM toolchain
id: cache-ghc-wasm
uses: actions/cache@v4
with:
path: ~/.ghc-wasm
key: ghc-wasm-${{ env.GHC_WASM_VERSION }}-${{ runner.os }}
- name: Install GHC WASM toolchain
if: steps.cache-ghc-wasm.outputs.cache-hit != 'true'
run: |
curl -L https://gitlab.haskell.org/haskell-wasm/ghc-wasm-meta/-/raw/master/bootstrap.sh | FLAVOUR=${{ env.GHC_WASM_VERSION }} bash
shell: bash
- name: Verify GHC WASM installation
run: |
source ~/.ghc-wasm/env
wasm32-wasi-ghc --version
wasm32-wasi-cabal --version
- name: Cache WASM dependencies
uses: actions/cache@v4
with:
path: |
~/.ghc-wasm/.cabal/store
dist-newstyle
key: wasm-deps-${{ env.GHC_WASM_VERSION }}-${{ hashFiles('cabal-wasm.project', 'jl4-core/jl4-core.cabal', 'jl4-wasm/jl4-wasm.cabal') }}
restore-keys: |
wasm-deps-${{ env.GHC_WASM_VERSION }}-
- name: Build WASM binary
run: |
source ~/.ghc-wasm/env
wasm32-wasi-cabal update
wasm32-wasi-cabal build jl4-wasm --project-file=cabal-wasm.project
- name: Generate JS FFI glue code
run: |
source ~/.ghc-wasm/env
WASM_PATH=$(find dist-newstyle -name 'jl4-wasm.wasm' -path '*/opt/build/*' | head -1)
echo "Found WASM at: $WASM_PATH"
$(wasm32-wasi-ghc --print-libdir)/post-link.mjs -i "$WASM_PATH" -o dist-newstyle/jl4-wasm.mjs
- name: Optimize WASM binary
run: |
WASM_PATH=$(find dist-newstyle -name 'jl4-wasm.wasm' -path '*/opt/build/*' | head -1)
echo "Running wasm-opt with -Oz..."
wasm-opt -Oz "$WASM_PATH" -o "$WASM_PATH.opt"
mv "$WASM_PATH.opt" "$WASM_PATH"
echo "WASM binary size (after wasm-opt):"
ls -lh "$WASM_PATH"
- name: Test WASM binary loads
run: |
source ~/.ghc-wasm/env
WASM_PATH=$(find dist-newstyle -name 'jl4-wasm.wasm' -path '*/opt/build/*' | head -1)
~/.ghc-wasm/nodejs/bin/node -e "
const fs = require('fs');
async function test() {
console.log('Loading WASM module...');
const wasmBuffer = fs.readFileSync('$WASM_PATH');
const module = await WebAssembly.compile(wasmBuffer);
console.log('Module compiled successfully');
console.log('Exports:', WebAssembly.Module.exports(module).map(e => e.name).slice(0, 10), '...');
// Check for our expected exports
const exports = WebAssembly.Module.exports(module).map(e => e.name);
const required = ['l4_check', 'l4_hover', 'l4_completions', 'l4_semantic_tokens', 'l4_eval'];
const missing = required.filter(r => !exports.includes(r));
if (missing.length > 0) {
console.error('Missing exports:', missing);
process.exit(1);
}
console.log('All required exports present!');
}
test().catch(e => { console.error(e); process.exit(1); });
"
- name: Build summary
run: |
WASM_PATH=$(find dist-newstyle -name 'jl4-wasm.wasm' -path '*/opt/build/*' | head -1)
WASM_SIZE=$(stat --format=%s "$WASM_PATH")
WASM_SIZE_MB=$(echo "scale=2; $WASM_SIZE / 1048576" | bc)
echo "## WASM Build Summary" >> $GITHUB_STEP_SUMMARY
echo "" >> $GITHUB_STEP_SUMMARY
echo "- **GHC WASM:** ${{ env.GHC_WASM_VERSION }}" >> $GITHUB_STEP_SUMMARY
echo "- **Binary Size:** ${WASM_SIZE_MB} MB" >> $GITHUB_STEP_SUMMARY
echo "- **Status:** All tests passed" >> $GITHUB_STEP_SUMMARY
# ============================================
# Nix Flake Check
# ============================================
nix-check:
name: Nix Flake Check
runs-on: ubuntu-latest
permissions:
id-token: "write"
contents: "read"
steps:
- uses: actions/checkout@v4
- uses: cachix/install-nix-action@v31
- run: nix flake check
- run: nix -Lv build .#jl4-web