scripts and links: five reports from the 2026-08-26 batch (#664, #663, #671, #670, #677) - #683
Merged
Conversation
The main link path read `--module-kind`, then the project's `jsKind`. The test path declared `ScalaJsLinkConfig.Debug` and took its `CommonJSModule` along with the debug semantics it actually wanted, so a build saying `jsKind: esmodule` had its tests linked as CommonJS, with no flag able to change it and nothing saying so. Debug semantics for a test link stay: nobody wants their tests run through the optimizer, and the output directory is named after the mode, so this changes where nothing lands. The module kind is not a semantics choice — it is what the build declared. Both paths now derive it through one function, and `runScalaJsTestSuite` is handed the kind that was linked rather than deriving a second opinion from a constant. The adapter picks its `Input` from that value, and a NoModule program loaded as a module — or the reverse — fails before any test runs, so the one thing it must never be is a guess. Asserted on the emitted JavaScript, because the suite passed either way: the constant was consistent, consistently wrong, and a test that checked which config was passed around would have gone on passing throughout. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
`bleep <script>` ran everything through `cli.apply`, which throws on any non-zero exit with the whole command line in the message. For git or gpg that is right — something went wrong and the reader needs the command to diagnose it. For a script that exits 1 to report a problem it has already described, it means the report is followed by the java path, two `--add-opens` flags and a hundred-jar classpath on one line, and the reader stops looking for the two readable lines above it. The code the script chose was replaced by bleep's flat 1 as well. `cli.exitCode` hands the code back instead of throwing; `cli.apply` is that plus the throw, so every existing caller keeps the diagnosis it had. `bleep run` and `bleep <script>` — JVM, node and native alike — use the former and return `BleepException.SubprocessExit`, which renders as one line and carries the code through to bleep's own exit status. Covered through `JvmRunner.Forked`, which the ITs otherwise never touch: the harness swaps in `InProcess`, and a `System.exit` there would take the test JVM down with it. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…hing (#663, #671) Two gaps in the surface a build script reaches, both reported against a real build. `Commands` declared no `link`, so a script packaging linked JavaScript into a jar had to shell out to the `bleep` command line and pay the start-up cost a second time. It takes the same `LinkOptions` the command line does. `compile` returned `Unit`, so a caller could not tell a compile that rebuilt everything from one that found nothing to do. bleep already knows: the server reports a `CompileReason` per project and `UpToDate` is the one that means the compiler ran and found nothing. That reason reached the client and was dropped on the floor with a comment saying it was purely informational. It is now accumulated, and `compile` hands back the `BuildSummary` the run produced — verdict unchanged, so a caller who only wants "did it work" can go on ignoring it. `BuildSummary.noOp` is BSP's `CompileReport.noOp` computed from that. A run in which nothing compiled at all is deliberately not a no-op: there was no compile to be a no-op about, and answering true would let a deploy skip on the strength of a run that never looked. Mirrored on the Java scripting API, which is the other half of the same surface — with a `CompileReport` narrower than the summary it comes from, since `BuildSummary` changes shape whenever the build display does and a published interface should not. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
`commands.link` said whether it worked and nothing else, so a script that wants
the linked JavaScript — to copy it into a jar, to serve it — had to rebuild
link-output/<debug|release>/js/main.js
by hand. That is a layout bleep owns and has already renamed once, when
`--release` grew minification and the directory had to follow the mode rather
than the optimizer.
The authoritative answer already existed and already crossed the wire.
`BuildEvent.LinkSucceeded` carries `generatedFiles`, with a comment on the field
saying it is taken from the linker "rather than found by looking in the output
directory afterwards, which is guesswork that goes stale the moment a platform
changes its layout". bleep declined to guess for its own use and then left
scripts with nothing else to do. `BuildState` dropped the field on the floor,
the same way it was dropping `CompileReason` before #671.
It is accumulated now as `BuildSummary.linkedOutputs`, and `link` returns the
summary the way `compile` does. `LinkedOutput.mainArtifact` names the ordering
convention — main artifact first, then source map and chunks — instead of
leaving callers to know that `files.head` is special.
This is what #677 actually needed. The rest of that report is already true:
`bleep test` and `bleep run` both link first, `LinkResult` is a first-class task
result the DAG tracks, and packaging is a script, which since #663 can drive the
link itself. What was missing was never the trigger — it was the address.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Same three lines as `compile` and `link`: the run already builds a `BuildSummary` and `Commands.test` threw it away. The counts are the useful part, and not for the reason one might expect. A failing test throws, so "it returned" already meant "nothing failed" — what a caller could not learn is whether anything *ran*, which is the failure mode that reads as green in CI. That is the same hole `testProjectsWithoutSuites` exists to close inside bleep, now visible to a script. Deliberately not solved here: the failure detail. `failures` and `cancelledSuites` describe a run that threw before the caller could read them, so a script that wants to inspect failures rather than propagate them needs a non-throwing entry point. The Java `TestReport` carries counts only, and says so, rather than exposing lists that are always empty by the time you hold them. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…side bleep `Publish.Options` carried `versionOverride: Option[String]` beside `versionFallback: Option[() => String]` — a two-field encoding of a one-of-two choice, with "exactly one must be set" enforced at runtime, and on the Java side thrown from a constructor. The thunk existed so the command line could skip shelling out to git when `--version` was given; that laziness belongs to resolving a version, not to the shape of the input, and the Java bridge was collapsing it on the spot anyway. It is now `PublishVersion`: `Specified(value)` or `Dynver`. Three commands that spelled the same idea three ways — `PublishLocal` a required `String`, `PublishSonatype` a bare `versionOverride`, `Publish` both fields — take one type now, and `bleep publish-local --version` becomes optional like every other publish path. Resolution moved with it. The command line used to build a `DynVerPlugin` and pass it in as a lambda, while `PublishSonatype` built its own inside bleep-core; one function builds it now, so `dynverSonatypeSnapshots = true` — and the paragraph explaining why it must be true — is stated once instead of twice. `PublishSonatype` still holds a dynver object, because `CiReleasePlugin` reads git state itself and wants more than a version string, but it gets it from that same function. That also settles a disagreement nobody had noticed. `--assert-release` asked "is this a snapshot" in two places and answered it two ways: `Publish` inspected the string for `+` and `-SNAPSHOT`, `PublishSonatype` asked dynver. One answer now, and it is dynver's. Behaviour preserved, including the wart: `--assert-release` does not check a version the caller spelled out. That used to read `versionOverride.isEmpty`, which said what it did and not why; it is a case in a match now, with the reason written down. Worth revisiting on its own, not while moving it. Docs: the scripts page gains `.versionFromGit()` and what `--assert-release` applies to, the CI/CD guide says the flag has nothing to check when a version is passed, the Maven Central tutorial says the same at the flag, and the CLI reference is regenerated for the now-optional `publish-local --version`. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…t share one output directory Two loose ends noted while fixing #664 and #673, both of which turned out to be hiding a third thing. **Kotlin/JS ignored `kotlin.js.moduleKind`.** The main link read the `--module-kind` flag and otherwise used a hardcoded CommonJS, so a build declaring `es` got CommonJS and nothing said otherwise — the Kotlin half of #664. The field is now read, with the same precedence Scala.js uses: flag, then project, then the default. `model.KotlinJs` had no reader anywhere in the server before this. All nine of its fields were dead, so only `moduleKind` is added to `schema.json`; listing the rest would advertise settings that still do nothing. **Kotlin/JS test links stay UMD, and now say so.** Unlike Scala.js, where the adapter picks its `Input` per module kind, bleep runs Kotlin/JS tests by generating a CommonJS script that installs a QUnit mock as a global and then `require`s the output. ES modules make that throw; plain and AMD do not export what the runner reads. So the declaration cannot be honoured there — and a project that declares one now gets a warning rather than a silent substitution. A warning and not a failure: those builds' tests pass today, and breaking them to report a limitation of the runner is a poor trade. **Honouring `es` uncovered why nobody could have used it.** Kotlin names ES output `<module>.mjs`; the output lookup only knew `.js`, so it found nothing — and the caller reported that as "Kotlin/JS linking failed" with an empty diagnostic list, for a compiler that had exited 0 with nothing to complain about. Both fixed: `.mjs` is recognised, and a linker that succeeds while producing no module now says that, and lists what it did write. **link and test wrote to different directories.** The compile/link path passed `LinkExecutor` a base of `targetDir/link-output`; the test path passed `targetDir`. One project linked by both landed in two trees, each with an up-to-date check blind to the other. `bleep run` reads the first, which is why nothing noticed. This is #673's shape one level up: that fix stopped every *suite* linking its own copy, this stops the two *commands* doing the same. `TaskDag.KotlinJsConfig.outputDir` is gone with it. It had no reader — `LinkExecutor` computes the directory for every platform — and the two callers filled it with two different values, which is what made the paths look like they disagreed when neither was being consulted. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
`bleep test` has had a matrix since #682: every framework on every target, run end to end, asserted on the JUnit XML. `bleep link` had per-piece tests — a release link minifies, a test link honours `jsKind` — and nothing that took a declaration, linked it, and ran the result. That gap is where three defects sat at once, all found in the last few days: a `--release` Scala.js link never called `withMinify`, a Kotlin/JS `es` link reported "linking failed" because the output lookup only knew `.js`, and a test link and a main link wrote into two different directories. 13 cases across four targets: | target | cases | |---|---| | Scala.js | jsKind none / commonjs / esmodule, plus release | | Kotlin/JS | default, umd, plain, es, amd | | Scala Native | debug, release | | Kotlin/Native | debug, release | Each links, asserts on the artifact, and then **runs it and checks what it printed**. Running is the part the per-piece tests could not do: a linked program that is the wrong module kind still exists and still has plausible bytes, and only node or the loader says otherwise. Two details worth stating rather than leaving to be rediscovered: - Every case asserts the path bleep *reported* — `BuildSummary.linkedOutputs`, new in this branch — rather than one the test reconstructed. A link that writes somewhere unexpected fails here instead of being quietly found by a directory walk. - The AMD case is shape-only and says so. An AMD module defines itself into a loader node does not have, so "we did not run it" is recorded in the output rather than looking like coverage. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
`model.KotlinJs` had nine fields and, until this branch, no reader at all. Now that one of them is wired, the rest had to be settled either way. **Threaded through — five.** `moduleName`, `sourceMap`, `sourceMapPrefix`, `sourceMapEmbedSources`, `generateDts`. This turned out to be nearly free, and for a reason worth writing down: bleep sets every one of these on the compiler arguments unconditionally, so the question was never whether to support them but which constant to hardcode. They now come from the build in the same order the module kind does — flag, then project, then bleep's default. `sourceMapPrefix` stays an `Option`, since it is the only one where "unset" is a real state the compiler is never told about. **Deleted — three, everywhere they appeared.** - `target` (browser/nodejs/both) was never passed to kotlinc at all: there is no `setField` for it in either the compile or the link path, so it was dead on bleep's own `KotlinJsCompilerConfig` too, not just in the build model. It is not a compiler flag — in Gradle it selects a runner and a toolchain, which bleep does not have. Gone from the model, the schema's vocabulary, the compiler config, and its enum. If a browser runner ever lands it comes back with something behind it. - `outputMode` was equally unread at both levels; the paths set `irProduceJs` / `irProduceKlibFile` directly, which is bleep deciding per phase, correctly. - `developmentMode` is the same switch as `dce`, driven by `--optimize` and release mode. Two names for one knob is an invitation for them to disagree. **And `moduleName` did not work when first wired.** The linker identifies the project's own KLIB by matching file names against `config.moduleName` — fine while that was always the project name, wrong the moment a build could change it: the KLIB on disk is named after the project, so nothing matched, `includes` was never set, and the link failed. One string was doing two jobs, and only one of them is the user's to choose; the lookup now takes the project's name explicitly. Six link-matrix cases, one per field, each asserting on what the compiler produced — a `.d.ts` appears, the artifact is `custom_name.js`, the map carries `sourcesContent`, its sources are prefixed — rather than on the configuration it was handed. Schema and the cross-building docs list the six that do something. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Two things the in-process BSP server used to invent for itself, both of which undid what its caller was trying to say. **The config.** `handleTest` and `handleCompile` re-read the developer's own `config.yaml` per request. That is right for the real daemon — `parallelism`, the heaps and the idle timeout are machine settings, and picking up an edit without a restart is the point — but there was no way for a caller that already has a config to be heard. So bleep's own integration tests were carefully setting `parallelism = 1`, `testRunnerHeap = 512m` and `kspRunnerMaxMemory = 384m`, with a long comment about the explosion of test JVMs those prevent, and every one of them was ignored in favour of whatever the person running the suite happened to have in their file. `configOverride` is `None` for the daemon, so production behaviour is unchanged. **The governor.** Each connection built `MachineResources.forThisMachine` — every core, most of the RAM — so a suite running integration tests concurrently had one governor per connection, each admitting forks as though nothing else were running. A governor bounds forks *across* clients, which is only true if there is one of it. The harness now makes one and shares it, deliberately small at 2 cores and 2GB: this JVM is itself one of several the outer `bleep test` forked, and those outer forks are governed by a daemon that cannot see anything started in here. **And `close` waits.** Closing the pipes is what tells `run()` to return, but nothing waited for it, so a test could finish and the next one start while the previous server was still cancelling requests and killing child processes. The wait is bounded — a server that will not come down must not hang the suite trying to leave. `SuiteIdleTimeoutIT` pins both this and the idle-timeout semantics it depends on: a suite that prints while finishing no test is idle *by design*, and is killed. Showing that needs a one-minute timeout, which needs the harness's config to actually arrive — with the override removed the fixture runs to completion against the developer's own three-minute setting and the test fails saying so. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…ndows)
`PublishVersionTest` builds a real git repository in a temp directory and
deletes it afterwards. That passed everywhere except the Windows CI runner,
where all five of its cases failed in cleanup with
java.nio.file.AccessDeniedException:
...\bleep-publish-version-...\.git\objects\07\0774631921...
Windows will not unlink a read-only file at all; POSIX only consults the
containing directory's permissions, so the same tree deletes cleanly there. git
marks every object under `.git/objects` read-only, so a `.git` directory is the
easiest way to meet this — but nothing about it is specific to tests.
`FileUtils.deleteDirectory` is what `bleep clean` uses on Windows, so any
read-only file under a target directory would have refused the same way.
Fixed where the difference is, not in the test that tripped over it: a delete
that hits `AccessDeniedException` clears the attribute and tries once more. The
retry is narrow on purpose — that exception only, once, and the second attempt
is allowed to throw, because a tree that still cannot be emptied is a real
failure rather than something to walk past.
`FileUtilsDeleteTest` asserts the outcome rather than the mechanism, since the
mechanism differs by platform: on Windows it exercises the clear-and-retry, on
POSIX the plain path, and on either one a regression fails it on the platform
that has the problem.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Fallout from giving the in-process server a `configOverride`, and the good kind: this test had a workaround for the defect that fix removed, and the workaround is now the thing that is wrong. `configuredDefaultMb` read the developer's own `config.yaml`, with a comment saying it deliberately did not read `IntegrationTestHarness.testConfig` because "the in-process server does not consult" it. That was accurate — and it meant the assertions were written against whatever heap the person running the suite had configured, which is exactly the problem: on this machine the harness asked for 512m and the fork got 4g, and the test agreed with the fork because it was reading the same file the server was. Now that the server uses the config it is handed, the test reads it too. The assertions stop depending on a file outside the repository, which is what the old comment was trying to achieve by the only means then available. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
`munit 1.0.0` on Scala Native failed the version sweep on all three Scala versions, with one assertion: the constructor failure reached the report without stack frames. That is true, and it is not a defect. munit 1.0.0 lets the process die where 1.3.4 reports through the adapter, so the same fixture yields a bleep process error there and a named cause with frames here. The expectations it was measured against — `ctorFailureReport` and `reportsCtorStackFrames` — are keyed on framework and platform. The comment on the first already says the behaviour "cannot be keyed on the framework alone", and the same reasoning extends one axis further: it cannot be keyed on the platform alone either. Rather than record every framework's reporting shape per version, the sweep stops asserting it. Its stated job is discovery, runner selection and the fork protocol — the ways an old release breaks a *user* — and holding a five-year-old release to today's reporting detail only produces red that has to be explained away, which is how a matrix stops being read. That file already warns about exactly this for resolution errors. What every version is still held to is unchanged and is the part that matters: the suite whose constructor throws appears in the report, and something about it is marked failed. A failure that disappears still fails here. `pinnedVersion` is derived from `pinnedFor` rather than passed in, so it stays right whichever matrix calls `checkFixture`. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Five of the seven open reports from the 2026-08-26 batch, plus a cleanup the
last of them dragged in. Six commits, one concern each.
Branched off #682 while it was open, since
#664lives in the block that PRrewrites. #682 has since merged, so this applies cleanly to
master.Closes #664. Closes #663. Closes #671. Closes #670. Closes #677.
#664 — a Scala.js test link ignored the project's
jsKindTwo places decided how to emit the program, and they disagreed. The main link
read
--module-kind, then the project's ownjsKind. The test link declaredScalaJsLinkConfig.Debugand took itsCommonJSModulealong with the debugsemantics it actually wanted — so a build saying
jsKind: esmodulehad itstests linked as CommonJS, with no flag able to change it and nothing reporting
the substitution.
Both paths now derive the kind through one function, and
runScalaJsTestSuiteis handed the kind that was linked rather than deriving a second opinion
from a constant. That part matters more than it looks: the test adapter picks
its
Inputfrom this value, and a NoModule program loaded as a module — or thereverse — fails before any test runs. A second opinion is the one thing it must
never be.
Debug semantics stay. Nobody wants their tests run through the optimizer, and
the output directory is named after the mode, so nothing moved.
The suite passed either way
This is why the regression test asserts on the emitted JavaScript rather than on
the run's verdict. With the fix reverted, the ESModule case reports:
while the suite it ran still reported
PASSED example.ModuleKindSuite: 1 passed, 0 failed. The link and the adapter agreed with each other and disagreed withthe build, so every behavioural signal was green. A test checking which config
object was passed around would have passed throughout too — the constant was
consistent, consistently wrong.
The fixture carries an
@JSExportTopLevel, because a Scala.js program with noexported members looks nearly identical under CommonJS and under ESModule. The
issue said as much; without an export there is nothing to tell them apart by.
Both directions are covered —
esmodulemust emitexport,commonjsmustemit
exports.greet— since a test asserting only the ESModule case would leave"does it still honour the declaration when the declaration is the old constant"
unchecked.
#671 —
commands.compilecould not say whether it did anythingNo new machinery was needed. The server already reports a
CompileReasonperproject,
UpToDatealready reached the client, andBuildStatedropped it onthe floor under a comment saying it was purely informational.
It is accumulated now, and
compilereturns theBuildSummarythe runproduced. The verdict is unchanged — a failed compile still throws — so a caller
who only wants "did it work" goes on ignoring the value.
BuildSummary.noOpis BSP'sCompileReport.noOpcomputed from that: true whenevery project that compiled was already up to date.
A run in which nothing compiled at all is deliberately not a no-op. There
was no compile to be a no-op about, and answering true would tell a deploy
script it may skip on the strength of a run that never looked. That is the case
the unit tests spend a third of their assertions on.
#663 — no
linka script could callThree lines, once #671 had opened the return path. Takes the same
LinkOptionsthe command line does.
#670 — a script's own exit code got the full-command dump
bleep <script>went throughcli.apply, which throws on any non-zero exit withthe whole command line in the message. For git or gpg that is right: something
went wrong, and the reader needs the command to diagnose it. For a script that
exits 1 to report a problem it has already described, it means the report is
followed by a java path, two
--add-opensflags and a hundred-jar classpath onone line — and the reader stops looking for the two readable lines above it. The
code the script chose was replaced by bleep's flat 1 as well.
cli.exitCodehands the code back;cli.applyis that plus the throw, so everyexisting caller keeps the diagnosis it had.
bleep runandbleep <script>—JVM, node and native alike — use the former and return
BleepException.SubprocessExit, which renders as one line and carries the codethrough to bleep's own exit status via
ExitCode.FromSubprocess.Covered through
JvmRunner.Forked, which the ITs otherwise never touch: theharness swaps in
InProcessfor every other test, and aSystem.exittherewould take the test JVM down with it.
#677 — the link would not say where it put things
The report asks for an artifact model: the graph should track a link output the
way it tracks compiled classes, and a downstream task should trigger the link.
Most of that premise is already true.
bleep testbuilds aLinkTaskandDiscoverTaskdepends on it;bleep runcallslinkProjectbeforerunJs;LinkResultis a first-class task result the DAG stores and reads. The one verbleft is package, which is not a bleep command but a script — and #663 above
just handed that script
commands.link.What was actually missing is smaller and sharper: a script could trigger a link
and could not find out what it produced. It had to rebuild
by hand — a layout bleep owns and has already renamed once, when
--releasegrew minification and the directory had to follow the mode rather than the
optimizer.
The authoritative answer already existed and already crossed the wire.
BuildEvent.LinkSucceededcarriesgeneratedFiles, with a comment on the fieldsaying it is taken from the linker "rather than found by looking in the output
directory afterwards, which is guesswork that goes stale the moment a platform
changes its layout". bleep declined to guess for its own use and then left
scripts nothing else.
BuildStatematched the field as_, the same way it wasdropping
CompileReasonbefore #671.It is
BuildSummary.linkedOutputsnow, andLinkedOutput.mainArtifactgivesthe ordering convention a name instead of leaving callers to know that
files.headis special.What this deliberately does not build: the model field, the DAG edge and the
artifact model the report asks for. Once the address is available, everything
else in #677 is four lines of script, and a script can do all of it. The one
thing mechanics would still buy is watch — edit a frontend source, have a server
pick up new JS — because
bleep <script> -wwatches the script project'stransitive deps and a JVM script project cannot legitimately depend on a JS one.
That is thin, and not worth a model concept until someone wants it.
testreports what ranSame three lines as
compileandlink. Worth having for a reason that is notthe obvious one: a failing test throws, so "it returned" already meant nothing
failed. What a caller could not learn is whether anything ran, which is the
failure mode that reads as green in CI — the same hole
testProjectsWithoutSuitescloses inside bleep, now visible to a script.
Its limit is stated rather than papered over:
failuresandcancelledSuitesdescribe a run that threw before the caller could read them, so the Java
TestReportcarries counts only. Inspecting failures rather than propagatingthem needs a non-throwing entry point, which is a separate decision.
Publish takes one version input, and dynver moved inside
Found while auditing the rest of
Commandsfor return values worth forwarding.Publish.OptionscarriedversionOverride: Option[String]besideversionFallback: Option[() => String]— a two-field encoding of a one-of-twochoice, with "exactly one must be set" enforced at runtime and, on the Java
side, thrown from a constructor. The thunk existed so the command line could
skip shelling out to git when
--versionwas given; that laziness belongs toresolving a version, not to the shape of the input, and the Java bridge was
collapsing it on the spot anyway.
It is now
PublishVersion:Specified(value)orDynver. Three commands thatspelled the same idea three ways —
PublishLocala requiredString,PublishSonatypea bareversionOverride,Publishboth fields — take onetype, and
bleep publish-local --versionbecomes optional like every otherpublish path.
Two things fell out of moving the resolution in:
dynverSonatypeSnapshots = true, and the paragraph explaining why it must betrue, was written twice — once in
Main, once inPublishSonatype. Once now.--assert-releasewas asking "is this a snapshot" two ways.Publishinspected the string for
+and-SNAPSHOT;PublishSonatypeasked dynver.One answer now, and it is dynver's.
Behaviour is preserved, including the wart:
--assert-releasedoes not check aversion the caller spelled out. That used to read
versionOverride.isEmpty—what it did, not why. It is a case in a match now with the reason written down,
and worth revisiting on its own rather than while moving it.
Also here
The Java scripting API gets the same two additions. Leaving it out would keep
"bleep's
Commandsexposes nolink" literally true for Java scripts. The JavaCompileReportis deliberately narrower than the summary it comes from —BuildSummarychanges shape whenever the build display does, and a publishedinterface should not.
docs/concepts/bleep-scripts.mdxshows all of it — thenoOp()check a scriptactually writes, reading a linked path off the
LinkReportinstead ofrebuilding it, and
.versionFromGit(). The CI/CD guide and the Maven Centraltutorial both now say what
--assert-releaseapplies to, and the CLI referenceis regenerated for the now-optional
publish-local --version.Also in here: the Kotlin/JS half of #664, and what it uncovered
#664is the Scala.js side. The Kotlin lane had the same defect and no report:model.KotlinJs.moduleKindwas in the build model, decodable frombleep.yaml,and had no reader anywhere in the server — the link read
--module-kindandotherwise used a hardcoded CommonJS.
Wiring it up immediately failed, which is why nobody could have been using it:
Kotlin names ES output
<module>.mjs, the output lookup only knew.js, so itfound nothing — and reported that as
"Kotlin/JS linking failed"with an emptydiagnostic list, for a compiler that had exited 0 with nothing to say. Both
fixed. A linker that succeeds while producing no module now says so and lists
what it did write.
Kotlin/JS test links stay UMD and now warn instead of silently substituting:
bleep runs those tests by
require-ing the output from a generated CommonJSscript, which an ES module cannot satisfy. A warning and not a failure — those
builds' tests pass today.
model.KotlinJshad nine fields and no reader, so the rest were settled eitherway rather than left ambiguous. Five threaded through (
moduleName,sourceMap,sourceMapPrefix,sourceMapEmbedSources,generateDts) — nearlyfree, since bleep already set every one of them on the compiler arguments
unconditionally and the only question was which constant to hardcode. Three
deleted at every level they appeared:
targetwas never passed to kotlinc atall and is a Gradle runner concept bleep does not implement,
outputModeisbleep's per-phase decision, and
developmentModeis the same switch asdce.Threading
moduleNameexposed one more: the linker identified the project's ownKLIB by matching file names against
config.moduleName. Fine while that wasalways the project name, wrong the moment a build could change it — the KLIB on
disk is named after the project, so nothing matched and the link failed. One
string doing two jobs; the lookup takes the project's name explicitly now.
Also in here: link and test share one output directory
The compile/link path handed
LinkExecutora base oftargetDir/link-output,the test path handed it
targetDir. One project linked by both landed in twotrees, each with an up-to-date check blind to the other. This is #673's shape
one level up: that fix stopped every suite linking its own copy, this stops
the two commands doing the same.
TaskDag.KotlinJsConfig.outputDiris gonewith it — no reader, and the two callers filled it with different values, which
is what made the paths look like they disagreed.
Also in here: a link matrix, and the test harness that runs it
bleep testhas had a matrix since #682.bleep linkhad per-piece tests andnothing that took a declaration, linked it, and ran the result — which is where
three defects sat at once, all found in the last few days.
13 cases across four targets: Scala.js (
jsKindnone/commonjs/esmodule, plusrelease), Kotlin/JS (default, umd, plain, es, amd, plus one per threaded field),
Scala Native and Kotlin/Native (debug, release). Each links, asserts on the
artifact, and runs it and checks what it printed. The AMD case is shape-only
and says so, rather than looking like coverage it does not have.
Two harness problems surfaced while building it, both of which had been quietly
shaping every integration test:
handleTestre-read thedeveloper's own
config.yaml, soparallelism = 1,testRunnerHeap = 512mand
kspRunnerMaxMemory = 384m— set deliberately, with a comment about theexplosion of test JVMs they prevent — were all ignored in favour of whatever
the person running the suite happened to have. The in-process server takes a
configOverridenow; it isNonefor the real daemon, whose per-requestre-read is deliberate.
most of RAM. A suite running integration tests concurrently therefore had one
governor per connection, each admitting forks as though nothing else were
running — and a governor bounds forks across clients, which is only true if
there is one of it. The harness shares one now, deliberately small.
SuiteIdleTimeoutITpins both, and the idle-timeout semantics they depend on: asuite that prints while finishing no test is idle by design and is killed.
Also in here: a Windows delete bug
PublishVersionTestbuilds a real git repository and deletes it, which failedonly on the Windows runner: git marks every object under
.git/objectsread-only, and Windows will not unlink a read-only file at all, where POSIX only
consults the containing directory. Fixed in
FileUtils.deleteDirectoryratherthan in the test, because
bleep cleanuses it on Windows too and any read-onlyfile under a target directory would have refused identically.
Testing
Full suite on a frozen tree, matrix included: 1554 passed, 0 failed, 1 timed
out, 1 skipped, 229 suites, 755s. The three
munit 1.0.0-on-Scala-Nativefailures and the load-sensitive
ScalaNativeAdvancedTestIntegrationTestthatearlier runs showed are gone.
The one timeout is
KotlinNativeTestFrameworkIT, and it is contention ratherthan a slow test. Alone on an idle machine that suite runs in 33s; under the
full 13x-parallel run it measured 145s, 169s, and then breached the 120s idle
bound — a 4-5x stretch. The thread dump agrees:
cpu=1389msagainstelapsed=213s, parked inrunInProcesswaiting on one Kotlin/Native innerbuild (compile, LLVM link, run). It is a single test, so there is no
intermediate test event between
TestStartedandTestFinishedto reset thetimer, and stdout deliberately does not reset it — output is not progress. So a
33-second fixture dies because the machine is oversubscribed while it waits.
That is a scheduling problem, not a fixture-size one, and it is not addressed
here. Note the local run included the
matrixsweeps, which CI excludes, so CIcarries substantially less of this load.
New:
ScalaJsTestModuleKindIT(2),ScriptCommandsIT(3),ScriptExitCodeIT(2),
PublishVersionTest(5, against a real tagged git repo — including that adirty tree fails
--assert-release),KotlinJsModuleKindIT(2),TestLinkOutputDirIT,FileUtilsDeleteTest, theLinkMatrixITfamily, and 5cases in
BuildStateReducerTestfornoOpandlinkedOutputs. Each fix wasconfirmed by reverting it and watching the new test fail.
Not in here
kotlin.native— ten fields in the model, none of them read, andtargethardcoded to
"host"against nineteen the model enumerates. The samedelete-or-support question
kotlin.jsjust answered, deferred becausetargetthere means real cross-compilation rather than plumbing: it needs a toolchain
per target, and a cross-compiled binary cannot be run on the host, so the link
matrix's "run it and check what it printed" stops applying.
--assert-releasestill does not check a version the caller spelled out. Thatis preserved on purpose — it used to read
versionOverride.isEmpty, which saidwhat it did and not why, and is now a case in a match with the reason written
down. Worth deciding on its own rather than while moving it.
Still open from the batch: #669 (sourcegen publishing a stale git-derived
constant) and #666 (
bleep scalafix).🤖 Generated with Claude Code