Merge pull request #301 from jolt-lang/release/windows-v011

Windows release binaries (x86_64) via MSYS2/MinGW
This commit is contained in:
Dmitri Sotnikov 2026-07-02 18:57:46 +00:00 committed by GitHub
commit 7add315394
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
12 changed files with 117 additions and 28 deletions

View file

@ -12,6 +12,7 @@ on:
push:
tags:
- 'v*'
workflow_dispatch: {} # dry-run the build matrix without tagging
permissions:
contents: write # create/update the GitHub Release and upload assets
@ -26,10 +27,18 @@ jobs:
include:
- os: ubuntu-latest
target: x86_64-linux
shell: bash
# No x86_64-macos: GitHub is retiring the macos-13 Intel runner (jobs
# queue forever). Intel Macs build from source. macos-14 is arm64.
- os: macos-14
target: aarch64-macos
shell: bash
- os: windows-latest
target: x86_64-windows
shell: msys2 {0}
defaults:
run:
shell: ${{ matrix.shell }}
steps:
- uses: actions/checkout@v5
with:
@ -78,6 +87,54 @@ jobs:
if: runner.os == 'macOS'
run: brew install chezscheme lz4
# --- Windows: MSYS2/MinGW-w64 toolchain + Chez built from source (ta6nt).
# The whole job runs in the msys2 shell so cc/xxd/paths behave; the
# produced joltc.exe is a plain Windows binary (no MSYS runtime dep). ---
- name: Set up MSYS2 (Windows)
if: runner.os == 'Windows'
uses: msys2/setup-msys2@v2
with:
msystem: MINGW64
update: false
install: >-
git make vim unzip zip
mingw-w64-x86_64-gcc
mingw-w64-x86_64-lz4
mingw-w64-x86_64-zlib
- name: Cache Chez Scheme (Windows)
if: runner.os == 'Windows'
id: cache-chez-win
uses: actions/cache@v4
with:
path: chez-install
key: chez-${{ runner.os }}-v10.4.1-mingw64
- name: Build Chez Scheme from source (Windows)
if: runner.os == 'Windows' && steps.cache-chez-win.outputs.cache-hit != 'true'
run: |
git clone --depth 1 --branch v10.4.1 https://github.com/cisco/ChezScheme.git /tmp/chez-src
cd /tmp/chez-src
./configure --threads --installprefix="$GITHUB_WORKSPACE/chez-install"
make -j"$(nproc)"
make install
- name: Put chez on PATH (Windows)
if: runner.os == 'Windows'
run: |
printf '#!/bin/sh
exec "%s/chez-install/bin/scheme" "$@"
' "$GITHUB_WORKSPACE" > "$GITHUB_WORKSPACE/chez-install/bin/chez"
chmod +x "$GITHUB_WORKSPACE/chez-install/bin/chez"
echo "$GITHUB_WORKSPACE/chez-install/bin" >> "$GITHUB_PATH"
# cc is the build's compiler name; alias it to mingw gcc
printf '#!/bin/sh
exec gcc "$@"
' > /usr/local/bin/cc || printf '#!/bin/sh
exec gcc "$@"
' > "$GITHUB_WORKSPACE/chez-install/bin/cc"
chmod +x /usr/local/bin/cc 2>/dev/null || chmod +x "$GITHUB_WORKSPACE/chez-install/bin/cc"
- name: Show Chez version
run: chez --version
@ -132,16 +189,24 @@ jobs:
ver="${GITHUB_REF_NAME}"
name="joltc-${ver}-${{ matrix.target }}"
mkdir -p "dist/${name}"
cp target/release/joltc "dist/${name}/joltc"
cp README.md LICENSE "dist/${name}/"
tar -C dist -czf "dist/${name}.tar.gz" "${name}"
( cd dist && shasum -a 256 "${name}.tar.gz" > "${name}.tar.gz.sha256" )
if [ "${{ runner.os }}" = "Windows" ]; then
cp target/release/joltc.exe "dist/${name}/joltc.exe"
( cd dist && zip -r "${name}.zip" "${name}" && sha256sum "${name}.zip" > "${name}.zip.sha256" )
else
cp target/release/joltc "dist/${name}/joltc"
tar -C dist -czf "dist/${name}.tar.gz" "${name}"
( cd dist && shasum -a 256 "${name}.tar.gz" > "${name}.tar.gz.sha256" )
fi
ls -la dist
- name: Upload to the GitHub Release
if: startsWith(github.ref, 'refs/tags/')
uses: softprops/action-gh-release@v2
with:
files: |
dist/*.tar.gz
dist/*.tar.gz.sha256
fail_on_unmatched_files: true
dist/*.zip
dist/*.zip.sha256
fail_on_unmatched_files: false

View file

@ -56,7 +56,11 @@ jobs:
- name: Install JDK + Clojure (certify oracle)
run: |
sudo apt-get install -y default-jdk rlwrap
curl -L -O https://github.com/clojure/brew-install/releases/latest/download/linux-install.sh
# --retry + --fail so a transient CDN error retries instead of handing
# bash an HTML error page (a 2min timeout page flaked a run)
curl --fail --retry 5 --retry-delay 10 --retry-all-errors -L -O \
https://github.com/clojure/brew-install/releases/latest/download/linux-install.sh
head -1 linux-install.sh | grep -q '^#!' || { echo "installer download corrupt"; cat linux-install.sh | head -5; exit 1; }
sudo bash linux-install.sh
clojure --version

View file

@ -245,6 +245,6 @@
;; table so `build` can foreign-entry them to spill the bundled Chez boots. On
;; Linux dlsym can't see executable symbols otherwise (macOS exports them anyway).
(bld-system (string-append
"cc -O2 -rdynamic -I'" bld-csv-dir "' -I'" jb-build "' '" jb-main-c "' '"
"cc -O2 " (if bld-nt? "" "-rdynamic ") "-I'" bld-csv-dir "' -I'" jb-build "' '" jb-main-c "' '"
bld-csv-dir "/libkernel.a' -o '" jb-out "' " (bld-link-libs)))
(display (string-append "build-joltc: wrote " jb-out "\n"))

View file

@ -57,6 +57,7 @@
;; --- toolchain discovery ----------------------------------------------------
(define bld-machine (symbol->string (machine-type)))
(define bld-osx? (bld-contains? bld-machine "osx"))
(define bld-nt? (bld-contains? bld-machine "nt"))
;; The Chez executable, for the isolated compile pass (see build-binary step 4).
(define bld-chez
@ -94,14 +95,19 @@
;; Link flags. macOS Homebrew layout for the kernel's lz4/zlib/ncurses deps.
(define (bld-link-libs)
(if bld-osx?
(let ((lz4 (bld-sh-capture "brew --prefix lz4 2>/dev/null")))
(string-append
(if (> (string-length lz4) 0) (string-append "-L" lz4 "/lib ") "")
"-llz4 -lz -lncurses -framework Foundation -liconv -lm"))
;; Linux: the Chez kernel pulls in compression (lz4/z), the expression
;; editor (ncurses + terminfo), threads, dlopen, libuuid, and clock_gettime.
"-llz4 -lz -lncurses -ltinfo -ldl -lm -lpthread -luuid -lrt"))
(cond
(bld-osx?
(let ((lz4 (bld-sh-capture "brew --prefix lz4 2>/dev/null")))
(string-append
(if (> (string-length lz4) 0) (string-append "-L" lz4 "/lib ") "")
"-llz4 -lz -lncurses -framework Foundation -liconv -lm")))
;; Windows (ta6nt, MinGW-w64 under MSYS2): the Chez kernel pulls in
;; compression, winsock, COM/UUID, and the registry.
(bld-nt?
"-llz4 -lz -lws2_32 -lrpcrt4 -lole32 -ladvapi32 -luser32 -lshell32 -lm")
;; Linux: the Chez kernel pulls in compression (lz4/z), the expression
;; editor (ncurses + terminfo), threads, dlopen, libuuid, and clock_gettime.
(else "-llz4 -lz -lncurses -ltinfo -ldl -lm -lpthread -luuid -lrt")))
;; --- runtime manifest (mirrors host/chez/cli.ss's load order) ---------------
;; A line is either literal Scheme text to inline, or a tag whose emission the build
@ -411,7 +417,15 @@
;; direct-link?: opt-in closed-world direct-linking (app->app calls bind directly,
;; no runtime redefinition). Off by default in every mode — release stays
;; dynamically linked.
(define (bld-suffix? s suf)
(let ((n (string-length s)) (m (string-length suf)))
(and (>= n m) (string=? (substring s (- n m) n) suf))))
(define (build-binary entry-ns out-path mode natives embed-dirs ext-roots direct-link? tree-shake?)
;; Windows executables carry .exe; normalize here so the append-payload and
;; cc paths agree and the shell can run the result.
(let ((out-path (if (and bld-nt? (not (bld-suffix? out-path ".exe")))
(string-append out-path ".exe")
out-path)))
;; The self-contained path (jolt-embedded-bytes "stub/launcher") needs no csv
;; kernel files, no Chez, no cc — only the legacy cc path does.
(unless (jolt-embedded-bytes "stub/launcher") (bld-check-toolchain))
@ -558,7 +572,7 @@
(build-self-contained entry-ns out-path mode builddir flat-ss flat-so boot
(bld-native-link-flags natives))
(build-with-cc entry-ns out-path mode builddir flat-ss flat-so boot boot-h main-c
(bld-native-link-flags natives))))))))
(bld-native-link-flags natives)))))))))
;; --- self-contained link (in-process compile + append the boot to the stub) ---
;; compile-file runs against the DEFAULT interaction environment, so the boot's

View file

@ -11,7 +11,7 @@
;; reset between cases so there is no leakage — same isolation a fresh process gives.
;;
;; chez --script host/chez/run-corpus.ss
;; JOLT_CHEZ_ZJ_FLOOR=N override the regression floor (default 2730)
;; JOLT_CHEZ_ZJ_FLOOR=N override the regression floor (default 3390)
;; JOLT_CORPUS_LIMIT=N every-Nth stride (fast iteration; floor drops to 0)
;; JOLT_DUMP_CRASH_LABELS=1 list crash + allowlisted labels
(import (chezscheme))
@ -196,7 +196,7 @@
;; Regression floor: fail on any NEW divergence or if pass drops below the floor.
(define base-floor (let ((s (getenv "JOLT_CHEZ_ZJ_FLOOR")))
(if s (string->number s) 2730)))
(if s (string->number s) 3390)))
(define floor (if limit 0 base-floor))
(when (or (> (length diverged) 0) (< pass floor))
(printf "REGRESSION: pass ~a < floor ~a or ~a new divergence(s)\n"

View file

@ -361,7 +361,7 @@
(guard (e (#t #f))
(def-var! "clojure.core" "char-name-string" (letrec ((char-name-string (lambda (c) (let fnrec4381 ((c c)) (jolt-get (var-deref "clojure.core" "char-name-strings") c))))) char-name-string)))
(guard (e (#t #f))
(def-var! "clojure.core" "rand-nth" (letrec ((rand-nth (lambda (coll) (let fnrec4382 ((coll coll)) (let* ((v (jolt-invoke (var-deref "clojure.core" "vec") coll))) (jolt-nth v (jolt-invoke (var-deref "clojure.core" "rand-int") (jolt-count v)))))))) rand-nth)))
(def-var! "clojure.core" "rand-nth" (letrec ((rand-nth (lambda (coll) (let fnrec4382 ((coll coll)) (jolt-nth coll (jolt-invoke (var-deref "clojure.core" "rand-int") (jolt-count coll))))))) rand-nth)))
(guard (e (#t #f))
(def-var! "clojure.core" "random-sample" (letrec ((random-sample (case-lambda ((prob) (let fnrec4383 ((prob prob)) (jolt-invoke jolt-filter (lambda (_) (let fnrec4384 ((_ _)) (jolt-n< (jolt-invoke (var-deref "clojure.core" "rand")) prob)))))) ((prob coll) (let fnrec4385 ((prob prob) (coll coll)) (jolt-filter (lambda (_) (let fnrec4386 ((_ _)) (jolt-n< (jolt-invoke (var-deref "clojure.core" "rand")) prob))) coll)))))) random-sample)))
(guard (e (#t #f))

View file

@ -26,6 +26,12 @@ static int self_path(char *buf, uint32_t size) {
/* _NSGetExecutablePath fills buf and reports the needed size on overflow. */
return _NSGetExecutablePath(buf, &size);
}
#elif defined(_WIN32)
#include <windows.h>
static int self_path(char *buf, uint32_t size) {
DWORD n = GetModuleFileNameA(NULL, buf, size);
return (n == 0 || n >= size) ? -1 : 0;
}
#else
#include <unistd.h>
static int self_path(char *buf, uint32_t size) {

View file

@ -66,9 +66,10 @@
\backspace "backspace" \space "space"})
(defn char-name-string [c] (get char-name-strings c))
;; Random selection over the host rand primitives.
;; Random selection over the host rand primitives — the reference shape:
;; nth directly (nil returns nil via RT.nth; a set throws like the JVM).
(defn rand-nth [coll]
(let [v (vec coll)] (nth v (rand-int (count v)))))
(nth coll (rand-int (count coll))))
(defn random-sample
([prob] (filter (fn [_] (< (rand) prob))))

View file

@ -7,7 +7,7 @@ test` from the repo root.
## The spec corpus
`corpus.edn` is the contract: ~2920 rows `{:suite :label :expected :actual}`, with
`corpus.edn` is the contract: ~3570 rows `{:suite :label :expected :actual :portability}`, with
`:expected` sourced from reference JVM Clojure by `test/conformance/regen-corpus.clj`.
It is frozen (the canonical source) — add or change cases here, then re-source the
answers with `regen-corpus.clj` and re-certify with `test/conformance/certify.clj`.
@ -22,7 +22,7 @@ answers with `regen-corpus.clj` and re-certify with `test/conformance/certify.cl
chez --script host/chez/run-corpus.ss
JOLT_CORPUS_LIMIT=200 … # every-Nth stride, fast iteration
JOLT_CHEZ_ZJ_FLOOR=N … # override the floor (default 2678)
JOLT_CHEZ_ZJ_FLOOR=N … # override the floor (see run-corpus.ss)
- `run-unit.ss` — host-specific unit cases (`test/chez/unit.edn`) that aren't in the
JVM-portable corpus: dot-forms, java statics, io, reader, walk, vars/namespaces,

View file

@ -3583,4 +3583,5 @@
{:suite "contracts / misc" :label "vector invocation has nth semantics; run! honors reduced; eval self-evaluates values" :expected "[:ioob 1 true]" :actual "[(try ([1 2] 5) (catch IndexOutOfBoundsException _ :ioob)) (let [c (atom 0)] (run! (fn [x] (swap! c inc) (reduced x)) [1 2 3]) (deref c)) (fn? (eval +))]" :portability :common}
{:suite "contracts / misc" :label "intern demands an existing ns; counted? excludes strings; seqable? includes arrays" :expected "[:t false true]" :actual "[(try (intern (quote no-such-ns-xyz) (quote v) 1) (catch Throwable _ :t)) (counted? \"s\") (seqable? (object-array 2))]" :portability :jvm}
{:suite "contracts / misc" :label "nth of nil honors notFound; a nil index throws; the starred specials are special" :expected "[:default :npe [true true true true true]]" :actual "[(nth nil 5 :default) (try (nth [1] nil) (catch NullPointerException _ :npe)) [(special-symbol? (quote case*)) (special-symbol? (quote deftype*)) (special-symbol? (quote letfn*)) (special-symbol? (quote reify*)) (special-symbol? (quote &))]]" :portability :common}
{:suite "contracts / misc" :label "rand-nth of nil is nil; of a set throws" :expected "[nil :t]" :actual "[(rand-nth nil) (try (rand-nth #{1 2}) (catch Throwable _ :t))]" :portability :common}
]

View file

@ -23,7 +23,6 @@ clojure.core-test.plus 11 0
clojure.core-test.plus-squote 11 0
clojure.core-test.pos-int-qmark 1 0
clojure.core-test.quot 25 0
clojure.core-test.rand-nth 0 1
clojure.core-test.realized-qmark 1 0
clojure.core-test.rem 16 0
clojure.core-test.remove-watch 0 1

View file

@ -10,7 +10,7 @@ read one data file, run each case, compare, report.
| File | Role | Generated by |
|------|------|--------------|
| `test/chez/corpus.edn` | **The spec.** ~2900 cases of `{:suite :label :expected :actual}`, `:expected` **sourced from reference JVM Clojure**. | `test/conformance/regen-corpus.clj` |
| `test/chez/corpus.edn` | **The spec.** ~3570 cases of `{:suite :label :expected :actual :portability}`, `:expected` **sourced from reference JVM Clojure**. | `test/conformance/regen-corpus.clj` |
| `test/conformance/profile.edn` | Per-case **feature classification** — which non-portable cases need which host capability. | `certify.clj --profile` |
| `test/conformance/known-divergences.edn` | The few rows whose JVM value is an opaque host object that can't round-trip to readable source (Java arrays/transients/atoms/beans/proxies print as `#object[..@addr]`), so the corpus keeps jolt's value. | `regen-corpus.clj` leftovers, hand-checked |
| `test/conformance/regen-corpus.clj` | Sources every `:expected` from reference **JVM Clojure** in one process. | — |
@ -54,8 +54,8 @@ the canonical, frozen contract**: it is what every runtime consumes, what
Historically every `:expected` was hand-written. `certify.clj` removes that
weakness: it evaluates every `:actual` (and `:expected`) on **JVM Clojure** in a
fresh `user` namespace and checks jolt's `:expected` against what real Clojure
produces. Of ~2740 vanilla-certifiable rows, **>2730 match reference Clojure
exactly**. The rest are classified (see below) — none are silently wrong.
produces. Of ~3400 vanilla-certifiable rows, **all but ~26 allowlisted rows
match reference Clojure exactly**. The rest are classified (see below) — none are silently wrong.
```sh
clojure -M test/conformance/certify.clj # gate
@ -243,8 +243,7 @@ documented model divergence — nothing in the baseline is an unexplained bug:
- **vec of an array copies**: the reference ADOPTS an Object array (mutating
the array mutates the vector); jolt copies — immutable semantics win over
the implementation leak (`vec` namespace, one row).
- **eval of JVM shapes** (`rand-nth`/`eval` residue): rows needing JVM-only
evaluation shapes (e.g. evaluating a Java array literal).
A future change that makes any of these rows pass will fail the cts gate as
STALE, forcing this section and the baseline to be updated together.