diff options
Diffstat (limited to 'nixpkgs/pkgs/build-support/emacs')
-rw-r--r-- | nixpkgs/pkgs/build-support/emacs/buffer.nix | 77 | ||||
-rw-r--r-- | nixpkgs/pkgs/build-support/emacs/elpa.nix | 37 | ||||
-rw-r--r-- | nixpkgs/pkgs/build-support/emacs/elpa2nix.el | 33 | ||||
-rw-r--r-- | nixpkgs/pkgs/build-support/emacs/emacs-funcs.sh | 34 | ||||
-rw-r--r-- | nixpkgs/pkgs/build-support/emacs/generic.nix | 90 | ||||
-rw-r--r-- | nixpkgs/pkgs/build-support/emacs/melpa.nix | 125 | ||||
-rw-r--r-- | nixpkgs/pkgs/build-support/emacs/melpa2nix.el | 32 | ||||
-rw-r--r-- | nixpkgs/pkgs/build-support/emacs/mk-wrapper-subdirs.el | 6 | ||||
-rw-r--r-- | nixpkgs/pkgs/build-support/emacs/package-build-dont-use-mtime.patch | 21 | ||||
-rw-r--r-- | nixpkgs/pkgs/build-support/emacs/trivial.nix | 28 | ||||
-rw-r--r-- | nixpkgs/pkgs/build-support/emacs/wrapper.nix | 236 | ||||
-rw-r--r-- | nixpkgs/pkgs/build-support/emacs/wrapper.sh | 53 |
12 files changed, 772 insertions, 0 deletions
diff --git a/nixpkgs/pkgs/build-support/emacs/buffer.nix b/nixpkgs/pkgs/build-support/emacs/buffer.nix new file mode 100644 index 000000000000..48a7996916e8 --- /dev/null +++ b/nixpkgs/pkgs/build-support/emacs/buffer.nix @@ -0,0 +1,77 @@ +# Functions to build elisp files to locally configure emcas buffers. +# See https://github.com/shlevy/nix-buffer + +{ lib, writeText, inherit-local }: + +rec { + withPackages = pkgs': let + pkgs = builtins.filter (x: x != null) pkgs'; + extras = map (x: x.emacsBufferSetup pkgs) (builtins.filter (builtins.hasAttr "emacsBufferSetup") pkgs); + in writeText "dir-locals.el" '' + (require 'inherit-local "${inherit-local}/share/emacs/site-lisp/elpa/inherit-local-${inherit-local.version}/inherit-local.elc") + + ; Only set up nixpkgs buffer handling when we have some buffers active + (defvar nixpkgs--buffer-count 0) + (when (eq nixpkgs--buffer-count 0) + (make-variable-buffer-local 'nixpkgs--is-nixpkgs-buffer) + ; When generating a new temporary buffer (one whose name starts with a space), do inherit-local inheritance and make it a nixpkgs buffer + (defun nixpkgs--around-generate (orig name &optional ibh) + (if (and nixpkgs--is-nixpkgs-buffer (eq (aref name 0) ?\s)) + (let ((buf (funcall orig name ibh))) + (progn + (inherit-local-inherit-child buf) + (with-current-buffer buf + (setq nixpkgs--buffer-count (1+ nixpkgs--buffer-count)) + (add-hook 'kill-buffer-hook 'nixpkgs--decrement-buffer-count nil t))) + buf) + (funcall orig name ibh))) + (advice-add 'generate-new-buffer :around #'nixpkgs--around-generate) + ; When we have no more nixpkgs buffers, tear down the buffer handling + (defun nixpkgs--decrement-buffer-count () + (setq nixpkgs--buffer-count (1- nixpkgs--buffer-count)) + (when (eq nixpkgs--buffer-count 0) + (advice-remove 'generate-new-buffer #'nixpkgs--around-generate) + (fmakunbound 'nixpkgs--around-generate) + (fmakunbound 'nixpkgs--decrement-buffer-count)))) + (setq nixpkgs--buffer-count (1+ nixpkgs--buffer-count)) + (add-hook 'kill-buffer-hook 'nixpkgs--decrement-buffer-count nil t) + + ; Add packages to PATH and exec-path + (make-local-variable 'process-environment) + (put 'process-environment 'permanent-local t) + (inherit-local 'process-environment) + ; setenv modifies in place, so copy the environment first + (setq process-environment (copy-tree process-environment)) + (setenv "PATH" (concat "${lib.makeSearchPath "bin" pkgs}:" (getenv "PATH"))) + (inherit-local-permanent exec-path (append '(${builtins.concatStringsSep " " (map (p: "\"${p}/bin\"") pkgs)}) exec-path)) + + (inherit-local-permanent eshell-path-env (concat "${lib.makeSearchPath "bin" pkgs}:" (if (boundp 'eshell-path-env) eshell-path-env (getenv "PATH")))) + + (setq nixpkgs--is-nixpkgs-buffer t) + (inherit-local 'nixpkgs--is-nixpkgs-buffer) + + ${lib.concatStringsSep "\n" extras} + ''; + # nix-buffer function for a project with a bunch of haskell packages + # in one directory + haskellMonoRepo = { project-root # The monorepo root + , haskellPackages # The composed haskell packages set that contains all of the packages + }: { root }: + let # The haskell paths. + haskell-paths = lib.filesystem.haskellPathsInDir project-root; + # Find the haskell package that the 'root' is in, if any. + haskell-path-parent = + let filtered = builtins.filter (name: + lib.hasPrefix (toString (project-root + "/${name}")) (toString root) + ) (builtins.attrNames haskell-paths); + in + if filtered == [] then null else builtins.head filtered; + # We're in the directory of a haskell package + is-haskell-package = haskell-path-parent != null; + haskell-package = haskellPackages.${haskell-path-parent}; + # GHC environment with all needed deps for the haskell package + haskell-package-env = + builtins.head haskell-package.env.nativeBuildInputs; + in + lib.optionalAttrs is-haskell-package (withPackages [ haskell-package-env ]); +} diff --git a/nixpkgs/pkgs/build-support/emacs/elpa.nix b/nixpkgs/pkgs/build-support/emacs/elpa.nix new file mode 100644 index 000000000000..a43578fd3936 --- /dev/null +++ b/nixpkgs/pkgs/build-support/emacs/elpa.nix @@ -0,0 +1,37 @@ +# builder for Emacs packages built for packages.el + +{ lib, stdenv, emacs, texinfo, writeText, gcc }: + +let + handledArgs = [ "files" "fileSpecs" "meta" ]; + genericBuild = import ./generic.nix { inherit lib stdenv emacs texinfo writeText gcc; }; + +in + +{ pname +, version +, src +, meta ? {} +, ... +}@args: + +genericBuild ({ + + dontUnpack = true; + + installPhase = '' + runHook preInstall + + emacs --batch -Q -l ${./elpa2nix.el} \ + -f elpa2nix-install-package \ + "$src" "$out/share/emacs/site-lisp/elpa" + + runHook postInstall + ''; + + meta = { + homepage = args.src.meta.homepage or "https://elpa.gnu.org/packages/${pname}.html"; + } // meta; +} + +// removeAttrs args handledArgs) diff --git a/nixpkgs/pkgs/build-support/emacs/elpa2nix.el b/nixpkgs/pkgs/build-support/emacs/elpa2nix.el new file mode 100644 index 000000000000..64587c0fad1a --- /dev/null +++ b/nixpkgs/pkgs/build-support/emacs/elpa2nix.el @@ -0,0 +1,33 @@ +(require 'package) +(package-initialize) + +(defun elpa2nix-install-package () + (if (not noninteractive) + (error "`elpa2nix-install-package' is to be used only with -batch")) + (pcase command-line-args-left + (`(,archive ,elpa) + (progn (setq package-user-dir elpa) + (elpa2nix-install-file archive))))) + +(defun elpa2nix-install-from-buffer () + "Install a package from the current buffer." + (let ((pkg-desc (if (derived-mode-p 'tar-mode) + (package-tar-file-info) + (package-buffer-info)))) + ;; Install the package itself. + (package-unpack pkg-desc) + pkg-desc)) + +(defun elpa2nix-install-file (file) + "Install a package from a file. +The file can either be a tar file or an Emacs Lisp file." + (let ((is-tar (string-match "\\.tar\\'" file))) + (with-temp-buffer + (if is-tar + (insert-file-contents-literally file) + (insert-file-contents file)) + (when is-tar (tar-mode)) + (elpa2nix-install-from-buffer)))) + +;; Allow installing package tarfiles larger than 10MB +(setq large-file-warning-threshold nil) diff --git a/nixpkgs/pkgs/build-support/emacs/emacs-funcs.sh b/nixpkgs/pkgs/build-support/emacs/emacs-funcs.sh new file mode 100644 index 000000000000..e1e6a3b62208 --- /dev/null +++ b/nixpkgs/pkgs/build-support/emacs/emacs-funcs.sh @@ -0,0 +1,34 @@ +addToEmacsLoadPath() { + local lispDir="$1" + if [[ -d $lispDir && ${EMACSLOADPATH-} != *"$lispDir":* ]] ; then + # It turns out, that the trailing : is actually required + # see https://www.gnu.org/software/emacs/manual/html_node/elisp/Library-Search.html + export EMACSLOADPATH="$lispDir:${EMACSLOADPATH-}" + fi +} + +addToEmacsNativeLoadPath() { + local nativeDir="$1" + if [[ -d $nativeDir && ${EMACSNATIVELOADPATH-} != *"$nativeDir":* ]]; then + export EMACSNATIVELOADPATH="$nativeDir:${EMACSNATIVELOADPATH-}" + fi +} + +addEmacsVars () { + addToEmacsLoadPath "$1/share/emacs/site-lisp" + + if [ -n "${addEmacsNativeLoadPath:-}" ]; then + addToEmacsNativeLoadPath "$1/share/emacs/native-lisp" + fi + + # Add sub paths to the Emacs load path if it is a directory + # containing .el files. This is necessary to build some packages, + # e.g., using trivialBuild. + for lispDir in \ + "$1/share/emacs/site-lisp/"* \ + "$1/share/emacs/site-lisp/elpa/"*; do + if [[ -d $lispDir && "$(echo "$lispDir"/*.el)" ]] ; then + addToEmacsLoadPath "$lispDir" + fi + done +} diff --git a/nixpkgs/pkgs/build-support/emacs/generic.nix b/nixpkgs/pkgs/build-support/emacs/generic.nix new file mode 100644 index 000000000000..bdf1cd4e50f3 --- /dev/null +++ b/nixpkgs/pkgs/build-support/emacs/generic.nix @@ -0,0 +1,90 @@ +# generic builder for Emacs packages + +{ lib, stdenv, emacs, texinfo, writeText, gcc, ... }: + +let + inherit (lib) optionalAttrs getLib; + handledArgs = [ "buildInputs" "packageRequires" "meta" ]; + + setupHook = writeText "setup-hook.sh" '' + source ${./emacs-funcs.sh} + + if [[ ! -v emacsHookDone ]]; then + emacsHookDone=1 + + # If this is for a wrapper derivation, emacs and the dependencies are all + # run-time dependencies. If this is for precompiling packages into bytecode, + # emacs is a compile-time dependency of the package. + addEnvHooks "$hostOffset" addEmacsVars + addEnvHooks "$targetOffset" addEmacsVars + fi + ''; + +in + +{ pname +, version +, buildInputs ? [] +, packageRequires ? [] +, meta ? {} +, ... +}@args: + +stdenv.mkDerivation (finalAttrs: ({ + name = "emacs-${pname}-${finalAttrs.version}"; + + unpackCmd = '' + case "$curSrc" in + *.el) + # keep original source filename without the hash + local filename=$(basename "$curSrc") + filename="''${filename:33}" + cp $curSrc $filename + chmod +w $filename + sourceRoot="." + ;; + *) + _defaultUnpack "$curSrc" + ;; + esac + ''; + + buildInputs = [emacs texinfo] ++ packageRequires ++ buildInputs; + propagatedBuildInputs = packageRequires; + propagatedUserEnvPkgs = packageRequires; + + inherit setupHook; + + doCheck = false; + + meta = { + broken = false; + platforms = emacs.meta.platforms; + } // optionalAttrs ((args.src.meta.homepage or "") != "") { + homepage = args.src.meta.homepage; + } // meta; +} + +// optionalAttrs (emacs.withNativeCompilation or false) { + + LIBRARY_PATH = "${getLib stdenv.cc.libc}/lib"; + + nativeBuildInputs = [ gcc ]; + + addEmacsNativeLoadPath = true; + + postInstall = '' + # Besides adding the output directory to the native load path, make sure + # the current package's elisp files are in the load path, otherwise + # (require 'file-b) from file-a.el in the same package will fail. + mkdir -p $out/share/emacs/native-lisp + source ${./emacs-funcs.sh} + addEmacsVars "$out" + + find $out/share/emacs -type f -name '*.el' -print0 \ + | xargs -0 -I {} -n 1 -P $NIX_BUILD_CORES sh -c \ + "emacs --batch --eval '(setq large-file-warning-threshold nil)' -f batch-native-compile {} || true" + ''; +} + +// removeAttrs args handledArgs)) diff --git a/nixpkgs/pkgs/build-support/emacs/melpa.nix b/nixpkgs/pkgs/build-support/emacs/melpa.nix new file mode 100644 index 000000000000..c8f656704918 --- /dev/null +++ b/nixpkgs/pkgs/build-support/emacs/melpa.nix @@ -0,0 +1,125 @@ +# builder for Emacs packages built for packages.el +# using MELPA package-build.el + +{ lib, stdenv, fetchFromGitHub, emacs, texinfo, writeText, gcc }: + +let + genericBuild = import ./generic.nix { inherit lib stdenv emacs texinfo writeText gcc; }; + + packageBuild = stdenv.mkDerivation { + name = "package-build"; + src = fetchFromGitHub { + owner = "melpa"; + repo = "package-build"; + rev = "c48aa078c01b4f07b804270c4583a0a58ffea1c0"; + sha256 = "sha256-MzPj375upIiYXdQR+wWXv3A1zMqbSrZlH0taLuxx/1M="; + }; + + patches = [ ./package-build-dont-use-mtime.patch ]; + + dontConfigure = true; + dontBuild = true; + + installPhase = " + mkdir -p $out + cp -r * $out + "; + }; + +in + +{ /* + pname: Nix package name without special symbols and without version or + "emacs-" prefix. + */ + pname + /* + ename: Original Emacs package name, possibly containing special symbols. + Default: pname + */ +, ename ? pname +, version + /* + commit: Optional package history commit. + Default: src.rev or "unknown" + This will be written into the generated package but it is not needed during + the build process. + */ +, commit ? (args.src.rev or "unknown") + /* + files: Optional recipe property specifying the files used to build the package. + If null, do not set it in recipe, keeping the default upstream behaviour. + Default: null + */ +, files ? null + /* + recipe: Optional MELPA recipe. + Default: a minimally functional recipe + */ +, recipe ? (writeText "${pname}-recipe" '' + (${ename} :fetcher git :url "" + ${lib.optionalString (files != null) ":files ${files}"}) + '') +, meta ? {} +, ... +}@args: + +genericBuild ({ + + elpa2nix = ./elpa2nix.el; + melpa2nix = ./melpa2nix.el; + + inherit packageBuild commit ename recipe; + + preUnpack = '' + mkdir -p "$NIX_BUILD_TOP/recipes" + if [ -n "$recipe" ]; then + cp "$recipe" "$NIX_BUILD_TOP/recipes/$ename" + fi + + ln -s "$packageBuild" "$NIX_BUILD_TOP/package-build" + + mkdir -p "$NIX_BUILD_TOP/packages" + ''; + + postUnpack = '' + mkdir -p "$NIX_BUILD_TOP/working" + ln -s "$NIX_BUILD_TOP/$sourceRoot" "$NIX_BUILD_TOP/working/$ename" + ''; + + buildPhase = '' + runHook preBuild + + cd "$NIX_BUILD_TOP" + + emacs --batch -Q \ + -L "$NIX_BUILD_TOP/package-build" \ + -l "$melpa2nix" \ + -f melpa2nix-build-package \ + $ename $version $commit + + runHook postBuild + ''; + + installPhase = '' + runHook preInstall + + archive="$NIX_BUILD_TOP/packages/$ename-$version.el" + if [ ! -f "$archive" ]; then + archive="$NIX_BUILD_TOP/packages/$ename-$version.tar" + fi + + emacs --batch -Q \ + -l "$elpa2nix" \ + -f elpa2nix-install-package \ + "$archive" "$out/share/emacs/site-lisp/elpa" + + runHook postInstall + ''; + + meta = { + homepage = args.src.meta.homepage or "https://melpa.org/#/${pname}"; + } // meta; +} + +// removeAttrs args [ "meta" ]) diff --git a/nixpkgs/pkgs/build-support/emacs/melpa2nix.el b/nixpkgs/pkgs/build-support/emacs/melpa2nix.el new file mode 100644 index 000000000000..3de77dbf5e5c --- /dev/null +++ b/nixpkgs/pkgs/build-support/emacs/melpa2nix.el @@ -0,0 +1,32 @@ +(require 'package) +(package-initialize) + +(require 'package-recipe) +(require 'package-build) + +(setq package-build-working-dir (expand-file-name "working/")) +(setq package-build-archive-dir (expand-file-name "packages/")) +(setq package-build-recipes-dir (expand-file-name "recipes/")) + +;; Allow installing package tarfiles larger than 10MB +(setq large-file-warning-threshold nil) + +(defun melpa2nix-build-package-1 (rcp) + (let* ((default-directory (package-recipe--working-tree rcp))) + (unwind-protect + (let ((files (package-build-expand-files-spec rcp t))) + (unless files + (error "Unable to find files matching recipe patterns")) + (if (> (length files) 1) + (package-build--build-multi-file-package rcp files) + (package-build--build-single-file-package rcp files)))))) + +(defun melpa2nix-build-package () + (unless noninteractive + (error "`melpa2nix-build-package' is to be used only with -batch")) + (pcase command-line-args-left + (`(,package ,version ,commit) + (let ((recipe (package-recipe-lookup package))) + (setf (oref recipe commit) commit) + (setf (oref recipe version) version) + (melpa2nix-build-package-1 recipe))))) diff --git a/nixpkgs/pkgs/build-support/emacs/mk-wrapper-subdirs.el b/nixpkgs/pkgs/build-support/emacs/mk-wrapper-subdirs.el new file mode 100644 index 000000000000..7d30400a5c65 --- /dev/null +++ b/nixpkgs/pkgs/build-support/emacs/mk-wrapper-subdirs.el @@ -0,0 +1,6 @@ +(defmacro mk-subdirs-expr (path) + `(setq load-path + (delete-dups (append '(,path) + ',(let ((default-directory path)) + (normal-top-level-add-subdirs-to-load-path)) + load-path)))) diff --git a/nixpkgs/pkgs/build-support/emacs/package-build-dont-use-mtime.patch b/nixpkgs/pkgs/build-support/emacs/package-build-dont-use-mtime.patch new file mode 100644 index 000000000000..1ace7771ea3a --- /dev/null +++ b/nixpkgs/pkgs/build-support/emacs/package-build-dont-use-mtime.patch @@ -0,0 +1,21 @@ +diff --git a/package-build.el b/package-build.el +index 29cdb61..c19be1b 100644 +--- a/package-build.el ++++ b/package-build.el +@@ -923,7 +923,6 @@ DIRECTORY is a temporary directory that contains the directory + that is put in the tarball." + (let* ((name (oref rcp name)) + (version (oref rcp version)) +- (time (oref rcp time)) + (tar (expand-file-name (concat name "-" version ".tar") + package-build-archive-dir)) + (dir (concat name "-" version))) +@@ -939,7 +938,7 @@ that is put in the tarball." + ;; prevent a reproducible tarball as described at + ;; https://reproducible-builds.org/docs/archives. + "--sort=name" +- (format "--mtime=@%d" time) ++ "--mtime=@0" + "--owner=0" "--group=0" "--numeric-owner" + "--pax-option=exthdr.name=%d/PaxHeaders/%f,delete=atime,delete=ctime")) + (when (and package-build-verbose noninteractive) diff --git a/nixpkgs/pkgs/build-support/emacs/trivial.nix b/nixpkgs/pkgs/build-support/emacs/trivial.nix new file mode 100644 index 000000000000..11c28c0133e4 --- /dev/null +++ b/nixpkgs/pkgs/build-support/emacs/trivial.nix @@ -0,0 +1,28 @@ +# trivial builder for Emacs packages + +{ callPackage, lib, ... }@envargs: + +args: + +callPackage ./generic.nix envargs ({ + buildPhase = '' + runHook preBuild + + emacs -L . --batch -f batch-byte-compile *.el + + runHook postBuild + ''; + + installPhase = '' + runHook preInstall + + LISPDIR=$out/share/emacs/site-lisp + install -d $LISPDIR + install *.el *.elc $LISPDIR + emacs --batch -l package --eval "(package-generate-autoloads \"${args.pname}\" \"$LISPDIR\")" + + runHook postInstall + ''; +} + +// args) diff --git a/nixpkgs/pkgs/build-support/emacs/wrapper.nix b/nixpkgs/pkgs/build-support/emacs/wrapper.nix new file mode 100644 index 000000000000..59a694286d09 --- /dev/null +++ b/nixpkgs/pkgs/build-support/emacs/wrapper.nix @@ -0,0 +1,236 @@ +/* + +# Usage + +`emacs.pkgs.withPackages` takes a single argument: a function from a package +set to a list of packages (the packages that will be available in +Emacs). For example, +``` +emacs.pkgs.withPackages (epkgs: [ epkgs.evil epkgs.magit ]) +``` +All the packages in the list should come from the provided package +set. It is possible to add any package to the list, but the provided +set is guaranteed to have consistent dependencies and be built with +the correct version of Emacs. + +# Overriding + +`emacs.pkgs.withPackages` inherits the package set which contains it, so the +correct way to override the provided package set is to override the +set which contains `emacs.pkgs.withPackages`. For example, to override +`emacs.pkgs.emacs.pkgs.withPackages`, +``` +let customEmacsPackages = + emacs.pkgs.overrideScope (self: super: { + # use a custom version of emacs + emacs = ...; + # use the unstable MELPA version of magit + magit = self.melpaPackages.magit; + }); +in customEmacsPackages.withPackages (epkgs: [ epkgs.evil epkgs.magit ]) +``` + +*/ + +{ lib, lndir, makeBinaryWrapper, runCommand, gcc }: +self: +let + inherit (self) emacs; + withNativeCompilation = emacs.withNativeCompilation or false; + withTreeSitter = emacs.withTreeSitter or false; +in +packagesFun: # packages explicitly requested by the user +let + explicitRequires = + if lib.isFunction packagesFun + then packagesFun self + else packagesFun; +in +runCommand + (lib.appendToName "with-packages" emacs).name + { + inherit emacs explicitRequires; + nativeBuildInputs = [ emacs lndir makeBinaryWrapper ]; + + preferLocalBuild = true; + allowSubstitutes = false; + + # Store all paths we want to add to emacs here, so that we only need to add + # one path to the load lists + deps = runCommand "emacs-packages-deps" + ({ + inherit explicitRequires lndir emacs; + nativeBuildInputs = lib.optional withNativeCompilation gcc; + } // lib.optionalAttrs withNativeCompilation { + inherit (emacs) LIBRARY_PATH; + }) + '' + findInputsOld() { + local pkg="$1"; shift + local var="$1"; shift + local propagatedBuildInputsFiles=("$@") + + # TODO(@Ericson2314): Restore using associative array once Darwin + # nix-shell doesn't use impure bash. This should replace the O(n) + # case with an O(1) hash map lookup, assuming bash is implemented + # well :D. + local varSlice="$var[*]" + # ''${..-} to hack around old bash empty array problem + case "''${!varSlice-}" in + *" $pkg "*) return 0 ;; + esac + unset -v varSlice + + eval "$var"'+=("$pkg")' + + if ! [ -e "$pkg" ]; then + echo "build input $pkg does not exist" >&2 + exit 1 + fi + + local file + for file in "''${propagatedBuildInputsFiles[@]}"; do + file="$pkg/nix-support/$file" + [[ -f "$file" ]] || continue + + local pkgNext + for pkgNext in $(< "$file"); do + findInputsOld "$pkgNext" "$var" "''${propagatedBuildInputsFiles[@]}" + done + done + } + mkdir -p $out/bin + mkdir -p $out/share/emacs/site-lisp + ${lib.optionalString withNativeCompilation '' + mkdir -p $out/share/emacs/native-lisp + ''} + ${lib.optionalString withTreeSitter '' + mkdir -p $out/lib + ''} + + local requires + for pkg in $explicitRequires; do + findInputsOld $pkg requires propagated-user-env-packages + done + # requires now holds all requested packages and their transitive dependencies + + linkPath() { + local pkg=$1 + local origin_path=$2 + local dest_path=$3 + + # Add the path to the search path list, but only if it exists + if [[ -d "$pkg/$origin_path" ]]; then + $lndir/bin/lndir -silent "$pkg/$origin_path" "$out/$dest_path" + fi + } + + linkEmacsPackage() { + linkPath "$1" "bin" "bin" + linkPath "$1" "share/emacs/site-lisp" "share/emacs/site-lisp" + ${lib.optionalString withNativeCompilation '' + linkPath "$1" "share/emacs/native-lisp" "share/emacs/native-lisp" + ''} + ${lib.optionalString withTreeSitter '' + linkPath "$1" "lib" "lib" + ''} + } + + # Iterate over the array of inputs (avoiding nix's own interpolation) + for pkg in "''${requires[@]}"; do + linkEmacsPackage $pkg + done + + siteStart="$out/share/emacs/site-lisp/site-start.el" + siteStartByteCompiled="$siteStart"c + subdirs="$out/share/emacs/site-lisp/subdirs.el" + subdirsByteCompiled="$subdirs"c + + # A dependency may have brought the original siteStart or subdirs, delete + # it and create our own + # Begin the new site-start.el by loading the original, which sets some + # NixOS-specific paths. Paths are searched in the reverse of the order + # they are specified in, so user and system profile paths are searched last. + # + # NOTE: Avoid displaying messages early at startup by binding + # inhibit-message to t. This would prevent the Emacs GUI from showing up + # prematurely. The messages would still be logged to the *Messages* + # buffer. + rm -f $siteStart $siteStartByteCompiled $subdirs $subdirsByteCompiled + cat >"$siteStart" <<EOF + (let ((inhibit-message t)) + (load "$emacs/share/emacs/site-lisp/site-start")) + ;; "$out/share/emacs/site-lisp" is added to load-path in wrapper.sh + ;; "$out/share/emacs/native-lisp" is added to native-comp-eln-load-path in wrapper.sh + (add-to-list 'exec-path "$out/bin") + ${lib.optionalString withTreeSitter '' + (add-to-list 'treesit-extra-load-path "$out/lib/") + ''} + EOF + + # Generate a subdirs.el that statically adds all subdirectories to load-path. + $emacs/bin/emacs \ + --batch \ + --load ${./mk-wrapper-subdirs.el} \ + --eval "(prin1 (macroexpand-1 '(mk-subdirs-expr \"$out/share/emacs/site-lisp\")))" \ + > "$subdirs" + + # Byte-compiling improves start-up time only slightly, but costs nothing. + $emacs/bin/emacs --batch -f batch-byte-compile "$siteStart" "$subdirs" + + ${lib.optionalString withNativeCompilation '' + $emacs/bin/emacs --batch \ + --eval "(add-to-list 'native-comp-eln-load-path \"$out/share/emacs/native-lisp/\")" \ + -f batch-native-compile "$siteStart" "$subdirs" + ''} + ''; + + inherit (emacs) meta; + } + '' + mkdir -p "$out/bin" + + # Wrap emacs and friends so they find our site-start.el before the original. + for prog in $emacs/bin/*; do # */ + local progname=$(basename "$prog") + rm -f "$out/bin/$progname" + + substitute ${./wrapper.sh} $out/bin/$progname \ + --subst-var-by bash ${emacs.stdenv.shell} \ + --subst-var-by wrapperSiteLisp "$deps/share/emacs/site-lisp" \ + --subst-var-by wrapperSiteLispNative "$deps/share/emacs/native-lisp" \ + --subst-var prog + chmod +x $out/bin/$progname + # Create a “NOP” binary wrapper for the pure sake of it becoming a + # non-shebang, actual binary. See the makeBinaryWrapper docs for rationale + # (summary: it allows you to use emacs as a shebang itself on Darwin, + # e.g. #!$ {emacs}/bin/emacs --script) + wrapProgramBinary $out/bin/$progname + done + + # Wrap MacOS app + # this has to pick up resources and metadata + # to recognize it as an "app" + if [ -d "$emacs/Applications/Emacs.app" ]; then + mkdir -p $out/Applications/Emacs.app/Contents/MacOS + cp -r $emacs/Applications/Emacs.app/Contents/Info.plist \ + $emacs/Applications/Emacs.app/Contents/PkgInfo \ + $emacs/Applications/Emacs.app/Contents/Resources \ + $out/Applications/Emacs.app/Contents + + + substitute ${./wrapper.sh} $out/Applications/Emacs.app/Contents/MacOS/Emacs \ + --subst-var-by bash ${emacs.stdenv.shell} \ + --subst-var-by wrapperSiteLisp "$deps/share/emacs/site-lisp" \ + --subst-var-by wrapperSiteLispNative "$deps/share/emacs/native-lisp" \ + --subst-var-by prog "$emacs/Applications/Emacs.app/Contents/MacOS/Emacs" + chmod +x $out/Applications/Emacs.app/Contents/MacOS/Emacs + wrapProgramBinary $out/Applications/Emacs.app/Contents/MacOS/Emacs + fi + + mkdir -p $out/share + # Link icons and desktop files into place + for dir in applications icons info man; do + ln -s $emacs/share/$dir $out/share/$dir + done + '' diff --git a/nixpkgs/pkgs/build-support/emacs/wrapper.sh b/nixpkgs/pkgs/build-support/emacs/wrapper.sh new file mode 100644 index 000000000000..44762bd4582b --- /dev/null +++ b/nixpkgs/pkgs/build-support/emacs/wrapper.sh @@ -0,0 +1,53 @@ +#!@bash@ + +IFS=: + +newLoadPath=() +newNativeLoadPath=() +addedNewLoadPath= +addedNewNativeLoadPath= + +if [[ -n $EMACSLOADPATH ]] +then + while read -rd: entry + do + if [[ -z $entry && -z $addedNewLoadPath ]] + then + newLoadPath+=(@wrapperSiteLisp@) + addedNewLoadPath=1 + fi + newLoadPath+=("$entry") + done <<< "$EMACSLOADPATH:" +else + newLoadPath+=(@wrapperSiteLisp@) + newLoadPath+=("") +fi + +# NOTE: Even though we treat EMACSNATIVELOADPATH like EMACSLOADPATH in +# this wrapper, empty elements in EMACSNATIVELOADPATH have no special +# meaning for Emacs. Only non-empty elements in EMACSNATIVELOADPATH +# will be prepended to native-comp-eln-load-path. +# https://git.savannah.gnu.org/cgit/emacs.git/tree/lisp/startup.el?id=3685387e609753293c4518be75e77c659c3b2d8d#n599 +if [[ -n $EMACSNATIVELOADPATH ]] +then + while read -rd: entry + do + if [[ -z $entry && -z $addedNewNativeLoadPath ]] + then + newNativeLoadPath+=(@wrapperSiteLispNative@) + addedNewNativeLoadPath=1 + fi + newNativeLoadPath+=("$entry") + done <<< "$EMACSNATIVELOADPATH:" +else + newNativeLoadPath+=(@wrapperSiteLispNative@) + newNativeLoadPath+=("") +fi + +export EMACSLOADPATH="${newLoadPath[*]}" +export emacsWithPackages_siteLisp=@wrapperSiteLisp@ + +export EMACSNATIVELOADPATH="${newNativeLoadPath[*]}" +export emacsWithPackages_siteLispNative=@wrapperSiteLispNative@ + +exec @prog@ "$@" |