diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index f6af5d4..f74c3af 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -8,7 +8,8 @@ on: # The build is Mill (build.mill, wasm/package.mill). `./mill` is the bootstrap script: it downloads Mill # 1.3.1, and Mill downloads the JDK it runs on and forks tests with (Zulu 25, `mill-jvm-version` in -# build.mill) through coursier, so no setup-java is needed for the build itself. +# build.mill) through coursier, so no setup-java is needed for the build itself. The meta-build loads an +# unreleased mill-scalablytyped that scripts/upstream-snapshot.sh installs (see the first steps of every job). jobs: format: runs-on: ubuntu-latest @@ -17,6 +18,16 @@ jobs: with: fetch-depth: 0 - uses: coursier/cache-action@v8 + # mill-scalablytyped (main, which has https://github.com/lolgab/mill-scalablytyped/pull/50 and /51, + /52) is + # unreleased: build it from the commit pinned in scripts/upstream-snapshot.sh and install it locally, cached by + # that file's hash. Mill itself is the released 1.3.1, downloaded by `./mill`. + - name: Cache the upstream mill-scalablytyped snapshot + uses: actions/cache@v4 + with: + path: ~/.ivy2/local/com.github.lolgab + key: upstream-snapshot-${{ hashFiles('scripts/upstream-snapshot.sh') }} + - name: Build the upstream mill-scalablytyped snapshot + run: scripts/upstream-snapshot.sh - name: Check formatting (scalafmt) run: | # Files using experimental capture-checking `^{...}` syntax that scalafmt can't parse are @@ -59,6 +70,16 @@ jobs: with: fetch-depth: 0 - uses: coursier/cache-action@v8 + # mill-scalablytyped (main, which has https://github.com/lolgab/mill-scalablytyped/pull/50 and /51, + /52) is + # unreleased: build it from the commit pinned in scripts/upstream-snapshot.sh and install it locally, cached by + # that file's hash. Mill itself is the released 1.3.1, downloaded by `./mill`. + - name: Cache the upstream mill-scalablytyped snapshot + uses: actions/cache@v4 + with: + path: ~/.ivy2/local/com.github.lolgab + key: upstream-snapshot-${{ hashFiles('scripts/upstream-snapshot.sh') }} + - name: Build the upstream mill-scalablytyped snapshot + run: scripts/upstream-snapshot.sh # Wasm 3.0 + JSPI are on by default from Node 25; the runner's default Node has neither. - name: Set up Node 25 if: matrix.platform == 'js' @@ -75,8 +96,7 @@ jobs: path: | ~/.cache/scalablytyped out/stFacades - out/stGen - key: st-facades-mill-${{ hashFiles('package-lock.json', 'build.mill', 'buildsupport.mill', 'build-tools/st-gen/**') }} + key: st-facades-mill-${{ hashFiles('package-lock.json', 'build.mill', 'buildsupport.mill', 'scripts/upstream-snapshot.sh') }} restore-keys: st-facades-mill- - name: Compile if: matrix.platform != 'wasm' @@ -198,14 +218,23 @@ jobs: with: fetch-depth: 0 - uses: coursier/cache-action@v8 + # mill-scalablytyped (main, which has https://github.com/lolgab/mill-scalablytyped/pull/50 and /51, + /52) is + # unreleased: build it from the commit pinned in scripts/upstream-snapshot.sh and install it locally, cached by + # that file's hash. Mill itself is the released 1.3.1, downloaded by `./mill`. + - name: Cache the upstream mill-scalablytyped snapshot + uses: actions/cache@v4 + with: + path: ~/.ivy2/local/com.github.lolgab + key: upstream-snapshot-${{ hashFiles('scripts/upstream-snapshot.sh') }} + - name: Build the upstream mill-scalablytyped snapshot + run: scripts/upstream-snapshot.sh - name: Cache ScalablyTyped facades uses: actions/cache@v4 with: path: | ~/.cache/scalablytyped out/stFacades - out/stGen - key: st-facades-mill-${{ hashFiles('package-lock.json', 'build.mill', 'buildsupport.mill', 'build-tools/st-gen/**') }} + key: st-facades-mill-${{ hashFiles('package-lock.json', 'build.mill', 'buildsupport.mill', 'scripts/upstream-snapshot.sh') }} restore-keys: st-facades-mill- - name: Install wasm tools for the wasm publish (wkg, wasm-tools, wit-bindgen-scala) if: env.PUBLISH_WASM_TO_CENTRAL == 'true' @@ -240,7 +269,7 @@ jobs: if [[ "$PUBLISH_WASM_TO_CENTRAL" == true ]]; then ./mill --ticker false core.js.publishLocal export DAPR4S_WASM_CORE_VERSION="$version" - artifacts='{core.jvm,core.js,wasm.lib[release].wall,wasm.glue,wasm.glueExports,wasm.scalalibOverlay,wasm.linker,wasm.millPlugin,wasm.sbtPlugin}.publishArtifacts' + artifacts='{core.jvm,core.js,wasm.lib[release].wall,wasm.glue,wasm.glueExports,wasm.scalalibOverlay,wasm.linker[_],wasm.millPlugin,wasm.sbtPlugin}.publishArtifacts' fi ./mill --ticker false mill.javalib.SonatypeCentralPublishModule/publishAll --publishArtifacts "$artifacts" env: diff --git a/AGENTS.md b/AGENTS.md index 2894b28..eeb4b03 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -28,15 +28,23 @@ All three must stay in sync with the code at all times. never written into the POM). - **Build tool**: **Mill 1.3.1** (`build.mill`, `buildsupport.mill`, `wasm/package.mill`, and the meta-build sources `mill-build/src/` = the `mill-dapr4s-wasm` plugin; `./mill` is the bootstrap - script, so nothing needs to be installed). + script). Mill 1.3.1 contains [com-lihaoyi/mill#7783](https://github.com/com-lihaoyi/mill/pull/7783) + (`scalaJSOrganization`, `ModuleKind.WasmComponent`) and + [com-lihaoyi/mill#7786](https://github.com/com-lihaoyi/mill/pull/7786). The meta-build loads an + unreleased mill-scalablytyped `0.4.1-14-c21896` + (`main`, which has [lolgab/mill-scalablytyped#50](https://github.com/lolgab/mill-scalablytyped/pull/50) and + [#51](https://github.com/lolgab/mill-scalablytyped/pull/51) merged, + + [#52](https://github.com/lolgab/mill-scalablytyped/pull/52)). **Run `scripts/upstream-snapshot.sh` + once before the first `./mill`**: it builds it from a pinned commit and installs it locally + (`~/.ivy2/local`). CI runs it in every job, cached by the script's hash. Bump the pin = edit the + script. RETIRES with the mill-scalablytyped release: pin it and delete the script. History: scala-cli until 2026-10-12, sbt until 2026-10-13 (the sbt build lived in `build.sbt`, `project/` and a nested `wasm/build.sbt`; `git log` has it). Modules: - `core.jvm` / `core.js` — the published library (`dapr4s_3` / `dapr4s_sjs1_3`); `core.jvm.test` / `core.js.test` are its **unit** tests (every test source outside an `integration/` directory). - `integration.jvm` / `integration.js` — the Docker integration suites (`test/{shared,jvm,js}/integration`), never published. `integration.js` links with the Wasm + JSPI linker config. - - `stFacades` — the ScalablyTyped facades (see "Scala.js layer rules"), never published itself; - `stGen` — the generator of their sources (`build-tools/st-gen`). + - `stFacades` — the ScalablyTyped facades (see "Scala.js layer rules"), never published itself. - `k8s.orderService` / `k8s.inventoryService` — runnable fat jars of the two example mains (`test/jvm/apps`) for `scripts/k8s-test.sh`. - `wasm.*` — the `wasm` platform (WASI components), see "Wasm component layer rules" below. @@ -311,16 +319,13 @@ the `_sjs1_3` artifact carries no jobs/conversation API at all. JavaScript stack frame — skipping this throws `WebAssembly.SuspendError` at runtime. - **Facades are ScalablyTyped-generated**, not hand-written. The `stFacades` module converts the npm packages of `package.json` `dependencies`, as installed in the repo's `node_modules` from - `package-lock.json` (`npmInstall` runs `npm ci` when needed), into Scala sources - (`out/stFacades/stSources.dest`) and compiles them (~10 min cold; the converter's parse cache is - `~/.cache/scalablytyped`). The generator is `build-tools/st-gen` (`stGen` module): it calls - sbt-converter's own `ImportTypingsGenSources` (converter `1.0.0-beta45`; the sbt plugin jar is used - as a plain library, without sbt) with the options the sbt build used: output package `dapr4styped`, - stdlib `es2022`, Scala 3.3.6 (the converter needs a released Scala to target; the nightly reads its - TASTy), plugin defaults otherwise — its output is byte-identical to the sbt build's. WHY not - `mill-scalablytyped`: 0.4.1 hard-codes stdlib `es6`; RETIRES with - [lolgab/mill-scalablytyped#51](https://github.com/lolgab/mill-scalablytyped/pull/51) (stdlib option) + - [#52](https://github.com/lolgab/mill-scalablytyped/pull/52) once released. Facade imports use the + `package-lock.json` (`scalablyTypedNpmCi` runs `npm ci` when needed), into Scala sources and + compiles them (~10 min cold; the converter's parse cache is `~/.cache/scalablytyped`). The module + extends mill-scalablytyped's `ScalablyTyped` (converter `1.0.0-beta45`) with the options the sbt + build used: `scalablyTypedOutputPackage = "dapr4styped"`, `scalablyTypedStdLibs = Seq("es2022")` + ([lolgab/mill-scalablytyped#51](https://github.com/lolgab/mill-scalablytyped/pull/51)), + `scalablyTypedNpmCi = true` ([#52](https://github.com/lolgab/mill-scalablytyped/pull/52)), Scala + 3.3.6 (the converter needs a released Scala to target; the nightly reads its TASTy). Facade imports use the **`dapr4styped.*` root package**, never ST's default `typings.*` — the facades ship inside the published jar and must not collide with a consumer's own ST generation. `stFacades` is compiled without our flags (no CC, no explicit nulls, no fatal warnings) — but @@ -365,7 +370,7 @@ The `wasm` platform builds a dapr4s app as a WASI **component** for [dapr-wasm-components](https://github.com/sideeffffect/dapr-wasm-components) 0.7.5, with the scala-wasm fork of Scala.js (`io.github.scala-wasm`, `ModuleKind.WasmComponent`). Status, blockers and numbers: `docs/WASM-COMPONENTS-PROPOSAL.md` §6 and -`wiki/scala-js/scala-wasm-component-spike.md`. The seven artifacts are **published to Maven Central** +`wiki/scala-js/scala-wasm-component-spike.md`. The eight artifacts are **published to Maven Central** by the same `publish` job as core (snapshots on master pushes, releases on tags); `publishLocal` works. Upstream gaps are worked around here AND fixed upstream (the earlier "no upstream PRs" rule was lifted on 2026-10-13). Open upstream PRs, each retiring one workaround: scala-wasm/scala-wasm @@ -383,26 +388,27 @@ agents push branches / open drafts with an EMPTY description; the user writes th 2026-10-13. Mill has no such clash. **The build logic is the Mill plugin `mill-dapr4s-wasm`**, whose sources (`mill-build/src/dapr4s/millwasm/Dapr4sWasmModules.scala`) are compiled into dapr4s's own meta-build, so the `wasm.*` modules use the very traits a Mill consumer gets: - `ScalaWasmModule` swaps `org.scala-js` for `io.github.scala-wasm` in the tasks that name it - (`mandatoryMvnDeps`, `scalacPluginMvnDeps`, `scalaJSLinkerClasspath`); `Dapr4sWasmModule` adds the - exclusions; `Dapr4sWasmTests` the fork's test bridge; `WitModule` / `WitBindgenModule` run `wkg wit fetch` + `ScalaWasmModule` sets Mill's `scalaJSOrganization` to `io.github.scala-wasm` (mill#7783: library, + compiler plugin, test bridge and linker come from the fork); `Dapr4sWasmModule` adds the + exclusions; `Dapr4sWasmTests` its tests; `WitModule` / `WitBindgenModule` run `wkg wit fetch` / wit-bindgen-scala; `Dapr4sWasmComponentModule` links a component. Only `wasm.*` modules see the fork, so `core.js` is untouched. Mill's Scala.js worker links the plain-JS wall unit tests with the fork's - released 2.13 linker; Mill knows no `ModuleKind.WasmComponent`, so COMPONENTS are linked by the driver - `dapr4s.wasmlink.WasmLink` (`wasm/linker/src`, shipped IN `dapr4s-wasm-linker`), run by the plugin in a - separate JVM on the linker's classpath. dapr4s's components (`WasmComponent` in `wasm/package.mill`) - only point the artifact-backed tasks at in-build modules: `dapr4sWasmLinkerClasspath` → `wasm.linker`, + released 2.13 linker, and COMPONENTS with `ModuleKind.WasmComponent` (mill#7783) on the patched + `dapr4s-wasm-linker_2.13` (`fastLinkJS` / `fullLinkJS`, no separate link driver). dapr4s's components + (`WasmComponent` in `wasm/package.mill`) only point the artifact-backed tasks at in-build modules: + `dapr4sWasmLinker` → `wasm.linker[2.13.18]`, `dapr4sWasmScalalibOverlay` → `wasm.scalalibOverlay`, `dapr4sWasmAddGlueExports = false` (a module dependency on `wasm.glueExports` instead); every module shares `wasm.wit` (the `wasm` object is itself a `WitModule`). Nothing in `wasm/` runs unless a `wasm.*` task is asked for: `./mill core.jvm.test` needs no wasm tool. The nightly, `scalaJavaTime` and `munit` come from `Versions`; `NightlyWasm` uses the same compiler flags. **Upstream (retire the plugin's Mill workarounds)**: [com-lihaoyi/mill#7783](https://github.com/com-lihaoyi/mill/pull/7783) (Scala.js organization + - `ModuleKind.WasmComponent` → `ScalaWasmModule`'s overrides and the link driver), + `ModuleKind.WasmComponent`) is already USED here, released in Mill 1.3.1 (it retired + `ScalaWasmModule`'s per-task overrides and the link driver); [#7784](https://github.com/com-lihaoyi/mill/pull/7784) (wit-bindgen-scala task → `WitBindgenModule`), [#7785](https://github.com/com-lihaoyi/mill/pull/7785) (run/test components with wasmtime → the shell e2e's `wasmtime serve` wiring could become Mill tasks). -- **The seven artifacts** (all `com.github.sideeffffect`, at core's `publishVersion`, POM metadata as core's, +- **The eight artifacts** (all `com.github.sideeffffect`, at core's `publishVersion`, POM metadata as core's, normal suffixes): | artifact | module | contains | |---|---|---| @@ -410,7 +416,7 @@ agents push branches / open drafts with an EMPTY description; the user writes th | `dapr4s-wasm-glue_sjs1_2.13` | `wasm.glue` | the wit-bindgen-scala bindings of world `app` + the hand-written 2.13 glue | | `dapr4s-wasm-glue-exports_sjs1_2.13` | `wasm.glueExports` | the hand-written `@WitExport` trampolines (a COMPONENT's dependency only; the plugin adds it) | | `dapr4s-wasm-scalalib-overlay` | `wasm.scalalibOverlay` | the fork-patched stdlib `.sjsir` (unmodified, from `io.github.scala-wasm:scalajs-scalalib_2.13:3.8.3-RC1-wasm.4`) + LICENSE/NOTICE attribution | - | `dapr4s-wasm-linker_2.12` | `wasm.linker` | scala-wasm's 1.22.1-wasm.6 linker with our patch (`scripts/wasm/patches/scala-wasm-params-via-memory.patch`): the patched sources compiled by Mill, the released jar's resources, LICENSE/NOTICE, patched sources in the sources jar; depends on the released ir/linker-interface. Also carries the component link driver `dapr4s.wasmlink.WasmLink` (`wasm/linker/src`) | + | `dapr4s-wasm-linker_2.12`, `dapr4s-wasm-linker_2.13` | `wasm.linker[2.12.21]`, `wasm.linker[2.13.18]` | scala-wasm's 1.22.1-wasm.6 linker with our patch (`scripts/wasm/patches/scala-wasm-params-via-memory.patch`): the patched sources compiled by Mill, the released jar's resources, LICENSE/NOTICE, patched sources in the sources jar; depends on the released ir/linker-interface. 2.12 for sbt-dapr4s-wasm (sbt loads a 2.12 linker), 2.13 for Mill's Scala.js worker (mill-dapr4s-wasm) | | `mill-dapr4s-wasm` (`_mill1_3`) | `wasm.millPlugin` | the build logic for Mill consumers (below): the sources of `mill-build/src`, compiled with the Scala and against the `mill-libs` of the Mill running the build (1.3.1), + a `dapr4s/millwasm/version` resource (the version of the wasm artifacts it resolves) | | `sbt-dapr4s-wasm` (`_2.12_1.0`) | `wasm.sbtPlugin` | the build logic for sbt consumers (below), the twin of `mill-dapr4s-wasm`. Mill builds it as a plain sbt-1 plugin jar: sbt `provided`, a generated `sbt/sbt.autoplugins`, `Implementation-Version` in the manifest, sbt's POM properties | - **Core: published or source** (`wasm.lib` is a cross module). `wasm.lib[release]` (the default for @@ -435,7 +441,7 @@ agents push branches / open drafts with an EMPTY description; the user writes th directory (`witFiles` overrides the location), and the fixed-name `object dapr4s.wasm.ComponentRoot { def component: DaprComponent = ... }`. `./mill show app.dapr4sComponent` (`dapr4sComponentFull`: optimized) prints the component's path. `scripts/wasm/consumer-check-mill.sh` - publishes the seven artifacts into a local Maven repo (`publishM2Local`), builds `test/wasm/counter` + publishes the artifacts into a local Maven repo (`publishM2Local`), builds `test/wasm/counter` exactly so (a fresh Mill build under `out/consumer-check-mill`, header `//| repositories:` pointing at the local repo) and runs its e2e; dapr4s-examples (examples 18/19) is the worked example. - **The sbt consumer** needs only: @@ -451,10 +457,11 @@ agents push branches / open drafts with an EMPTY description; the user writes th swap), `Dapr4sWasmModule` (+ exclusions; `dapr4sWasmVersion` = the plugin's own version, from the jar's resource), `Dapr4sWasmTests`, `WitModule` (`witFiles` → `wit`, `wkg wit fetch`), `WitBindgenModule` (`witBindgenBasePackage`, `witBindings`; `wasm.glue` uses it), `Dapr4sWasmComponentModule` - (`dapr4sWasmLinkerClasspath`, `dapr4sWasmScalalibOverlay` — both resolved as TOOLS from the module's - repositories, not with `defaultResolver()`, which would force the app's Scala 3 `scala-library` onto - the 2.12 linker — `dapr4sWasmAddGlueExports`, `dapr4sComponent` / `dapr4sComponentFull`: link driver - with the overlay first, then the utf16 re-embed of the real WIT world + `component new`). + (`ModuleKind.WasmComponent` + Wasm + ES2022; `dapr4sWasmLinker` replaces the fork's linker in + `scalaJSLinkerClasspath`; `dapr4sWasmScalalibOverlay` goes first on `runClasspath` — both resolved as + TOOLS from the module's repositories, not with `defaultResolver()`, which would force the app's Scala 3 + `scala-library` onto the linker — `dapr4sWasmAddGlueExports`, `dapr4sComponent` / `dapr4sComponentFull`: + `fastLinkJS` / `fullLinkJS`, then the utf16 re-embed of the real WIT world + `component new`). - `sbt-dapr4s-wasm` (`wasm/sbt-plugin/.../Dapr4sWasmPlugin.scala`): `Dapr4sWasmPlugin` (every nightly-compiled project) excludes the 2.13 stdlib and mainline `org.scala-js` library/javalib and sets the linker to `dapr4s-wasm-linker` (`dapr4sWasmLinkerClasspath`); `Dapr4sWasmComponentPlugin` (every @@ -467,10 +474,10 @@ agents push branches / open drafts with an EMPTY description; the user writes th linear memory on both sides): drop `dapr4s-wasm-linker` and the plugins' linker override; WIT re-embed → scala-wasm#269 (the linker keeps WIT aliases): use its component as is; exclusions → scala-wasm#271 (the fork's core libraries evict mainline's); overlay → when the nightly's library-js is Wasm-clean, drop - `dapr4s-wasm-scalalib-overlay`; the 2.13 glue → when Scala 3 gets WIT codegen; Mill only: the fork swap - (`ScalaWasmModule`) + the link driver → mill#7783, `WitBindgenModule` → mill#7784, running/testing - components → mill#7785. -- **The patched linker** (`scripts/wasm/prepare-patched-linker.sh`, run by `wasm.linker.patchedSources`: + `dapr4s-wasm-scalalib-overlay`; the 2.13 glue → when Scala 3 gets WIT codegen; Mill only: + `WitBindgenModule` → mill#7784, running/testing components → mill#7785 (mill#7783 is released in Mill 1.3.1 + and used). +- **The patched linker** (`scripts/wasm/prepare-patched-linker.sh`, run by `wasm.linker[*].patchedSources`: `git archive` of the release commit's linker sources from a cached clone in `~/.cache/dapr4s/scala-wasm-src` + the patch; Mill compiles them, no scala-wasm sbt build): the released linker leaves the EXPORT side of @@ -517,7 +524,7 @@ agents push branches / open drafts with an EMPTY description; the user writes th first IR file per class wins). - **Central publishing switch**: the `publish` job of `.github/workflows/ci.yml` sets `PUBLISH_WASM_TO_CENTRAL: 'true'` (ON since 2026-10-13, approved by the user); with it the job adds - the seven wasm artifacts to the same `SonatypeCentralPublishModule/publishAll` call as core. Nothing local + the eight wasm artifacts to the same `SonatypeCentralPublishModule/publishAll` call as core. Nothing local reaches Central: publishing happens only through that command with the Central credentials. - **Shared code on the `wasm` path** must avoid `java.time` formatting/parsing, wall-clock (`Instant.now`), `println` and `UUID.randomUUID`: they reach JS and fail to link. @@ -528,7 +535,7 @@ agents push branches / open drafts with an EMPTY description; the user writes th - `./mill show 'wasm.lib[release].counter.dapr4sComponent'` (prints the component's path; also `wasm.counterRaw`, `wasm.importsProbe`; `dapr4sComponentFull` for the optimized link) - `./mill 'wasm.lib[release].wall.publishLocal' + wasm.glue.publishLocal + ... + wasm.millPlugin.publishLocal` - (the seven artifacts, for dapr4s-examples; `publishM2Local` for a Maven layout) + (the eight artifacts, for dapr4s-examples; `publishM2Local` for a Maven layout) - `scripts/wasm/e2e.sh` (counter-raw, counter, imports-probe: imports check, compose, daprd + Redis, for the probe also a `daprio/dapr` scheduler container and daprd's echo conversation component; the probe asserts `converse` → `ok: `, `schedule` → `ok: scheduled …`, register-reminder/-timer on an unhosted actor type → @@ -601,9 +608,8 @@ legs (the wasm artifacts are published by the same job). unit suites on the **plain JS backend** under Node. The integration suites contain orphan `js.await`, and the plain-JS linker **wedges on orphan-await test sources** (hangs, no error), so they must never be linked here — that is why they live in a separate module, not behind a - test filter. Every Scala.js test module mixes in `Scala3JsTests` (`build.mill`): Mill prepends the test - bridge and its TRANSITIVE `scalajs-scalalib_2.13:2.13.x` to the link classpath, whose 2.13 stdlib IR - would shadow the Scala 3.8+ one ("Referring to non-existent method scala.Some.orNull()"). + test filter. (The test bridge used to bring a 2.13 stdlib IR that shadowed the Scala 3.8+ one; Mill + 1.3.1 contains the fix, [com-lihaoyi/mill#7786](https://github.com/com-lihaoyi/mill/pull/7786).) - **Scala.js integration tests** (Docker + Node >= 25 first on PATH): `./mill integration.js.testForked`. `integration.js` links with `ModuleKind.ESModule`, `ESVersion.ES2022`, `scalaJSUseWebAssembly` and `scalaJSUseWebAssemblyJSPI` (the JSPI switch Scala.js 1.22 requires; Mill >= 1.3.0 exposes it). Node 25+ has Wasm 3.0 and JSPI on by default. The suites themselves @@ -627,7 +633,7 @@ legs (the wasm artifacts are published by the same job). - CI gates `publish` on the `format` job and all three `test` matrix legs (`jvm`, `js`, `wasm`) passing. `publish` runs `./mill mill.javalib.SonatypeCentralPublishModule/publishAll` (Mill's Central Portal publisher: a release tag publishes the clean version, a master push a `-SNAPSHOT` to the Central snapshots - repository) for `core.jvm` + `core.js` (+ the seven wasm artifacts, see "Central publishing switch"); + repository) for `core.jvm` + `core.js` (+ the eight wasm artifacts, see "Central publishing switch"); the scala-cli era secrets (`PUBLISH_USER`, `PUBLISH_PASSWORD`, `PUBLISH_SECRET_KEY`, `PUBLISH_SECRET_KEY_PASSWORD`) are mapped to Mill's `MILL_SONATYPE_*` / `MILL_PGP_*` variables in the workflow. Check a publish locally with `./mill core.{jvm,js}.publishLocal` (or `.pom`) and inspect diff --git a/README.md b/README.md index c32a3e8..ddd26b9 100644 --- a/README.md +++ b/README.md @@ -18,7 +18,8 @@ see only Scala types. - Scala `3.10.0-RC1-…-NIGHTLY` (capture checking / safe mode; exact version pinned in `buildsupport.mill`) - Nothing else for the build: [Mill](https://mill-build.org/) 1.3.1 runs from the `./mill` bootstrap script and - downloads the JDK it needs (Zulu 25) + downloads the JDK it needs (Zulu 25). The build currently uses an UNRELEASED mill-scalablytyped: + run `scripts/upstream-snapshot.sh` once first (it builds and installs it locally) - For Scala.js builds: npm (the build installs `package-lock.json` itself); Node 25+ to run capability-touching code (the Wasm + JSPI integration tests) - Docker (only for the integration tests, which exercise a real `daprd` sidecar + Redis) diff --git a/build-tools/st-gen/src/dapr4s/stgen/StGen.scala b/build-tools/st-gen/src/dapr4s/stgen/StGen.scala deleted file mode 100644 index 40bd7c9..0000000 --- a/build-tools/st-gen/src/dapr4s/stgen/StGen.scala +++ /dev/null @@ -1,76 +0,0 @@ -package dapr4s.stgen - -import com.olvind.logging -import com.olvind.logging.LogLevel -import org.scalablytyped.converter.{Flavour, Selection} -import org.scalablytyped.converter.internal.{constants, ImportTypingsGenSources, InFolder, IArray} -import org.scalablytyped.converter.internal.importer.{ConversionOptions, EnabledTypeMappingExpansion} -import org.scalablytyped.converter.internal.scalajs.{Name, QualifiedName, Versions} -import org.scalablytyped.converter.internal.ts.TsIdentLibrary - -import scala.collection.immutable.{SortedMap, SortedSet} - -/** Generates the ScalablyTyped facade SOURCES of dapr4s (the `stFacades` module of build.mill). - * - * WHAT: the very code path of sbt-converter's `ScalablyTypedConverterGenSourcePlugin.stImportSources` - * (`ImportTypingsGenSources.apply`, which lives in the sbt-converter jar but uses no sbt API), driven with the - * conversion options dapr4s's sbt build set (`stOutputPackage := "dapr4styped"`, `stStdlib := List("es2022")`, the - * plugin defaults for the rest). Same converter, same options, same input node_modules: the same generated sources, so - * the embedded facade .sjsir do not change with the build tool. - * - * WHY not mill-scalablytyped (com.github.lolgab, 0.4.1 for Mill 1): its worker hard-codes `stdLibs = es6` (dapr4s - * needs es2022: the @types/node and @dapr/dapr typings use ES2020+ library types) and offers no way to pass the - * standard library through. RETIRES when a mill-scalablytyped release exposes the stdlib option: then stFacades - * extends its `ScalablyTyped` trait instead. - * - * Arguments: - * ... - */ -object StGen { - def main(args: Array[String]): Unit = { - val Array(nodeModules, target, scalaVersion, scalaJsVersion, outputPackage, stdlibs, libs @ _*) = args.toSeq.toArray - val outPkg = Name(outputPackage) - val conversion = ConversionOptions( - useScalaJsDomTypes = true, - flavour = Flavour.Normal, - outputPackage = outPkg, - // sbt-converter: Selection.All for a Scala 3 project, None for Scala 2. - enableScalaJsDefined = if (scalaVersion.startsWith("3")) Selection.All else Selection.None, - stdLibs = SortedSet(stdlibs.split(',').toSeq: _*), - expandTypeMappings = EnabledTypeMappingExpansion.DefaultSelection, - ignored = SortedSet("typescript"), - versions = Versions(Versions.Scala(scalaVersion), Versions.ScalaJs(scalaJsVersion)), - organization = s"org.scalablytyped.${outPkg.unescaped}", - enableReactTreeShaking = Selection.None, - enableLongApplyMethod = false, - privateWithin = None, - useDeprecatedModuleNames = false, - ) - val wanted = SortedMap(libs.map { l => - val i = l.lastIndexOf('=') - TsIdentLibrary(l.substring(0, i)) -> l.substring(i + 1) - }: _*) - val input = ImportTypingsGenSources.Input( - fromFolder = InFolder(os.Path(nodeModules)), - targetFolder = os.Path(target), - overrideTargetFolder = Map.empty, - converterVersion = "1.0.0-beta45", - conversion = conversion, - wantedLibs = wanted, - minimize = Selection.None, - minimizeKeep = IArray.Empty: IArray[QualifiedName], - ) - val cacheDir = constants.defaultCacheFolder - ImportTypingsGenSources( - input, - logging.stdout.filter(LogLevel.warn), - parseCacheDirOpt = Some(cacheDir.toNIO.resolve("parse")), - cacheDirOpt = cacheDir, - ) match { - case Right(files) => println(s"StGen: ${files.size} files in $target") - case Left(errors) => - errors.foreach { case (lib, e) => System.err.println(s"StGen: $lib: $e") } - sys.exit(1) - } - } -} diff --git a/build.mill b/build.mill index 160793d..fe0ab8b 100644 --- a/build.mill +++ b/build.mill @@ -1,8 +1,13 @@ //| mill-version: 1.3.1 //| mill-jvm-version: zulu:25.0.3 //| mill-jvm-opts: ["-Xss8m", "-Xmx6g"] +//| mvnDeps: ["com.github.lolgab::mill-scalablytyped::0.4.1-14-c21896"] package build +// THE MILL-SCALABLYTYPED ABOVE IS UNRELEASED: its main + https://github.com/lolgab/mill-scalablytyped/pull/52, built from +// a pinned commit and installed locally (~/.ivy2/local) by scripts/upstream-snapshot.sh. Run that script once before +// the first `./mill`; CI runs it in every job. RETIRES with the mill-scalablytyped release that contains these PRs. + // ================================================================================================= // dapr4s build (Mill) // @@ -12,7 +17,6 @@ package build // integration.jvm / .js the Docker-based integration suites (never published) // stFacades the ScalablyTyped-generated Scala.js facades (never published on its own; // its .sjsir are embedded into dapr4s_sjs1_3, see core.js) -// stGen the generator of the facade sources (build tooling, never published) // wasm.* the `wasm` platform (WASI components), see wasm/package.mill // // Source layout: src/{shared,jvm,js,wasm} and test/{shared,jvm,js,wasm}. The modules point INTO it @@ -29,6 +33,7 @@ import mill.scalalib.* import mill.scalajslib.* import mill.scalajslib.api.* import mill.javalib.publish.* +import com.github.lolgab.mill.scalablytyped.ScalablyTyped // Single source of truth for the nightly, wasm included (scripts read it with `./mill show scala3Nightly`). def scala3Nightly: T[String] = Task(Versions.scala3Nightly) @@ -80,27 +85,6 @@ trait Dapr4sJs extends ScalaJSModule { def scalaJSVersion = Versions.scalaJS } -/** Scala.js test modules on Scala >= 3.8. - * - * WHAT: `scalaJSTestDeps` (the test bridge and the Scala.js library, which Mill PREPENDS to the test link classpath) - * without their transitive `scalajs-scalalib_2.13:2.13.x+1.22.0`. WHY: since Scala 3.8 the stdlib IR comes from - * `scalajs-scalalib_2.13:`; Mill resolves the bridge transitively, which brings the Scala 2.13 stdlib - * IR as well and puts it FIRST, and the linker keeps the first IR file per class: the Wasm link of the integration - * suites then fails with "Referring to non-existent method scala.Some.orNull()" (2.13's Option shadowing 3.10's). WHY - * SAFE: the right stdlib IR is on the module's own run classpath (via scala3-library_sjs1_3), exactly as sbt-scalajs - * links it. RETIRES when Mill resolves the test deps together with the module's dependencies (upstream Mill fix). - */ -trait Scala3JsTests extends TestScalaJSModule { - override def scalaJSTestDeps = Task { - defaultResolver().classpath( - Seq( - mvn"org.scala-js::scalajs-library:${scalaJSVersion()}", - mvn"org.scala-js::scalajs-test-bridge:${scalaJSVersion()}", - ).map(_.withDottyCompat(scalaVersion()).exclude("org.scala-js" -> "scalajs-scalalib_2.13")), - ) - } -} - // ------------------------------------------------------------------------------------------------- // The library // ------------------------------------------------------------------------------------------------- @@ -169,7 +153,7 @@ object core extends Module { PathRef(out) } - object test extends ScalaJSTests with Scala3JsTests with TestModule.Munit with UnitTestSources { + object test extends ScalaJSTests with TestModule.Munit with UnitTestSources { override def sources = Task.Sources(Dapr4sPaths.test / "shared", Dapr4sPaths.test / "js") override def resources = Task.Sources() override def mvnDeps = Seq( @@ -230,7 +214,7 @@ object integration extends Module { ) } - object js extends Dapr4sFlags with Dapr4sJs with Scala3JsTests with TestModule.Munit { + object js extends Dapr4sFlags with Dapr4sJs with TestScalaJSModule with TestModule.Munit { override def moduleDeps = Seq(core.js.test) override def sources = Task.Sources(Dapr4sPaths.test / "shared" / "integration", Dapr4sPaths.test / "js" / "integration") @@ -270,70 +254,26 @@ object integration extends Module { // `typings.*`, including its own `typings.std`/`typings.node`) would collide with them at link time. // ------------------------------------------------------------------------------------------------- -/** `npm ci` in the repo root unless node_modules is already installed from the current package-lock.json. The SAME - * node_modules serves the converter and the JS integration test runtime. Returns package.json + package-lock.json (by - * content), so the facades regenerate exactly when they change. +/** The facades come from mill-scalablytyped (com.github.lolgab), with the settings sbt's converter plugin had: + * `scalablyTypedStdLibs` = sbt's `stStdlib` and `scalablyTypedOutputPackage` = `stOutputPackage`. The conversion roots + * are the `dependencies` of package.json (the single source of truth), `npm ci` installs package-lock.json first + * (`scalablyTypedNpmCi`, re-run whenever the lock file changes), and the SAME node_modules serves the JS integration + * test runtime. ~10 min cold; the converter's parse cache is ~/.cache/scalablytyped (CI caches it with out/stFacades). */ -def npmInstall: T[Seq[PathRef]] = Task.Input { - val root = Dapr4sPaths.root - val lock = root / "package-lock.json" - val marker = root / "node_modules" / ".package-lock.json" // npm writes it on every install - if (!os.exists(marker) || os.mtime(marker) < os.mtime(lock)) { - Task.log.info(s"Running `npm ci` in $root") - os.proc("npm", "ci").call(cwd = root, stdout = os.Inherit, stderr = os.Inherit) - } - Seq(PathRef(root / "package.json"), PathRef(lock)) -} - -/** The conversion roots: the `dependencies` of package.json (the single source of truth). */ -def npmRoots: T[Seq[(String, String)]] = Task { - val json = ujson.read(os.read(npmInstall().head.path)) - json("dependencies").obj.toSeq.map { case (k, v) => k -> v.str } -} - -object stGen extends ScalaModule { - def scalaVersion = "2.12.20" - override def sources = Task.Sources(Dapr4sPaths.root / "build-tools" / "st-gen" / "src") - override def resources = Task.Sources() - override def mvnDeps = Seq( - mvn"org.scalablytyped.converter::importer-portable:${Versions.scalablytypedConverter}", - // ImportTypingsGenSources (the sbt plugin's source generator) lives in the sbt plugin jar but uses - // no sbt API; take the jar alone, without sbt. - mvn"org.scalablytyped.converter:sbt-converter_2.12_1.0:${Versions.scalablytypedConverter}".exclude("*" -> "*"), - ) -} - -object stFacades extends Dapr4sJs with Dapr4sPublish { +object stFacades extends Dapr4sJs with ScalablyTyped with Dapr4sPublish { def scalaVersion = Versions.scala3Facades def artifactName = "dapr4s-st-facades" override def sources = Task.Sources() override def resources = Task.Sources() - override def mvnDeps = Seq( - mvn"com.olvind::scalablytyped-runtime::${Versions.scalablytypedRuntime}", - mvn"org.scala-js::scalajs-dom::${Versions.scalajsDom}", + override def mvnDeps = Task( + super.mvnDeps() ++ Seq( + mvn"com.olvind::scalablytyped-runtime::${Versions.scalablytypedRuntime}", + mvn"org.scala-js::scalajs-dom::${Versions.scalajsDom}", + ), ) - - /** The generated facade sources (dapr4styped.*). ~10 min cold; the converter's parse cache is ~/.cache/scalablytyped - * (CI caches it with out/stFacades). - */ - def stSources: T[PathRef] = Task { - val dest = Task.dest / "scalablytyped" - stGen - .runner() - .run( - args = Seq[String]( - (Dapr4sPaths.root / "node_modules").toString, - dest.toString, - scalaVersion(), - scalaJSVersion(), - "dapr4styped", - "es2022", - ) ++ npmRoots().map { case (k, v) => s"$k=$v" }, - mainClass = "dapr4s.stgen.StGen", - ) - PathRef(dest) - } - override def generatedSources = Task(Seq(stSources())) + override def scalablyTypedOutputPackage = "dapr4styped" + override def scalablyTypedStdLibs = Seq("es2022") + override def scalablyTypedNpmCi = true // Facade sources are generated, not documented (never published on their own anyway). override def docSources = Task(Seq.empty[PathRef]) diff --git a/buildsupport.mill b/buildsupport.mill index 851d6ce..c440f66 100644 --- a/buildsupport.mill +++ b/buildsupport.mill @@ -24,7 +24,6 @@ object Versions { val scalaJavaTime = "2.8.0" val scalablytypedRuntime = "2.4.2" val scalajsDom = "2.8.1" - val scalablytypedConverter = "1.0.0-beta45" val compilerFlags = Seq( "-language:experimental.captureChecking", diff --git a/docs/DESIGN.md b/docs/DESIGN.md index e511783..3e88563 100644 --- a/docs/DESIGN.md +++ b/docs/DESIGN.md @@ -672,9 +672,8 @@ dapr4s/ ├── buildsupport.mill # Versions (nightly Scala, compiler flags, dependency versions), │ # Dynver (the sbt-dynver version scheme), Dapr4sPom (sbt-layout POMs) ├── mill-build/src/ # the mill-dapr4s-wasm plugin sources (also this build's meta-build) -├── build-tools/ # st-gen (facade source generator) ├── wasm/ # package.mill (the wasm platform modules), wit/, wkg files, -│ # linker/src (component link driver), sbt-plugin/ (sbt-dapr4s-wasm +│ # linker/ (NOTICE of the patched linker), sbt-plugin/ (sbt-dapr4s-wasm │ # sources), NOTICE files ├── package.json # npm pins for the JS layer: @dapr/dapr (runtime + converter input), │ # @types/express + @types/node (converter inputs), testcontainers + @@ -1078,11 +1077,11 @@ Registration uses `registerWorkflowWithName`/`registerActivityWithName` with the ### ScalablyTyped-generated facades -The JS interop layer's facades over `@dapr/dapr`, express and the Node stdlib are **generated, not hand-written**. The `stFacades` Mill module runs sbt-converter's own source generator `ImportTypingsGenSources` (ScalablyTyped converter `1.0.0-beta45`, driven by `build-tools/st-gen` with output package `dapr4styped`, stdlib `es2022`, Scala 3.3.6 — the same options and byte-identical output as the former sbt plugin setup) and converts the npm packages of `package.json` `dependencies` (`@dapr/dapr` 3.18.0, the conversion roots `@types/express` and `@types/node`, plus the test-only `testcontainers` and `@dapr/testcontainer-node`) into Scala sources in `out/stFacades/`, then compiles them. The converter reads the repo's own `node_modules`, installed by `npm ci` from the committed `package-lock.json` (the build runs `npm ci` when needed, so no unlocked install ever happens). `stFacades` compiles without dapr4s's compiler flags (generated facade code would fail capture checking, explicit nulls and fatal warnings). Generated code is **never committed** and never published as a standalone artifact — its `.sjsir` are embedded into the dapr4s jar (below). (Until 2026-10 the scala-cli build ran the converter CLI from `scripts/generate-st-facades.sh` into `~/.ivy2/local`, pinned the digest-versioned jars as `compileOnly.dep`, and embedded them with `scripts/embed-st-facades.sh`; the sbt build replaced all three; since 2026-10-13 the Mill build generates the same sources.) +The JS interop layer's facades over `@dapr/dapr`, express and the Node stdlib are **generated, not hand-written**. The `stFacades` Mill module extends mill-scalablytyped's `ScalablyTyped` (an unreleased build of `main` (which has [lolgab/mill-scalablytyped#50](https://github.com/lolgab/mill-scalablytyped/pull/50) and [#51](https://github.com/lolgab/mill-scalablytyped/pull/51) merged) + [#52](https://github.com/lolgab/mill-scalablytyped/pull/52); ScalablyTyped converter `1.0.0-beta45`, output package `dapr4styped`, stdlib `es2022`, Scala 3.3.6 — the options of the former sbt plugin setup) and converts the npm packages of `package.json` `dependencies` (`@dapr/dapr` 3.18.0, the conversion roots `@types/express` and `@types/node`, plus the test-only `testcontainers` and `@dapr/testcontainer-node`) into Scala sources in `out/stFacades/`, then compiles them. The converter reads the repo's own `node_modules`, installed by `npm ci` from the committed `package-lock.json` (the build runs `npm ci` when needed, so no unlocked install ever happens). `stFacades` compiles without dapr4s's compiler flags (generated facade code would fail capture checking, explicit nulls and fatal warnings). Generated code is **never committed** and never published as a standalone artifact — its `.sjsir` are embedded into the dapr4s jar (below). (Until 2026-10 the scala-cli build ran the converter CLI from `scripts/generate-st-facades.sh` into `~/.ivy2/local`, pinned the digest-versioned jars as `compileOnly.dep`, and embedded them with `scripts/embed-st-facades.sh`; the sbt build replaced all three; since 2026-10-13 the Mill build generates the same sources.) **Why `stOutputPackage := "dapr4styped"` instead of ST's default `typings`**: the facade classes ship inside the published dapr4s jar, and a consumer running its own ScalablyTyped generation always gets `typings.*` (including its own `typings.std`/`typings.node`) — a default-named embedded tree would collide with it at link time. The rename keeps the embedded tree in a dapr4s-owned namespace. It must be a single identifier (the converter parses it as one `Name`; a dotted value would be backtick-escaped into one bizarre identifier, not a nested package). -**Reproducibility**: the facades are a function of (`package-lock.json`, converter version, converter settings). All three are committed (`package-lock.json`, `buildsupport.mill`, `build-tools/st-gen`), so every machine generates the same sources; CI caches `out/stFacades` and `~/.cache/scalablytyped` keyed on those files. +**Reproducibility**: the facades are a function of (`package-lock.json`, converter version, converter settings). All three are committed (`package-lock.json`, `build.mill`, `scripts/upstream-snapshot.sh` for the plugin version), so every machine generates the same sources; CI caches `out/stFacades` and `~/.cache/scalablytyped` keyed on those files. **The one hand-written exception**: `src/js/internal/facade/ExpressModule.scala`. ST's entry point for calling the express module captures the module as a namespace import, which under Node ES modules is never callable (`express()` throws `TypeError`), and `express.text` lost its type to a converter warning — so a small `JSImport.Default` shim provides those two members, typed against the ST-generated `Express`/`Handler` types. Everything else uses `dapr4styped.*` directly. @@ -1142,8 +1141,8 @@ plan and status are in `docs/WASM-COMPONENTS-PROPOSAL.md` §6. The rules are in the Scala 2.13 code is a small hand-written glue in `src/wasm/glue*` (only what the wall uses): plain import/export traits with live import objects, and the `@WitExport` trampolines. So the wall is plain Scala 3 against traits, and it is unit-tested with fakes. -- **Seven artifacts.** `dapr4s-wasm` (adapters), `dapr4s-wasm-glue` (+ `-exports`), `dapr4s-wasm-scalalib-overlay`, - `dapr4s-wasm-linker` (scala-wasm's linker + our params-via-memory patch + the component link driver) and two build +- **Eight artifacts.** `dapr4s-wasm` (adapters), `dapr4s-wasm-glue` (+ `-exports`), `dapr4s-wasm-scalalib-overlay`, + `dapr4s-wasm-linker` (`_2.12` for sbt, `_2.13` for Mill: scala-wasm's linker + our params-via-memory patch) and two build plugins that carry every build-level workaround: `mill-dapr4s-wasm` for Mill consumers (its sources are also dapr4s's own meta-build, so dapr4s's test components build exactly like a consumer's) and `sbt-dapr4s-wasm` for sbt consumers (Mill builds it as a plain sbt-1 plugin jar). A consumer: a plugin + one dependency + the fixed-name diff --git a/docs/WASM-COMPONENTS-PROPOSAL.md b/docs/WASM-COMPONENTS-PROPOSAL.md index 2cc8e94..dcb46d7 100644 --- a/docs/WASM-COMPONENTS-PROPOSAL.md +++ b/docs/WASM-COMPONENTS-PROPOSAL.md @@ -204,8 +204,9 @@ The spike was ported into this repository ([PR #44](https://github.com/sideeffff - **Build**: the `wasm.*` modules of the Mill build ([`wasm/package.mill`](../wasm/package.mill)), built with the traits of the `mill-dapr4s-wasm` plugin ([`mill-build/src`](../mill-build/src/dapr4s/millwasm/Dapr4sWasmModules.scala), - compiled into the meta-build). They swap the `org.scala-js` artifacts for the fork's and link components with a - driver on the patched linker (`wasm/linker/src`, shipped in `dapr4s-wasm-linker`), so `core.jvm`/`core.js` and + compiled into the meta-build). They swap the `org.scala-js` artifacts for the fork's (Mill's `scalaJSOrganization`) and link components with + Mill's own `ModuleKind.WasmComponent` on the patched linker (`dapr4s-wasm-linker_2.13`), both from + [com-lihaoyi/mill#7783](https://github.com/com-lihaoyi/mill/pull/7783) in Mill 1.3.1, so `core.jvm`/`core.js` and their POMs are unchanged. (Until 2026-10-13 a nested sbt build in `wasm/`: the fork's `sbt-scalajs` could not share the root meta-build.) `wasm/` also holds `wit/world.wit`, `wkg-config.toml` and `wkg.lock`. WIT comes only through `wkg wit fetch` (OCI). @@ -241,8 +242,8 @@ The spike was ported into this repository ([PR #44](https://github.com/sideeffff version (same scheme as sbt-dynver): `dapr4s-wasm_sjs1_3` (the Scala 3 adapters + `DaprComponent` + inert callbacks; depends on dapr4s core `dapr4s_sjs1_3` and the glue), `dapr4s-wasm-glue_sjs1_2.13` (WIT bindings + hand-written glue), `dapr4s-wasm-glue-exports_sjs1_2.13` (the trampolines; components only), `dapr4s-wasm-scalalib-overlay` (the - fork-patched stdlib `.sjsir`, with attribution), `dapr4s-wasm-linker_2.12` (scala-wasm's linker + our - params-via-memory patch + the component link driver, republished under our coordinates with LICENSE/NOTICE) and two + fork-patched stdlib `.sjsir`, with attribution), `dapr4s-wasm-linker_2.12` / `_2.13` (scala-wasm's linker + our + params-via-memory patch; 2.12 for sbt, 2.13 for Mill, republished under our coordinates with LICENSE/NOTICE) and two build plugins with the same feature set: `mill-dapr4s-wasm_mill1_3` ([`mill-build/src`](../mill-build/src/dapr4s/millwasm/Dapr4sWasmModules.scala): fork swap, exclusions, linker, overlay, glue-exports, `wkg wit fetch`, wit-bindgen-scala, `dapr4sComponent`) and `sbt-dapr4s-wasm` diff --git a/mill-build/src/dapr4s/millwasm/Dapr4sWasmModules.scala b/mill-build/src/dapr4s/millwasm/Dapr4sWasmModules.scala index e2be463..3651c8e 100644 --- a/mill-build/src/dapr4s/millwasm/Dapr4sWasmModules.scala +++ b/mill-build/src/dapr4s/millwasm/Dapr4sWasmModules.scala @@ -3,6 +3,7 @@ package dapr4s.millwasm import mill.* import mill.scalalib.* import mill.scalajslib.* +import mill.scalajslib.api.{ESFeatures, ESVersion, ModuleKind, Report} /** The build logic a dapr4s `wasm` app needs, as a Mill plugin (`mill-dapr4s-wasm`), the twin of the sbt plugin * `sbt-dapr4s-wasm` (dapr4s's wasm/sbt-plugin). Keep the two in sync. @@ -158,42 +159,13 @@ object Dapr4sWasm { import Dapr4sWasm.* -/** A Scala.js module on the scala-wasm fork (`io.github.scala-wasm`) instead of mainline `org.scala-js`. - * - * WHAT: overrides of every task that names the `org.scala-js` organization: the Scala.js library (and, for Scala 2.13, - * the stdlib IR), the compiler plugin, the linker in Mill's Scala.js worker. WHY: Mill's ScalaJSModule has no setting - * for the Scala.js organization. WHY SAFE: the fork keeps mainline's package and class names, so Mill's Scala.js - * worker drives the fork's linker unchanged (for the plain-JS links of unit tests; components are linked by the - * patched linker, see [[Dapr4sWasmComponentModule]]). RETIRES with com-lihaoyi/mill#7783 (a configurable Scala.js - * organization). +/** A Scala.js module on the scala-wasm fork (`io.github.scala-wasm`) instead of mainline `org.scala-js`: Mill's + * `scalaJSOrganization` (com-lihaoyi/mill#7783) swaps the Scala.js library (and, for Scala 2.13, the stdlib IR and the + * compiler plugin), the test bridge and the linker in Mill's Scala.js worker. */ trait ScalaWasmModule extends ScalaJSModule { override def scalaJSVersion: T[String] = Task(ScalaWasmVersion) - - override def scalacPluginMvnDeps: T[Seq[Dep]] = Task { - super.scalacPluginMvnDeps().filterNot(_.dep.module.organization.value == "org.scala-js") ++ - Option.when(!scalaVersion().startsWith("3."))(mvn"$ScalaWasmOrg:::scalajs-compiler:${scalaJSVersion()}") - } - - override def mandatoryMvnDeps: T[Seq[Dep]] = Task { - val sv = scalaVersion() - super.mandatoryMvnDeps().filterNot(_.dep.module.organization.value == "org.scala-js") ++ - Seq(mvn"$ScalaWasmOrg::scalajs-library:${scalaJSVersion()}".withDottyCompat(sv)) ++ - Option.when(sv.startsWith("2."))(mvn"$ScalaWasmOrg::scalajs-scalalib:$sv+${scalaJSVersion()}") - } - - // Plain-JS linking (unit tests on Node) uses the fork's released 2.13 linker in Mill's Scala.js worker. - override def scalaJSLinkerClasspath: T[Seq[PathRef]] = Task { - jvmWorker() - .defaultResolver() - .classpath( - Seq( - mvn"$ScalaWasmOrg:scalajs-linker_2.13:${scalaJSVersion()}", - mvn"$ScalaWasmOrg:scalajs-sbt-test-adapter_2.13:${scalaJSVersion()}", - mvn"org.scala-js:scalajs-env-nodejs_2.13:1.4.0", - ).map(Lib.depToBoundDep(_, mill.util.BuildInfo.scalaVersion, "")), - ) - } + override def scalaJSOrganization: T[String] = Task(ScalaWasmOrg) } /** A module compiled by Scala 3 against dapr4s's wasm artifacts (a library or an app): the fork + the dependency @@ -207,19 +179,10 @@ trait Dapr4sWasmModule extends ScalaWasmModule { override def allMvnDeps: T[Seq[Dep]] = Task(super.allMvnDeps().map(_.exclude(Exclusions*))) } -/** Plain-JS unit tests (Node) of a [[Dapr4sWasmModule]]: the fork's test bridge instead of mainline's, with the - * exclusions. Mill prepends `scalaJSTestDeps` to the test link classpath, so they must not bring the 2.13 stdlib. +/** Plain-JS unit tests (Node) of a [[Dapr4sWasmModule]]: the fork's test bridge (`scalaJSOrganization`), with the + * exclusions. */ -trait Dapr4sWasmTests extends TestScalaJSModule with Dapr4sWasmModule { - override def scalaJSTestDeps: T[Seq[PathRef]] = Task { - defaultResolver().classpath( - Seq( - mvn"$ScalaWasmOrg::scalajs-library:${scalaJSVersion()}", - mvn"$ScalaWasmOrg::scalajs-test-bridge:${scalaJSVersion()}", - ).map(_.withDottyCompat(scalaVersion()).exclude(Exclusions*)), - ) - } -} +trait Dapr4sWasmTests extends TestScalaJSModule with Dapr4sWasmModule /** The WIT world of a module, resolved through OCI. */ trait WitModule extends Module { @@ -269,33 +232,39 @@ trait WitBindgenModule extends ScalaWasmModule with WitModule { } /** A dapr4s app linked as a WASI component for dapr-wasm-components: `dapr4sComponent` (`dapr4sComponentFull`: from the - * optimized link) is the component, typed by the real WIT world ([[WitModule.wit]]). - * - * WHY a link driver and not Mill's own linking: Mill's Scala.js worker is compiled against mainline Scala.js, whose - * linker interface has no `ModuleKind.WasmComponent`. The driver (`dapr4s.wasmlink.WasmLink`, shipped in - * dapr4s-wasm-linker) calls the fork's linker through its public API, in a separate JVM. RETIRES with - * com-lihaoyi/mill#7783 (`ModuleKind.WasmComponent` in Mill's ScalaJSModule); com-lihaoyi/mill#7785 then adds running - * and testing components (wasmtime). + * optimized link) is the component, typed by the real WIT world ([[WitModule.wit]]). Mill's own `fastLinkJS` / + * `fullLinkJS` link it, with `ModuleKind.WasmComponent` (com-lihaoyi/mill#7783; the fork's linker runs + * `wasm-tools component new` itself). Running and testing a component in Mill (wasmtime) is com-lihaoyi/mill#7785; + * until then the e2e scripts run it. */ trait Dapr4sWasmComponentModule extends Dapr4sWasmModule with WitModule { + override def moduleKind: T[ModuleKind] = Task(ModuleKind.WasmComponent) + override def scalaJSUseWebAssembly: T[Boolean] = Task(true) + override def esFeatures: T[ESFeatures] = Task(super.esFeatures().withESVersion(ESVersion.ES2022)) + // ----------------------------------------------------------------------------------------------- // The released scala-wasm linker leaves the EXPORT side of WIT params via linear memory (> 16 flat - // params) a `???` and silently emits nothing on the IMPORT side. Link with dapr4s-wasm-linker_2.12, + // params) a `???` and silently emits nothing on the IMPORT side. Link with dapr4s-wasm-linker_2.13, // the release rebuilt with dapr4s's patch, republished under dapr4s's coordinates with scala-wasm's - // LICENSE/NOTICE (+ the link driver). Only the LINKER is replaced; the library, javalib and compiler - // plugin stay the released ones. RETIRES with scala-wasm/scala-wasm#268 (params via memory on both - // sides), together with the driver (see above): link with the released linker. + // LICENSE/NOTICE: it replaces the fork's scalajs-linker_2.13 in Mill's Scala.js worker (the + // test adapter and the JS environment stay the defaults). Only the LINKER is replaced; the library, + // javalib and compiler plugin stay the released ones. RETIRES with scala-wasm/scala-wasm#268 (params + // via memory on both sides): drop this override. // ----------------------------------------------------------------------------------------------- - /** Classpath of the linker + link driver (default: `dapr4s-wasm-linker_2.12` and its dependencies). */ - def dapr4sWasmLinkerClasspath: T[Seq[PathRef]] = Task { - dapr4sWasmResolveTool(Seq(mvn"$Org:dapr4s-wasm-linker_2.12:$dapr4sWasmVersion"))() + /** The patched linker (default: `dapr4s-wasm-linker_2.13` and its dependencies). */ + def dapr4sWasmLinker: T[Seq[PathRef]] = Task { + dapr4sWasmResolveTool(Seq(mvn"$Org:dapr4s-wasm-linker_2.13:$dapr4sWasmVersion"))() + } + + override def scalaJSLinkerClasspath: T[Seq[PathRef]] = Task { + val released = (n: String) => n.startsWith("scalajs-linker_2.13-") && !n.startsWith("scalajs-linker-interface") + dapr4sWasmLinker() ++ super.scalaJSLinkerClasspath().filterNot(p => released(p.path.last)) } /** Resolve build TOOLING (not dependencies of the app) from this module's repositories. NOT with `defaultResolver()`: * a ScalaModule's resolver forces scala-library to the module's Scala version, which would put the Scala 3 library - * under the Scala 2.12 linker (`NoClassDefFoundError: scala/Serializable`); and the app's exclusions must not apply - * either. + * under the linker; and the app's exclusions must not apply either. */ def dapr4sWasmResolveTool(deps: Seq[Dep]): Task[Seq[PathRef]] = Task.Anon { Lib @@ -312,8 +281,8 @@ trait Dapr4sWasmComponentModule extends Dapr4sWasmModule with WitModule { // The Scala 3 stdlib IR (org.scala-js:scalajs-scalalib_2.13:) still uses JS // interop: BoxesRunTime.equals uses JS `===`, reachable from any program; Symbol's UniquenessCache // is a js.Dictionary; ... scala-wasm's Scala 3 fork patched exactly those files; - // dapr4s-wasm-scalalib-overlay carries their .sjsir and goes FIRST on the linker classpath (the IR - // loader keeps the first IR file per class name). Resolved on its own, intransitively (never a + // dapr4s-wasm-scalalib-overlay carries their .sjsir and goes FIRST on the run (= linker) classpath + // (the IR loader keeps the first IR file per class name). Resolved on its own, intransitively (never a // dependency: the module's exclusions would apply). RETIRES when the Scala 3 stdlib's Scala.js // sources are Wasm-clean (scala-wasm's Scala 3 patches upstreamed): drop the overlay. // ----------------------------------------------------------------------------------------------- @@ -322,6 +291,8 @@ trait Dapr4sWasmComponentModule extends Dapr4sWasmModule with WitModule { dapr4sWasmResolveTool(Seq(mvn"$Org:dapr4s-wasm-scalalib-overlay:$dapr4sWasmVersion".exclude("*" -> "*")))() } + override def runClasspath: T[Seq[PathRef]] = Task(dapr4sWasmScalalibOverlay() ++ super.runClasspath()) + /** Add `dapr4s-wasm-glue-exports` (the `@WitExport` trampolines) to the dependencies. They are a COMPONENT's * dependency only: every `@WitExport` is a linker entry point, which a plain-JS link (unit tests) rejects. Hence not * a dependency of dapr4s-wasm. @@ -334,26 +305,15 @@ trait Dapr4sWasmComponentModule extends Dapr4sWasmModule with WitModule { ) } - def dapr4sWasmLinked(full: Boolean): Task[PathRef] = Task.Anon { - val out = Task.dest / "linked" - os.makeDir.all(out) - val cp = (dapr4sWasmScalalibOverlay() ++ runClasspath()).map(_.path).filter(os.exists) - mill.util.Jvm.callProcess( - mainClass = "dapr4s.wasmlink.WasmLink", - classPath = dapr4sWasmLinkerClasspath().map(_.path), - mainArgs = Seq[String](if (full) "full" else "fast", out.toString) ++ cp.map(_.toString), - stdout = os.Inherit, - ) - PathRef(out / "main.wasm") - } + private def linkedComponent(report: Report): os.Path = report.dest.path / "main.wasm" /** The app as a WASI component typed by the real WIT world (fast link). */ def dapr4sComponent: T[PathRef] = Task { - PathRef(buildComponent(dapr4sWasmLinked(full = false)().path, Task.dest, wit().path, witWorld)) + PathRef(buildComponent(linkedComponent(fastLinkJS()), Task.dest, wit().path, witWorld)) } /** Like [[dapr4sComponent]], from the optimized link. */ def dapr4sComponentFull: T[PathRef] = Task { - PathRef(buildComponent(dapr4sWasmLinked(full = true)().path, Task.dest, wit().path, witWorld)) + PathRef(buildComponent(linkedComponent(fullLinkJS()), Task.dest, wit().path, witWorld)) } } diff --git a/scripts/upstream-snapshot.sh b/scripts/upstream-snapshot.sh new file mode 100755 index 0000000..ab62a61 --- /dev/null +++ b/scripts/upstream-snapshot.sh @@ -0,0 +1,42 @@ +#!/usr/bin/env bash +# Builds the UNRELEASED upstream mill-scalablytyped that this build uses, from a pinned commit, and publishes it +# locally (~/.ivy2/local) unless it is there already: +# +# mill-scalablytyped 0.4.1-14-c21896 main (has https://github.com/lolgab/mill-scalablytyped/pull/50 and /pull/51) +# + /pull/52 merged (branch dapr4s-snapshot of +# github.com/sideeffffect/mill-scalablytyped). +# +# Mill itself is released: Mill 1.3.1 contains https://github.com/com-lihaoyi/mill/pull/7783 (scalaJSOrganization, +# ModuleKind.WasmComponent) and https://github.com/com-lihaoyi/mill/pull/7786, so `./mill` downloads it as usual. +# +# RETIRES when mill-scalablytyped releases these changes: pin the release in build.mill and delete this script and +# its CI steps. CI caches the ~/.ivy2/local organization by the hash of this file, so a new pin rebuilds. +set -euo pipefail + +MST_SHA=c218964986ac520c3f0bbf6bcc7e684e1a3d16f2 +MST_VERSION=0.4.1-14-c21896 + +work="${XDG_CACHE_HOME:-$HOME/.cache}/dapr4s/upstream-snapshot" +ivy="$HOME/.ivy2/local" + +checkout() { # + local dir=$1 + if [[ ! -d "$dir/.git" ]]; then + git clone --quiet "$2" "$dir" + fi + git -C "$dir" fetch --quiet --tags origin + git -C "$dir" fetch --quiet "$3" "$4" + git -C "$dir" checkout --quiet --force --detach "$5" +} + +if [[ -d "$ivy/com.github.lolgab/mill-scalablytyped_mill1_3/$MST_VERSION" ]]; then + echo "mill-scalablytyped $MST_VERSION: already installed" +else + echo "mill-scalablytyped $MST_VERSION: building from source" + checkout "$work/mill-scalablytyped" https://github.com/lolgab/mill-scalablytyped.git \ + https://github.com/sideeffffect/mill-scalablytyped.git dapr4s-snapshot "$MST_SHA" + (cd "$work/mill-scalablytyped" && ./mill --ticker false __.publishLocal) + (cd "$work/mill-scalablytyped" && ./mill --ticker false shutdown) || true + [[ -d "$ivy/com.github.lolgab/mill-scalablytyped_mill1_3/$MST_VERSION" ]] || + { echo "mill-scalablytyped did not publish $MST_VERSION" >&2; exit 1; } +fi diff --git a/scripts/wasm/consumer-check-mill.sh b/scripts/wasm/consumer-check-mill.sh index 7ee61bc..ddf8709 100755 --- a/scripts/wasm/consumer-check-mill.sh +++ b/scripts/wasm/consumer-check-mill.sh @@ -15,7 +15,7 @@ mill() { "$repo/mill" --ticker false "$@"; } local_repo="$repo/out/consumer-check-mill-repo" rm -rf "$local_repo" mkdir -p "$local_repo" -modules=('wasm.lib[release].wall' wasm.glue wasm.glueExports wasm.scalalibOverlay wasm.linker wasm.millPlugin) +modules=('wasm.lib[release].wall' wasm.glue wasm.glueExports wasm.scalalibOverlay 'wasm.linker[2.13.18]' wasm.millPlugin) args=() for m in "${modules[@]}"; do [[ ${#args[@]} -gt 0 ]] && args+=(+) diff --git a/scripts/wasm/consumer-check.sh b/scripts/wasm/consumer-check.sh index 897f16e..b8e2264 100755 --- a/scripts/wasm/consumer-check.sh +++ b/scripts/wasm/consumer-check.sh @@ -15,7 +15,7 @@ mill() { "$repo/mill" --ticker false "$@"; } local_repo="$repo/wasm/target/consumer-check-repo" rm -rf "$local_repo" mkdir -p "$local_repo" -modules=('wasm.lib[release].wall' wasm.glue wasm.glueExports wasm.scalalibOverlay wasm.linker wasm.sbtPlugin) +modules=('wasm.lib[release].wall' wasm.glue wasm.glueExports wasm.scalalibOverlay 'wasm.linker[2.12.21]' wasm.sbtPlugin) args=() for m in "${modules[@]}"; do [[ ${#args[@]} -gt 0 ]] && args+=(+) diff --git a/scripts/wasm/prepare-patched-linker.sh b/scripts/wasm/prepare-patched-linker.sh index 87430eb..2ebd455 100755 --- a/scripts/wasm/prepare-patched-linker.sh +++ b/scripts/wasm/prepare-patched-linker.sh @@ -1,10 +1,10 @@ #!/usr/bin/env bash # Prepare the SOURCES of scala-wasm's linker at the exact 1.22.1-wasm.6 release commit + dapr4s's patch # (scripts/wasm/patches/scala-wasm-params-via-memory.patch) in $1 (default: out/wasm-linker-src). The -# `wasm.linker` module of wasm/package.mill runs this script, compiles those sources with Scala 2.12 against -# the RELEASED scala-wasm ir / linker-interface, adds the released linker jar's resources (the private -# library .sjsir) and republishes the result, under dapr4s's coordinates, as -# com.github.sideeffffect:dapr4s-wasm-linker_2.12 (with scala-wasm's LICENSE and NOTICE). +# `wasm.linker[*]` modules of wasm/package.mill run this script, compile those sources with Scala 2.12 and 2.13 against +# the RELEASED scala-wasm ir / linker-interface, add the released linker jar's resources (the private +# library .sjsir) and republish the result, under dapr4s's coordinates, as +# com.github.sideeffffect:dapr4s-wasm-linker_2.12 and _2.13 (with scala-wasm's LICENSE and NOTICE). # # WHY: the released linker cannot pass WIT function params through linear memory, which the canonical # ABI requires once the flattened params exceed MAX_FLAT_PARAMS = 16: the EXPORT side is a `???` diff --git a/wasm/linker/src/dapr4s/wasmlink/WasmLink.scala b/wasm/linker/src/dapr4s/wasmlink/WasmLink.scala deleted file mode 100644 index 3d972b4..0000000 --- a/wasm/linker/src/dapr4s/wasmlink/WasmLink.scala +++ /dev/null @@ -1,54 +0,0 @@ -package dapr4s.wasmlink - -import java.nio.file.{Path, Paths} - -import scala.concurrent.{Await, ExecutionContext} -import scala.concurrent.duration.Duration - -import org.scalajs.linker.{PathIRContainer, PathOutputDirectory, StandardImpl} -import org.scalajs.linker.interface.{ESVersion, ModuleKind, StandardConfig} -import org.scalajs.logging.{Level, ScalaConsoleLogger} - -/** Links a dapr4s `wasm` app as a WASI component (`ModuleKind.WasmComponent`) with dapr4s-wasm-linker (the scala-wasm - * linker + dapr4s's params-via-memory patch). Shipped IN dapr4s-wasm-linker_2.12, so the linker's classpath is the - * driver's: the Mill plugin mill-dapr4s-wasm (`Dapr4sWasmComponentModule.dapr4sComponent`) runs it in a separate JVM, - * and so do dapr4s's own test components (on the in-build `wasm.linker`). - * - * WHY a driver and not Mill's ScalaJSModule linking: Mill's Scala.js worker is compiled against mainline Scala.js, - * whose linker interface has no `ModuleKind.WasmComponent` (it exists only in the scala-wasm fork), so Mill cannot ask - * any linker for a component. The driver is compiled against the fork's linker interface and calls the linker through - * its public API (`StandardImpl.linker(config).link(...)`), exactly what sbt-scalajs's `fastLinkJS` / `fullLinkJS` do - * for the fork's sbt plugin (same config: `WasmComponent`, ES2022, Wasm; full = optimized semantics + minify + IR - * checking). RETIRES with com-lihaoyi/mill#7783 (a configurable Scala.js organization + `ModuleKind.WasmComponent`). - * - * Arguments: `fast|full ...` (the first IR file per class name wins, so the stdlib - * overlay comes first). Writes `/main.wasm`. - */ -object WasmLink { - def main(args: Array[String]): Unit = { - val full = args(0) match { - case "full" => true - case "fast" => false - case other => sys.error(s"WasmLink: expected fast|full, got $other") - } - val out = Paths.get(args(1)) - val classpath: Seq[Path] = args.drop(2).toSeq.map(Paths.get(_)) - implicit val ec: ExecutionContext = ExecutionContext.global - - val base = StandardConfig() - .withModuleKind(ModuleKind.WasmComponent) - .withESFeatures(_.withUseWebAssembly(true).withESVersion(ESVersion.ES2022)) - val config = - if (full) base.withSemantics(_.optimized).withMinify(true).withCheckIR(true) - else base - - val cache = StandardImpl.irFileCache().newCache - val logger = new ScalaConsoleLogger(Level.Info) - val linking = for { - (containers, _) <- PathIRContainer.fromClasspath(classpath) - irFiles <- cache.cached(containers) - report <- StandardImpl.linker(config).link(irFiles, Nil, PathOutputDirectory(out), logger) - } yield report - Await.result(linking, Duration.Inf) - } -} diff --git a/wasm/package.mill b/wasm/package.mill index 845c98e..f40f621 100644 --- a/wasm/package.mill +++ b/wasm/package.mill @@ -15,8 +15,8 @@ package build.wasm // dapr4s-wasm-glue-exports_sjs1_2.13 glueExports: the hand-written `@WitExport` trampolines. A // COMPONENT's dependency only (the build plugins add it) // dapr4s-wasm-scalalib-overlay scalalibOverlay: the fork-patched stdlib .sjsir -// dapr4s-wasm-linker_2.12 linker: scala-wasm's linker with dapr4s's params-via-memory patch + the -// component link driver (dapr4s.wasmlink.WasmLink) +// dapr4s-wasm-linker_2.12 / _2.13 linker[*]: scala-wasm's linker with dapr4s's params-via-memory patch (2.12: +// for sbt-dapr4s-wasm; 2.13: for Mill's Scala.js worker, mill-dapr4s-wasm) // mill-dapr4s-wasm (_mill1_3) millPlugin: the build logic for Mill consumers (dapr4s-examples) // sbt-dapr4s-wasm (sbt 1, 2.12) sbtPlugin: the build logic for sbt consumers // @@ -259,12 +259,12 @@ object `package` extends Module with WitModule { // ----------------------------------------------------------------------------------------------- /** A dapr4s test app linked as a WASI component (`dapr4sComponent`, `dapr4sComponentFull`), exactly as a Mill - * consumer's app, except that the linker (+ link driver) and the stdlib overlay come from `linker` and - * `scalalibOverlay` here, and the trampolines from a module dependency on `glueExports`. + * consumer's app, except that the linker and the stdlib overlay come from `linker` and `scalalibOverlay` here, and + * the trampolines from a module dependency on `glueExports`. */ trait WasmComponent extends Dapr4sWasmComponentModule with NightlyWasm with SharedWit { override def compileModuleDeps = Nil - override def dapr4sWasmLinkerClasspath = Task(linker.runClasspath()) + override def dapr4sWasmLinker = Task(linker(Scala2Wit).runClasspath()) override def dapr4sWasmScalalibOverlay = Task(Seq(scalalibOverlay.jar())) override def dapr4sWasmAddGlueExports = false } @@ -284,7 +284,7 @@ object `package` extends Module with WitModule { } // ----------------------------------------------------------------------------------------------- - // The build-level artifacts: the stdlib overlay, the patched linker (+ link driver), the build plugins + // The build-level artifacts: the stdlib overlay, the patched linker, the build plugins // ----------------------------------------------------------------------------------------------- // The .sjsir of the classes scala-wasm's Scala 3 fork patched in library-js (scala-wasm/scala3@17a0deeb: @@ -335,16 +335,16 @@ object `package` extends Module with WitModule { } } - /** dapr4s-wasm-linker_2.12: scala-wasm's 1.22.1-wasm.6 linker with dapr4s's params-via-memory patch, republished - * under dapr4s's coordinates. scripts/wasm/prepare-patched-linker.sh puts the patched sources in out/; this module - * compiles them against the RELEASED ir and linker-interface (the patch touches only the linker module) and adds the - * released linker jar's resources (its private library .sjsir), so the classes are the patched ones and everything - * else is the release's. Plus the component link driver (wasm/linker/src: `dapr4s.wasmlink.WasmLink`), which the - * Mill plugin runs on this artifact's classpath. + /** dapr4s-wasm-linker_2.12 / _2.13: scala-wasm's 1.22.1-wasm.6 linker with dapr4s's params-via-memory patch, + * republished under dapr4s's coordinates. scripts/wasm/prepare-patched-linker.sh puts the patched sources in out/; + * this module compiles them against the RELEASED ir and linker-interface (the patch touches only the linker module) + * and adds the released linker jar's resources (its private library .sjsir), so the classes are the patched ones and + * everything else is the release's. Built for both Scala versions the fork's linker is released for: sbt + * (sbt-dapr4s-wasm) loads the 2.12 linker, Mill's Scala.js worker the 2.13 one (mill-dapr4s-wasm). */ - object linker extends ScalaModule with Dapr4sPublish { + object linker extends Cross[Linker](Scala2Linker, Scala2Wit) + trait Linker extends CrossScalaModule with Dapr4sPublish { def artifactName = "dapr4s-wasm-linker" - def scalaVersion = Scala2Linker def patchInputs = Task.Sources( Dapr4sPaths.root / "scripts" / "wasm" / "prepare-patched-linker.sh", Dapr4sPaths.root / "scripts" / "wasm" / "patches", @@ -363,13 +363,14 @@ object `package` extends Module with WitModule { src / "shared" / "src" / "main" / "scala-2", src / "jvm" / "src" / "main" / "scala", ) - .map(PathRef(_)) ++ driverSources() + .map(PathRef(_)) } - def driverSources = Task.Sources(wasmDir / "linker" / "src") override def mvnDeps = Seq( mvn"$ScalaWasmOrg::scalajs-linker-interface:$ScalaWasmVersion", mvn"$ScalaWasmOrg::scalajs-ir:$ScalaWasmVersion", mvn"com.google.javascript:closure-compiler:v20220202", + ) ++ Option.when(scalaVersion().startsWith("2.13."))( // the parallel optimizer (ParCollOps), as in the release + mvn"org.scala-lang.modules::scala-parallel-collections:1.2.0", ) def releasedLinkerResources: T[PathRef] = Task { val jars = diff --git a/wiki/log.md b/wiki/log.md index 3a558d0..2c4fd62 100644 --- a/wiki/log.md +++ b/wiki/log.md @@ -2,6 +2,7 @@ ## [2026-10-17] update | dapr-wasm-components 0.7.5 - Updated: scala-js/scala-wasm-component-spike.md — 0.7.3–0.7.5 leave the WIT and `wkg-config.toml` unchanged; 0.7.4 moves the OCI names, 0.7.5's compose.sh follows them (own provider pull dropped); dapr4s targets 0.7.5 +- Updated: scala-js/dapr4s-mill-build.md — OCI rename note: v0.7.5's compose.sh follows it ## [2026-10-13] update | mill-dapr4s-wasm: the Mill plugin for Wasm components - Updated: scala-js/dapr4s-mill-build.md — Mill 1.x plugin shape (`_mill1_3`, header mvnDeps/repositories), mill-build/src as shared meta-build sources, defaultResolver forces scala-library (tool resolution via Lib.resolveDependencies), link driver shipped in dapr4s-wasm-linker, dynver timestamp race @@ -191,5 +192,13 @@ - Created: scala-js/dapr4s-mill-build.md — parity results, Mill facts, ScalablyTyped without the sbt plugin, scala-wasm in Mill - Note: the sbt-era notes in scalablytyped-with-scala-cli.md and scala-js-async-jspi-wasm.md are kept as history +## [2026-10-16] update | dapr4s Mill build on unreleased upstream +- Updated: scala-js/dapr4s-mill-build.md — locally built Mill (dist.installLocal + launcher cache), retired workarounds, TestScalaJSModule gotcha, 2.13 patched linker, dapr-wasm-components OCI rename + +## [2026-10-18] update | mill-scalablytyped #50 merged +- Updated: scala-js/dapr4s-mill-build.md — the snapshot is now upstream main (with #50) + #51 + #52, version 0.4.1-9-393207 +- Updated: scala-js/dapr4s-mill-build.md — Mill is now main at the merge of mill#7783 (1.3.0-20-56dcdf), fetched from upstream (the fork branch is deleted) + ## [2026-10-22] update | dapr4s on Mill 1.3.1 - Updated: scala-js/dapr4s-mill-build.md — the build is on the released Mill 1.3.1 (still Scala 3.9.0 for 1.x plugins) +- Updated: scala-js/dapr4s-mill-build.md — Mill 1.3.1 contains mill#7783 + mill#7786, so the snapshot script builds only mill-scalablytyped: main (with #50 + #51) + #52, version 0.4.1-14-c21896 diff --git a/wiki/scala-js/dapr4s-mill-build.md b/wiki/scala-js/dapr4s-mill-build.md index 0ea479e..c0fcd82 100644 --- a/wiki/scala-js/dapr4s-mill-build.md +++ b/wiki/scala-js/dapr4s-mill-build.md @@ -91,11 +91,38 @@ Options to reproduce sbt: `enableScalaJsDefined = Selection.All` for Scala 3, `i module's `moduleDir`: good defaults for consumers, overridable. - Dynver on a dirty tree has a minute timestamp: two Mill runs can compute different versions. A script that publishes and then consumes must read the version from what was published. -- Upstream Mill work that retires the workarounds: [#7783](https://github.com/com-lihaoyi/mill/pull/7783) +- Upstream Mill work that retires the workarounds: [#7783](https://github.com/com-lihaoyi/mill/pull/7783) (merged 2026-10-17, released in Mill 1.3.1 on 2026-10-19) (organization + WasmComponent), [#7784](https://github.com/com-lihaoyi/mill/pull/7784) (wit-bindgen-scala), [#7785](https://github.com/com-lihaoyi/mill/pull/7785) (Wasmtime JS env). In the plugin: #7783 → `ScalaWasmModule` + the link driver, #7784 → `WitBindgenModule`. +## On unreleased upstream (2026-10-16, branch build/mill-upstream-snapshot) + +The build runs on the released Mill 1.3.1 (it contains [mill#7783](https://github.com/com-lihaoyi/mill/pull/7783) and +[mill#7786](https://github.com/com-lihaoyi/mill/pull/7786); until 2026-10-22 `scripts/upstream-snapshot.sh` built Mill +`1.3.0-20-56dcdf` from source) and the unreleased mill-scalablytyped `0.4.1-14-c21896` (`main`, which has +[#50](https://github.com/lolgab/mill-scalablytyped/pull/50) and [#51](https://github.com/lolgab/mill-scalablytyped/pull/51) +merged, + [#52](https://github.com/lolgab/mill-scalablytyped/pull/52)), built by `scripts/upstream-snapshot.sh`. Facts: + +- **Using a locally built Mill** (history, how the script ran Mill before 1.3.1): `CI=true ./mill dist.installLocal --binFile ` publishes ~110 modules to + `~/.ivy2/local` and writes an assembly. `CI=true` drops the dirty hash, so the version (`--`) is the + same on every machine with tags fetched. Copy the assembly to `~/.cache/mill/download/` and write + `//| mill-version: -jvm`: the launcher then uses it and never downloads. +- **What retired**: `Scala3JsTests` (mill#7786); `build-tools/st-gen` + `npmInstall` (mill-scalablytyped + `scalablyTypedStdLibs`, `scalablyTypedNpmCi`, `scalablyTypedOutputPackage`; the facade jar entries are identical + to 3.0.0's, 3559 sources); `ScalaWasmModule`'s per-task overrides (now `scalaJSOrganization`); the component link + driver `WasmLink` (Mill's `fastLinkJS` with `ModuleKind.WasmComponent`). +- **Gotcha**: a module that was a `TestScalaJSModule` only through a removed workaround trait silently becomes a + JVM test module: `testForked` then runs munit's JS runner on the JVM ("A method defined in a native JavaScript type + ... has been called"). Mix `TestScalaJSModule` in explicitly. +- **Mill's Scala.js worker is Scala 3 and loads a `_2.13` linker**; sbt loads `_2.12`. The patched linker is now + published for both (`dapr4s-wasm-linker_2.12` / `_2.13`). The 2.13 build needs + `scala-parallel-collections` 1.2.0 (`ParCollOps`), as scala-wasm's own build adds it. +- **dapr-wasm-components OCI rename (2026-10-15)**: the flat `ghcr.io/sideeffffect/dapr-wasm-components-` + packages are deleted; modules live under `ghcr.io/sideeffffect/dapr-wasm-components/`. Upstream + `compose.sh` up to v0.7.4 still pulled the flat names, so dapr4s pulled the providers itself and passed + `--outbound` / `--inbound`; v0.7.5's `compose.sh` uses the new names, so dapr4s calls it with `--tag 0.7.5` again. + ## See also - [scala-wasm Component Spike](scala-wasm-component-spike.md)