Skip to content

Commit 2ea1907

Browse files
committed
fix: make beam installer and daemon launch portable
1 parent 258427b commit 2ea1907

6 files changed

Lines changed: 119 additions & 45 deletions

File tree

‎Beam/Cli.lean‎

Lines changed: 51 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ Author: Emilio J. Gallego Arias
77
import Lean
88
import Beam.Broker.Client
99
import Beam.Broker.Transport
10+
import RunAt.Lib.NativeLib
1011
import RunAt.Protocol
1112
import Std.Internal.UV.Signal
1213

@@ -213,10 +214,25 @@ private def readCmdTrim (cmd : String) (args : Array String := #[]) (cwd? : Opti
213214
throw <| IO.userError s!"command failed: {cmd} {String.intercalate " " args.toList}\n{out.stderr}"
214215
pure <| trimLine out.stdout
215216

216-
private def commandAvailable (cmd : String) : IO Bool := do
217+
private def commandAvailable (cmd : String) (args : Array String := #["--help"]) : IO Bool := do
217218
try
218-
let out ← IO.Process.output { cmd := "sh", args := #["-c", s!"command -v {shellQuote cmd} >/dev/null 2>&1"] }
219-
pure (out.exitCode == 0)
219+
let child ← IO.Process.spawn {
220+
cmd := cmd
221+
args := args
222+
stdin := .null
223+
stdout := .null
224+
stderr := .null
225+
}
226+
if (← child.tryWait).isNone then
227+
try
228+
child.kill
229+
catch _ =>
230+
pure ()
231+
try
232+
discard <| child.wait
233+
catch _ =>
234+
pure ()
235+
pure true
220236
catch _ =>
221237
pure false
222238

@@ -236,10 +252,10 @@ private def runAtHome : IO System.FilePath := do
236252
private def defaultBundlePaths (home : System.FilePath) : IO BundlePaths := do
237253
let installedDaemon := home / "libexec" / "beam-daemon"
238254
let installedClient := home / "libexec" / "beam-client"
239-
let installedPlugin := home / "libexec" / "librunAt_RunAt.so"
255+
let installedPlugin := RunAt.Lib.pluginSharedLibPath (home / "libexec")
240256
let checkoutDaemon := home / ".lake" / "build" / "bin" / "beam-daemon"
241257
let checkoutClient := home / ".lake" / "build" / "bin" / "beam-client"
242-
let checkoutPlugin := home / ".lake" / "build" / "lib" / "librunAt_RunAt.so"
258+
let checkoutPlugin := RunAt.Lib.pluginSharedLibPath (home / ".lake" / "build" / "lib")
243259
let installedReady :=
244260
(← installedDaemon.pathExists) &&
245261
(← installedClient.pathExists) &&
@@ -371,7 +387,7 @@ private def bundlePathsFor (workspace : System.FilePath) : BundlePaths :=
371387
{
372388
daemon := workspace / ".lake" / "build" / "bin" / "beam-daemon"
373389
client := workspace / ".lake" / "build" / "bin" / "beam-client"
374-
plugin := workspace / ".lake" / "build" / "lib" / "librunAt_RunAt.so"
390+
plugin := RunAt.Lib.pluginSharedLibPath (workspace / ".lake" / "build" / "lib")
375391
}
376392

377393
private def bundleArtifactsReady (workspace : System.FilePath) : IO Bool := do
@@ -407,7 +423,7 @@ private def bundleSourceHashInputLabels : List String :=
407423

408424
private def installRuntimePaths : List String :=
409425
["libexec/beam-cli", "libexec/beam-daemon", "libexec/beam-client",
410-
"libexec/librunAt_RunAt.so", ".lake/packages"]
426+
s!"libexec/{RunAt.Lib.pluginSharedLibName}", ".lake/packages"]
411427

412428
private def installWrapperPaths : List String :=
413429
["bin/lean-beam", "bin/lean-beam-search"]
@@ -543,11 +559,18 @@ private def fallbackBuildFailureMessage (toolchain : String) (cacheRoot bundleDi
543559
stderr
544560
]
545561

562+
private def killCommand : IO String := do
563+
let candidates := [System.FilePath.mk "/bin/kill", System.FilePath.mk "/usr/bin/kill"]
564+
for candidate in candidates do
565+
if ← candidate.pathExists then
566+
return candidate.toString
567+
if ← commandAvailable "kill" #["-l"] then
568+
pure "kill"
569+
else
570+
throw <| IO.userError "could not find kill command"
571+
546572
private def pidAlive (pid : Nat) : IO Bool := do
547-
let out ← IO.Process.output {
548-
cmd := "sh"
549-
args := #["-c", s!"kill -0 {pid} >/dev/null 2>&1"]
550-
}
573+
let out ← IO.Process.output { cmd := (← killCommand), args := #["-0", toString pid] }
551574
pure (out.exitCode == 0)
552575

553576
private partial def acquireLock (lockDir : System.FilePath) : IO Unit := do
@@ -718,19 +741,17 @@ private def leanBin (root : System.FilePath) : IO String := do
718741
private def rocqCandidates (root : System.FilePath) : List System.FilePath :=
719742
[root / "_opam" / "bin" / "coq-lsp", root / "_opam" / "_opam" / "bin" / "coq-lsp"]
720743

721-
private def pathCmd? (cmd : String) : IO (Option String) := do
722-
try
723-
return some (← readCmdTrim "sh" #["-c", s!"command -v {shellQuote cmd}"])
724-
catch _ =>
725-
return none
726-
727744
private def maybeRocqCmd (root : System.FilePath) : IO (Option String) := do
728745
for candidate in rocqCandidates root do
729746
if ← candidate.pathExists then
730747
return some candidate.toString
731748
match ← IO.getEnv "BEAM_ROCQ_CMD" with
732749
| some cmd => pure (some cmd)
733-
| none => pathCmd? "coq-lsp"
750+
| none =>
751+
if ← commandAvailable "coq-lsp" then
752+
pure (some "coq-lsp")
753+
else
754+
pure none
734755

735756
private def rocqCmd (root : System.FilePath) : IO String := do
736757
match ← maybeRocqCmd root with
@@ -804,11 +825,11 @@ private def daemonResponds (endpoint : Transport.Endpoint) : IO Bool := do
804825
pure false
805826

806827
private def killPid (pid : Nat) : IO Unit := do
807-
let _ ← IO.Process.output {
808-
cmd := "sh"
809-
args := #["-c", s!"kill {pid} >/dev/null 2>&1 || true"]
810-
}
811-
pure ()
828+
try
829+
let _ ← IO.Process.output { cmd := (← killCommand), args := #[toString pid] }
830+
pure ()
831+
catch _ =>
832+
pure ()
812833

813834
private partial def waitForPidGone (pid : Nat) (tries : Nat := 20) : IO Unit := do
814835
if tries == 0 then
@@ -918,16 +939,16 @@ private def startDaemon (desired : DesiredConfig) (endpoint : Transport.Endpoint
918939
IO.FS.createDirAll parent
919940
IO.FS.writeFile logPath ""
920941
let cmd := String.intercalate " " ((desired.daemonBin.toString :: args).map shellQuote)
921-
let shell := s!"cd {shellQuote desired.root.toString} && {cmd} >{shellQuote logPath.toString} 2>&1 < /dev/null & echo $!"
922-
let out ← IO.Process.output {
942+
let shell := s!"exec {cmd} >{shellQuote logPath.toString} 2>&1 < /dev/null"
943+
let child ← IO.Process.spawn {
923944
cmd := "sh"
924945
args := #["-c", shell]
946+
cwd := some desired.root
947+
stdin := .null
948+
stdout := .null
949+
stderr := .null
925950
}
926-
if out.exitCode != 0 then
927-
throw <| IO.userError s!"failed to start Beam daemon for {desired.root}\n{out.stderr}"
928-
let pidText := trimLine out.stdout
929-
let some pid := pidText.toNat?
930-
| throw <| IO.userError s!"failed to capture Beam daemon pid for {desired.root}"
951+
let pid := child.pid.toNat
931952
pure pid
932953

933954
private partial def waitForDaemon (pid : Nat) (endpoint : Transport.Endpoint) (logPath : System.FilePath)

‎RunAt/Lib/NativeLib.lean‎

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
import Lake.Util.NativeLib
2+
3+
namespace RunAt.Lib
4+
5+
def pluginSharedLibName : String :=
6+
Lake.nameToSharedLib "runAt_RunAt"
7+
8+
def pluginSharedLibPath (dir : System.FilePath) : System.FilePath :=
9+
dir / pluginSharedLibName
10+
11+
end RunAt.Lib

‎scripts/install-beam.sh‎

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
set -euo pipefail
88

99
repo_root="$(cd "$(dirname "$0")/.." && pwd)"
10+
. "$repo_root/scripts/shared-lib.sh"
1011
codex_skills_home="${CODEX_HOME:-$HOME/.codex}/skills"
1112
claude_skills_home="${CLAUDE_HOME:-$HOME/.claude}/skills"
1213
bin_home="${HOME}/.local/bin"
@@ -29,6 +30,7 @@ style_green=""
2930
style_blue=""
3031
style_yellow=""
3132
style_dim=""
33+
runat_plugin_shared_lib="$(beam_shared_lib_name runAt_RunAt)"
3234

3335
runtime_payload_spec=(
3436
"copy|rootFiles|RunAt.lean|RunAt.lean"
@@ -44,7 +46,7 @@ runtime_payload_spec=(
4446
"copy|runtimePaths|.lake/build/bin/beam-cli|libexec/beam-cli"
4547
"copy|runtimePaths|.lake/build/bin/beam-daemon|libexec/beam-daemon"
4648
"copy|runtimePaths|.lake/build/bin/beam-client|libexec/beam-client"
47-
"copy|runtimePaths|.lake/build/lib/librunAt_RunAt.so|libexec/librunAt_RunAt.so"
49+
"copy|runtimePaths|.lake/build/lib/$runat_plugin_shared_lib|libexec/$runat_plugin_shared_lib"
4850
"copy|runtimePaths|.lake/packages|.lake/packages"
4951
"copy|wrapperPaths|scripts/lean-beam|bin/lean-beam"
5052
"copy|wrapperPaths|scripts/lean-beam-search|bin/lean-beam-search"
@@ -219,7 +221,8 @@ replace_symlink_atomically() {
219221
require_path_within "$tmp_dir" "$link_dir" "$label temp dir"
220222
tmp_link="$tmp_dir/link"
221223
ln -s "$target" "$tmp_link"
222-
mv -Tf "$tmp_link" "$link_path"
224+
rm -f -- "$link_path"
225+
mv "$tmp_link" "$link_path"
223226
rmdir "$tmp_dir"
224227
}
225228

@@ -365,7 +368,7 @@ ensure_runtime_artifacts() {
365368
if [ -x "$beam_cli" ] \
366369
&& [ -x "$repo_root/.lake/build/bin/beam-daemon" ] \
367370
&& [ -x "$repo_root/.lake/build/bin/beam-client" ] \
368-
&& [ -f "$repo_root/.lake/build/lib/librunAt_RunAt.so" ]; then
371+
&& [ -f "$repo_root/.lake/build/lib/$runat_plugin_shared_lib" ]; then
369372
return 0
370373
fi
371374
echo "building beam runtime artifacts" >&2

‎scripts/shared-lib.sh‎

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
#!/usr/bin/env bash
2+
3+
# Copyright (c) 2026 Lean FRO LLC. All rights reserved.
4+
# Released under Apache 2.0 license as described in the file LICENSE.
5+
# Author: Emilio J. Gallego Arias
6+
7+
beam_shared_lib_ext() {
8+
case "$(uname -s)" in
9+
Darwin)
10+
printf 'dylib\n'
11+
;;
12+
CYGWIN*|MINGW*|MSYS*|Windows_NT)
13+
printf 'dll\n'
14+
;;
15+
*)
16+
printf 'so\n'
17+
;;
18+
esac
19+
}
20+
21+
beam_shared_lib_name() {
22+
local base="$1"
23+
local ext
24+
ext="$(beam_shared_lib_ext)"
25+
case "$(uname -s)" in
26+
CYGWIN*|MINGW*|MSYS*|Windows_NT)
27+
printf '%s.%s\n' "$base" "$ext"
28+
;;
29+
*)
30+
printf 'lib%s.%s\n' "$base" "$ext"
31+
;;
32+
esac
33+
}

‎tests/test-beam-wrapper.sh‎

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,15 @@ else:
7474
PY
7575
}
7676

77+
sed_in_place_portable() {
78+
local expr="$1"
79+
local path="$2"
80+
local tmp
81+
tmp="$(mktemp "${path}.sed-XXXXXX")"
82+
sed "$expr" "$path" >"$tmp"
83+
mv "$tmp" "$path"
84+
}
85+
7786
read_json_array_len() {
7887
python3 - "$1" <<'PY'
7988
import json, os, sys
@@ -834,7 +843,7 @@ EOF
834843
exit 1
835844
fi
836845

837-
sed -i 's/1/2/' SaveSmoke/B.lean
846+
sed_in_place_portable 's/1/2/' SaveSmoke/B.lean
838847
sync_out="$("$beam_script" lean-sync SaveSmoke/B.lean)"
839848
if [ "$(RUNAT_JSON_PAYLOAD="$sync_out" read_json_text_field ok)" != "true" ]; then
840849
echo "expected lean-sync after first edit to succeed" >&2
@@ -923,7 +932,7 @@ EOF
923932
exit 1
924933
fi
925934

926-
sed -i 's/2/3/' SaveSmoke/B.lean
935+
sed_in_place_portable 's/2/3/' SaveSmoke/B.lean
927936
open_files_dirty="$("$beam_script" open-files)"
928937
if [ "$(RUNAT_JSON_PAYLOAD="$open_files_dirty" read_json_text_field result.sessions.lean.files.0.status)" != "notSaved" ]; then
929938
echo "expected open-files to detect an on-disk edit for an already known file incrementally" >&2
@@ -1722,7 +1731,7 @@ sleep 1
17221731
printf '%s\n' "$doctor_out" >&2
17231732
exit 1
17241733
fi
1725-
sed -i 's/1/2/' SaveSmoke/B.lean
1734+
sed_in_place_portable 's/1/2/' SaveSmoke/B.lean
17261735
sync_out="$("$beam_script" --port "$busy_port" lean-sync SaveSmoke/B.lean)"
17271736
if [ "$(RUNAT_JSON_PAYLOAD="$sync_out" read_json_text_field ok)" != "true" ]; then
17281737
echo "expected lean-sync with a busy requested port to reuse the live Beam daemon" >&2

‎tests/test-install.sh‎

Lines changed: 6 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
set -euo pipefail
88

99
cd "$(dirname "$0")/.."
10+
. scripts/shared-lib.sh
1011

1112
tmp_root="$(mktemp -d /tmp/runat-install-XXXXXX)"
1213

@@ -61,6 +62,7 @@ mkdir -p "$HOME" "$BEAM_INSTALL_ROOT"
6162
mapfile -t supported_toolchains < <(grep -v '^[[:space:]]*#' supported-lean-toolchains | sed '/^[[:space:]]*$/d')
6263
toolchain="${supported_toolchains[0]}"
6364
source_checkout="$tmp_root/source-checkout"
65+
runat_plugin_shared_lib="$(beam_shared_lib_name runAt_RunAt)"
6466

6567
assert_file() {
6668
local path="$1"
@@ -80,7 +82,7 @@ assert_not_exists() {
8082

8183
assert_no_skill_socket_guidance() {
8284
local skill_doc="$1"
83-
if rg -n -- '--socket|Unix domain socket|unix domain socket' "$skill_doc" > /dev/null; then
85+
if grep -E -- '--socket|Unix domain socket|unix domain socket' "$skill_doc" > /dev/null; then
8486
echo "unexpected socket guidance in installed skill: $skill_doc" >&2
8587
exit 1
8688
fi
@@ -109,7 +111,7 @@ assert_runtime_layout() {
109111
assert_file "$runtime_root/libexec/beam-cli"
110112
assert_file "$runtime_root/libexec/beam-daemon"
111113
assert_file "$runtime_root/libexec/beam-client"
112-
assert_file "$runtime_root/libexec/librunAt_RunAt.so"
114+
assert_file "$runtime_root/libexec/$runat_plugin_shared_lib"
113115
assert_not_exists "$runtime_root/.lake/build"
114116
assert_file "$runtime_root/bin/lean-beam"
115117
assert_file "$runtime_root/bin/lean-beam-search"
@@ -222,12 +224,7 @@ assert_bundle_layout() {
222224
for expected_toolchain in "$@"; do
223225
found=""
224226
for metadata in "${metadata_files[@]}"; do
225-
if command -v rg >/dev/null 2>&1; then
226-
if rg -n --fixed-strings "\"toolchain\": \"$expected_toolchain\"" "$metadata" > /dev/null; then
227-
found="$metadata"
228-
break
229-
fi
230-
elif grep -F "\"toolchain\": \"$expected_toolchain\"" "$metadata" > /dev/null; then
227+
if grep -F "\"toolchain\": \"$expected_toolchain\"" "$metadata" > /dev/null; then
231228
found="$metadata"
232229
break
233230
fi
@@ -245,7 +242,7 @@ assert_bundle_layout() {
245242
assert_file "$workspace/RunAt/Internal/DirectImports.lean"
246243
assert_file "$workspace/.lake/build/bin/beam-daemon"
247244
assert_file "$workspace/.lake/build/bin/beam-client"
248-
assert_file "$workspace/.lake/build/lib/librunAt_RunAt.so"
245+
assert_file "$workspace/.lake/build/lib/$runat_plugin_shared_lib"
249246
done
250247
}
251248

0 commit comments

Comments
 (0)