From: Mekeor Melire <mekeor@posteo.de>
To: Guix Devel <guix-devel@gnu.org>
Subject: Proof of Concept: Import Emacs' use-packaged packages into Guix' manifest.scm
Date: Sun, 18 Dec 2022 01:54:06 +0000 [thread overview]
Message-ID: <87r0wxbhm5.fsf@posteo.de> (raw)
[-- Attachment #1: Type: text/plain, Size: 3390 bytes --]
Hello,
I'd like to share some code, thus prove a concept, ask for
feedback, and ask if it could makes sense, under certain
circumstances, to provide similar code in GNU Guix. The code can
be used in a ~manifest.scm~ file (but probably could also be used
with ~guix package -e~ or inside ~guix repl~) where it can be used
to import all ~emacs-*~ packages that are loaded with
~(use-package PACKAGE [...])~ inside an Elisp-file like ~init.el~:
#+begin_src scheme
;; ~/some/manifest.scm
(specifications->manifest
(append
(el/file->specifications "/home/user/.emacs.d/init.el")
(list
"emacs"
"git"
"gnupg"
"mu"
"syncthing")))
#+end_src
It has many severe caveats, including but not limited to:
1. The code uses Guile/Scheme's ~read~ function which interprets
the Elisp-file as Scheme. Thus, the code does not really find out
which packages are loaded, but rather it only looks for the
~(use-package PACKAGE [...])~ pattern of an S-expression. E.g.
something like ~(apply 'use-package foo)~ won't be caught. (I
don't expect Guile's Emacs-Lisp language-feature to be mature
enough to be usable for this.)
2. Not all external packages are loaded with ~use-package~. E.g.
~(require 'foo)~ might be used directly. But it'd be possible to
extend the code to allow several ways/patterns of importing
packages, and maybe also to specify custom patterns.
3. Not all S-expressions that match against ~(use-package
PACKAGE)~ are load external packages. E.g. ~(if nil (use-package
foo))~ won't load anything at all. Also, neither ~(use-package
emacs)~ nor ~(use-package savehist)~ will load anything, because
they ~require~ features that ship with GNU Emacs by default.
That's why the functions in the appended code offer an optional
keyword argument, #:block, in order to block a list packages.
4. The ~manifest.scm~ is not the single source of truth anymore.
I.e. this code compromises the purpose of a manifest. (It's
possible to extend the code such that the Elisp-file's hash can
optionally be verified with given hash.)
Nevertheless, for me, personally, it's pretty neat and handy to
use, because I don't need to maintain the list of emacs-packages
in two places. I also think that it could come pretty handy for
many others, at least in order to initialize their user-profile,
by running something like ~guix package -e '(some-magic
"/home/user/.emacs.d/init.el")'~.
What do you think? Should this go into a separate, private
channel? Into the Guix Cookbook? Into Guix, if so, then probably
with lots of changes? Should it just stay here, in this mailing
list thread? Or do you think this is just a bad idea in general?
I should mention that I go the idea for this code from the
Nix-Community's Emacs-Overlay which offers a similar feature; as
described in their README as follows:
#+begin_src nix
#
https://github.com/nix-community/emacs-overlay#extra-library-functionality
{
environment.systemPackages = [
(emacsWithPackagesFromUsePackage {
# Your Emacs config file. Org mode babel files are also
# supported.
# NB: Config files cannot contain unicode characters, since
# they're being parsed in nix, which lacks unicode
# support.
# config = ./emacs.org;
config = ./emacs.el;
# ...
})]}
#+end_src
Here's the code. Please show mercy, it's my first real Scheme
code:
[-- Attachment #2: el-manifest.scm --]
[-- Type: text/plain, Size: 4133 bytes --]
(define-module (manifest)
#:autoload (gnu packages) (specifications->manifest)
#:autoload (srfi srfi-1) (drop fold-right)
#:autoload (ice-9 textual-ports) (get-string-all))
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; LIBRARY
(define el/default-block
;; "Default list of packages, as symbols, which will be blocked. It's a subset
;; default features provided by GNU Emacs."
'(emacs hl-line outline savehist))
(define* (el/file->specifications content-f #:key (block el/default-block))
"Given the file-path CONTENT-F, interprete it as an Emacs-Lisp source-code
and extract a list of package-names, as strings, that have been loaded using
(use-package PACKAGE [...])."
(call-with-input-file content-f
(lambda (input) (el/port->specifications input #:block block))))
(define* (el/string->specifications content-s #:key (block el/default-block))
"Given a string CONTENT-S, interprete it as an Emacs-Lispsource-code and
extract a list of package-names, as strings, that have been loaded using
(use-package PACKAGE [...])."
(call-with-input-string content-s
(lambda (input) (el/port->specifications input #:block block))))
(define* (el/port->specifications content-p #:key (block el/default-block))
"Given the input-port CONTENT-P, interprete it as an Emacs-Lisp source-code
and extract a list of package-names, as strings, that have been loaded using
(use-package PACKAGE [...])."
(let*
((content-string-or-eof
(get-string-all content-p))
(content-string
(if
(eof-object? content-string-or-eof)
""
content-string-or-eof))
;; let's wrap the file into a (quote ...) so that a single invocation of ~read~ will read the whole file
(quoted-content-string
(string-append "(quote " content-string "\n)"))
(content-expression
(with-input-from-string quoted-content-string read)))
(map
(lambda (package-string) (string-append "emacs-" package-string))
(map
symbol->string
(filter
(lambda (package-expression) (not (member package-expression block)))
(el/find-used-packages content-expression (list)))))))
(define (el/find-used-packages expression accumulator)
"Given a quoted expression EXPRESSION and an accumulator ACCUMULATOR,
being a list of package names, add all package-names into ACCUMULATOR, that
have been loaded with (use-package PACKAGE [...]), and return that possibly
expanded list."
(cond
;; in case of no s-expression, return accumulator
((not (list? (peek expression))) accumulator)
;; in case of an empty s-expression, (), return accumulator
((nil? expression) accumulator)
;; in case of an s-expression with a single element, (X), walk into that element
((nil? (cdr expression))
(el/find-used-packages (car expression) accumulator))
;; in case of an use-package s-expression, (use-package PACKAGE [...]), add PACKAGE to the accumulator and also add those elements which possibly result from walking through the remaining elements
((and
(eq? (car expression) 'use-package)
(symbol? (cadr expression)))
(fold-right
el/find-used-packages
(let
((package (cadr expression)))
(if
(member package accumulator)
accumulator
(cons package accumulator)))
(drop expression 2)))
;; in case of a s-expression with at least two elements, (X Y [...]), walk through all of them
(#t
(fold-right
el/find-used-packages
accumulator
expression))))
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; MANIFEST
(specifications->manifest
(append
(el/file->specifications "/home/user/.emacs.d/configuration.el" #:block
;; block some packages as they won't be loaded on this host:
(cons*
'gist
'guix-emacs
'hide-lines
'json
'kubernetes
'vertico-directory
el/default-block))
(list
"emacs"
"git"
"isync"
"mu")))
next reply other threads:[~2022-12-18 1:56 UTC|newest]
Thread overview: 13+ messages / expand[flat|nested] mbox.gz Atom feed top
2022-12-18 1:54 Mekeor Melire [this message]
2022-12-18 8:11 ` Proof of Concept: Import Emacs' use-packaged packages into Guix' manifest.scm Liliana Marie Prikler
2022-12-19 10:15 ` Andrew Tropin
2022-12-20 9:45 ` Mekeor Melire
2022-12-20 14:56 ` Andrew Tropin
2022-12-20 9:16 ` Mekeor Melire
2022-12-20 15:06 ` Andrew Tropin
2022-12-19 10:42 ` zimoun
2022-12-28 0:51 ` Mitchell Schmeisser via Development of GNU Guix and the GNU System distribution.
2023-02-02 9:44 ` Mekeor Melire
2023-02-03 2:20 ` Mitchell Schmeisser via Development of GNU Guix and the GNU System distribution.
2023-02-03 2:31 ` Mitchell Schmeisser via Development of GNU Guix and the GNU System distribution.
[not found] <875ydxi4xn.fsf@posteo.de>
2022-12-27 18:52 ` Mitchell Schmeisser via Development of GNU Guix and the GNU System distribution.
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
List information: https://guix.gnu.org/
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=87r0wxbhm5.fsf@posteo.de \
--to=mekeor@posteo.de \
--cc=guix-devel@gnu.org \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
Code repositories for project(s) associated with this public inbox
https://git.savannah.gnu.org/cgit/guix.git
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for read-only IMAP folder(s) and NNTP newsgroup(s).