├── .github
├── FUNDING.yml
└── workflows
│ └── ci.yml
├── Makefile
├── README.md
├── inheritenv-tests.el
└── inheritenv.el
/.github/FUNDING.yml:
--------------------------------------------------------------------------------
1 | patreon: sanityinc
2 |
--------------------------------------------------------------------------------
/.github/workflows/ci.yml:
--------------------------------------------------------------------------------
1 | name: CI
2 |
3 | on:
4 | pull_request:
5 | push:
6 | paths-ignore:
7 | - '**.md'
8 |
9 | jobs:
10 | build:
11 | runs-on: ubuntu-latest
12 | strategy:
13 | matrix:
14 | emacs_version:
15 | - 24.4
16 | - 24.5
17 | - 25.1
18 | - 25.3
19 | - 26.1
20 | - 26.3
21 | - 27.1
22 | - 27.2
23 | - 28.1
24 | - 28.2
25 | - 29.1
26 | - snapshot
27 | steps:
28 | - uses: purcell/setup-emacs@master
29 | with:
30 | version: ${{ matrix.emacs_version }}
31 | - uses: actions/checkout@v2
32 | - name: Run tests
33 | run: make
34 |
--------------------------------------------------------------------------------
/Makefile:
--------------------------------------------------------------------------------
1 | EMACS ?= emacs
2 |
3 | # A space-separated list of required package names
4 | NEEDED_PACKAGES = package-lint
5 |
6 | INIT_PACKAGES="(progn \
7 | (require 'package) \
8 | (push '(\"melpa\" . \"https://melpa.org/packages/\") package-archives) \
9 | (package-initialize) \
10 | (dolist (pkg '(${NEEDED_PACKAGES})) \
11 | (unless (package-installed-p pkg) \
12 | (unless (assoc pkg package-archive-contents) \
13 | (package-refresh-contents)) \
14 | (package-install pkg))) \
15 | )"
16 |
17 | all: compile package-lint test clean-elc
18 |
19 | package-lint:
20 | ${EMACS} -Q --eval ${INIT_PACKAGES} -batch -f package-lint-batch-and-exit inheritenv.el
21 |
22 | test:
23 | ${EMACS} -Q --eval ${INIT_PACKAGES} -batch -l inheritenv.el -l inheritenv-tests.el -f ert-run-tests-batch-and-exit
24 |
25 | compile: clean-elc
26 | ${EMACS} -Q --eval ${INIT_PACKAGES} -L . -batch -f batch-byte-compile *.el
27 |
28 | clean-elc:
29 | rm -f f.elc
30 |
31 | .PHONY: all compile clean-elc package-lint
32 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | [](http://melpa.org/#/inheritenv)
2 | [](http://stable.melpa.org/#/inheritenv)
3 | [](https://github.com/purcell/inheritenv/actions/workflows/ci.yml)
4 |
5 |
6 | # Make Emacs temp buffers inherit buffer-local environment variables
7 |
8 | Environment variables in Emacs can be set buffer-locally, like many
9 | Emacs preferences, and this allows users to have different buffer-local
10 | paths for executables in different projects, specified by a
11 | `.dir-locals.el` file or via a `direnv` integration like
12 | [envrc](https://github.com/purcell/envrc).
13 |
14 | However, there's a fairly common pitfall when Emacs libraries run
15 | background processes on behalf of a user: many such libraries run
16 | processes in temporary buffers that do not inherit the calling
17 | buffer's environment if it is set buffer-locally.
18 | This can result in executables not being found,
19 | or the wrong versions of executables being picked up.
20 |
21 | An example is the Emacs built-in command
22 | `shell-command-to-string`. Whatever buffer-local `process-environment`
23 | (or `exec-path`) the user has set, that command will always use the
24 | Emacs-wide default. This is *specified* behaviour, but not *expected*
25 | or *helpful*.
26 |
27 | `inheritenv` provides a couple of tools for dealing with this
28 | issue:
29 |
30 | 1. Library authors can wrap code that plans to execute processes in
31 | temporary buffers with the `inheritenv` macro.
32 | 2. Users can modify commands like `shell-command-to-string` using the
33 | `inheritenv-add-advice` macro.
34 |
35 | ## Installation
36 |
37 | ### Manual
38 |
39 | Ensure `inheritenv.el` is in a directory on your load-path, and add
40 | the following to your `~/.emacs` or `~/.emacs.d/init.el`:
41 |
42 | ```elisp
43 | (require 'inheritenv)
44 | ```
45 |
46 | ### MELPA
47 |
48 | If you're an Emacs 24 user or you have a recent version of
49 | `package.el` you can install `inheritenv` from the
50 | [MELPA](http://melpa.org) repository. The version of
51 | `inheritenv` there will always be up-to-date.
52 |
53 | ## About
54 |
55 | Author: Steve Purcell
56 |
57 | Homepage: https://github.com/purcell/inheritenv
58 |
59 |
60 |
61 | [💝 Support this project and my other Open Source work](https://www.patreon.com/sanityinc)
62 |
63 | [💼 LinkedIn profile](https://uk.linkedin.com/in/stevepurcell)
64 |
65 | [✍ sanityinc.com](https://www.sanityinc.com/)
66 |
--------------------------------------------------------------------------------
/inheritenv-tests.el:
--------------------------------------------------------------------------------
1 | ;;; inheritenv-test.el --- Tests for inheritenv.el -*- lexical-binding: t -*-
2 | ;;; Commentary:
3 | ;;; Code:
4 |
5 | (require 'ert)
6 | (require 'inheritenv)
7 |
8 | (defun inheritenv-test--get-vars-in-temp-buffer (&rest vars)
9 | "Return a list of the values of the named environment VARS."
10 | (with-temp-buffer
11 | (mapcar 'getenv vars)))
12 |
13 | (inheritenv-add-advice 'inheritenv-test--get-vars-in-temp-buffer)
14 |
15 | (ert-deftest inheritenv-test-propagation ()
16 | (with-temp-buffer
17 | (setq-local process-environment '("FOO=BAR"))
18 | ;; Default behaviour
19 | (should
20 | (equal
21 | nil
22 | (with-temp-buffer
23 | (getenv "FOO"))))
24 | ;; With inheritenv
25 | (should
26 | (equal
27 | "BAR"
28 | (inheritenv (with-temp-buffer
29 | (getenv "FOO")))))
30 | (should
31 | (equal
32 | '("BAR")
33 | (inheritenv-test--get-vars-in-temp-buffer "FOO")))))
34 |
35 |
36 |
37 | (provide 'inheritenv-test)
38 | ;;; inheritenv-test.el ends here
39 |
--------------------------------------------------------------------------------
/inheritenv.el:
--------------------------------------------------------------------------------
1 | ;;; inheritenv.el --- Make temp buffers inherit buffer-local environment variables -*- lexical-binding: t; -*-
2 |
3 | ;; Copyright (C) 2021 Steve Purcell
4 |
5 | ;; Author: Steve Purcell
6 | ;; URL: https://github.com/purcell/inheritenv
7 | ;; Package-Requires: ((emacs "24.4"))
8 | ;; Version: 0.2
9 | ;; Keywords: unix
10 |
11 | ;; This program is free software; you can redistribute it and/or modify
12 | ;; it under the terms of the GNU General Public License as published by
13 | ;; the Free Software Foundation, either version 3 of the License, or
14 | ;; (at your option) any later version.
15 |
16 | ;; This program is distributed in the hope that it will be useful,
17 | ;; but WITHOUT ANY WARRANTY; without even the implied warranty of
18 | ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
19 | ;; GNU General Public License for more details.
20 |
21 | ;; You should have received a copy of the GNU General Public License
22 | ;; along with this program. If not, see .
23 |
24 | ;;; Commentary:
25 |
26 | ;; Environment variables in Emacs can be set buffer-locally, like many
27 | ;; Emacs preferences, which allows users to have different buffer-local
28 | ;; paths for executables in different projects, specified by a
29 | ;; ".dir-locals.el" file or via a "direnv" integration like
30 | ;; envrc (see https://github.com/purcell/envrc).
31 |
32 | ;; However, there's a fairly common pitfall when Emacs libraries run
33 | ;; background processes on behalf of a user: many such libraries run
34 | ;; processes in temporary buffers that do not inherit the calling
35 | ;; buffer's environment. This can result in executables not being found,
36 | ;; or the wrong versions of executables being picked up.
37 |
38 | ;; An example is the Emacs built-in command
39 | ;; `shell-command-to-string'. Whatever buffer-local `process-environment'
40 | ;; (or `exec-path') the user has set, that command will always use the
41 | ;; Emacs-wide default. This is *specified* behaviour, but not *expected*
42 | ;; or *helpful*.
43 |
44 | ;; `inheritenv' provides a couple of tools for dealing with this
45 | ;; issue:
46 |
47 | ;; 1. Library authors can wrap code that plans to execute processes in
48 | ;; temporary buffers with the `inheritenv' macro.
49 | ;; 2. End users can modify commands like `shell-command-to-string' using
50 | ;; the `inheritenv-add-advice' macro.
51 |
52 | ;; Any buffer-local values for TRAMP's corresponding vars
53 | ;; (`tramp-remote-path' and `tramp-remote-process-environment') are
54 | ;; also propagated.
55 |
56 | ;;; Code:
57 |
58 | (require 'cl-lib)
59 |
60 |
61 | ;;;###autoload
62 | (defun inheritenv-apply (func &rest args)
63 | "Apply FUNC such that the environment it sees will match the current value.
64 | This is useful if FUNC creates a temp buffer, because that will
65 | not inherit any buffer-local values of variables `exec-path' and
66 | `process-environment'.
67 |
68 | This function is designed for convenient use as an \"around\" advice.
69 |
70 | ARGS is as for ORIG."
71 | (cl-letf* (((default-value 'process-environment) process-environment)
72 | ((default-value 'exec-path) exec-path))
73 | ;; Don't force tramp to be loaded, but propagate its env/path vars if it is
74 | (if (and (boundp 'tramp-remote-path) (boundp 'tramp-remote-process-environment))
75 | (cl-letf* (((default-value 'tramp-remote-path) tramp-remote-path)
76 | ((default-value 'tramp-remote-process-environment) tramp-remote-process-environment))
77 | (apply func args))
78 | (apply func args))))
79 |
80 |
81 | (defmacro inheritenv (&rest body)
82 | "Wrap BODY so that the environment it sees will match the current value.
83 | This is useful if BODY creates a temp buffer, because that will
84 | not inherit any buffer-local values of variables `exec-path' and
85 | `process-environment'."
86 | `(inheritenv-apply (lambda () ,@body)))
87 |
88 |
89 | (defmacro inheritenv-add-advice (func)
90 | "Advise function FUNC with `inheritenv-apply'.
91 | This will ensure that any buffers (including temporary buffers)
92 | created by FUNC will inherit the caller's environment."
93 | `(advice-add ,func :around 'inheritenv-apply))
94 |
95 |
96 | (provide 'inheritenv)
97 | ;;; inheritenv.el ends here
98 |
--------------------------------------------------------------------------------