Skip to content

Commit e0660ab

Browse files
committed
fix: reuse local install lifecycle
1 parent 50ef62e commit e0660ab

5 files changed

Lines changed: 119 additions & 9 deletions

File tree

‎README.md‎

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,16 @@ Development bootstrap:
2121
./install.sh --dev
2222
```
2323

24+
Reusing an existing local install:
25+
26+
```bash
27+
./install.sh
28+
./install.sh --dev
29+
./install.sh --reinstall
30+
```
31+
32+
`install.sh` now reuses the existing `.venv` and skips `pip install -e` when `pyproject.toml` and the selected install profile are unchanged. This matches pip's editable-install behavior: reinstall is mainly needed when project metadata changes.
33+
2434
The installer also publishes launchers into `~/.local/bin` by default:
2535

2636
```bash
@@ -41,9 +51,12 @@ Standalone uninstall:
4151

4252
```bash
4353
./uninstall.sh
54+
./uninstall.sh --purge-venv
4455
./uninstall.sh --purge-config --purge-state
4556
```
4657

58+
`./uninstall.sh` now removes launchers, completion, and managed shell wiring by default, but preserves `.venv` for faster reinstall. Use `--purge-venv` if you want a full local removal.
59+
4760
## Configuration
4861

4962
Convention de nommage:

‎install.sh‎

Lines changed: 51 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -3,12 +3,14 @@ set -euo pipefail
33

44
ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
55
VENV_DIR="$ROOT_DIR/.venv"
6+
INSTALL_STATE_FILE="$VENV_DIR/.o2switch-cli-install-state"
67
PYTHON_BIN="${PYTHON_BIN:-python3}"
78
PACKAGE_SPEC="."
89
RUN_SETUP=1
910
TEST_API=0
1011
ENV_FILE=".env"
1112
LINK_LOCAL_BIN=1
13+
FORCE_REINSTALL=0
1214
LOCAL_BIN_DIR="${HOME}/.local/bin"
1315
COMPLETION_DIR="${HOME}/.local/share/bash-completion/completions"
1416
BASHRC_FILE="${HOME}/.bashrc"
@@ -53,6 +55,15 @@ print_shell_refresh_note() {
5355
echo " Run: source \"$BASHRC_FILE\" && hash -r"
5456
}
5557

58+
compute_install_state() {
59+
PYTHONPATH="$ROOT_DIR" "$PYTHON_BIN" - <<PY
60+
from pathlib import Path
61+
from o2switch_cli.install_support import compute_install_state
62+
63+
print(compute_install_state(Path(r"$ROOT_DIR"), "$PACKAGE_SPEC"))
64+
PY
65+
}
66+
5667
usage() {
5768
cat <<'EOF'
5869
Usage: ./install.sh [OPTIONS]
@@ -61,6 +72,7 @@ Bootstrap o2switch-cli in a local virtual environment and optionally run the set
6172
6273
Options:
6374
--dev Install development dependencies too.
75+
--reinstall Force `pip install -e` even when the existing venv metadata is unchanged.
6476
--skip-setup Do not launch `o2switch-cli config init` after install.
6577
--test-api Ask the setup command to test API access after writing credentials.
6678
--env-file PATH Write credentials to PATH instead of .env.
@@ -75,6 +87,10 @@ while [[ $# -gt 0 ]]; do
7587
PACKAGE_SPEC=".[dev]"
7688
shift
7789
;;
90+
--reinstall)
91+
FORCE_REINSTALL=1
92+
shift
93+
;;
7894
--skip-setup)
7995
RUN_SETUP=0
8096
shift
@@ -103,15 +119,43 @@ while [[ $# -gt 0 ]]; do
103119
esac
104120
done
105121

106-
echo "==> Creating virtual environment in $VENV_DIR"
107-
"$PYTHON_BIN" -m venv "$VENV_DIR"
122+
if [[ -x "$VENV_DIR/bin/python" ]]; then
123+
echo "==> Reusing virtual environment in $VENV_DIR"
124+
else
125+
echo "==> Creating virtual environment in $VENV_DIR"
126+
"$PYTHON_BIN" -m venv "$VENV_DIR"
127+
fi
108128

109-
echo "==> Installing o2switch-cli"
110-
"$VENV_DIR/bin/python" -m pip install --upgrade pip
111-
if [[ "$PACKAGE_SPEC" == ".[dev]" ]]; then
112-
"$VENV_DIR/bin/python" -m pip install -e "${ROOT_DIR}[dev]"
129+
DESIRED_INSTALL_STATE="$(compute_install_state)"
130+
CURRENT_INSTALL_STATE=""
131+
if [[ -f "$INSTALL_STATE_FILE" ]]; then
132+
CURRENT_INSTALL_STATE="$(<"$INSTALL_STATE_FILE")"
133+
fi
134+
135+
INSTALL_REASON=""
136+
if [[ "$FORCE_REINSTALL" -eq 1 ]]; then
137+
INSTALL_REASON="forced reinstall"
138+
elif ! "$VENV_DIR/bin/python" -m pip show o2switch-cli >/dev/null 2>&1; then
139+
INSTALL_REASON="package missing from venv"
140+
elif [[ ! -x "$VENV_DIR/bin/o2switch-cli" ]]; then
141+
INSTALL_REASON="launcher missing from venv"
142+
elif [[ "$CURRENT_INSTALL_STATE" != "$DESIRED_INSTALL_STATE" ]]; then
143+
INSTALL_REASON="project metadata changed"
144+
fi
145+
146+
if [[ -n "$INSTALL_REASON" ]]; then
147+
echo "==> Installing o2switch-cli"
148+
echo " Reason: $INSTALL_REASON"
149+
"$VENV_DIR/bin/python" -m pip install --upgrade pip
150+
if [[ "$PACKAGE_SPEC" == ".[dev]" ]]; then
151+
"$VENV_DIR/bin/python" -m pip install -e "${ROOT_DIR}[dev]"
152+
else
153+
"$VENV_DIR/bin/python" -m pip install -e "$ROOT_DIR"
154+
fi
155+
printf '%s\n' "$DESIRED_INSTALL_STATE" > "$INSTALL_STATE_FILE"
113156
else
114-
"$VENV_DIR/bin/python" -m pip install -e "$ROOT_DIR"
157+
echo "==> Reusing existing editable install"
158+
echo " Metadata unchanged; skipping pip install"
115159
fi
116160

117161
echo "==> Installed successfully"

‎o2switch_cli/install_support.py‎

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
from __future__ import annotations
2+
3+
from hashlib import sha256
4+
from pathlib import Path
5+
6+
7+
def compute_install_state(root_dir: Path, package_spec: str) -> str:
8+
resolved_root = root_dir.expanduser().resolve()
9+
pyproject_path = resolved_root / "pyproject.toml"
10+
11+
hasher = sha256()
12+
hasher.update(str(resolved_root).encode("utf-8"))
13+
hasher.update(b"\0")
14+
hasher.update(package_spec.encode("utf-8"))
15+
hasher.update(b"\0")
16+
hasher.update(pyproject_path.read_bytes())
17+
return hasher.hexdigest()

‎tests/test_install_support.py‎

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
from __future__ import annotations
2+
3+
from pathlib import Path
4+
5+
from o2switch_cli.install_support import compute_install_state
6+
7+
8+
def test_compute_install_state_changes_with_package_spec(tmp_path: Path) -> None:
9+
(tmp_path / "pyproject.toml").write_text("[project]\nname = 'demo'\n")
10+
base = compute_install_state(tmp_path, ".")
11+
dev = compute_install_state(tmp_path, ".[dev]")
12+
assert base != dev
13+
14+
15+
def test_compute_install_state_changes_with_pyproject_content(tmp_path: Path) -> None:
16+
pyproject = tmp_path / "pyproject.toml"
17+
pyproject.write_text("[project]\nname = 'demo'\n")
18+
before = compute_install_state(tmp_path, ".")
19+
pyproject.write_text("[project]\nname = 'demo'\nversion = '0.2.0'\n")
20+
after = compute_install_state(tmp_path, ".")
21+
assert before != after

‎uninstall.sh‎

Lines changed: 17 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ COMPLETION_DIR="${HOME}/.local/share/bash-completion/completions"
88
ENV_FILE="$ROOT_DIR/.env"
99
REMOVE_ENV=0
1010
REMOVE_STATE=0
11+
PURGE_VENV=0
1112
YES=0
1213
BASHRC_FILE="${HOME}/.bashrc"
1314
BASHRC_MARKER_START="# >>> o2switch-cli >>>"
@@ -74,6 +75,7 @@ Usage: ./uninstall.sh [OPTIONS]
7475
Remove the local o2switch-cli installation created by ./install.sh.
7576
7677
Options:
78+
--purge-venv Also remove the repository .venv. By default it is kept for fast reinstall.
7779
--purge-config Also remove the repository .env file.
7880
--purge-state Also remove the default audit log file and its state directory.
7981
--yes Do not ask for confirmation.
@@ -83,6 +85,10 @@ EOF
8385

8486
while [[ $# -gt 0 ]]; do
8587
case "$1" in
88+
--purge-venv)
89+
PURGE_VENV=1
90+
shift
91+
;;
8692
--purge-config)
8793
REMOVE_ENV=1
8894
shift
@@ -112,11 +118,15 @@ STATE_DIR="$(dirname "$AUDIT_LOG_PATH")"
112118

113119
if [[ "$YES" -ne 1 ]]; then
114120
echo "This will remove:"
115-
echo " $VENV_DIR"
116121
echo " $LOCAL_BIN_DIR/o2switch-cli"
117122
echo " $LOCAL_BIN_DIR/o2switch_cli"
118123
echo " $COMPLETION_DIR/o2switch-cli"
119124
echo " $COMPLETION_DIR/o2switch_cli"
125+
if [[ "$PURGE_VENV" -eq 1 ]]; then
126+
echo " $VENV_DIR"
127+
else
128+
echo " $VENV_DIR (preserved)"
129+
fi
120130
if [[ "$REMOVE_ENV" -eq 1 ]]; then
121131
echo " $ENV_FILE"
122132
fi
@@ -143,7 +153,9 @@ else
143153
remove_bashrc_completion_block
144154
fi
145155

146-
rm -rf "$VENV_DIR"
156+
if [[ "$PURGE_VENV" -eq 1 ]]; then
157+
rm -rf "$VENV_DIR"
158+
fi
147159

148160
for launcher in "$LOCAL_BIN_DIR/o2switch-cli" "$LOCAL_BIN_DIR/o2switch_cli"; do
149161
if [[ -e "$launcher" || -L "$launcher" ]]; then
@@ -162,4 +174,7 @@ if [[ "$REMOVE_STATE" -eq 1 ]]; then
162174
fi
163175

164176
echo "==> Uninstall completed"
177+
if [[ "$PURGE_VENV" -eq 0 ]]; then
178+
echo " Preserved $VENV_DIR for faster future installs"
179+
fi
165180
print_shell_refresh_note

0 commit comments

Comments
 (0)