2

Use case

I run my Emacs config on many machines. Most do not have a Racket installed, and I'd rather not have my Racket related packages installed by default. However, I would like to be able to edit Racket code if I need to.

This is just an example, I'm interested in doing this for a large number of modes, possibly all of the avalible major modes I can find.

Question

Is there any way I can tell Emacs to autoload a package from the package repository? racket-mode won't be installed by default, but if I ever edit a *.rkt file, the config will download, install, load, and run racket-mode?

I'm sure I can do this with ugly hacks (just shadow the functions with a helper)? Is there a cleaner way to do it?

1

IMO, the best way to handle deferred installation is to use my package manager straight.el together with use-package. This is the only general-purpose deferred-installation mechanism that I know of (others are embedded in a particular Emacs configuration, like Prelude). I don't actually use racket-mode but here are some other examples from my configuration.

Basic usage

(use-package apples-mode
  :defer-install t
  :mode "\\.\\(applescri\\|sc\\)pt\\'")

More modes and interpreters

(use-package haskell-mode
  :defer-install t
  :mode (("\\.[gh]s\\'" . haskell-mode)
         ("\\.l[gh]s\\'" . literate-haskell-mode)
         ("\\.hsc\\'" . haskell-mode))
  :interpreter (("runghc" . haskell-mode)
                ("runhaskell" . haskell-mode)))

Together with configuration (which won't be run until the package is installed)

(use-package web-mode
  :defer-install t
  :commands (web-mode)

  :init
  (add-hook 'html-mode-hook #'web-mode)

  :config
  (setq web-mode-markup-indent-offset 2)
  (setq web-mode-enable-auto-closing t))

Installing an associated package

(use-package company-auctex
  :defer-install t
  :after tex

  :config
  (company-auctex-init))

Note that while use-package has generalized deferred-installation support, the package.el backend is untested AFAIK, while the straight.el backend is fully supported.

For verbose documentation on straight.el, including comparisons to other package managers, see the README.

  • I eventually ended up switching from my hand-rolled macro to this. – PythonNut Aug 15 '17 at 19:49
2

Emacs prelude has its own mechanism for doing it, you can get some inspiration from there. The relevant code can be found in core/prelude-packages.el

  • That's really quite nice. It foregoes having autoloads for the mode functions, but that's probably fine in most cases anyway, and it simplifies matters greatly. – phils Sep 12 '15 at 2:30
  • I ended up rolling my own solution based on this idea, but with something like 70 modes and some other bits to help it fit in. – PythonNut Sep 12 '15 at 21:32
2

(deleted mention of use-package; apparently that was a red herring)

If you want to roll your own approach, then the existing auto-mode-alist and autoload facilities should suffice as a basis. You could make autoload declarations for all the modes in question (either before package initialisation, or else only conditionally declaring the autoload, in order to avoid your autoloads clobbering real ones from installed packages). Each of your custom mode functions could then fetch & install the appropriate package from ELPA, load it (at which point the mode function definition would get clobbered by the real one), and finally call that actual mode function.

Or maybe don't even bother with autoloads. I see that Prelude's approach just tests package-installed-p, which seems a sensible condition. A basic approach might be:

(defun my-autoelpa-loader (mode pkg args)
  "Install PKG and then call MODE with ARGS."
  (package-refresh-contents) ;; could be an annoying default
  (package-install pkg)
  (require pkg)
  (apply mode args))

(defmacro my-autoelpa-mode (mode auto-mode-forms &optional pkg)
  "Arrange for MODE to be installed from ELPA on demand.

AUTO-MODE-FORMS will be added to `auto-mode-alist'."
  `(progn
     ;; Specify auto-mode-alist entries regardless
     (mapc (lambda (form) (add-to-list 'auto-mode-alist form))
           ',auto-mode-forms)
     ;; Conditionally define a MODE which installs PKG first.
     (unless (package-installed-p ',(or pkg mode))
       (defun ,mode (&rest args)
         "ELPA package installed on demand."
         (interactive)
         (my-autoelpa-loader ',mode ',(or pkg mode) args)))))

;; Don't process autoelpa declarations until after init, to make
;; sure that packages have already been initialised.
(add-hook 'after-init-hook 'my-autoelpa-declarations)
(defun my-autoelpa-declarations ()
  (unless (fboundp 'package-installed-p)
    (package-initialize))
  ;; sequence of mode/package declarations...
  (my-autoelpa-mode js2-mode (("\\.js" . js2-mode)))
  ;; ...
  )
  • That's cool. I'd like to do it without a package, if possible. But worst case scenario, I can just Frankenstein up a solution based on bits of use-package. – PythonNut Sep 12 '15 at 1:13
  • I would certainly recommend using (one way or another) code which has already been through the process of implementing this. – phils Sep 12 '15 at 1:15
  • I know use-package is awesome, it's just that it does so much more than I need for this specific case. – PythonNut Sep 12 '15 at 1:24
  • use-package is nice, but it doesn't do (and never did) this. – npostavs Sep 12 '15 at 3:49
  • 1
    @phils: emacswiki.org/emacs/UsePackage talked mainly about emacswiki.org/emacs/use-package.el which is different from github.com/jwiegley/use-package. I've edited the wiki page to make that more clear. – npostavs Sep 12 '15 at 13:14

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.