Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Substitute :require' with :config' to separate dependency-related configurations #18

Closed
Alexander-Shukaev opened this issue Aug 14, 2015 · 10 comments

Comments

@Alexander-Shukaev
Copy link

This issue is a follow-up to the req-package discussion. Currently, the :require keyword is too strict as it will force req-package to fail if at least one package listed after the :require keyword is not available. As a result, the whole configuration, even those parts which have nothing to do with the absent package, will inevitably fail, what makes req-package quite rigid. For instance:

(req-package evil
  :require recentf
  :config
  (progn
    ;; configurations not related to `recentf'
  )
  (progn
    ;; configurations related to `recentf'
    (evil-make-overriding-map recentf-dialog-mode-map 'motion)
    (evil-set-initial-state 'recentf-dialog-mode 'motion)
    (evil-ex-define-cmd "rfm[enu]" #'recentf-open-files))
  (progn
    ;; configurations not related to `recentf'
  ))

Now, if recentf is not available, then all of the code under the :config keyword fails.

The idea is to improve granularity and flexibility of control over dependency-related configurations by slightly extending the existing interface of the :config keyword in a natural way. Here is a fairly complex commented example to demonstrate the idea:

(req-package evil
  :config
  ;; Here we configure only `evil' itself, e.g.:
  (let ((keymap evil-emacs-state-map))
    (setcdr keymap nil)
    (define-key keymap (read-kbd-macro evil-toggle-key) #'evil-exit-emacs-state))
  (setq-default evil-ex-commands nil)
  (evil-ex-define-cmd "w[rite]" #'evil-write)
  :config recentf
  ;; Here we configure what is related to `recentf' (plus `evil' of course).
  ;; It's kind of `:require recentf' (in a sense that it should also
  ;; manage dependencies as `:require recentf' does), but it also
  ;; includes corresponding configuration as well.  The benefit is
  ;; that if `recentf' is not available, then this code is simply
  ;; disabled/ignored (similar to how `with-eval-after-load' functions).
  ;; This is more general and flexible approach then `:require'.
  (evil-make-overriding-map recentf-dialog-mode-map 'motion)
  (evil-set-initial-state 'recentf-dialog-mode 'motion)
  (evil-ex-define-cmd "rfm[enu]" #'recentf-open-files)
  :config help
  ;; Again the same concept as with `recentf'.
  (let ((keymap evil-emacs-state-map))
    (define-key keymap (kbd "C-?") #'help))
  (evil-ex-define-cmd "h[elp]" #'help)
  :config (my minibuffer)
  ;; Again the same concept as with `recentf', but now two packages
  ;; are prerequisites.
  (my-add-hook 'minibuffer-setup-hook #'evil-insert-state))

By the way, those :config ... forms should execute exactly in the order they appear in the req-package macro. Why is it important? Consider (setq-default evil-ex-commands nil). It erases all of the default Evil ex-commands (set in the evil-maps subpackage). After that, we add our own ex-command: (evil-ex-define-cmd "w[rite]" #'evil-write). However, later on, if recentf is available, then (evil-ex-define-cmd "rfm[enu]" #'recentf-open-files) should run and obviously we want it to definitely run after (setq-default evil-ex-commands nil). Note that the same concept applies to how evil-emacs-state-map is managed (first reset and then gradually repopulated). Hence, the situation can occur quite often, and the deterministic control of dependency-related configurations execution order is vital.

Please, note that the benefits (dependency tracking, automatic downloading, etc.) of the original :require keyword should still apply to the new :config ... extension. However, it should definitely NOT do (require '...) behind the scenes. Instead, :config ... should behave similarly to the default :config, which simply translates to eval-after-load without require-ing anything, and at the same time, it should still build the dependency graph in the similar way as :require does in order to force evaluation of (req-package recentf ...) BEFORE the current one ((req-package evil ...) in this example), so that the default :config from (req-package recentf ...) is definitely executed BEFORE :config recentf from (req-package evil ...). Note that the code under either default :config from (req-package recentf ...) or from :config recentf from (req-package evil ...) will really execute only when recentf is truly loaded (by autoload, manually, or whatever) thanks to eval-after-load behind :config.

Finally, (req-package recentf ...) on its own without :demand (and that's the encouraged practice to avoid :demand, which is the same as (require 'recentf)) does not load recentf; and therefore, does not have to be touched at all if one does not want to use recentf anymore (i.e. one does not have to comment or erase it, but should rather only ensure that recentf is not loaded anywhere).

@jwiegley
Copy link

What if we introduced :dep-config PKG FORMS... to indicate per-dependency configuration? This way it would keep the implementation of :config separate (although, you can always have req-package override use-packages definition for this keyword too).

@Alexander-Shukaev
Copy link
Author

That's also a possibility. I had a thought on the back of my mind that :config ... could be difficult to distinguish from :config during parsing. Thus, I guess :dep-config is fine, but then I think :config-dep looks even better.

Also don't forget that PKG can also be a list of packages as well (see the example above), which would probably translate into a series of nested eval-after-load with each corresponding package from this list.

@aspiers
Copy link
Collaborator

aspiers commented Nov 16, 2015

Any more thoughts on this? I'm struggling to understand how I can reliably set up my init files to load combinations of packages and configure them in the right way, e.g.

  • key-chord and guide-key
  • multiple-cursors and region-bindings-mode

@edvorg
Copy link
Collaborator

edvorg commented Nov 16, 2015

Currently I just load some framework-like packages without deferring and then load other packages. It looks like this:

(req-package cider
  :require (key-chord guide-key)
  :config 
  (key-chord-define-global "09" 'cider-jack-in-clojurescript)
  (guide-key-related-config))

What we plan to do here is per dependency config sections like:

(req-package key-chord
  :defer t)

(req-package guide-key
  :defer t)

(req-package cider
  :config key-chord
  (key-chord-define-global "09" 'cider-jack-in-clojurescript)
  :config guide-key
  (guide-key-related-config))

So all branches will behave independently

@aspiers
Copy link
Collaborator

aspiers commented Nov 16, 2015

Makes sense - looking forward to it!

@edvorg edvorg changed the title Substitute :require' with :config' to separete dependency-related configurations Substitute :require' with :config' to separate dependency-related configurations May 29, 2016
@edvorg edvorg closed this as completed in 3df738a May 30, 2016
@edvorg
Copy link
Collaborator

edvorg commented May 30, 2016

(req-package banana)

(req-package cucumber)

(req-package mango
  :require banana
  :config (printf "%s" (+ mango banana)))

(req-package mango
  :require cucumber
  :config (printf "%s" (+ mango cucumber)))

@Alexander-Shukaev
Copy link
Author

Do I understand correctly that

(req-package banana
  :defer t)

(req-package cucumber
  :defer t)

(req-package mango
  :require banana
  :config (printf "%s" (+ mango banana)))

(req-package mango
  :require cucumber
  :config (printf "%s" (+ mango cucumber)))

and

(req-package banana
  :defer t)

(req-package cucumber
  :defer t)

(req-package mango
  :dep-config banana
  (printf "%s" (+ mango banana)))

(req-package mango
  :dep-config cucumber
  (printf "%s" (+ mango cucumber)))

are equivalent?

@edvorg
Copy link
Collaborator

edvorg commented Dec 8, 2016

Correct. I just decided to go with first option, because it was easier in implementation and arguments parsing.

@Alexander-Shukaev
Copy link
Author

I see. Thank you for the effort, good job!

@edvorg
Copy link
Collaborator

edvorg commented Dec 9, 2016 via email

ivan-m added a commit to ivan-m/.emacs.d that referenced this issue Mar 24, 2017
A lot of it is just blind guess/trying and relying upon autoloading
from other req-package calls, but everything seems to be loading now.

Originally had tried to use the functionality documented in
emacsorphanage/req-package#18 for more closely
specifying each individual section but couldn't get this to
work (e.g. it worked for the subword aspect but not for shm, etc.).
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Development

No branches or pull requests

4 participants