about summary refs log tree commit diff
diff options
context:
space:
mode:
authorSteve Purcell <steve@sanityinc.com>2020-06-20 15:24:47 +1200
committerSteve Purcell <steve@sanityinc.com>2020-06-20 15:54:57 +1200
commit8439afbe1e1fca1b64c91aac302764d465212bca (patch)
treee6110f0278d5d5355ba08270c08cc652657d8625
parent49597c22188e20dc05a7dcf2cfbe6f12162a7ed1 (diff)
downloadnixlib-8439afbe1e1fca1b64c91aac302764d465212bca.tar
nixlib-8439afbe1e1fca1b64c91aac302764d465212bca.tar.gz
nixlib-8439afbe1e1fca1b64c91aac302764d465212bca.tar.bz2
nixlib-8439afbe1e1fca1b64c91aac302764d465212bca.tar.lz
nixlib-8439afbe1e1fca1b64c91aac302764d465212bca.tar.xz
nixlib-8439afbe1e1fca1b64c91aac302764d465212bca.tar.zst
nixlib-8439afbe1e1fca1b64c91aac302764d465212bca.zip
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.
-rw-r--r--README.org26
-rw-r--r--default.nix3
-rw-r--r--elisp.nix25
-rw-r--r--packreq.nix26
-rw-r--r--parse.nix61
5 files changed, 115 insertions, 26 deletions
diff --git a/README.org b/README.org
index 49c83ea5bc59..4fc855a90ae8 100644
--- a/README.org
+++ b/README.org
@@ -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.
 
 ** Extra library functionality
-This overlay comes with an extra function to generate an Emacs closure from =use-package= declarations.
-This is an abstraction on top of =emacsWithPackages=.
+This overlay comes with extra functions to generate an Emacs closure
+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
 {
   environment.systemPackages = [
@@ -52,6 +56,24 @@ This is an abstraction on top of =emacsWithPackages=.
 }
 #+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
 *** Latest master each rebuild
diff --git a/default.nix b/default.nix
index de2afeb70158..e4acaa83e658 100644
--- a/default.nix
+++ b/default.nix
@@ -82,7 +82,6 @@ let
 
     buildInputs = old.buildInputs ++ [ self.libgccjit ];
   });
-
 in {
   inherit emacsGit emacsUnstable;
 
@@ -106,6 +105,8 @@ in {
 
   emacsWithPackagesFromUsePackage = import ./elisp.nix { pkgs = self; };
 
+  emacsWithPackagesFromPackageRequires = import ./packreq.nix { pkgs = self; };
+
   emacsPackagesFor = emacs: (
     (super.emacsPackagesFor emacs).overrideScope'(eself: esuper: let
 
diff --git a/elisp.nix b/elisp.nix
index 23a7e5a36bf4..d1edb2699f4d 100644
--- a/elisp.nix
+++ b/elisp.nix
@@ -6,35 +6,14 @@ use-package declarations.
 { pkgs }:
 
 let
-  isStrEmpty = s: (builtins.replaceStrings [" "] [""] s) == "";
-
-  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);
-
+  parse = pkgs.callPackage ./parse.nix {};
 in {
   config,
   extraEmacsPackages ? epkgs: [],
   package ? pkgs.emacs,
   override ? (epkgs: epkgs)
 }: let
-  packages = parsePackages config;
+  packages = parse.parsePackagesFromUsePackage config;
   emacsPackages = pkgs.emacsPackagesGen package;
   emacsWithPackages = emacsPackages.emacsWithPackages;
 in emacsWithPackages (epkgs: let
diff --git a/packreq.nix b/packreq.nix
new file mode 100644
index 000000000000..5e432201a51a
--- /dev/null
+++ b/packreq.nix
@@ -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)
diff --git a/parse.nix b/parse.nix
new file mode 100644
index 000000000000..9fbe7c747389
--- /dev/null
+++ b/parse.nix
@@ -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;
+}