Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
26 changes: 18 additions & 8 deletions bin/fm-remote-job-lib.sh
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,10 @@
# PATH, HOME, FM_HOME, FM_ROOT_OVERRIDE, and FM_REMOTE_JOB_ACTIVE=1. The PATH
# is intentionally filesystem-discovered rather than login-shell-derived:
# ~/.local/bin; nvm, asdf, and mise shims/install bins; Nix; Homebrew; and the
# system tail. No shell startup files are evaluated.
# system tail. No shell startup files are evaluated. Each discovered set is
# appended in the shell's own sorted pathname-expansion order, so which install
# of a multi-version tool wins is fixed by this composition rather than by the
# order the filesystem happens to return.
#
# On macOS the worker is Firstmate's Aqua LaunchAgent
# dev.firstmate.remote-job at ~/Library/LaunchAgents/dev.firstmate.remote-job.plist
Expand Down Expand Up @@ -130,11 +133,18 @@ fm_remote_job_path_append_resolved_dir() { # <directory>
fm_remote_job_path_append "$physical"
}

fm_remote_job_append_glob_dirs() { # <glob whose matches are directories>
local pattern=$1 directory
while IFS= read -r directory; do
# Callers pass an already-expanded glob rather than the pattern, because only
# the shell's own pathname expansion sorts its matches: bash sorts
# glob_filename's result in pathexp.c, while `compgen -G` reaches the same
# glob_filename through pcomplete.c, which does not sort. On bash 3.2 (macOS
# /bin/bash) that handed back raw readdir order, so which install of a
# multi-version tool a remote job resolved depended on the filesystem instead
# of on this composition.
fm_remote_job_append_dirs() { # <expanded glob matches>
local directory
for directory in "$@"; do
fm_remote_job_path_append_if_dir "$directory"
done < <(compgen -G "$pattern" || true)
done
}

fm_remote_job_nvm_default_selector() { # <account-home>
Expand Down Expand Up @@ -217,11 +227,11 @@ fm_remote_job_compose_operator_path() { # <account-home>
nvm_bin=$(fm_remote_job_nvm_selected_bin "$account_home" 2>/dev/null || true)
[ -z "$nvm_bin" ] || fm_remote_job_path_append "$nvm_bin"
fm_remote_job_path_append_if_dir "$account_home/.asdf/shims"
fm_remote_job_append_glob_dirs "$account_home/.asdf/installs/*/*/bin"
fm_remote_job_append_dirs "$account_home"/.asdf/installs/*/*/bin
fm_remote_job_path_append_if_dir "$account_home/.local/share/mise/shims"
fm_remote_job_path_append_if_dir "$account_home/.mise/shims"
fm_remote_job_append_glob_dirs "$account_home/.local/share/mise/installs/*/*/bin"
fm_remote_job_append_glob_dirs "$account_home/.mise/installs/*/*/bin"
fm_remote_job_append_dirs "$account_home"/.local/share/mise/installs/*/*/bin
fm_remote_job_append_dirs "$account_home"/.mise/installs/*/*/bin
fm_remote_job_path_append_resolved_dir "$account_home/.nix-profile/bin"
account_user=$(id -un 2>/dev/null || true)
if [ -n "$account_user" ]; then
Expand Down
19 changes: 19 additions & 0 deletions tests/fm-remote-job.test.sh
Original file line number Diff line number Diff line change
Expand Up @@ -165,6 +165,25 @@ case ":$FM_REMOTE_JOB_OPERATOR_PATH:" in
esac
pass "operator PATH resolves the authorized Nix profile bin link"

# Which install of a multi-version tool a remote job resolves is decided by the
# order these directories land on PATH, so the composition has to be sorted
# rather than whatever order the filesystem returns. The fixture is created in
# a deliberately unsorted order, and the expectation is the shell's own
# pathname expansion - the mechanism the portable-PATH contract in
# tests/fm-on.test.sh reconstructs.
MISE_INSTALLS="$ACCOUNT_HOME/.local/share/mise/installs"
for TOOL_VERSION in node/26.7.0 node/8.1 node/26 bun/1.4 bun/1.3.14 python/3.12.7; do
mkdir -p "$MISE_INSTALLS/$TOOL_VERSION/bin"
done
fm_remote_job_compose_operator_path "$ACCOUNT_HOME" >/dev/null
MISE_COMPOSED=$(printf '%s\n' "$FM_REMOTE_JOB_OPERATOR_PATH" | tr ':' '\n' | grep -F "$MISE_INSTALLS/" || true)
MISE_EXPECTED=$(printf '%s\n' "$MISE_INSTALLS"/*/*/bin)
[ "$MISE_COMPOSED" = "$MISE_EXPECTED" ] \
|| fail "the composed operator PATH did not order tool installs like the shell's own expansion"$'\n'"expected: $MISE_EXPECTED"$'\n'"actual: $MISE_COMPOSED"
# This assertion detects the defect on bash 3.2 and 5.2, where compgen -G returns unsorted glob matches, but reads green on bash 5.3+ because glob sorting moved into the glob library so both mechanisms agree there.
rm -rf -- "$ACCOUNT_HOME/.local/share/mise"
pass "operator PATH orders discovered tool installs deterministically"

HOME="$ACCOUNT_HOME" PATH="$RUNTIME_BIN:/usr/bin:/bin:/usr/sbin:/sbin" FM_FAKE_PERL_LOG="$FAKE_PERL_LOG" \
FM_ROOT_OVERRIDE="$REMOTE_ROOT" FM_REMOTE_JOB_STATE_ROOT="$STATE_ROOT" \
FM_REMOTE_JOB_PLATFORM_OVERRIDE=Linux FM_REMOTE_JOB_TIMEOUT=5 \
Expand Down
Loading