[READ-ONLY] Mirror of https://github.com/CanadaHonk/porffor. An ahead-of-time JavaScript compiler porffor.dev
Something went wrong. Try again.
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352name: CI
on: push: branches: [main]
permissions: contents: write
concurrency: group: release cancel-in-progress: false
jobs: prepare: if: > github.event.head_commit && github.event.head_commit.author.email == 'honk@goose.icu' runs-on: ubuntu-latest outputs: version: ${{ steps.version.outputs.version }} tag: ${{ steps.version.outputs.tag }} previous_tag: ${{ steps.version.outputs.previous_tag }} steps: - uses: actions/checkout@v4 with: fetch-depth: 0 - uses: actions/setup-node@v4 with: node-version: '24' - name: install LLVM 21 run: | wget -q https://apt.llvm.org/llvm.sh chmod +x llvm.sh sudo ./llvm.sh 21 sudo apt-get install -y llvm-21 echo /usr/lib/llvm-21/bin >> "$GITHUB_PATH" - name: determine version id: version run: | node --input-type=module - <<'EOF' import fs from 'node:fs'; import { execFileSync } from 'node:child_process'; const tags = execFileSync('git', [ 'tag', '--list' ], { encoding: 'utf8' }).trim().split('\n'); let latest = 0; let latestPreAlpha = 0; for (const tag of tags) { let match = tag.match(/^alpha-(\d+)$/); if (match) { latest = Math.max(latest, +match[1]); continue; } match = tag.match(/^pre-alpha-(\d+)$/); if (match) latestPreAlpha = Math.max(latestPreAlpha, +match[1]); } const release = `alpha ${latest + 1}`; const sha = process.env.GITHUB_SHA.slice(0, 7); const date = new Date().toISOString().slice(0, 10); const version = `${release} (${sha} ${date})`; const path = 'runtime/index.js'; const src = fs.readFileSync(path, 'utf8'); const replaced = src.replace(/globalThis\.version = '.*?';/, `globalThis.version = '${version}';`); if (replaced === src) throw new Error('version string not found'); fs.writeFileSync(path, replaced); const tag = release.replace(' ', '-'); const previousTag = latest === 0 ? (latestPreAlpha === 0 ? '' : `pre-alpha-${latestPreAlpha}`) : `alpha-${latest}`; fs.appendFileSync(process.env.GITHUB_OUTPUT, `release=${release}\nversion=${version}\ntag=${tag}\nprevious_tag=${previousTag}\n`); EOF - name: bundle selfhost compiler run: node selfhosted/build.mjs - name: compile bundle to c run: ./porf c --compress-data selfhosted/bundle.js -o selfhosted/stage1.c - name: profile selfhost compiler run: | mkdir -p /tmp/porffor-pgo clang -O0 -fuse-ld=lld -fno-sanitize=undefined -w \ -fprofile-instr-generate=/tmp/porffor-pgo/release.profraw \ selfhosted/stage1.c -o /tmp/porffor-pgo/porf -lm /tmp/porffor-pgo/porf c selfhosted/bundle.js -o /tmp/porffor-pgo/stage2.c llvm-profdata merge -o /tmp/porffor-pgo/release.profdata /tmp/porffor-pgo/release.profraw - name: collect build inputs run: | mkdir -p build-inputs cp selfhosted/stage1.c build-inputs/ cp /tmp/porffor-pgo/release.profdata build-inputs/ - name: upload build inputs uses: actions/upload-artifact@v4 with: name: build-inputs path: build-inputs retention-days: 1
# the runner-native build is its own job (not a matrix leg) so test can # start as soon as it finishes instead of waiting for the slowest leg build-host: needs: prepare runs-on: ubuntu-latest name: build linux-x64 steps: - uses: mlugg/setup-zig@v2 with: version: 0.16.0 - name: download build inputs uses: actions/download-artifact@v4 with: name: build-inputs path: build-inputs - name: compile env: NAME: linux-x64 TRIPLE: x86_64-linux-gnu.2.28 run: | echo "$NAME" mkdir -p build out selfhosted cp build-inputs/stage1.c selfhosted/stage1.c zig cc -O3 -flto=thin -fno-sanitize=undefined -w -s -target "$TRIPLE" \ -fprofile-instr-use=build-inputs/release.profdata \ -mllvm -force-attribute=porf_strict_eq:noinline \ selfhosted/stage1.c -o build/porf -lm tar -czf "out/porffor-$NAME.tar.gz" -C build porf - name: upload release archive uses: actions/upload-artifact@v4 with: name: release-linux-x64 path: out/porffor-linux-x64.tar.gz retention-days: 1
build: needs: prepare runs-on: ubuntu-latest strategy: fail-fast: false matrix: include: - name: linux-arm64 triple: aarch64-linux-gnu.2.28 - name: linux-x64-musl triple: x86_64-linux-musl - name: linux-arm64-musl triple: aarch64-linux-musl - name: darwin-x64 triple: x86_64-macos - name: darwin-arm64 triple: aarch64-macos name: build ${{ matrix.name }} steps: - uses: mlugg/setup-zig@v2 with: version: 0.16.0 - name: download build inputs uses: actions/download-artifact@v4 with: name: build-inputs path: build-inputs - name: compile env: NAME: ${{ matrix.name }} TRIPLE: ${{ matrix.triple }} run: | echo "$NAME" mkdir -p build out selfhosted cp build-inputs/stage1.c selfhosted/stage1.c lto=-flto=thin link= case "$TRIPLE" in *-macos) lto=; link=-Wl,-stack_size,0x4000000;; esac zig cc -O3 $lto -fno-sanitize=undefined -w -s -target "$TRIPLE" $link \ -fprofile-instr-use=build-inputs/release.profdata \ -mllvm -force-attribute=porf_strict_eq:noinline \ selfhosted/stage1.c -o build/porf -lm tar -czf "out/porffor-$NAME.tar.gz" -C build porf - name: upload release archive uses: actions/upload-artifact@v4 with: name: release-${{ matrix.name }} path: out/porffor-${{ matrix.name }}.tar.gz retention-days: 1
test: needs: [prepare, build-host] runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - name: checkout test262 uses: actions/checkout@v4 with: repository: tc39/test262 ref: 6eec1ac9ee144dafd8f344d73a21f36bfc9f6755 path: test262/test262 - uses: actions/setup-node@v4 with: node-version: '24' - name: apply release version env: VERSION: ${{ needs.prepare.outputs.version }} run: | node --input-type=module - <<'EOF' import fs from 'node:fs'; const path = 'runtime/index.js'; const pattern = /globalThis\.version = '.*?';/; const src = fs.readFileSync(path, 'utf8'); if (!pattern.test(src)) throw new Error('version string not found'); fs.writeFileSync(path, src.replace(pattern, `globalThis.version = '${process.env.VERSION}';`)); EOF - name: bundle selfhost compiler run: node selfhosted/build.mjs - name: download release compiler uses: actions/download-artifact@v4 with: name: release-linux-x64 path: build/linux-x64 - name: extract release compiler run: tar -xzf build/linux-x64/porffor-linux-x64.tar.gz -C build/linux-x64 - name: verify release version run: test "$(build/linux-x64/porf --version)" = '${{ needs.prepare.outputs.version }}' - name: use release compiler for selfhost tests run: cp build/linux-x64/porf selfhosted/porf - name: verify selfhost compiler run: ./selfhost verify - name: test selfhost test262 harness run: node test262/selfhost.js harness --threads=4 --expect-passes=107
release: needs: [prepare, build-host, build, test] runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 with: fetch-depth: 0 - name: download release archives uses: actions/download-artifact@v4 with: pattern: release-* path: out merge-multiple: true - name: generate release notes env: PREVIOUS_TAG: ${{ needs.prepare.outputs.previous_tag }} run: | if test -n "$PREVIOUS_TAG"; then range="$PREVIOUS_TAG..$GITHUB_SHA" else range="$GITHUB_SHA" fi git log --first-parent --format='- [`%h`](https://github.com/${{ github.repository }}/commit/%H) %s' "$range" > out/release-notes.md - name: create release env: GH_TOKEN: ${{ github.token }} run: | cd out gh release create '${{ needs.prepare.outputs.tag }}' --repo '${{ github.repository }}' --target '${{ github.sha }}' --title '${{ needs.prepare.outputs.version }}' --notes-file release-notes.md *.tar.gz - name: announce release on discord env: DISCORD_WEBHOOK: ${{ secrets.DISCORD_RELEASE_WEBHOOK }} GH_TOKEN: ${{ github.token }} VERSION: ${{ needs.prepare.outputs.version }} TAG: ${{ needs.prepare.outputs.tag }} PREVIOUS_TAG: ${{ needs.prepare.outputs.previous_tag }} run: | if test -z "$DISCORD_WEBHOOK"; then echo 'DISCORD_RELEASE_WEBHOOK not set, skipping announce' exit 0 fi mkdir -p previous-release if test -n "$PREVIOUS_TAG"; then gh release download "$PREVIOUS_TAG" --repo "$GITHUB_REPOSITORY" --pattern '*.tar.gz' --dir previous-release fi node --input-type=module - <<'EOF' import fs from 'node:fs'; import { execFileSync } from 'node:child_process';
const repo = process.env.GITHUB_REPOSITORY; const tag = process.env.TAG;
const commits = fs.readFileSync('out/release-notes.md', 'utf8').trim().split('\n');
let changes = `${commits.length} commit${commits.length === 1 ? '' : 's'}`; if (process.env.PREVIOUS_TAG) { const stat = execFileSync('git', [ 'diff', '--shortstat', `${process.env.PREVIOUS_TAG}..${process.env.GITHUB_SHA}` ], { encoding: 'utf8' }); const files = stat.match(/(\d+) files? changed/); const insertions = stat.match(/(\d+) insertions?\(/); const deletions = stat.match(/(\d+) deletions?\(/); let separator = ', '; if (files) { changes += `, ${files[1]} file${files[1] === '1' ? '' : 's'} changed.`; separator = ' '; } if (insertions) { changes += `${separator}+${(+insertions[1]).toLocaleString('en-US')}`; separator = ', '; } if (deletions) changes += `${separator}-${(+deletions[1]).toLocaleString('en-US')}`; }
let description = changes + '\n\n'; let shown = 0; for (const line of commits) { if (description.length + line.length + 1 > 3500) break; description += line + '\n'; shown++; } if (shown < commits.length) description += `...and ${commits.length - shown} more`;
const formatSize = bytes => bytes < 1024 * 1024 ? `${(bytes / 1024).toFixed(0)} KB` : `${(bytes / (1024 * 1024)).toFixed(2)} MB`; const formatDelta = (current, previous) => { if (previous === undefined) return ''; const difference = current - previous; const sign = difference > 0 ? '+' : difference < 0 ? '-' : ''; const percentage = Math.abs(difference / previous * 100); return ` (${sign}${percentage.toFixed(1)}%)`; }; const binarySize = path => +execFileSync( 'bash', [ '-o', 'pipefail', '-c', 'tar -xOzf "$1" porf | wc -c', 'bash', path ], { encoding: 'utf8' } ).trim(); const sizes = fs.readdirSync('out') .filter(name => name.endsWith('.tar.gz')) .sort() .map(name => { const platform = name.replace(/^porffor-/, '').replace(/\.tar\.gz$/, ''); const archive = `out/${name}`; const previousArchive = `previous-release/${name}`; const downloadBytes = fs.statSync(archive).size; const binaryBytes = binarySize(archive); const hasPrevious = fs.existsSync(previousArchive); const previousDownloadBytes = hasPrevious ? fs.statSync(previousArchive).size : undefined; const previousBinaryBytes = hasPrevious ? binarySize(previousArchive) : undefined; return { name, platform, downloadBytes, binaryBytes, previousDownloadBytes, previousBinaryBytes }; }); const downloads = sizes .map(({ name, platform, downloadBytes, binaryBytes, previousDownloadBytes, previousBinaryBytes }) => `[${platform}](https://github.com/${repo}/releases/download/${tag}/${name}) - ` + `**${formatSize(downloadBytes)}**${formatDelta(downloadBytes, previousDownloadBytes)} / ` + `**${formatSize(binaryBytes)}**${formatDelta(binaryBytes, previousBinaryBytes)}`) .join('\n');
const payload = { embeds: [ { title: process.env.VERSION, url: `https://github.com/${repo}/releases/tag/${tag}`, color: 0xa855f7, description, fields: [ { name: 'Downloads', value: downloads } ], timestamp: new Date().toISOString() } ] }; fs.writeFileSync('payload.json', JSON.stringify(payload)); EOF curl -sS --fail-with-body -H 'Content-Type: application/json' -d @payload.json "$DISCORD_WEBHOOK"