Skip to content

Shopify/shadowenv

master
Switch branches/tags

Name already in use

A tag already exists with the provided branch name. Many Git commands accept both tag and branch names, so creating this branch may cause unexpected behavior. Are you sure you want to create this branch?
Code

Files

Permalink
Failed to load latest commit information.
Type
Name
Latest commit message
Commit time
 
 
 
 
man
 
 
sh
 
 
src
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Shadowenv

Shadowenv provides a way to perform a set of manipulations to the process environment upon entering a directory in a shell. These manipulations are reversed when leaving the directory, and there is some limited ability to make the manipulations dynamic.

shadowenv in action

In order to use shadowenv, add a line to your shell profile (.zshrc, .bash_profile, or config.fish) reading:

eval "$(shadowenv init bash)" # for bash
eval "$(shadowenv init zsh)"  # for zsh
shadowenv init fish | source  # for fish

With this code loaded, upon entering a directory containing a .shadowenv.d directory, any *.lisp files in that directory will be executed and you will see "activated shadowenv." in your shell.

The syntax for the .shadowenv.d/*.lisp files is Shadowlisp, a minimal Scheme-like language. Unlike other tools like direnv, this has the interesting property of allowing us to do things like simulate chruby reset upon entry into a directory without the user having chruby installed (and undo these changes to the environment when cd'ing back out):

(provide "ruby")

(when-let ((ruby-root (env/get "RUBY_ROOT")))
  (env/remove-from-pathlist "PATH" (path-concat ruby-root "bin"))
  (when-let ((gem-root (env/get "GEM_ROOT")))
    (env/remove-from-pathlist "PATH" (path-concat gem-root "bin"))
    (env/remove-from-pathlist "GEM_PATH" gem-root))
  (when-let ((gem-home (env/get "GEM_HOME")))
    (env/remove-from-pathlist "PATH" (path-concat gem-home "bin"))
    (env/remove-from-pathlist "GEM_PATH" gem-home)))

The intention isn't really for users to write these files directly, nor to commit them to repositories , but for other tool authors to generate configuration on the user's machine. Here's an example of a generated Shadowlisp file for activating ruby 2.7.1:

(provide "ruby" "2.7.1")

(when-let ((ruby-root (env/get "RUBY_ROOT")))
 (env/remove-from-pathlist "PATH" (path-concat ruby-root "bin"))
 (when-let ((gem-root (env/get "GEM_ROOT")))
   (env/remove-from-pathlist "PATH" (path-concat gem-root "bin")))
 (when-let ((gem-home (env/get "GEM_HOME")))
   (env/remove-from-pathlist "PATH" (path-concat gem-home "bin"))))

(env/set "GEM_PATH" ())
(env/set "GEM_HOME" ())
(env/set "RUBYOPT" ())

(env/set "RUBY_ROOT" "/opt/rubies/2.7.1")
(env/prepend-to-pathlist "PATH" "/opt/rubies/2.7.1/bin")
(env/set "RUBY_ENGINE" "ruby")
(env/set "RUBY_VERSION" "2.7.1")
(env/set "GEM_ROOT" "/opt/rubies/2.7.1/lib/ruby/gems/2.7.0")

(when-let ((gem-root (env/get "GEM_ROOT")))
  (env/prepend-to-pathlist "GEM_PATH" gem-root)
  (env/prepend-to-pathlist "PATH" (path-concat gem-root "bin")))

(let ((gem-home
      (path-concat (env/get "HOME") ".gem" (env/get "RUBY_ENGINE") (env/get "RUBY_VERSION"))))
  (do
    (env/set "GEM_HOME" gem-home)
    (env/prepend-to-pathlist "GEM_PATH" gem-home)
    (env/prepend-to-pathlist "PATH" (path-concat gem-home "bin"))))

.shadowenv.d

The .shadowenv.d directory will generally exist at the root of your repository (in the same directory as .git.

We strongly recommend creating gitignore'ing everything under .shadowenv.d (echo '*' > .shadowenv.d/.gitignore).

A .shadowenv.d will contain any number of *.lisp files. These are evaluated in the order in which the OS returns when reading the directory: generally alphabetically. We strongly recommend using a prefix like 090_something.lisp to make it easy to maintain ordering.

.shadowenv.d will also contain a .trust-<fingerprint> file if it has been marked as trusted. (see the trust section).

Language

See https://shopify.github.io/shadowenv/ for Shadowlisp documentation.

Integrations

Shadowenv has plugins for multiple editors and/or IDEs:

Trust

If you cd into a directory containing .shadowenv.d/*.lisp files, they will not be run and you will see a message indicating so. This is for security reasons: we don't want to enable random stuff downloaded from the internet to modify your PATH, for example.

You can run shadowenv trust to mark a directory as trusted.

Technically, running shadowenv trust will create a file at .shadowenv.d/.trust-<fingerprint>, indicating that it's okay for shadowenv to run this code. The .shadowenv.d/.trust-* file contains a cryptographic signature of the directory path. The key is generated the first time shadowenv is run, and the fingerprint is an identifier for the key.