Add emacsWithPackagesFromPackageRequires

This provides a mechanism for creating an Emacs closure that contains
the runtime dependencies for a given Emacs package source file, by
inspecting its Package-Requires header.
This commit is contained in:
Steve Purcell 2020-06-20 15:24:47 +12:00
parent 49597c2218
commit 8439afbe1e
5 changed files with 115 additions and 26 deletions

View file

@ -28,8 +28,12 @@ We also provide two attributes named =emacsGit-nox= and =emacsUnstable-nox=
if you wish to have Emacs built without X dependencies. if you wish to have Emacs built without X dependencies.
** Extra library functionality ** Extra library functionality
This overlay comes with an extra function to generate an Emacs closure from =use-package= declarations. This overlay comes with extra functions to generate an Emacs closure
This is an abstraction on top of =emacsWithPackages=. from various types of dependency declaration. (These are abstractions
on top of =emacsWithPackages=.)
For example, =emacsWithPackagesFromUsePackage= adds packages which are required in a user's config via =use-package=:
#+BEGIN_SRC nix #+BEGIN_SRC nix
{ {
environment.systemPackages = [ environment.systemPackages = [
@ -52,6 +56,24 @@ This is an abstraction on top of =emacsWithPackages=.
} }
#+END_SRC #+END_SRC
Similarly, =emacsWithPackagesFromPackageRequires= adds packages which
are declared in a =.el= package file's =Package-Requires= header, which
can be handy for CI purposes:
#+BEGIN_SRC nix
...
let
emacsForCI = pkgs.emacsWithPackagesFromPackageRequires {
packageFile = builtins.readFile ./flycheck.el;
extraEmacsPackages = epkgs: [
epkgs.package-lint
];
};
pkgs.mkShell {
buildInputs = [ emacsForCI ];
}
#+END_SRC
** Usage of the overlay ** Usage of the overlay
*** Latest master each rebuild *** Latest master each rebuild

View file

@ -82,7 +82,6 @@ let
buildInputs = old.buildInputs ++ [ self.libgccjit ]; buildInputs = old.buildInputs ++ [ self.libgccjit ];
}); });
in { in {
inherit emacsGit emacsUnstable; inherit emacsGit emacsUnstable;
@ -106,6 +105,8 @@ in {
emacsWithPackagesFromUsePackage = import ./elisp.nix { pkgs = self; }; emacsWithPackagesFromUsePackage = import ./elisp.nix { pkgs = self; };
emacsWithPackagesFromPackageRequires = import ./packreq.nix { pkgs = self; };
emacsPackagesFor = emacs: ( emacsPackagesFor = emacs: (
(super.emacsPackagesFor emacs).overrideScope'(eself: esuper: let (super.emacsPackagesFor emacs).overrideScope'(eself: esuper: let

View file

@ -6,35 +6,14 @@ use-package declarations.
{ pkgs }: { pkgs }:
let let
isStrEmpty = s: (builtins.replaceStrings [" "] [""] s) == ""; parse = pkgs.callPackage ./parse.nix {};
splitString = _sep: _s: builtins.filter
(x: builtins.typeOf x == "string")
(builtins.split _sep _s);
stripComments = dotEmacs: let
lines = splitString "\n" dotEmacs;
stripped = builtins.map (l:
builtins.elemAt (splitString ";;" l) 0) lines;
in builtins.concatStringsSep " " stripped;
parsePackages = dotEmacs: let
strippedComments = stripComments dotEmacs;
tokens = builtins.filter (t: !(isStrEmpty t)) (builtins.map
(t: if builtins.typeOf t == "list" then builtins.elemAt t 0 else t)
(builtins.split "([\(\)])" strippedComments));
matches = builtins.map (t:
builtins.match "^use-package[[:space:]]+([A-Za-z0-9_-]+).*" t) tokens;
in builtins.map (m: builtins.elemAt m 0)
(builtins.filter (m: m != null) matches);
in { in {
config, config,
extraEmacsPackages ? epkgs: [], extraEmacsPackages ? epkgs: [],
package ? pkgs.emacs, package ? pkgs.emacs,
override ? (epkgs: epkgs) override ? (epkgs: epkgs)
}: let }: let
packages = parsePackages config; packages = parse.parsePackagesFromUsePackage config;
emacsPackages = pkgs.emacsPackagesGen package; emacsPackages = pkgs.emacsPackagesGen package;
emacsWithPackages = emacsPackages.emacsWithPackages; emacsWithPackages = emacsPackages.emacsWithPackages;
in emacsWithPackages (epkgs: let in emacsWithPackages (epkgs: let

26
packreq.nix Normal file
View file

@ -0,0 +1,26 @@
/*
Parse an emacs package file to derive packages from
Package-Requires declarations.
*/
{ pkgs }:
let
parse = pkgs.callPackage ./parse.nix { };
in
{ packageFile
, extraEmacsPackages ? epkgs: [ ]
, package ? pkgs.emacs
, override ? (epkgs: epkgs)
}:
let
packages = parse.parsePackagesFromPackageRequires packageFile;
emacsPackages = pkgs.emacsPackagesGen package;
emacsWithPackages = emacsPackages.emacsWithPackages;
in
emacsWithPackages (epkgs:
let
overriden = override epkgs;
usePkgs = builtins.map (name: overriden.${name}) packages;
extraPkgs = extraEmacsPackages overriden;
in
[ overriden.use-package ] ++ usePkgs ++ extraPkgs)

61
parse.nix Normal file
View file

@ -0,0 +1,61 @@
{ lib }:
let
isStrEmpty = s: (builtins.replaceStrings [ " " ] [ "" ] s) == "";
splitString = _sep: _s: builtins.filter
(x: builtins.typeOf x == "string")
(builtins.split _sep _s);
parsePackagesFromPackageRequires = packageFile:
let
lines = splitString "\r?\n" packageFile;
requires =
lib.concatMapStrings
(line:
let match = builtins.match "^;;;* *[pP]ackage-[rR]equires *: *\\((.*)\\)" line;
in if match == null then "" else builtins.head match)
lines;
parseReqList = s:
let matchAndRest = builtins.match " *\\(? *([^ \"\\)]+)( +\"[^\"]+\" *\\))?(.*)" s;
in
if isStrEmpty s then
[ ]
else
if matchAndRest == null then
throw "Failed to parse package requirements list: ${s}"
else
[ (builtins.head matchAndRest) ] ++ (parseReqList (builtins.elemAt matchAndRest 2));
in
parseReqList requires;
stripComments = dotEmacs:
let
lines = splitString "\n" dotEmacs;
stripped = builtins.map
(l:
builtins.elemAt (splitString ";;" l) 0)
lines;
in
builtins.concatStringsSep " " stripped;
parsePackagesFromUsePackage = dotEmacs:
let
strippedComments = stripComments dotEmacs;
tokens = builtins.filter (t: !(isStrEmpty t)) (builtins.map
(t: if builtins.typeOf t == "list" then builtins.elemAt t 0 else t)
(builtins.split "([\(\)])" strippedComments)
);
matches = builtins.map
(t:
builtins.match "^use-package[[:space:]]+([A-Za-z0-9_-]+).*" t)
tokens;
in
builtins.map
(m: builtins.elemAt m 0)
(builtins.filter (m: m != null) matches);
in
{
inherit parsePackagesFromPackageRequires;
inherit parsePackagesFromUsePackage;
}