From mboxrd@z Thu Jan 1 00:00:00 1970 Return-Path: Received: from mp10.migadu.com ([2001:41d0:2:bcc0::]) (using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits)) by ms5.migadu.com with LMTPS id 8NdmMkNznmOQ0wAAbAwnHQ (envelope-from ) for ; Sun, 18 Dec 2022 02:56:19 +0100 Received: from aspmx1.migadu.com ([2001:41d0:2:bcc0::]) (using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits)) by mp10.migadu.com with LMTPS id 6Jm7MENznmMaMgEAG6o9tA (envelope-from ) for ; Sun, 18 Dec 2022 02:56:19 +0100 Received: from lists.gnu.org (lists.gnu.org [209.51.188.17]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by aspmx1.migadu.com (Postfix) with ESMTPS id 79DF931CC1 for ; Sun, 18 Dec 2022 02:56:19 +0100 (CET) Received: from localhost ([::1] helo=lists1p.gnu.org) by lists.gnu.org with esmtp (Exim 4.90_1) (envelope-from ) id 1p6iu5-0005Tx-0m; Sat, 17 Dec 2022 20:55:49 -0500 Received: from eggs.gnu.org ([2001:470:142:3::10]) by lists.gnu.org with esmtps (TLS1.2:ECDHE_RSA_AES_256_GCM_SHA384:256) (Exim 4.90_1) (envelope-from ) id 1p6iu0-0005SZ-Hv for guix-devel@gnu.org; Sat, 17 Dec 2022 20:55:44 -0500 Received: from mout02.posteo.de ([185.67.36.66]) by eggs.gnu.org with esmtps (TLS1.2:ECDHE_RSA_AES_256_GCM_SHA384:256) (Exim 4.90_1) (envelope-from ) id 1p6itx-0001b8-6l for guix-devel@gnu.org; Sat, 17 Dec 2022 20:55:44 -0500 Received: from submission (posteo.de [185.67.36.169]) by mout02.posteo.de (Postfix) with ESMTPS id E8343240103 for ; Sun, 18 Dec 2022 02:55:38 +0100 (CET) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=posteo.de; s=2017; t=1671328538; bh=a0i+Lfn1aesjYm2r+5BnlailepkMXdWMz4yihiDCdmU=; h=From:To:Subject:Date:From; b=BxKQw/ZB05Y+l60GsaPxrvN004o8x5bleDqgM3sKGIHtSH8444cZtr5e6GY4sPsS3 E5sjfW09RJ+U+ngbiMNc7fhTY38+0oC/kxW4cREPUGOuyQr+lbP75lywagbqcDwtAX WGaRx1YI7XVoy9UJLyAmE56FjZkDFg/d8bZ9on/rEzBuP/g69nVIFEx2YL5FA58Xo8 HDZ9Sfht0ESNy4OjVjvAGZh5iBup1pECO1zKyg56GKJ5usJvu9FY/M1lIWf3uG0kgv m1mc9o3yG3YYG0pS+ruB6YgVKE6uSfPG5qPEbrcIVbXNVepxRGsAsW0jxRPtXH1Kcn mEKxBzGwK2Vww== Received: from customer (localhost [127.0.0.1]) by submission (posteo.de) with ESMTPSA id 4NZQsQ21Czz6tqj; Sun, 18 Dec 2022 02:55:38 +0100 (CET) From: Mekeor Melire To: Guix Devel Subject: Proof of Concept: Import Emacs' use-packaged packages into Guix' manifest.scm Date: Sun, 18 Dec 2022 01:54:06 +0000 Message-ID: <87r0wxbhm5.fsf@posteo.de> MIME-Version: 1.0 Content-Type: multipart/mixed; boundary="=-=-=" Received-SPF: pass client-ip=185.67.36.66; envelope-from=mekeor@posteo.de; helo=mout02.posteo.de X-Spam_score_int: -43 X-Spam_score: -4.4 X-Spam_bar: ---- X-Spam_report: (-4.4 / 5.0 requ) BAYES_00=-1.9, DKIM_SIGNED=0.1, DKIM_VALID=-0.1, DKIM_VALID_AU=-0.1, DKIM_VALID_EF=-0.1, RCVD_IN_DNSWL_MED=-2.3, RCVD_IN_MSPIKE_H2=-0.001, SPF_HELO_NONE=0.001, SPF_PASS=-0.001 autolearn=ham autolearn_force=no X-Spam_action: no action X-BeenThere: guix-devel@gnu.org X-Mailman-Version: 2.1.29 Precedence: list List-Id: "Development of GNU Guix and the GNU System distribution." List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Errors-To: guix-devel-bounces+larch=yhetil.org@gnu.org Sender: guix-devel-bounces+larch=yhetil.org@gnu.org X-Migadu-Country: US X-Migadu-Flow: FLOW_IN ARC-Authentication-Results: i=1; aspmx1.migadu.com; dkim=pass header.d=posteo.de header.s=2017 header.b="BxKQw/ZB"; dmarc=pass (policy=none) header.from=posteo.de; spf=pass (aspmx1.migadu.com: domain of "guix-devel-bounces+larch=yhetil.org@gnu.org" designates 209.51.188.17 as permitted sender) smtp.mailfrom="guix-devel-bounces+larch=yhetil.org@gnu.org" ARC-Seal: i=1; s=key1; d=yhetil.org; t=1671328579; a=rsa-sha256; cv=none; b=EHlncoh8bHQTlpO8QloIIVJIMQABXN9VqBSfx5kmwAlmHGqLm8PaQXYRLW8Rdgs/dQiWrW 0uW8J9Iaw0cUMkYDFWY1J7ZASO1JRtzWoVpLuMn+bkyFf2kROQmSsmP0eWO2A4IwrsCcfb Y10OjV28sUpPj1czt1pEoTAmF6BD4YUcddiqfTXj9NhbRHZfwtGbupOEV6rTYmC8pSS4XS 09gTQupby2k4SF7xJKKrTxcj3CbTW0CcVfStwMBKTZVW1+vdDIL9OnFTwe6JHKnYxlMMDd eqCMPbKvk+wlidB2g3OgHA6fShK3kkiuJQDznV9JB7CaVgTYpx7Pe2FCxnNXJw== ARC-Message-Signature: i=1; a=rsa-sha256; c=relaxed/relaxed; d=yhetil.org; s=key1; t=1671328579; h=from:from:sender:sender:reply-to:subject:subject:date:date: message-id:message-id:to:to:cc:mime-version:mime-version: content-type:content-type:list-id:list-help:list-unsubscribe: list-subscribe:list-post:dkim-signature; bh=rcqC/m+EuqfClVlyb84qC0vzFsiUg8FlNi+wWoZWNWI=; b=EjHOXMrnya2c5DX6c8lkJNysm1pEm/3Susa6pVNg2OQsi6V7rUlLnufW0Icwyn6oEF4l74 ctUBmlrmyFJqYpuyON6++qO2HzGNhopbQGElV/Aeyx+Zwf+N05bQkLN32zFLW4yyN4CI+A EfdFOlx8ujq//7p8DTGT9OghdVCv7wfFxY5PYeqOJpP4S7HONzcvC1EDO0c7aVuqfxQOlf JY/y62m/vpqLpfaUmkBZPMGQfjTWiK5TFAhF40QzK94fkDv8tc/UO0wC4oItPasXLL7HPj v8lqga5IVzBVIP6pMTT8LuBCFeBTC8YFcND2ElpVOE0LjAtGIOU/9cTGQhmlvQ== Authentication-Results: aspmx1.migadu.com; dkim=pass header.d=posteo.de header.s=2017 header.b="BxKQw/ZB"; dmarc=pass (policy=none) header.from=posteo.de; spf=pass (aspmx1.migadu.com: domain of "guix-devel-bounces+larch=yhetil.org@gnu.org" designates 209.51.188.17 as permitted sender) smtp.mailfrom="guix-devel-bounces+larch=yhetil.org@gnu.org" X-Migadu-Scanner: scn1.migadu.com X-Migadu-Spam-Score: -5.89 X-Spam-Score: -5.89 X-Migadu-Queue-Id: 79DF931CC1 X-TUID: 3+XSlWQaCBqO --=-=-= Content-Type: text/plain; format=flowed 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: --=-=-= Content-Type: text/plain Content-Disposition: inline; filename=el-manifest.scm (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"))) --=-=-=--