Skip to content

Commit 77741b5

Browse files
committed
fix(launcher): bind installed app to repository runtime
1 parent 4383075 commit 77741b5

3 files changed

Lines changed: 75 additions & 25 deletions

File tree

scripts/AuraLauncher.swift

Lines changed: 18 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -2161,7 +2161,7 @@ final class AuraLauncherDelegate: NSObject, NSApplicationDelegate,
21612161

21622162
launchScript = auraRoot.appendingPathComponent("launch_aura.sh")
21632163
auraMainScript = auraRoot.appendingPathComponent("aura_main.py")
2164-
pythonExecutable = try resolvePythonExecutable()
2164+
pythonExecutable = try resolvePythonExecutable(resourcesURL: resourcesURL)
21652165

21662166
let semverFile = resourcesURL.appendingPathComponent("aura-version")
21672167
if let text = try? String(contentsOf: semverFile, encoding: .utf8) {
@@ -2643,21 +2643,24 @@ final class AuraLauncherDelegate: NSObject, NSApplicationDelegate,
26432643
])
26442644
}
26452645

2646-
private func resolvePythonExecutable() throws -> URL {
2647-
let candidates = [
2648-
auraRoot.appendingPathComponent(".venv/bin/python3"),
2649-
URL(fileURLWithPath: "/opt/homebrew/bin/python3.12"),
2650-
URL(fileURLWithPath: "/usr/local/bin/python3.12"),
2651-
URL(fileURLWithPath: "/Library/Frameworks/Python.framework/Versions/3.12/bin/python3.12"),
2652-
]
2653-
2654-
for candidate in candidates where fileManager.isExecutableFile(atPath: candidate.path) {
2655-
return candidate
2646+
private func resolvePythonExecutable(resourcesURL: URL) throws -> URL {
2647+
let runtimePathFile = resourcesURL.appendingPathComponent("aura-python-path")
2648+
guard let runtimePath = try? String(contentsOf: runtimePathFile, encoding: .utf8)
2649+
.trimmingCharacters(in: .whitespacesAndNewlines),
2650+
!runtimePath.isEmpty else {
2651+
throw NSError(domain: "AuraLauncher", code: 5, userInfo: [
2652+
NSLocalizedDescriptionKey: "Aura.app has no signed repository Python path. Rebuild the installed app.",
2653+
])
26562654
}
2657-
2658-
throw NSError(domain: "AuraLauncher", code: 5, userInfo: [
2659-
NSLocalizedDescriptionKey: "Aura needs a Python 3.12 runtime, but the launcher could not find one.",
2660-
])
2655+
let candidate = URL(fileURLWithPath: runtimePath)
2656+
.resolvingSymlinksInPath()
2657+
.standardizedFileURL
2658+
guard fileManager.isExecutableFile(atPath: candidate.path) else {
2659+
throw NSError(domain: "AuraLauncher", code: 5, userInfo: [
2660+
NSLocalizedDescriptionKey: "Aura's signed repository Python runtime is unavailable. Restore the repo environment or rebuild the installed app.",
2661+
])
2662+
}
2663+
return candidate
26612664
}
26622665

26632666
private func baseAuraEnvironment() -> [String: String] {

scripts/bundle_app.sh

Lines changed: 38 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -85,6 +85,7 @@ EXECUTABLE_NAME="aura-launcher"
8585
EXECUTABLE_PATH="${MACOS_DIR}/${EXECUTABLE_NAME}"
8686
ICON_SOURCE="${ROOT_DIR}/aura_icon.icns"
8787
ROOT_PATH_FALLBACK="${RESOURCES_DIR}/aura-root-path"
88+
PYTHON_RUNTIME_FILE="${RESOURCES_DIR}/aura-python-path"
8889
VERSION_FILE="${RESOURCES_DIR}/aura-version"
8990
VERSION_FULL_FILE="${RESOURCES_DIR}/aura-version-full"
9091
PROVENANCE_FILE="${RESOURCES_DIR}/aura-launch-provenance.json"
@@ -98,8 +99,41 @@ cd "${ROOT_DIR}"
9899

99100
echo "📦 Building ${APP_NAME} (live source mode)..."
100101

101-
if [ -x "${ROOT_DIR}/.venv/bin/python3" ] && [ -f "${ROOT_DIR}/scripts/build_launcher_icon.py" ]; then
102-
"${ROOT_DIR}/.venv/bin/python3" "${ROOT_DIR}/scripts/build_launcher_icon.py" >/dev/null
102+
PRIMARY_ROOT=""
103+
GIT_COMMON_DIR="$(git rev-parse --path-format=absolute --git-common-dir 2>/dev/null || true)"
104+
if [ -n "${GIT_COMMON_DIR}" ] && [ "$(basename "${GIT_COMMON_DIR}")" = ".git" ]; then
105+
PRIMARY_ROOT="$(dirname "${GIT_COMMON_DIR}")"
106+
fi
107+
108+
PYTHON_RUNTIME=""
109+
PYTHON_CANDIDATES=(
110+
"${AURA_PYTHON_RUNTIME:-}"
111+
"${ROOT_DIR}/.venv/bin/python3"
112+
"${ROOT_DIR}/.venv/bin/python"
113+
)
114+
if [ -n "${PRIMARY_ROOT}" ]; then
115+
PYTHON_CANDIDATES+=(
116+
"${PRIMARY_ROOT}/.venv/bin/python3"
117+
"${PRIMARY_ROOT}/.venv/bin/python"
118+
)
119+
fi
120+
for candidate in "${PYTHON_CANDIDATES[@]}"; do
121+
if [ -z "${candidate}" ] || [ ! -x "${candidate}" ]; then
122+
continue
123+
fi
124+
if "${candidate}" -c 'import sys, httpx; raise SystemExit(0 if sys.version_info[:2] == (3, 12) else 1)' >/dev/null 2>&1; then
125+
PYTHON_RUNTIME="$("${candidate}" -c 'import sys; print(sys.executable)')"
126+
break
127+
fi
128+
done
129+
if [ -z "${PYTHON_RUNTIME}" ]; then
130+
echo "❌ Aura needs the repository Python 3.12 environment with runtime dependencies installed." >&2
131+
echo " Expected .venv under ${ROOT_DIR}${PRIMARY_ROOT:+ or ${PRIMARY_ROOT}}." >&2
132+
exit 1
133+
fi
134+
135+
if [ -f "${ROOT_DIR}/scripts/build_launcher_icon.py" ]; then
136+
"${PYTHON_RUNTIME}" "${ROOT_DIR}/scripts/build_launcher_icon.py" >/dev/null
103137
fi
104138

105139
if [ ! -f "${LAUNCHER_SOURCE}" ]; then
@@ -128,16 +162,10 @@ rm -rf "${APP_DIR}"
128162
mkdir -p "${MACOS_DIR}" "${RESOURCES_DIR}"
129163

130164
printf '%s\n' "${ROOT_DIR}" > "${ROOT_PATH_FALLBACK}"
165+
printf '%s\n' "${PYTHON_RUNTIME}" > "${PYTHON_RUNTIME_FILE}"
131166
cp "${SCREEN_CAPTURE_POLICY_SOURCE}" "${SCREEN_CAPTURE_POLICY_RESOURCE}"
132167

133-
PYTHON_FOR_VERSION="${ROOT_DIR}/.venv/bin/python3"
134-
if [ ! -x "${PYTHON_FOR_VERSION}" ]; then
135-
PYTHON_FOR_VERSION="$(command -v python3 || true)"
136-
fi
137-
if [ -z "${PYTHON_FOR_VERSION}" ]; then
138-
echo "❌ python3 is required to write Aura.app metadata."
139-
exit 1
140-
fi
168+
PYTHON_FOR_VERSION="${PYTHON_RUNTIME}"
141169

142170
APP_SEMVER="2026.3.31"
143171
APP_FULL_VERSION="Aura Luna v${APP_SEMVER}"

tests/test_launcher_polish_contract.py

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -249,6 +249,25 @@ def test_bundle_app_prefers_stable_local_codesign_without_timestamp_by_default()
249249
assert 'CODESIGN_ARGS+=(--timestamp)' in bundle
250250

251251

252+
def test_installed_launcher_binds_the_repository_python_across_worktrees():
253+
bundle = (PROJECT_ROOT / "scripts" / "bundle_app.sh").read_text(encoding="utf-8")
254+
swift = (PROJECT_ROOT / "scripts" / "AuraLauncher.swift").read_text(encoding="utf-8")
255+
256+
assert 'PYTHON_RUNTIME_FILE="${RESOURCES_DIR}/aura-python-path"' in bundle
257+
assert "git rev-parse --path-format=absolute --git-common-dir" in bundle
258+
assert '"${PRIMARY_ROOT}/.venv/bin/python3"' in bundle
259+
assert "import sys, httpx" in bundle
260+
assert 'printf \'%s\\n\' "${PYTHON_RUNTIME}" > "${PYTHON_RUNTIME_FILE}"' in bundle
261+
assert "resolvePythonExecutable(resourcesURL: resourcesURL)" in swift
262+
resolver = swift.split(
263+
"private func resolvePythonExecutable(resourcesURL: URL) throws -> URL {",
264+
1,
265+
)[1].split("private func baseAuraEnvironment()", 1)[0]
266+
assert 'appendingPathComponent("aura-python-path")' in resolver
267+
assert "/opt/homebrew/bin/python3.12" not in resolver
268+
assert "/usr/local/bin/python3.12" not in resolver
269+
270+
252271
def test_launcher_cleanup_shim_exists_at_repo_root():
253272
shim = PROJECT_ROOT / "aura_cleanup.py"
254273
target = PROJECT_ROOT / "scripts" / "one_off" / "aura_cleanup.py"

0 commit comments

Comments
 (0)