bootstrap: add automatic .pth write for editable installs so top-level CLI is importable
Some checks failed
smoke-mm / Install & smoke test mm --help (push) Has been cancelled

This commit is contained in:
nose
2025-12-24 04:05:35 -08:00
parent 7598fd5b2a
commit 7c8b0edb5c
4 changed files with 211 additions and 9 deletions

View File

@@ -49,6 +49,8 @@ Running `python ./scripts/setup.py` is intentionally opinionated: it will create
These launchers prefer the local `./.venv` Python and console scripts so you can run the project with `./mm` or `mm.ps1` directly from the repo root.
- When installing in editable mode from a development checkout, the bootstrap will also add a small `.pth` file to the venv's `site-packages` pointing at the repository root. This ensures top-level scripts such as `CLI.py` are importable even when using PEP 660 editable wheels (avoids having to create an egg-link by hand).
Additionally, the setup helpers install a global `mm` launcher into your user bin so you can run `mm` from any shell session:
- POSIX: `~/.local/bin/mm` (created if missing; the script attempts to add `~/.local/bin` to `PATH` by updating `~/.profile` / shell RCs if required)

View File

@@ -170,6 +170,68 @@ if (-not $NoInstall) {
Write-Log "pip install failed: $_" "ERROR"; exit 6
}
# Verify top-level 'CLI' import and (if missing) attempt to make it available
Write-Log "Verifying installed CLI import..."
try {
& $venvPython -c "import importlib; importlib.import_module('medeia_macina.cli_entry')" 2>$null
if ($LASTEXITCODE -eq 0) { Write-Log "OK: 'medeia_macina.cli_entry' is importable in the venv." }
} catch {}
try {
& $venvPython -c "import importlib; importlib.import_module('CLI')" 2>$null
if ($LASTEXITCODE -eq 0) { Write-Log "Top-level 'CLI' is importable in the venv." }
else {
Write-Log "Top-level 'CLI' not importable; attempting to add repo root to venv site-packages via .pth" "INFO"
$sites = Get-SitePackages -python $venvPython
$siteDir = $sites | Where-Object { Test-Path $_ } | Select-Object -First 1
if ($siteDir) {
$pth = Join-Path $siteDir 'medeia_repo.pth'
if (Test-Path $pth) {
if (-not (Select-String -Path $pth -Pattern ([regex]::Escape($repoRoot)) -Quiet)) {
Add-Content -Path $pth -Value $repoRoot
Write-Log "Appended repo root to existing .pth: $pth" "INFO"
} else {
Write-Log ".pth already contains repo root: $pth" "INFO"
}
} else {
Set-Content -LiteralPath $pth -Value $repoRoot -Encoding UTF8
Write-Log "Wrote .pth adding repo root to venv site-packages: $pth" "INFO"
}
# Re-check import
& $venvPython -c "import importlib; importlib.import_module('CLI')" 2>$null
if ($LASTEXITCODE -eq 0) {
Write-Log "Top-level 'CLI' import works after adding .pth" "INFO"
} else {
Write-Log "Adding .pth did not make top-level 'CLI' importable." "ERROR"
if ($Editable) {
Write-Log "Editable install already requested; attempting editable reinstall for good measure..." "INFO"
try { & $venvPython -m pip install -e . } catch { Write-Log "Editable reinstall failed: $_" "ERROR"; exit 6 }
& $venvPython -c "import importlib; importlib.import_module('CLI')" 2>$null
if ($LASTEXITCODE -eq 0) { Write-Log "Top-level 'CLI' is now importable after reinstall." "INFO" }
else { Write-Log "Editable reinstall did not make 'CLI' importable; inspect the venv or create an egg-link manually." "ERROR"; exit 6 }
} else {
if (-not $Quiet) {
$ans = Read-Host "Top-level 'CLI' not importable; install project in editable mode now? (Y/n)"
if ($ans -eq 'y' -or $ans -eq 'Y') { try { & $venvPython -m pip install -e . } catch { Write-Log "Editable install failed: $_" "ERROR"; exit 6 } }
else { Write-Log "Warning: continuing without top-level 'CLI' importable; some entrypoints may fail." "ERROR" }
} else { Write-Log "Top-level 'CLI' not importable and cannot prompt (quiet mode); aborting." "ERROR"; exit 6 }
}
}
} else {
Write-Log "Unable to determine site-packages to write .pth; falling back to editable install prompt" "WARNING"
if ($Editable) { try { & $venvPython -m pip install -e . } catch { Write-Log "Editable install failed: $_" "ERROR"; exit 6 } }
elseif (-not $Quiet) {
$ans = Read-Host "Top-level 'CLI' not importable; install project in editable mode now? (Y/n)"
if ($ans -eq 'y' -or $ans -eq 'Y') { try { & $venvPython -m pip install -e . } catch { Write-Log "Editable install failed: $_" "ERROR"; exit 6 } }
} else { Write-Log "Top-level 'CLI' not importable and cannot prompt (quiet mode); aborting." "ERROR"; exit 6 }
}
}
} catch {
Write-Log "Failed to verify top-level 'CLI': $_" "ERROR"
exit 6
}
# Install Playwright browsers (default: chromium) unless explicitly disabled
if (-not $NoPlaywright) {
Write-Log "Ensuring Playwright browsers are installed (browsers=$PlaywrightBrowsers)..."

View File

@@ -285,12 +285,88 @@ if [[ "$NOINSTALL" != "true" ]]; then # If not explicitly requested, auto-selec
# Additional compatibility check: top-level 'CLI' module may be required by
# older entrypoints or direct imports when running from a development checkout.
if ! "$VENV_PY" -c 'import importlib,sys; importlib.import_module("CLI")' >/dev/null 2>&1; then
echo "Note: top-level 'CLI' module not importable; some entrypoints expect it." >&2
echo "Note: top-level 'CLI' module not importable; attempting to add the repo root to venv site-packages via a .pth file so top-level imports work." >&2
# If this appears to be a development checkout, offer to install editable mode
# Try to discover venv site-packages and write a .pth pointing at the repo root
site_pkgs=$("$VENV_PY" - <<'PY'
import site, sysconfig
out=[]
try:
out.extend(site.getsitepackages())
except Exception:
pass
try:
p = sysconfig.get_paths().get('purelib')
if p:
out.append(p)
except Exception:
pass
seen=set(); res=[]
for x in out:
if x and x not in seen:
seen.add(x); res.append(x)
for s in res:
print(s)
PY
)
site_pkg_dir=""
while IFS= read -r sp; do
if [[ -d "$sp" ]]; then site_pkg_dir="$sp"; break; fi
done <<< "$site_pkgs"
if [[ -n "$site_pkg_dir" ]]; then
pth_file="$site_pkg_dir/medeia_repo.pth"
if [[ -f "$pth_file" ]]; then
if grep -qxF "$REPO" "$pth_file" >/dev/null 2>&1; then
echo ".pth already present and contains repo root: $pth_file" >&2
else
echo "$REPO" >> "$pth_file"
echo "Appended repo root to existing .pth: $pth_file" >&2
fi
else
echo "$REPO" > "$pth_file"
echo "Wrote .pth adding repo root to venv site-packages: $pth_file" >&2
fi
# Re-check whether 'CLI' is now importable
if "$VENV_PY" -c 'import importlib,sys; importlib.import_module("CLI")' >/dev/null 2>&1; then
echo "Top-level 'CLI' import works after adding .pth" >&2
else
echo "Adding .pth did not make top-level 'CLI' importable." >&2
# Fallback: if this is a git checkout, try editable reinstall or prompt user
if [[ -d "$REPO/.git" ]] || git -C "$REPO" rev-parse --is-inside-work-tree >/dev/null 2>&1; then
if [[ "$EDITABLE" == "true" ]]; then
echo "Installing project in editable mode to provide top-level 'CLI'..."
echo "Editable install already requested; attempting editable reinstall for good measure..." >&2
"$VENV_PY" -m pip install -e "$REPO" || { echo "Editable install failed" >&2; exit 6; }
if "$VENV_PY" -c 'import importlib,sys; importlib.import_module("CLI")' >/dev/null 2>&1; then
echo "Top-level 'CLI' is now importable after reinstall." >&2
else
echo "Editable reinstall did not make 'CLI' importable. Please inspect the venv or create an egg-link." >&2
exit 6
fi
else
if [[ "$QUIET" != "true" && -t 0 ]]; then
read -p "Top-level 'CLI' not importable; install project in editable mode now? (Y/n) " devans2
if [[ -z "$devans2" || "$devans2" == "y" || "$devans2" == "Y" ]]; then
"$VENV_PY" -m pip install -e "$REPO" || { echo "Editable install failed" >&2; exit 6; }
else
echo "Warning: continuing without top-level 'CLI' importable; some entrypoints may fail." >&2
fi
else
echo "Top-level 'CLI' not importable and cannot prompt (quiet mode); aborting." >&2
exit 6
fi
fi
else
echo "Top-level 'CLI' not importable and not a git checkout; continuing at your own risk." >&2
fi
fi
else
echo "Unable to determine site-packages directory to write .pth; skipping .pth fallback." >&2
if [[ -d "$REPO/.git" ]] || git -C "$REPO" rev-parse --is-inside-work-tree >/dev/null 2>&1; then
if [[ "$EDITABLE" == "true" ]]; then
echo "Attempting editable install to provide top-level 'CLI'..."
"$VENV_PY" -m pip install -e "$REPO" || { echo "Editable install failed" >&2; exit 6; }
else
if [[ "$QUIET" != "true" && -t 0 ]]; then
@@ -298,6 +374,18 @@ if [[ "$NOINSTALL" != "true" ]]; then # If not explicitly requested, auto-selec
if [[ -z "$devans2" || "$devans2" == "y" || "$devans2" == "Y" ]]; then
"$VENV_PY" -m pip install -e "$REPO" || { echo "Editable install failed" >&2; exit 6; }
else
echo "Warning: continuing without top-level 'CLI' importable; some entrypoints may fail." >&2
fi
else
echo "Top-level 'CLI' not importable and cannot prompt (quiet mode); aborting." >&2
exit 6
fi
fi
else
echo "Top-level 'CLI' not importable and not a git checkout; continuing." >&2
fi
fi
fi
echo "Continuing without editable install; 'mm' may not work as expected." >&2
fi
else

View File

@@ -228,6 +228,56 @@ def main() -> int:
print("Installing project into local venv (editable mode)")
run([str(venv_python), "-m", "pip", "install", "-e", "."])
# Verify top-level 'CLI' import and, if missing, attempt to make it available
print("Verifying top-level 'CLI' import in venv...")
try:
import subprocess as _sub
rc = _sub.run([str(venv_python), "-c", "import importlib; importlib.import_module('CLI')"], check=False)
if rc.returncode == 0:
print("OK: top-level 'CLI' is importable in the venv.")
else:
print("Top-level 'CLI' not importable; attempting to add repo path to venv site-packages via a .pth file...")
cmd = [str(venv_python), "-c", (
"import site, sysconfig\n"
"out=[]\n"
"try:\n out.extend(site.getsitepackages())\nexcept Exception:\n pass\n"
"try:\n p = sysconfig.get_paths().get('purelib')\n if p:\n out.append(p)\nexcept Exception:\n pass\n"
"seen=[]; res=[]\n"
"for x in out:\n if x and x not in seen:\n seen.append(x); res.append(x)\n"
"for s in res:\n print(s)\n"
)]
out = _sub.check_output(cmd, text=True).strip().splitlines()
site_dir = None
for sp in out:
if sp and _Path(sp).exists():
site_dir = _Path(sp)
break
if site_dir is None:
print("Could not determine venv site-packages directory; skipping .pth fallback")
else:
pth_file = site_dir / "medeia_repo.pth"
if pth_file.exists():
txt = pth_file.read_text(encoding="utf-8")
if str(repo_root) in txt:
print(f".pth already contains repo root: {pth_file}")
else:
with pth_file.open("a", encoding="utf-8") as fh:
fh.write(str(repo_root) + "\n")
print(f"Appended repo root to existing .pth: {pth_file}")
else:
with pth_file.open("w", encoding="utf-8") as fh:
fh.write(str(repo_root) + "\n")
print(f"Wrote .pth adding repo root to venv site-packages: {pth_file}")
# Re-check whether CLI can be imported now
rc2 = _sub.run([str(venv_python), "-c", "import importlib; importlib.import_module('CLI')"], check=False)
if rc2.returncode == 0:
print("Top-level 'CLI' import works after adding .pth")
else:
print("Adding .pth did not make top-level 'CLI' importable; consider creating an egg-link or checking the venv.")
except Exception as exc:
print(f"Warning: failed to verify or modify site-packages for top-level CLI: {exc}")
# Optional: install Deno runtime (default: install unless --no-deno is passed)
install_deno_requested = True
if getattr(args, "no_deno", False):