From a6404723226440b463ff00cc59391489c9830731 Mon Sep 17 00:00:00 2001 From: Damian Reeves <957246+DamianReeves@users.noreply.github.com> Date: Thu, 28 May 2026 02:34:28 -0500 Subject: [PATCH 1/2] fix(mise): orchestrate elm builds serially to eliminate Windows race MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit When the elm package cache (~/.elm) or per-project elm-stuff/ is cold, multiple concurrent `elm make` invocations race on ~/.elm//artifacts.dat and the shared elm-stuff/ files, producing the "PROBLEM BUILDING DEPENDENCIES" failure that's been intermittently breaking the Windows CI build (e.g. PR #1280's 26558683076 and 26559185295 runs). Linux mostly avoids it thanks to friendlier file-locking semantics but the underlying race is the same. The previous round of fixes added `//MISE depends=` headers expecting mise to serialize tasks. mise reads those headers but its scheduler parallelizes any tasks whose deps are satisfied — both Linux and Windows CI logs show cli, cli2, dev-server, try-morphir, and treeview starting within ~1ms of each other after check-elm-docs finishes. The recent passing runs were the race coming out the other way, not a real fix. Changes: - .mise/tasks/build.ts: drive the elm-touching tasks via an explicit `mise run --skip-deps ` chain (check-elm-docs → cli → cli2 → dev-server → try-morphir → components → morphir-ts). --skip-deps stops each subtask from re-running its own declared deps. treeview (webpack only) runs in parallel alongside the chain. - .mise/tasks/build/dev-server.ts: serialize its three internal elm makes — they all target cli/elm.json, so the same race that motivated the build:cli serialization applies to them. morphir-ts is in the elm chain because it invokes elm indirectly via node-elm-compiler in cli/morphir-elm.js (visible as "Compiled in DEV mode" in its output). Verified locally on Windows with a cold elm-stuff: full build completes successfully. --- .mise/tasks/build.ts | 37 ++++++++++++++++++++++++++++++--- .mise/tasks/build/dev-server.ts | 33 +++++++++++++---------------- 2 files changed, 49 insertions(+), 21 deletions(-) diff --git a/.mise/tasks/build.ts b/.mise/tasks/build.ts index 3cbd09de9..5f08a42ca 100755 --- a/.mise/tasks/build.ts +++ b/.mise/tasks/build.ts @@ -1,7 +1,38 @@ #!/usr/bin/env bun -//MISE description="Run all build tasks" -//MISE depends=["build:check-elm-docs", "build:cli", "build:cli2", "build:treeview", "build:morphir-ts", "build:dev-server", "build:components", "build:try-morphir"] +//MISE description="Run all build tasks (orchestrates elm work serially to avoid Windows races)" -import { log } from "./_lib.ts"; +import { exec, log, ROOT_DIR } from "./_lib.ts"; + +// elm make is not concurrent-write-safe on Windows: multiple processes race on +// ~/.elm//artifacts.dat and per-project elm-stuff/, producing +// "PROBLEM BUILDING DEPENDENCIES" failures. +// +// Any task that invokes `elm` — directly (build:cli, build:cli2, build:dev-server, +// build:try-morphir, build:check-elm-docs) or indirectly via node-elm-compiler in +// cli/morphir-elm.js (build:morphir-ts) — must run serially. Non-elm tasks +// (build:treeview is webpack-only; build:components just concats JS) can run in +// parallel. +// +// mise reads `//MISE depends=` headers but its scheduler parallelizes any tasks +// whose deps are satisfied rather than enforcing strict ordering, so we orchestrate +// explicitly here. `--skip-deps` prevents the subtasks from re-running their own +// declared deps (which would otherwise repeatedly trigger build:cli, etc.). +const run = (task: string) => + exec("mise", ["run", "--skip-deps", task], { cwd: ROOT_DIR }); + +await Promise.all([ + // Serial chain — every task here invokes elm (directly or indirectly). + (async () => { + await run("build:check-elm-docs"); + await run("build:cli"); + await run("build:cli2"); + await run("build:dev-server"); + await run("build:try-morphir"); + await run("build:components"); // concat: depends on dev-server output + await run("build:morphir-ts"); // invokes elm via cli/morphir-elm.js + })(), + // Non-elm: webpack only, safe to run alongside the elm chain. + run("build:treeview"), +]); log("build", "All build tasks completed"); diff --git a/.mise/tasks/build/dev-server.ts b/.mise/tasks/build/dev-server.ts index 9f07fcc41..8607fb14f 100755 --- a/.mise/tasks/build/dev-server.ts +++ b/.mise/tasks/build/dev-server.ts @@ -6,23 +6,20 @@ import { elmMake, log, PATHS } from "../_lib.ts"; log("build:dev-server", "Building dev server Elm components..."); -// Build all dev server components in parallel -await Promise.all([ - // Main dev server app - elmMake(["src/Morphir/Web/DevelopApp.elm"], { - cwd: PATHS.cli, - output: "web/index.js", - }), - // Insight API - elmMake(["src/Morphir/Web/Insight.elm"], { - cwd: PATHS.cli, - output: "web/insight.js", - }), - // Dev server API variant - elmMake(["src/Morphir/Web/DevelopApp.elm"], { - cwd: PATHS.cli, - output: "web/insightapp.js", - }), -]); +// Sequential: concurrent elm make calls against the same elm.json race on +// cli/elm-stuff and ~/.elm//artifacts.dat, producing +// "PROBLEM BUILDING DEPENDENCIES" failures on Windows. +await elmMake(["src/Morphir/Web/DevelopApp.elm"], { + cwd: PATHS.cli, + output: "web/index.js", +}); +await elmMake(["src/Morphir/Web/Insight.elm"], { + cwd: PATHS.cli, + output: "web/insight.js", +}); +await elmMake(["src/Morphir/Web/DevelopApp.elm"], { + cwd: PATHS.cli, + output: "web/insightapp.js", +}); log("build:dev-server", "Done"); From d9a835d13fcd584cf5c4324f0fe292142d54daa1 Mon Sep 17 00:00:00 2001 From: Damian Reeves <957246+DamianReeves@users.noreply.github.com> Date: Thu, 28 May 2026 03:21:33 -0500 Subject: [PATCH 2/2] feat(mise): declare sources/outputs for incremental rebuilds MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Add `//MISE sources=[...]` / `outputs=[...]` to every build subtask so mise can skip a task whose inputs haven't changed. The orchestration in build.ts already invokes each subtask via `mise run --skip-deps`, so the freshness check kicks in on every step independently. Cold rebuild from a clean tree still does the full work serially. Warm rebuild with no source changes finishes in seconds (mise reports "sources up-to-date, skipping" for each elm task). Touching a single .elm file rebuilds only the affected tasks — for example, touching cli/src/Morphir/Web/TryMorphir.elm reruns cli, dev-server, try-morphir, components, and morphir-ts but leaves check-elm-docs, cli2, and treeview skipped. Notes: - Patterns are deliberately broad (`src/**/*.elm`, `cli/src/**/*.elm`) rather than module-precise. elm tracks transitive imports itself, so conservative globs trade a few extra rebuilds for safety. - build:components has no sources/outputs because its output overlaps its input (concat insight.js + morphir-insight-element.js → insight.js), which would make mtime-based freshness ambiguous. It's a fast concat and always re-runs. - Removed the Promise.all that ran build:treeview alongside the elm chain — the speedup was marginal and the fully serial form is easier to reason about now that each subtask gets independent freshness caching. --- .mise/tasks/build.ts | 37 ++++++++++++----------------- .mise/tasks/build/check-elm-docs.ts | 2 ++ .mise/tasks/build/cli.ts | 2 ++ .mise/tasks/build/cli2.ts | 2 ++ .mise/tasks/build/dev-server.ts | 2 ++ .mise/tasks/build/morphir-ts.ts | 2 ++ .mise/tasks/build/treeview.ts | 2 ++ .mise/tasks/build/try-morphir.ts | 2 ++ 8 files changed, 29 insertions(+), 22 deletions(-) diff --git a/.mise/tasks/build.ts b/.mise/tasks/build.ts index 5f08a42ca..e52ab01f0 100755 --- a/.mise/tasks/build.ts +++ b/.mise/tasks/build.ts @@ -5,34 +5,27 @@ import { exec, log, ROOT_DIR } from "./_lib.ts"; // elm make is not concurrent-write-safe on Windows: multiple processes race on // ~/.elm//artifacts.dat and per-project elm-stuff/, producing -// "PROBLEM BUILDING DEPENDENCIES" failures. -// -// Any task that invokes `elm` — directly (build:cli, build:cli2, build:dev-server, -// build:try-morphir, build:check-elm-docs) or indirectly via node-elm-compiler in -// cli/morphir-elm.js (build:morphir-ts) — must run serially. Non-elm tasks -// (build:treeview is webpack-only; build:components just concats JS) can run in -// parallel. +// "PROBLEM BUILDING DEPENDENCIES" failures. Most build tasks here invoke elm +// directly (build:cli, build:cli2, build:dev-server, build:try-morphir, +// build:check-elm-docs) or indirectly via node-elm-compiler in cli/morphir-elm.js +// (build:morphir-ts), so we run everything serially. // // mise reads `//MISE depends=` headers but its scheduler parallelizes any tasks // whose deps are satisfied rather than enforcing strict ordering, so we orchestrate // explicitly here. `--skip-deps` prevents the subtasks from re-running their own -// declared deps (which would otherwise repeatedly trigger build:cli, etc.). +// declared deps (which would otherwise repeatedly trigger build:cli, etc.). The +// `sources` and `outputs` declarations on each subtask let mise skip re-running +// a task whose inputs haven't changed. const run = (task: string) => exec("mise", ["run", "--skip-deps", task], { cwd: ROOT_DIR }); -await Promise.all([ - // Serial chain — every task here invokes elm (directly or indirectly). - (async () => { - await run("build:check-elm-docs"); - await run("build:cli"); - await run("build:cli2"); - await run("build:dev-server"); - await run("build:try-morphir"); - await run("build:components"); // concat: depends on dev-server output - await run("build:morphir-ts"); // invokes elm via cli/morphir-elm.js - })(), - // Non-elm: webpack only, safe to run alongside the elm chain. - run("build:treeview"), -]); +await run("build:check-elm-docs"); +await run("build:cli"); +await run("build:cli2"); +await run("build:dev-server"); +await run("build:try-morphir"); +await run("build:components"); // concat: depends on dev-server output +await run("build:morphir-ts"); // invokes elm via cli/morphir-elm.js +await run("build:treeview"); // webpack only, no elm log("build", "All build tasks completed"); diff --git a/.mise/tasks/build/check-elm-docs.ts b/.mise/tasks/build/check-elm-docs.ts index cfefcb95d..8db82d423 100755 --- a/.mise/tasks/build/check-elm-docs.ts +++ b/.mise/tasks/build/check-elm-docs.ts @@ -1,5 +1,7 @@ #!/usr/bin/env bun //MISE description="Check Elm documentation compiles" +//MISE sources=["src/**/*.elm", "elm.json"] +//MISE outputs=["docs.json"] import { elmMake, log } from "../_lib.ts"; diff --git a/.mise/tasks/build/cli.ts b/.mise/tasks/build/cli.ts index 0598d296d..303b12a82 100755 --- a/.mise/tasks/build/cli.ts +++ b/.mise/tasks/build/cli.ts @@ -1,6 +1,8 @@ #!/usr/bin/env bun //MISE description="Build CLI (Elm compilation)" //MISE depends=["build:check-elm-docs"] +//MISE sources=["src/**/*.elm", "cli/src/**/*.elm", "cli/elm.json"] +//MISE outputs=["cli/Morphir.Elm.CLI.js", "cli/Morphir.Elm.DevCLI.js"] import { elmMake, log, PATHS } from "../_lib.ts"; diff --git a/.mise/tasks/build/cli2.ts b/.mise/tasks/build/cli2.ts index dbb6bc6fc..d3bdfec44 100755 --- a/.mise/tasks/build/cli2.ts +++ b/.mise/tasks/build/cli2.ts @@ -1,6 +1,8 @@ #!/usr/bin/env bun //MISE description="Build CLI2 (TypeScript + Elm in parallel)" //MISE depends=["build:cli"] +//MISE sources=["src/**/*.elm", "cli2/src/**/*.elm", "cli2/elm.json", "cli2/*.ts"] +//MISE outputs=["cli2/Morphir.Elm.CLI.cjs", "cli2/lib/**/*.js"] import { elmMake, log, PATHS, join } from "../_lib.ts"; import { mkdir, rm } from "fs/promises"; diff --git a/.mise/tasks/build/dev-server.ts b/.mise/tasks/build/dev-server.ts index 8607fb14f..01a177755 100755 --- a/.mise/tasks/build/dev-server.ts +++ b/.mise/tasks/build/dev-server.ts @@ -1,6 +1,8 @@ #!/usr/bin/env bun //MISE description="Build development server Elm components" //MISE depends=["build:cli"] +//MISE sources=["src/**/*.elm", "cli/src/**/*.elm", "cli/elm.json"] +//MISE outputs=["cli/web/index.js", "cli/web/insight.js", "cli/web/insightapp.js"] import { elmMake, log, PATHS } from "../_lib.ts"; diff --git a/.mise/tasks/build/morphir-ts.ts b/.mise/tasks/build/morphir-ts.ts index a7380c5be..41413432a 100755 --- a/.mise/tasks/build/morphir-ts.ts +++ b/.mise/tasks/build/morphir-ts.ts @@ -1,6 +1,8 @@ #!/usr/bin/env bun //MISE description="Build Morphir TypeScript library" //MISE depends=["build:cli", "build:cli2"] +//MISE sources=["src/**/*.elm", "morphir.json", "morphir-ts/tsconfig.json", "morphir-ts/src/sdk/**/*", "cli/Morphir.Elm.CLI.js", "cli/Morphir.Elm.DevCLI.js", "cli2/Morphir.Elm.CLI.cjs"] +//MISE outputs=["morphir-ir.json", "morphir-ts/dist/**/*.js", "morphir-ts/src/generated/**/*.ts"] import { del, morphirMake, morphirGen, exec, log, PATHS, ROOT_DIR, copyGlob, join } from "../_lib.ts"; diff --git a/.mise/tasks/build/treeview.ts b/.mise/tasks/build/treeview.ts index b14070dff..9e8bb53a5 100755 --- a/.mise/tasks/build/treeview.ts +++ b/.mise/tasks/build/treeview.ts @@ -1,6 +1,8 @@ #!/usr/bin/env bun //MISE description="Build treeview webpack bundle" //MISE depends=["build:check-elm-docs"] +//MISE sources=["cli/treeview/src/*", "cli/treeview/webpack.config.js", "cli/treeview/tsconfig.json"] +//MISE outputs=["cli/treeview/dist/bundle.js", "cli/treeview/dist/index.html"] import { exec, log, PATHS, ROOT_DIR } from "../_lib.ts"; diff --git a/.mise/tasks/build/try-morphir.ts b/.mise/tasks/build/try-morphir.ts index cd873f1c7..7337adaea 100755 --- a/.mise/tasks/build/try-morphir.ts +++ b/.mise/tasks/build/try-morphir.ts @@ -1,6 +1,8 @@ #!/usr/bin/env bun //MISE description="Build Try Morphir web app" //MISE depends=["build:cli"] +//MISE sources=["src/**/*.elm", "cli/src/**/*.elm", "cli/elm.json"] +//MISE outputs=["cli/web/try-morphir.html"] import { elmMake, log, PATHS } from "../_lib.ts";