diff --git a/CHANGELOG.md b/CHANGELOG.md index 0d0531f..21ab98b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,9 @@ - Add an opt-in Linux `InstallShutdownHook` for SIGTERM/SIGINT. Its async-signal-safe self-pipe hands graceful state flushing to a normal watcher thread, while a repeated signal still terminates a stuck shutdown. +- Compile the Racket backend dependency graph with `raco make` before creating + `core.zo`, so a same-length edit to a transitive source module cannot leave + stale bytecode in a successful native build. - Add the Linux tray contract: `rivet::system::TrayIcon` hosts an org.kde.StatusNotifierItem with a com.canonical.dbusmenu menu over the session bus (GDBus), the pairing every desktop watcher serves (GNOME via diff --git a/rivet-cli/build.rkt b/rivet-cli/build.rkt index 879957b..ea1c8b4 100644 --- a/rivet-cli/build.rkt +++ b/rivet-cli/build.rkt @@ -154,14 +154,7 @@ (make-directory* runtime-dir) (define core (build-path res-dir "core.zo")) - (define raco (find-executable-path "raco")) - (run! 'build-project! - raco - "ctool" - "--runtime" (path->string runtime-dir) - "--runtime-access" "runtime" - "--mods" (path->string core) - (path->string backend)) + (compile-backend-module-bundle! backend core runtime-dir) (for ([source (in-list (list (racket-runtime-petite-boot runtime) @@ -172,6 +165,24 @@ (build-path runtime-dir (file-name-from-path source)))) core) +(define (compile-backend-module-bundle! backend core [runtime-dir #f]) + (define raco (find-executable-path "raco")) + ;; `raco ctool --mods` can consume an existing compiled entry module without + ;; refreshing its transitive dependencies. Compile the dependency graph first + ;; so same-length source edits cannot leave stale bytecode in the bundle. + (run! 'build-project! raco "make" (path->string backend)) + (apply run! + 'build-project! + raco + "ctool" + (append + (if runtime-dir + (list "--runtime" (path->string runtime-dir) + "--runtime-access" "runtime") + '()) + (list "--mods" (path->string core) + (path->string backend))))) + (define (prepare-windows-import-library! project runtime lib-exe) (unless lib-exe (error 'build-project! @@ -484,4 +495,5 @@ (provide copy-project-resources! write-app-info! required-project-icon - prepare-windows-icon-resource!)) + prepare-windows-icon-resource! + compile-backend-module-bundle!)) diff --git a/tests/backend-build.rkt b/tests/backend-build.rkt new file mode 100644 index 0000000..2308ac3 --- /dev/null +++ b/tests/backend-build.rkt @@ -0,0 +1,48 @@ +#lang racket/base + +(require rackunit + racket/file + (submod "../rivet-cli/build.rkt" test-support)) + +(define temp-root (make-temporary-file "rivet-backend-build-~a" 'directory)) +(define dependency-before + "#lang racket/base\n(provide value)\n(define value 'before)\n") +(define dependency-after + "#lang racket/base\n(provide value)\n(define value 'after!)\n") + +(define (write-module path text) + (call-with-output-file path + #:exists 'truncate/replace + (lambda (out) (display text out)))) + +(dynamic-wind + void + (lambda () + (define backend (build-path temp-root "backend.rkt")) + (define dependency (build-path temp-root "dependency.rkt")) + (define bundle (build-path temp-root "core.zo")) + (define compiled-dependency + (build-path temp-root "compiled" "dependency_rkt.zo")) + + (write-module backend + "#lang racket/base\n(require \"dependency.rkt\")\n(provide result)\n(define result value)\n") + (check-equal? (string-length dependency-before) + (string-length dependency-after)) + (write-module dependency dependency-before) + (compile-backend-module-bundle! backend bundle) + (define before (file->bytes compiled-dependency)) + + ;; Keep the source length unchanged while making it newer than the cached + ;; bytecode, matching the regression reported by a real Rivet application. + (write-module dependency dependency-after) + (define source-time (file-or-directory-modify-seconds dependency)) + (for ([compiled (in-list + (list compiled-dependency + (build-path temp-root "compiled" "dependency_rkt.dep")))]) + (file-or-directory-modify-seconds compiled (sub1 source-time))) + (compile-backend-module-bundle! backend bundle) + + (check-not-equal? (file->bytes compiled-dependency) before) + (check-true (file-exists? bundle))) + (lambda () + (delete-directory/files temp-root)))