Skip to content

Commit 75d99d0

Browse files
authored
Add install-on-activate to eagerly install selected packages on env activation (#559)
Hermit packages are normally downloaded on-demand when a stub binary in `./bin` is first invoked. This causes problems for packages like openjdk that export environment variables pointing to the package root (e.g. `JAVA_HOME`). Tools like Gradle, Android Studio, and `gradlew` read `JAVA_HOME` directly and run `$JAVA_HOME/bin/java` without going through hermit stubs — if the JDK hasn't been downloaded yet, that path doesn't exist and the tools fail. This adds an `install-on-activate` list to `bin/hermit.hcl` that specifies which packages should be eagerly downloaded and unpacked when the environment is activated: ```hcl install-on-activate = ["openjdk"] ``` Only the listed packages are installed eagerly; everything else remains lazy. This keeps activation fast for environments with many packages while ensuring critical ones like the JDK are always present.
1 parent a373085 commit 75d99d0

9 files changed

Lines changed: 127 additions & 5 deletions

File tree

app/activate_cmd.go

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,9 @@ func (a *activateCmd) Run(l *ui.UI, cache *cache.Cache, sta *state.State, global
3535
if err != nil {
3636
return errors.WithStack(err)
3737
}
38+
if err := env.EnsureInstalled(l); err != nil {
39+
return errors.WithStack(err)
40+
}
3841
messages, err := env.Trigger(l, manifest.EventEnvActivate)
3942
if err != nil {
4043
return errors.WithStack(err)

env.go

Lines changed: 31 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -90,11 +90,12 @@ const (
9090

9191
// Config for a Hermit environment.
9292
type Config struct {
93-
Envars envars.Envars `hcl:"env,optional" help:"Extra environment variables."`
94-
Sources []string `hcl:"sources,optional" help:"Package manifest sources."`
95-
ManageGit bool `hcl:"manage-git,optional" default:"true" help:"Whether Hermit should automatically 'git add' new packages."`
96-
InheritParent bool `hcl:"inherit-parent,optional" default:"false" help:"Whether this environment inherits a potential parent environment from one of the parent directories"`
97-
AddIJPlugin bool `hcl:"idea,optional" default:"false" help:"Whether Hermit should automatically add the IntelliJ IDEA plugin."`
93+
Envars envars.Envars `hcl:"env,optional" help:"Extra environment variables."`
94+
Sources []string `hcl:"sources,optional" help:"Package manifest sources."`
95+
ManageGit bool `hcl:"manage-git,optional" default:"true" help:"Whether Hermit should automatically 'git add' new packages."`
96+
InheritParent bool `hcl:"inherit-parent,optional" default:"false" help:"Whether this environment inherits a potential parent environment from one of the parent directories"`
97+
InstallOnActivate []string `hcl:"install-on-activate,optional" help:"List of packages to eagerly download and unpack when the environment is activated."`
98+
AddIJPlugin bool `hcl:"idea,optional" default:"false" help:"Whether Hermit should automatically add the IntelliJ IDEA plugin."`
9899

99100
GitHubTokenAuth GitHubTokenAuthConfig `hcl:"github-token-auth,block" help:"When to use GitHub token authentication."`
100101
}
@@ -431,6 +432,31 @@ next:
431432
return nil
432433
}
433434

435+
// EnsureInstalled downloads and unpacks packages listed in
436+
// install-on-activate when the environment is activated.
437+
func (e *Env) EnsureInstalled(l *ui.UI) error {
438+
if len(e.config.InstallOnActivate) == 0 {
439+
return nil
440+
}
441+
wanted := map[string]bool{}
442+
for _, name := range e.config.InstallOnActivate {
443+
wanted[name] = true
444+
}
445+
pkgs, err := e.ListInstalled(l)
446+
if err != nil {
447+
return errors.WithStack(err)
448+
}
449+
for _, pkg := range pkgs {
450+
if !wanted[pkg.Reference.Name] {
451+
continue
452+
}
453+
if err := e.state.CacheAndUnpack(l.Task(pkg.Reference.String()), pkg); err != nil {
454+
return errors.WithStack(err)
455+
}
456+
}
457+
return nil
458+
}
459+
434460
// Trigger an event for all installed packages.
435461
func (e *Env) Trigger(l *ui.UI, event manifest.Event) (messages []string, err error) {
436462
pkgs, err := e.ListInstalled(l)

integration/integration_test.go

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -673,6 +673,21 @@ EOF
673673
assert test "${CLEANUP_DONE:-}" = "yes"
674674
`,
675675
},
676+
{
677+
name: "InstallOnActivateEnsuresPackagesAreUnpacked",
678+
preparations: prep{fixture("testenv-install-on-activate"), activate(".")},
679+
script: `
680+
with-prompt-hooks hermit install testbin1
681+
# Clean extracted packages to simulate a fresh clone where
682+
# stubs exist but the package hasn't been unpacked yet.
683+
hermit clean --packages
684+
assert test ! -d "${TESTBIN1_ROOT}"
685+
# Re-activate; install-on-activate should re-download/unpack.
686+
. bin/activate-hermit
687+
assert test -d "${TESTBIN1_ROOT}"
688+
assert test -x "${TESTBIN1_ROOT}/testbin1"
689+
`,
690+
},
676691
}
677692

678693
checkForShells(t)
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
# Hermit environment
2+
3+
This is a [Hermit](https://github.com/cashapp/hermit) bin directory.
4+
5+
The symlinks in this directory are managed by Hermit and will automatically
6+
download and install Hermit itself as well as packages. These packages are
7+
local to this environment.
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
#!/bin/bash
2+
# This file must be used with "source bin/activate-hermit" from bash or zsh.
3+
# You cannot run it directly
4+
5+
if [ "${BASH_SOURCE-}" = "$0" ]; then
6+
echo "You must source this script: \$ source $0" >&2
7+
exit 33
8+
fi
9+
10+
BIN_DIR="$(dirname "${BASH_SOURCE[0]:-${(%):-%x}}")"
11+
if "${BIN_DIR}/hermit" noop > /dev/null; then
12+
eval "$("${BIN_DIR}/hermit" activate "${BIN_DIR}/..")"
13+
14+
if [ -n "${BASH-}" ] || [ -n "${ZSH_VERSION-}" ]; then
15+
hash -r 2>/dev/null
16+
fi
17+
18+
echo "Hermit environment $("${HERMIT_ENV}"/bin/hermit env HERMIT_ENV) activated"
19+
fi
Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
#!/bin/bash
2+
3+
set -eo pipefail
4+
5+
export HERMIT_USER_HOME=~
6+
7+
if [ -z "${HERMIT_STATE_DIR}" ]; then
8+
case "$(uname -s)" in
9+
Darwin)
10+
export HERMIT_STATE_DIR="${HERMIT_USER_HOME}/Library/Caches/hermit"
11+
;;
12+
Linux)
13+
export HERMIT_STATE_DIR="${XDG_CACHE_HOME:-${HERMIT_USER_HOME}/.cache}/hermit"
14+
;;
15+
esac
16+
fi
17+
18+
export HERMIT_DIST_URL="${HERMIT_DIST_URL:-https://github.com/cashapp/hermit/releases/download/stable}"
19+
HERMIT_CHANNEL="$(basename "${HERMIT_DIST_URL}")"
20+
export HERMIT_CHANNEL
21+
export HERMIT_EXE=${HERMIT_EXE:-${HERMIT_STATE_DIR}/pkg/hermit@${HERMIT_CHANNEL}/hermit}
22+
23+
if [ ! -x "${HERMIT_EXE}" ]; then
24+
echo "Bootstrapping ${HERMIT_EXE} from ${HERMIT_DIST_URL}" 1>&2
25+
INSTALL_SCRIPT="$(mktemp)"
26+
# This value must match that of the install script
27+
INSTALL_SCRIPT_SHA256="180e997dd837f839a3072a5e2f558619b6d12555cd5452d3ab19d87720704e38"
28+
if [ "${INSTALL_SCRIPT_SHA256}" = "BYPASS" ]; then
29+
curl -fsSL "${HERMIT_DIST_URL}/install.sh" -o "${INSTALL_SCRIPT}"
30+
else
31+
# Install script is versioned by its sha256sum value
32+
curl -fsSL "${HERMIT_DIST_URL}/install-${INSTALL_SCRIPT_SHA256}.sh" -o "${INSTALL_SCRIPT}"
33+
# Verify install script's sha256sum
34+
openssl dgst -sha256 "${INSTALL_SCRIPT}" | \
35+
awk -v EXPECTED="$INSTALL_SCRIPT_SHA256" \
36+
'$2!=EXPECTED {print "Install script sha256 " $2 " does not match " EXPECTED; exit 1}'
37+
fi
38+
/bin/bash "${INSTALL_SCRIPT}" 1>&2
39+
fi
40+
41+
exec "${HERMIT_EXE}" --level=fatal exec "$0" -- "$@"
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
sources = ["env:///packages"]
2+
install-on-activate = ["testbin1"]
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
description = "Test package one"
2+
env = {
3+
FOO: "bar",
4+
TESTBIN1VERSION: "${version}",
5+
TESTBIN1_ROOT: "${root}",
6+
}
7+
source = "${env}/packages/testbin1.tgz"
8+
binaries = ["testbin1"]
9+
version "1.0.0" "1.0.1" {}
Binary file not shown.

0 commit comments

Comments
 (0)