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
16 changes: 10 additions & 6 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -26,10 +26,14 @@ jobs:
with:
configFile: commitlint.config.mjs

# Lint install.sh: POSIX-only shellcheck plus canonical shfmt formatting.
# Lint the shell that puts a binary on someone's PATH: POSIX-only shellcheck
# plus canonical shfmt formatting. Both scripts here are install-path logic
# guarded by D-0004 — install.sh places a released binary, dev-install.sh
# symlinks a build-tree one — which is why they are held to a standard the
# rest of the repo's shell is not.
# Lightweight — stays on GitHub-hosted runners, same as commit-lint.
shell-lint:
name: Shell Lint (install.sh)
name: Shell Lint (install scripts)
runs-on: ubuntu-latest
timeout-minutes: 5
if: github.event.pull_request.draft == false
Expand All @@ -39,10 +43,10 @@ jobs:
uses: taiki-e/install-action@v2
with:
tool: shfmt
- name: shellcheck -s sh install.sh
run: shellcheck -s sh install.sh
- name: shfmt -d -s -ln posix -i 2 install.sh
run: shfmt -d -s -ln posix -i 2 install.sh
- name: shellcheck -s sh install.sh dev-install.sh
run: shellcheck -s sh install.sh dev-install.sh
- name: shfmt -d -s -ln posix -i 2 install.sh dev-install.sh
run: shfmt -d -s -ln posix -i 2 install.sh dev-install.sh

# Lint install.ps1: PSScriptAnalyzer at Warning severity, install.sh's Windows sibling.
# Lightweight — stays on GitHub-hosted runners, same as shell-lint above. `shell: pwsh`
Expand Down
12 changes: 9 additions & 3 deletions .zavet/decisions/D-0004-never-overwrite-a-development-install.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ guards:
- cli/dira/src/update/replace.rs
- install.sh
- install.ps1
- justfile
- dev-install.sh
origin: recorded
verified: true
---
Expand All @@ -20,8 +20,8 @@ only. Never a build-tree binary.

## Why

`just install` symlinks `target/release/{dira,dirad}` into `~/.local/bin`, so
a contributor's PATH entry points into their build tree. Silently replacing
`just install` symlinks `target/release/{dira,dirad}` into `~/.local/bin` (via
`dev-install.sh`), so a contributor's PATH entry points into their build tree. Silently replacing
that symlink with a released binary destroys their dev loop in a way that is
confusing to diagnose: `cargo build` keeps succeeding, the binary on PATH
just stops changing. Overwriting a file *inside* `target/` is worse. The
Expand All @@ -47,5 +47,11 @@ sees it.
never treat `current_exe()` as authoritative for this check.
- Any new install-like path must reuse `discover_install` rather than
re-deriving the rule.
- The dev install must stay a **symlink**. `dev-install.sh` exists so that
rule sits in one guarded, shellcheck'd file: copying the binaries onto PATH
instead would leave a dev install indistinguishable from a managed one and
silently defeat every refusal above. This guard used to name the whole
`justfile`, which fired on unrelated recipes and taught readers to skim past
it.
- Error text must name `just install` as the way to update a dev build, and
give the exact commands to switch to released binaries.
39 changes: 39 additions & 0 deletions dev-install.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
#!/bin/sh
# Create the development install: point the PATH entry at the build tree.
#
# Driven by `just link` (and so by `just install`); not a user-facing
# installer. install.sh is the one contributors and users run — this is its
# opposite number, the thing that deliberately puts a build-tree binary on
# PATH.
#
# The symlink is load-bearing, not a convenience. D-0004 has install.sh and
# `dira update` refuse to overwrite a development install, and the only thing
# that distinguishes one is that the PATH entry is a symlink into `target/`:
# `discover_install` reads it with `symlink_metadata`, because `current_exe()`
# resolves symlinks on both Linux and macOS and would see an ordinary managed
# install. Copy the binaries here instead of linking them and that detection
# goes blind — `dira update` would overwrite a contributor's dev build and
# `cargo build` would silently stop affecting the binary on PATH, which is the
# exact failure D-0004 exists to prevent. Keep it a symlink.
#
# Idempotent: safe to re-run, and `ln -sf` re-points an existing link rather
# than nesting one inside it.
set -eu

repo_dir="${1:?usage: dev-install.sh <repo-dir> <bin-dir>}"
bin_dir="${2:?usage: dev-install.sh <repo-dir> <bin-dir>}"

for name in dira dirad; do
built="$repo_dir/target/release/$name"
if [ ! -x "$built" ]; then
echo "dev-install: $built is missing — run \`just release\` first." >&2
exit 1
fi
done

mkdir -p "$bin_dir"
for name in dira dirad; do
ln -sf "$repo_dir/target/release/$name" "$bin_dir/$name"
done

echo "Linked dira + dirad -> $bin_dir"
12 changes: 8 additions & 4 deletions justfile
Original file line number Diff line number Diff line change
Expand Up @@ -89,11 +89,15 @@ install: release link daemon-restart
@echo "Installed. `dira` + `dirad` are live from {{bin_dir}} (latest build)."

# Symlink dira + dirad into {{bin_dir}} (idempotent; safe to re-run).
#
# The symlinking itself lives in dev-install.sh rather than inline here. It is
# install-path logic — D-0004's dev-install detection reads the PATH entry with
# `symlink_metadata` and goes blind the moment these become copies — so it
# belongs in a shellcheck'd script beside install.sh, under that decision's
# guard, instead of in a recipe the guard could only reach by claiming the
# whole justfile and every unrelated recipe in it.
link:
mkdir -p "{{bin_dir}}"
ln -sf "{{justfile_directory()}}/target/release/dira" "{{bin_dir}}/dira"
ln -sf "{{justfile_directory()}}/target/release/dirad" "{{bin_dir}}/dirad"
@echo "Linked dira + dirad -> {{bin_dir}}"
sh dev-install.sh "{{justfile_directory()}}" "{{bin_dir}}"

# Restart the resident daemon from the freshly built binary.
#
Expand Down