Skip to content

Commit 36a1ad5

Browse files
kiennqCopilot
authored andcommitted
Decouple module downloads from package version
Allow native module downloads to target the minimum supported release or an explicit release tag without depending on the package version. Keep the GitHub release URL customizable for forked release hosting. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
1 parent cee2601 commit 36a1ad5

3 files changed

Lines changed: 171 additions & 47 deletions

File tree

README.md

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -80,7 +80,8 @@ pre-built binary** or **compile from source** (controlled by
8080
`ghostel-module-auto-install`, default `ask`). You can also trigger these
8181
manually:
8282

83-
- `M-x ghostel-download-module` — download a pre-built binary from GitHub releases
83+
- `M-x ghostel-download-module` — download the minimum supported pre-built binary
84+
- `C-u M-x ghostel-download-module` — choose a specific release tag (leave blank for latest)
8485
- `M-x ghostel-module-compile` — build from source via `zig build`
8586

8687
## Building from source
@@ -116,10 +117,10 @@ zig build -Doptimize=ReleaseFast
116117

117118
When installed from MELPA, `M-x ghostel-module-compile` builds the native
118119
module from source using `zig build`. Zig's package manager fetches the
119-
ghostty dependency automatically — no git submodule needed.
120+
ghostty dependency automatically.
120121

121-
Alternatively, download a **pre-built binary** via
122-
`M-x ghostel-download-module`.
122+
Alternatively, download a **pre-built binary** via `M-x ghostel-download-module`
123+
(or `C-u M-x ghostel-download-module` to pick a specific release).
123124

124125
## Shell Integration
125126

ghostel.el

Lines changed: 43 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -374,9 +374,11 @@ before sending the input."
374374
ghostel-color-bright-white]
375375
"Color palette for the terminal (vector of 16 face names).")
376376

377-
(defvar ghostel-github-release-url
377+
(defcustom ghostel-github-release-url
378378
"https://github.com/dakra/ghostel/releases"
379-
"Base URL for ghostel GitHub releases.")
379+
"Base URL for Ghostel GitHub releases.
380+
Customize this when downloading pre-built modules from a fork or mirror."
381+
:type 'string)
380382

381383
(defconst ghostel--minimum-module-version "0.13.0"
382384
"Minimum native module version required by this Elisp version.
@@ -423,22 +425,26 @@ Returns nil if the platform is not recognized."
423425
(when tag
424426
(format "ghostel-module-%s%s" tag module-file-suffix))))
425427

426-
(defun ghostel--module-download-url ()
427-
"Return the download URL for the current platform's pre-built module."
428+
(defun ghostel--module-download-url (&optional version)
429+
"Return the download URL for the current platform's pre-built module.
430+
When VERSION is nil, use the latest release download URL."
428431
(let ((asset-name (ghostel--module-asset-name)))
429432
(when asset-name
430-
(let ((version (ghostel--package-version)))
431-
(if version
432-
(format "%s/download/v%s/%s"
433-
ghostel-github-release-url version asset-name)
434-
(format "%s/latest/download/%s"
435-
ghostel-github-release-url asset-name))))))
436-
437-
(defun ghostel--download-module (dir)
433+
(if version
434+
(format "%s/download/v%s/%s"
435+
ghostel-github-release-url version asset-name)
436+
(format "%s/latest/download/%s"
437+
ghostel-github-release-url asset-name)))))
438+
439+
(defun ghostel--download-module (dir &optional version latest-release)
438440
"Download a pre-built module into DIR.
441+
When VERSION is non-nil, download that release tag.
442+
When LATEST-RELEASE is non-nil, use the latest release asset URL.
439443
Returns non-nil on success."
440444
(condition-case err
441-
(let ((url (ghostel--module-download-url)))
445+
(let* ((requested-version (unless latest-release
446+
(or version ghostel--minimum-module-version)))
447+
(url (ghostel--module-download-url requested-version)))
442448
(when url
443449
(unless (string-prefix-p "https://" url)
444450
(error "Refusing non-HTTPS download URL: %s" url))
@@ -484,16 +490,28 @@ Behavior is controlled by `ghostel-module-auto-install'."
484490
('compile (ghostel--compile-module dir))
485491
(_ nil))))
486492

493+
(defun ghostel--read-module-download-version ()
494+
"Prompt for a release tag to download, or nil for the latest release."
495+
(let ((version (read-string
496+
(format "Ghostel module version (>= %s, empty for latest): "
497+
ghostel--minimum-module-version))))
498+
(unless (string= version "")
499+
(when (version< version ghostel--minimum-module-version)
500+
(user-error "Version %s is older than minimum supported version %s"
501+
version ghostel--minimum-module-version))
502+
version)))
503+
487504
(defun ghostel--ask-install-action (_dir)
488505
"Prompt the user to choose how to install the missing native module.
489506
Returns \\='download, \\='compile, or nil."
490-
(let* ((url (or (ghostel--module-download-url) "GitHub releases"))
507+
(let* ((url (or (ghostel--module-download-url ghostel--minimum-module-version)
508+
"GitHub releases"))
491509
(choice (read-char-choice
492510
(format "Ghostel native module not found.
493511
494512
[d] Download pre-built binary from:
495513
%s
496-
[c] Compile from source (requires Zig)
514+
[c] Compile from source via build.sh
497515
[s] Skip — install manually later
498516
499517
Choice: " url)
@@ -503,19 +521,6 @@ Choice: " url)
503521
(?c 'compile)
504522
(?s nil))))
505523

506-
(defun ghostel--package-version ()
507-
"Return ghostel release version string, or nil.
508-
Reads the Version header from ghostel.el so the download URL
509-
matches the GitHub release tag even when MELPA rewrites the
510-
version to a date-based string."
511-
(require 'lisp-mnt nil t)
512-
(when (fboundp 'lm-header)
513-
(let ((lib (or load-file-name (locate-library "ghostel.el" t))))
514-
(when lib
515-
(with-temp-buffer
516-
(insert-file-contents lib nil 0 1024)
517-
(lm-header "Version"))))))
518-
519524
(defun ghostel--download-file (url dest)
520525
"Download URL to DEST. Return non-nil on success."
521526
(condition-case nil
@@ -539,18 +544,23 @@ version to a date-based string."
539544
(kill-buffer buf))))))
540545
(error nil)))
541546

542-
(defun ghostel-download-module ()
543-
"Interactively download the pre-built native module for this platform."
544-
(interactive)
547+
(defun ghostel-download-module (&optional prompt-for-version)
548+
"Interactively download the pre-built native module for this platform.
549+
With PROMPT-FOR-VERSION, prompt for a release tag to download.
550+
Leaving the prompt empty downloads the latest release."
551+
(interactive "P")
545552
(let* ((dir (file-name-directory (or load-file-name
546553
(locate-library "ghostel")
547554
buffer-file-name)))
548555
(mod (expand-file-name
549-
(concat "ghostel-module" module-file-suffix) dir)))
556+
(concat "ghostel-module" module-file-suffix) dir))
557+
(version (when prompt-for-version
558+
(ghostel--read-module-download-version)))
559+
(latest-release (and prompt-for-version (null version))))
550560
(when (and (file-exists-p mod)
551561
(not (yes-or-no-p "Module already exists. Re-download? ")))
552562
(user-error "Cancelled"))
553-
(if (ghostel--download-module dir)
563+
(if (ghostel--download-module dir version latest-release)
554564
(progn
555565
(module-load mod)
556566
(message "ghostel: module loaded successfully"))
@@ -601,7 +611,6 @@ DIR is the module directory."
601611
(concat "Native module not found: " mod
602612
"\nRun M-x ghostel-download-module or M-x ghostel-module-compile")))))
603613

604-
605614
;;; Internal variables
606615

607616
(defvar-local ghostel--term nil

test/ghostel-test.el

Lines changed: 123 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1970,14 +1970,123 @@ buffer and hand nil to the native module."
19701970
;; -----------------------------------------------------------------------
19711971

19721972
;; -----------------------------------------------------------------------
1973-
;; Test: module version check
1974-
;; -----------------------------------------------------------------------
1975-
1976-
(ert-deftest ghostel-test-package-version ()
1977-
"Test that `ghostel--package-version' returns a version string."
1978-
(let ((ver (ghostel--package-version)))
1979-
(should (stringp ver))
1980-
(should (string-match-p "^[0-9]+\\.[0-9]+\\.[0-9]+" ver))))
1973+
;; Test: module download version selection
1974+
;; -----------------------------------------------------------------------
1975+
1976+
(ert-deftest ghostel-test-module-download-url-uses-requested-version ()
1977+
"Requested download versions are decoupled from the package version."
1978+
(let ((ghostel-github-release-url "https://example.invalid/releases"))
1979+
(cl-letf (((symbol-function 'ghostel--module-asset-name)
1980+
(lambda () "ghostel-module-x86_64-linux.so")))
1981+
(should (equal "https://example.invalid/releases/download/v0.7.1/ghostel-module-x86_64-linux.so"
1982+
(ghostel--module-download-url "0.7.1"))))))
1983+
1984+
(ert-deftest ghostel-test-module-download-url-uses-latest-release ()
1985+
"A nil download version uses the latest release asset."
1986+
(let ((ghostel-github-release-url "https://example.invalid/releases"))
1987+
(cl-letf (((symbol-function 'ghostel--module-asset-name)
1988+
(lambda () "ghostel-module-x86_64-linux.so")))
1989+
(should (equal "https://example.invalid/releases/latest/download/ghostel-module-x86_64-linux.so"
1990+
(ghostel--module-download-url nil))))))
1991+
1992+
(ert-deftest ghostel-test-download-module-defaults-to-minimum-version ()
1993+
"Automatic downloads pin to the minimum supported native module version."
1994+
(let ((ghostel--minimum-module-version "0.7.1")
1995+
(captured-version :unset)
1996+
(download-dest nil))
1997+
(cl-letf (((symbol-function 'ghostel--module-download-url)
1998+
(lambda (&optional version)
1999+
(setq captured-version version)
2000+
"https://example.invalid/releases/download/v0.7.1/ghostel-module-x86_64-linux.so"))
2001+
((symbol-function 'ghostel--download-file)
2002+
(lambda (_url dest)
2003+
(setq download-dest dest)
2004+
t))
2005+
((symbol-function 'message)
2006+
(lambda (&rest _))))
2007+
(should (ghostel--download-module "C:/ghostel/"))
2008+
(should (equal "0.7.1" captured-version))
2009+
(should (equal (downcase (expand-file-name
2010+
(concat "ghostel-module" module-file-suffix)
2011+
"C:/ghostel/"))
2012+
(downcase download-dest))))))
2013+
2014+
(ert-deftest ghostel-test-download-module-prefix-uses-requested-version ()
2015+
"Prefix downloads pass the requested release version through unchanged."
2016+
(let ((ghostel--minimum-module-version "0.7.1")
2017+
(captured-version :unset)
2018+
(captured-latest nil)
2019+
(loaded-module nil))
2020+
(let ((comp-enable-subr-trampolines nil)
2021+
(native-comp-enable-subr-trampolines nil))
2022+
(cl-letf (((symbol-function 'locate-library)
2023+
(lambda (_) "C:/ghostel/ghostel.el"))
2024+
((symbol-function 'file-exists-p)
2025+
(lambda (_) nil))
2026+
((symbol-function 'read-string)
2027+
(lambda (&rest _) "0.8.0"))
2028+
((symbol-function 'ghostel--download-module)
2029+
(lambda (_dir &optional version latest-release)
2030+
(setq captured-version version
2031+
captured-latest latest-release)
2032+
t))
2033+
((symbol-function 'module-load)
2034+
(lambda (path)
2035+
(setq loaded-module path)))
2036+
((symbol-function 'message)
2037+
(lambda (&rest _))))
2038+
(ghostel-download-module '(4))
2039+
(should (equal "0.8.0" captured-version))
2040+
(should-not captured-latest)
2041+
(should (equal (downcase (expand-file-name
2042+
(concat "ghostel-module" module-file-suffix)
2043+
"C:/ghostel/"))
2044+
(downcase loaded-module)))))))
2045+
2046+
(ert-deftest ghostel-test-download-module-prefix-empty-uses-latest ()
2047+
"Prefix download treats blank input as a request for the latest release."
2048+
(let ((captured-version :unset)
2049+
(captured-latest nil)
2050+
(loaded-module nil))
2051+
(let ((comp-enable-subr-trampolines nil)
2052+
(native-comp-enable-subr-trampolines nil))
2053+
(cl-letf (((symbol-function 'locate-library)
2054+
(lambda (_) "C:/ghostel/ghostel.el"))
2055+
((symbol-function 'file-exists-p)
2056+
(lambda (_) nil))
2057+
((symbol-function 'read-string)
2058+
(lambda (&rest _) ""))
2059+
((symbol-function 'ghostel--download-module)
2060+
(lambda (_dir &optional version latest-release)
2061+
(setq captured-version version
2062+
captured-latest latest-release)
2063+
t))
2064+
((symbol-function 'module-load)
2065+
(lambda (path)
2066+
(setq loaded-module path)))
2067+
((symbol-function 'message)
2068+
(lambda (&rest _))))
2069+
(ghostel-download-module '(4))
2070+
(should (null captured-version))
2071+
(should captured-latest)
2072+
(should (equal (downcase (expand-file-name
2073+
(concat "ghostel-module" module-file-suffix)
2074+
"C:/ghostel/"))
2075+
(downcase loaded-module)))))))
2076+
2077+
(ert-deftest ghostel-test-download-module-prefix-rejects-too-old-version ()
2078+
"Prefix download rejects versions below the minimum supported version."
2079+
(let ((ghostel--minimum-module-version "0.7.1"))
2080+
(let ((comp-enable-subr-trampolines nil)
2081+
(native-comp-enable-subr-trampolines nil))
2082+
(cl-letf (((symbol-function 'locate-library)
2083+
(lambda (_) "C:/ghostel/ghostel.el"))
2084+
((symbol-function 'file-exists-p)
2085+
(lambda (_) nil))
2086+
((symbol-function 'read-string)
2087+
(lambda (&rest _) "0.7.0")))
2088+
(should-error (ghostel-download-module '(4))
2089+
:type 'user-error)))))
19812090

19822091
(ert-deftest ghostel-test-compile-module-invokes-zig-build ()
19832092
"Source compilation runs zig build directly."
@@ -2787,9 +2896,14 @@ while :; do sleep 0.1; done'\n")
27872896
ghostel-test-project-universal-arg
27882897
ghostel-test-copy-all
27892898
ghostel-test-copy-mode-buffer-navigation
2790-
ghostel-test-package-version
27912899
ghostel-test-compile-module-invokes-zig-build
27922900
ghostel-test-module-compile-command-uses-zig-build
2901+
ghostel-test-module-download-url-uses-requested-version
2902+
ghostel-test-module-download-url-uses-latest-release
2903+
ghostel-test-download-module-defaults-to-minimum-version
2904+
ghostel-test-download-module-prefix-uses-requested-version
2905+
ghostel-test-download-module-prefix-empty-uses-latest
2906+
ghostel-test-download-module-prefix-rejects-too-old-version
27932907
ghostel-test-module-version-match
27942908
ghostel-test-module-version-mismatch
27952909
ghostel-test-module-version-newer-than-minimum

0 commit comments

Comments
 (0)