Skip to content

Commit 23874ab

Browse files
authored
Merge pull request #3 from magaransoft/hotfix-sbt-auto-mode
hotfix(sbt): auto-mode default — pick bsp when .bsp/sbt.json present
2 parents 0cdc87b + 9c32de3 commit 23874ab

5 files changed

Lines changed: 67 additions & 49 deletions

File tree

.gitignore

Lines changed: 13 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -1,32 +1,19 @@
1-
# OS
2-
.DS_Store
3-
Thumbs.db
4-
5-
# editors
6-
.vscode/
1+
# build artifacts — generated by backing tools during smoke tests
2+
project/
3+
target/
4+
.bsp/
5+
.bloop/
6+
.metals/
7+
.scala-build/
78
.idea/
8-
*.swp
9-
*~
10-
11-
# language servers / caches
12-
node_modules/
13-
.venv/
14-
venv/
15-
__pycache__/
16-
*.pyc
17-
.pytest_cache/
18-
.mypy_cache/
199

20-
# fixture build artifacts
21-
fixtures/**/obj/
22-
fixtures/**/bin/
23-
fixtures/**/dist/
24-
fixtures/**/.bloop/
25-
fixtures/**/target/
26-
# eclipse jdt.ls writes these into the java fixture on first start
10+
# java-direct / jdtls generates Eclipse project metadata on first open
2711
fixtures/java/.classpath
2812
fixtures/java/.project
2913
fixtures/java/.settings/
3014

31-
# local state (user-owned, not repo data)
32-
.claude/
15+
# dotnet restore + build outputs
16+
fixtures/csharp/obj/
17+
fixtures/csharp/bin/
18+
fixtures/dotnet-csproj/obj/
19+
fixtures/dotnet-csproj/bin/

README.md

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -119,9 +119,10 @@ of one cold + two warm runs.
119119
### Where direct wrappers don't help (or hurt)
120120

121121
- **sbt oneshot mode** — no persistence, so it strictly adds
122-
coordinator overhead on top of bare sbt. Exists only as a fallback
123-
for when BSP isn't configured in the workspace. **Use `bsp` mode
124-
(`SBT_DIRECT_MODE=bsp`) whenever possible.**
122+
coordinator overhead on top of bare sbt. Only hit when `.bsp/sbt.json`
123+
is absent in the workspace. `sbt-direct`'s **auto mode** (default)
124+
detects this at start and prints a log line pointing at
125+
`sbt bspConfig` to switch to the warm path.
125126
- **dotnet-direct** — MSBuild's build-server gives bare dotnet the
126127
same warm-path benefit. Direct wrapper is within 2× of bare;
127128
shipping it is about uniform CLI contract + `calls.log` observability,

bin/sbt-direct

Lines changed: 13 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -8,12 +8,15 @@ set -euo pipefail
88
STATE_ROOT="${SBT_DIRECT_STATE:-$HOME/.cache/sbt-direct}"
99
COORDINATOR="$HOME/.claude/bin/sbt-direct-coordinator.js"
1010
WORKSPACE_MARKERS=(build.sbt build.sc build.mill project/build.properties)
11-
# SBT_DIRECT_MODE=oneshot (default) | bsp
12-
# oneshot: each /call spawns fresh `sbt <task>` (20-40s each).
13-
# bsp: persistent sbt via Build Server Protocol (`.bsp/sbt.json`
14-
# launch descriptor). Cold init ~15-30s; warm calls sub-second.
15-
# Requires `sbt bspConfig` run once in the workspace.
16-
SBT_MODE="${SBT_DIRECT_MODE:-oneshot}"
11+
# SBT_DIRECT_MODE=auto (default) | bsp | oneshot
12+
# auto: coordinator probes <workspace>/.bsp/sbt.json. If present → bsp
13+
# mode (warm calls <200ms). If absent → oneshot (20-40s per call),
14+
# with a log message telling the user to run `sbt bspConfig`.
15+
# bsp: force Build Server Protocol mode. Errors if .bsp/sbt.json absent.
16+
# oneshot: force per-call subprocess. Slower than bare sbt; kept as a
17+
# fallback for projects without BSP and as an integration test
18+
# path.
19+
SBT_MODE="${SBT_DIRECT_MODE:-auto}"
1720

1821
die() { echo "sbt-direct: $*" >&2; exit 1; }
1922

@@ -69,10 +72,11 @@ cmd_start() {
6972
nohup node "$COORDINATOR" --tool-name sbt-direct --mode "$SBT_MODE" --workspace "$ws" --port "$port" >"$dir/log" 2>&1 &
7073
pid=$!
7174
echo "$pid" > "$dir/pid"
72-
# bsp mode waits for sbt JVM boot + BSP initialize (15-30s warm,
73-
# up to 120s on fresh ivy/coursier cache). oneshot binds immediately.
75+
# bsp + auto modes wait for sbt JVM boot + BSP initialize (15-30s
76+
# warm, up to 120s on fresh ivy/coursier cache). Pure oneshot binds
77+
# immediately since no persistent child spawns.
7478
local timeout=30
75-
[ "$SBT_MODE" = "bsp" ] && timeout=120
79+
[ "$SBT_MODE" != "oneshot" ] && timeout=120
7680
local i=0
7781
until port_ready "$port"; do
7882
sleep 1

bin/sbt-direct-coordinator.js

Lines changed: 19 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -21,16 +21,29 @@ const WORKSPACE = path.resolve(arg('workspace', process.cwd()));
2121
const PORT = parseInt(arg('port', '0'), 10);
2222
const TOOL_NAME = arg('tool-name', 'sbt-direct');
2323
const SBT_CMD = arg('sbt-cmd', 'sbt');
24-
// mode: oneshot (default) — per-call subprocess, no persistent JVM
25-
// bsp — persistent sbt via Build Server Protocol. Requires a
26-
// one-time `sbt bspConfig` in the workspace to generate
27-
// .bsp/sbt.json (sbt auto-creates it on first compile/bspConfig).
28-
// Warm calls sub-second; cold first init ~15-30s.
29-
const MODE = arg('mode', process.env.SBT_DIRECT_MODE || 'oneshot');
24+
// mode selection:
25+
// auto (default) — probe <ws>/.bsp/sbt.json; use bsp if present, oneshot otherwise
26+
// bsp — persistent sbt via Build Server Protocol (warm calls <200ms).
27+
// Requires `.bsp/sbt.json`; errors if absent.
28+
// oneshot — per-call `sbt <task>` subprocess (20-40s per call; slower
29+
// than bare sbt because we don't reuse sbt's launcher daemon).
30+
// Fallback only — users with a working bsp descriptor
31+
// should never end up here.
32+
let MODE = arg('mode', process.env.SBT_DIRECT_MODE || 'auto');
33+
if (MODE === 'auto') {
34+
const bspDescriptor = path.join(WORKSPACE, '.bsp', 'sbt.json');
35+
if (fs.existsSync(bspDescriptor)) {
36+
MODE = 'bsp';
37+
} else {
38+
MODE = 'oneshot';
39+
console.error(`[sbt-direct] ${bspDescriptor} not found — falling back to oneshot mode (slower). Run \`sbt bspConfig\` once in the workspace to enable bsp mode (warm calls <200ms).`);
40+
}
41+
}
3042
const adapterModule = MODE === 'bsp'
3143
? './adapters/sbt-bsp.js'
3244
: './adapters/sbt-oneshot.js';
3345
const { createAdapter } = require(adapterModule);
46+
console.error(`[sbt-direct] mode=${MODE}`);
3447

3548
if (!fs.existsSync(WORKSPACE)) die(`workspace does not exist: ${WORKSPACE}`);
3649

docs/per-language/sbt.md

Lines changed: 18 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,24 @@
11
# sbt — `sbt-direct`
22

3-
Per-workspace sbt coordinator with two modes:
3+
Per-workspace sbt coordinator. Default mode is **auto**: on `start`,
4+
the coordinator probes `<workspace>/.bsp/sbt.json` and selects:
45

5-
| mode | activation | cold first call | warm call | prereq |
6-
|---|---|---|---|---|
7-
| `oneshot` (default) | `SBT_DIRECT_MODE=oneshot` or unset | 20-40s | 20-40s | sbt on PATH |
8-
| `bsp` | `SBT_DIRECT_MODE=bsp` | 15-30s (JVM boot + BSP init) | sub-second (~150ms observed) | sbt on PATH + `sbt bspConfig` run once in the workspace |
6+
| detected state | selected mode | cold first call | warm call |
7+
|---|---|---|---|
8+
| `.bsp/sbt.json` present | `bsp` (persistent JVM) | 15-30s (JVM boot + BSP init) | **~130ms** |
9+
| `.bsp/sbt.json` absent | `oneshot` (per-call subprocess) | 4-5s | 3-4s |
10+
11+
`bsp` mode is strictly faster whenever it's available. If your
12+
workspace doesn't have `.bsp/sbt.json`, run `sbt bspConfig` once to
13+
generate it — every subsequent `sbt-direct call` in that workspace
14+
switches to the warm path automatically.
15+
16+
Override auto-detection with `SBT_DIRECT_MODE=bsp` (force; errors if
17+
descriptor absent) or `SBT_DIRECT_MODE=oneshot` (force; useful only
18+
for a deliberate test of the fallback path).
19+
20+
Prereq for either mode: `sbt` on `PATH`. `bsp` adds the one-time
21+
`sbt bspConfig` step per workspace.
922

1023
The bsp mode uses the Build Server Protocol (Scala-BSP 2.x). sbt writes
1124
`.bsp/sbt.json` describing how to launch itself in BSP server mode; the

0 commit comments

Comments
 (0)