diff --git a/CHANGELOG.md b/CHANGELOG.md index 1554fd7..79587fe 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -32,6 +32,9 @@ executable postinst/postrm scripts that refresh the desktop database. All behavior is opt-in per project key; projects without the keys build byte-identical installers to before. +- Recreate the Windows package destination before copying release payloads, so + build/package/release retries over a dirty `dist/` tree are idempotent and + cannot retain stale files or fail with `copy-file: destination exists`. - Add leaf-loadable distribution identity helpers for the canonical manifest platform, architecture, installer-extension, and UTC RFC 3339 vocabulary. Release tooling and the managed updater now consume the same implementation diff --git a/rivet-cli/package.rkt b/rivet-cli/package.rkt index 1f7162a..2968f2e 100644 --- a/rivet-cli/package.rkt +++ b/rivet-cli/package.rkt @@ -172,6 +172,10 @@ (regexp-match? pattern entry)))) (define (copy-windows-release! stage destination) + ;; Packaging may be retried in the same workspace after a partial publish. + ;; Recreate the target so copy-file never collides with stale payloads and + ;; removed files from the new stage cannot survive in the package. + (remove-path! destination) (make-directory* destination) (for ([entry (in-list (directory-list stage))]) (define source (build-path stage entry)) @@ -197,6 +201,9 @@ (sign-windows-production! (build-path destination "RivetHost.exe") settings)) destination) +(module+ test-support + (provide copy-windows-release!)) + (define (package-linux! project stage name) (define destination (project-path project "dist" (linux-package-directory-name project))) diff --git a/rivet/tests/package-freshness.rkt b/rivet/tests/package-freshness.rkt index 8246b46..0fe997f 100644 --- a/rivet/tests/package-freshness.rkt +++ b/rivet/tests/package-freshness.rkt @@ -7,7 +7,8 @@ (require rackunit racket/file racket/runtime-path - racket/string) + racket/string + (submod "../../rivet-cli/package.rkt" test-support)) (define-runtime-path repository-root "../..") @@ -23,3 +24,37 @@ (check-true (regexp-match? #px"clean-native[?][^)]*fresh-directory! build-dir" build-source) "macOS package builds must recreate the Swift scratch tree") + +;; A retry over a dirty Windows package target must replace the old tree. This +;; is the copy path whose previous create-only behavior raised "destination +;; exists" after a failed or repeated release. +(define temp-root (make-temporary-file "rivet-package-retry-~a" 'directory)) +(dynamic-wind + void + (lambda () + (define stage (build-path temp-root "stage")) + (define destination (build-path temp-root "dist" "Example-windows-x64")) + (make-directory* (build-path stage "res")) + (call-with-output-file (build-path stage "RivetHost.exe") + #:exists 'truncate/replace + (lambda (out) (display "first" out))) + (call-with-output-file (build-path stage "res" "current.txt") + #:exists 'truncate/replace + (lambda (out) (display "current" out))) + (copy-windows-release! stage destination) + + (call-with-output-file (build-path destination "stale.txt") + #:exists 'truncate/replace + (lambda (out) (display "stale" out))) + (call-with-output-file (build-path stage "RivetHost.exe") + #:exists 'truncate/replace + (lambda (out) (display "second" out))) + (copy-windows-release! stage destination) + + (check-equal? (file->string (build-path destination "RivetHost.exe")) + "second") + (check-true (file-exists? (build-path destination "res" "current.txt"))) + (check-false (file-exists? (build-path destination "stale.txt")))) + (lambda () + (when (directory-exists? temp-root) + (delete-directory/files temp-root))))