├── .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 | [![Melpa Status](http://melpa.org/packages/inheritenv-badge.svg)](http://melpa.org/#/inheritenv) 2 | [![Melpa Stable Status](http://stable.melpa.org/packages/inheritenv-badge.svg)](http://stable.melpa.org/#/inheritenv) 3 | [![Build Status](https://github.com/purcell/inheritenv/actions/workflows/ci.yml/badge.svg)](https://github.com/purcell/inheritenv/actions/workflows/ci.yml) 4 | Support me 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 | --------------------------------------------------------------------------------