Skip to content

Commit

Permalink
fix: manual environment switching with overlapping environments (#397)
Browse files Browse the repository at this point in the history
Previously, we deactivated previous environment after activating a new
one. This meant that if both evironments shared env variables, the
deactivation script would unset them for the new environment, leaving
them unset. This was not issue when using shell-hooks, as those
explicitly deactivate an environment before activating a new one.

For example:
```
> cat env1/bin/hermit.hcl
env = {
  FOO:BAR,
}
> cat env2/bin/hermit.hcl
env = {
  FOO:BAR,
}
> cd env1
> . bin/activate-hermit
Hermit environment /Users/juho/tmp/hermit-test/env1 activated
> echo $FOO
BAR
> cd ../env2
> . bin/activate-hermit
Hermit environment /Users/juho/tmp/hermit-test/env1 deactivated
Hermit environment /Users/juho/tmp/hermit-test/env2 activated
> echo $FOO

>
```
  • Loading branch information
jvmakine authored Mar 13, 2024
1 parent a3b130e commit 4a6b052
Show file tree
Hide file tree
Showing 12 changed files with 159 additions and 5 deletions.
9 changes: 9 additions & 0 deletions integration/integration_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -265,6 +265,15 @@ func TestIntegration(t *testing.T) {
assert test "$(testbin1.sh)" = "FOO=runtimefoo"
assert test "$(testbin2.sh)" = "BAR=hermitbar"
`},
{name: "EnvironmentsWithOverlappingEnvVariablesCanBeSwitched",
preparations: prep{fixture("overlapping_envs")},
script: `
. env1/bin/activate-hermit
assert test "$FOO" = "BAR"
. env2/bin/activate-hermit
assert test "$FOO" = "BAR"
`},
}

checkForShells(t)
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
# Hermit environment

This is a [Hermit](https://github.com/cashapp/hermit) bin directory.

The symlinks in this directory are managed by Hermit and will automatically
download and install Hermit itself as well as packages. These packages are
local to this environment.
19 changes: 19 additions & 0 deletions integration/testdata/overlapping_envs/env1/bin/activate-hermit
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
#!/bin/bash
# This file must be used with "source bin/activate-hermit" from bash or zsh.
# You cannot run it directly

if [ "${BASH_SOURCE-}" = "$0" ]; then
echo "You must source this script: \$ source $0" >&2
exit 33
fi

BIN_DIR="$(dirname "${BASH_SOURCE[0]:-${(%):-%x}}")"
if "${BIN_DIR}/hermit" noop > /dev/null; then
eval "$("${BIN_DIR}/hermit" activate "${BIN_DIR}/..")"

if [ -n "${BASH-}" ] || [ -n "${ZSH_VERSION-}" ]; then
hash -r 2>/dev/null
fi

echo "Hermit environment $("${HERMIT_ENV}"/bin/hermit env HERMIT_ENV) activated"
fi
41 changes: 41 additions & 0 deletions integration/testdata/overlapping_envs/env1/bin/hermit
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
#!/bin/bash

set -eo pipefail

export HERMIT_USER_HOME=~

if [ -z "${HERMIT_STATE_DIR}" ]; then
case "$(uname -s)" in
Darwin)
export HERMIT_STATE_DIR="${HERMIT_USER_HOME}/Library/Caches/hermit"
;;
Linux)
export HERMIT_STATE_DIR="${XDG_CACHE_HOME:-${HERMIT_USER_HOME}/.cache}/hermit"
;;
esac
fi

export HERMIT_DIST_URL="${HERMIT_DIST_URL:-https://github.com/cashapp/hermit/releases/download/stable}"
HERMIT_CHANNEL="$(basename "${HERMIT_DIST_URL}")"
export HERMIT_CHANNEL
export HERMIT_EXE=${HERMIT_EXE:-${HERMIT_STATE_DIR}/pkg/hermit@${HERMIT_CHANNEL}/hermit}

if [ ! -x "${HERMIT_EXE}" ]; then
echo "Bootstrapping ${HERMIT_EXE} from ${HERMIT_DIST_URL}" 1>&2
INSTALL_SCRIPT="$(mktemp)"
# This value must match that of the install script
INSTALL_SCRIPT_SHA256="180e997dd837f839a3072a5e2f558619b6d12555cd5452d3ab19d87720704e38"
if [ "${INSTALL_SCRIPT_SHA256}" = "BYPASS" ]; then
curl -fsSL "${HERMIT_DIST_URL}/install.sh" -o "${INSTALL_SCRIPT}"
else
# Install script is versioned by its sha256sum value
curl -fsSL "${HERMIT_DIST_URL}/install-${INSTALL_SCRIPT_SHA256}.sh" -o "${INSTALL_SCRIPT}"
# Verify install script's sha256sum
openssl dgst -sha256 "${INSTALL_SCRIPT}" | \
awk -v EXPECTED="$INSTALL_SCRIPT_SHA256" \
'$2!=EXPECTED {print "Install script sha256 " $2 " does not match " EXPECTED; exit 1}'
fi
/bin/bash "${INSTALL_SCRIPT}" 1>&2
fi

exec "${HERMIT_EXE}" --level=fatal exec "$0" -- "$@"
3 changes: 3 additions & 0 deletions integration/testdata/overlapping_envs/env1/bin/hermit.hcl
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
env = {
"FOO": "BAR",
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
# Hermit environment

This is a [Hermit](https://github.com/cashapp/hermit) bin directory.

The symlinks in this directory are managed by Hermit and will automatically
download and install Hermit itself as well as packages. These packages are
local to this environment.
19 changes: 19 additions & 0 deletions integration/testdata/overlapping_envs/env2/bin/activate-hermit
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
#!/bin/bash
# This file must be used with "source bin/activate-hermit" from bash or zsh.
# You cannot run it directly

if [ "${BASH_SOURCE-}" = "$0" ]; then
echo "You must source this script: \$ source $0" >&2
exit 33
fi

BIN_DIR="$(dirname "${BASH_SOURCE[0]:-${(%):-%x}}")"
if "${BIN_DIR}/hermit" noop > /dev/null; then
eval "$("${BIN_DIR}/hermit" activate "${BIN_DIR}/..")"

if [ -n "${BASH-}" ] || [ -n "${ZSH_VERSION-}" ]; then
hash -r 2>/dev/null
fi

echo "Hermit environment $("${HERMIT_ENV}"/bin/hermit env HERMIT_ENV) activated"
fi
41 changes: 41 additions & 0 deletions integration/testdata/overlapping_envs/env2/bin/hermit
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
#!/bin/bash

set -eo pipefail

export HERMIT_USER_HOME=~

if [ -z "${HERMIT_STATE_DIR}" ]; then
case "$(uname -s)" in
Darwin)
export HERMIT_STATE_DIR="${HERMIT_USER_HOME}/Library/Caches/hermit"
;;
Linux)
export HERMIT_STATE_DIR="${XDG_CACHE_HOME:-${HERMIT_USER_HOME}/.cache}/hermit"
;;
esac
fi

export HERMIT_DIST_URL="${HERMIT_DIST_URL:-https://github.com/cashapp/hermit/releases/download/stable}"
HERMIT_CHANNEL="$(basename "${HERMIT_DIST_URL}")"
export HERMIT_CHANNEL
export HERMIT_EXE=${HERMIT_EXE:-${HERMIT_STATE_DIR}/pkg/hermit@${HERMIT_CHANNEL}/hermit}

if [ ! -x "${HERMIT_EXE}" ]; then
echo "Bootstrapping ${HERMIT_EXE} from ${HERMIT_DIST_URL}" 1>&2
INSTALL_SCRIPT="$(mktemp)"
# This value must match that of the install script
INSTALL_SCRIPT_SHA256="180e997dd837f839a3072a5e2f558619b6d12555cd5452d3ab19d87720704e38"
if [ "${INSTALL_SCRIPT_SHA256}" = "BYPASS" ]; then
curl -fsSL "${HERMIT_DIST_URL}/install.sh" -o "${INSTALL_SCRIPT}"
else
# Install script is versioned by its sha256sum value
curl -fsSL "${HERMIT_DIST_URL}/install-${INSTALL_SCRIPT_SHA256}.sh" -o "${INSTALL_SCRIPT}"
# Verify install script's sha256sum
openssl dgst -sha256 "${INSTALL_SCRIPT}" | \
awk -v EXPECTED="$INSTALL_SCRIPT_SHA256" \
'$2!=EXPECTED {print "Install script sha256 " $2 " does not match " EXPECTED; exit 1}'
fi
/bin/bash "${INSTALL_SCRIPT}" 1>&2
fi

exec "${HERMIT_EXE}" --level=fatal exec "$0" -- "$@"
3 changes: 3 additions & 0 deletions integration/testdata/overlapping_envs/env2/bin/hermit.hcl
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
env = {
"FOO": "BAR",
}
4 changes: 4 additions & 0 deletions shell/files/activate.tmpl.sh
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,10 @@ if [ -n "${ACTIVE_HERMIT+_}" ]; then
fi
fi

{{ range $ENV_NAME, $ENV_VALUE := .Env }}
export {{ $ENV_NAME }}={{ $ENV_VALUE | Quote }}
{{ end }}

_hermit_deactivate() {
echo "Hermit environment $(${HERMIT_ENV}/bin/hermit env HERMIT_ENV) deactivated"
eval "$(${ACTIVE_HERMIT}/bin/hermit env --deactivate-from-ops="${HERMIT_ENV_OPS}")"
Expand Down
8 changes: 6 additions & 2 deletions shell/posix.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,8 @@ package shell
import (
_ "embed" // Embedding files.
"fmt"
"html/template"
"io"
"text/template"

"github.com/cashapp/hermit/envars"
"github.com/cashapp/hermit/errors"
Expand All @@ -13,7 +13,11 @@ import (
var (
//go:embed files/activate.tmpl.sh
posixActivationScript string
posixActivationScriptTmpl = template.Must(template.New("activation").Parse(posixActivationScript))
posixActivationScriptTmpl = template.Must(
template.New("activation").
Funcs(template.FuncMap{"Quote": Quote}).
Parse(posixActivationScript),
)
)

// Template context for activation script.
Expand Down
3 changes: 0 additions & 3 deletions shell/shell.go
Original file line number Diff line number Diff line change
Expand Up @@ -158,9 +158,6 @@ func (c Changes) Merge(o *Changes) *Changes {

// ActivateHermit prints out the hermit activation script for the given shell.
func ActivateHermit(w io.Writer, shell Shell, config ActivationConfig) error {
if err := shell.ApplyEnvars(w, config.Env); err != nil {
return errors.WithStack(err)
}
if err := shell.ActivationScript(w, config); err != nil {
return errors.WithStack(err)
}
Expand Down

0 comments on commit 4a6b052

Please sign in to comment.