Skip to content

Commit 1956c36

Browse files
authored
fix(mise): orchestrate elm builds serially to eliminate Windows race (#1281)
* fix(mise): orchestrate elm builds serially to eliminate Windows race When the elm package cache (~/.elm) or per-project elm-stuff/ is cold, multiple concurrent `elm make` invocations race on ~/.elm/<package>/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 <task>` 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. * feat(mise): declare sources/outputs for incremental rebuilds 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.
1 parent fbdd89f commit 1956c36

8 files changed

Lines changed: 56 additions & 21 deletions

File tree

.mise/tasks/build.ts

Lines changed: 27 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,31 @@
11
#!/usr/bin/env bun
2-
//MISE description="Run all build tasks"
3-
//MISE depends=["build:check-elm-docs", "build:cli", "build:cli2", "build:treeview", "build:morphir-ts", "build:dev-server", "build:components", "build:try-morphir"]
2+
//MISE description="Run all build tasks (orchestrates elm work serially to avoid Windows races)"
43

5-
import { log } from "./_lib.ts";
4+
import { exec, log, ROOT_DIR } from "./_lib.ts";
5+
6+
// elm make is not concurrent-write-safe on Windows: multiple processes race on
7+
// ~/.elm/<package>/artifacts.dat and per-project elm-stuff/, producing
8+
// "PROBLEM BUILDING DEPENDENCIES" failures. Most build tasks here invoke elm
9+
// directly (build:cli, build:cli2, build:dev-server, build:try-morphir,
10+
// build:check-elm-docs) or indirectly via node-elm-compiler in cli/morphir-elm.js
11+
// (build:morphir-ts), so we run everything serially.
12+
//
13+
// mise reads `//MISE depends=` headers but its scheduler parallelizes any tasks
14+
// whose deps are satisfied rather than enforcing strict ordering, so we orchestrate
15+
// explicitly here. `--skip-deps` prevents the subtasks from re-running their own
16+
// declared deps (which would otherwise repeatedly trigger build:cli, etc.). The
17+
// `sources` and `outputs` declarations on each subtask let mise skip re-running
18+
// a task whose inputs haven't changed.
19+
const run = (task: string) =>
20+
exec("mise", ["run", "--skip-deps", task], { cwd: ROOT_DIR });
21+
22+
await run("build:check-elm-docs");
23+
await run("build:cli");
24+
await run("build:cli2");
25+
await run("build:dev-server");
26+
await run("build:try-morphir");
27+
await run("build:components"); // concat: depends on dev-server output
28+
await run("build:morphir-ts"); // invokes elm via cli/morphir-elm.js
29+
await run("build:treeview"); // webpack only, no elm
630

731
log("build", "All build tasks completed");

.mise/tasks/build/check-elm-docs.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
#!/usr/bin/env bun
22
//MISE description="Check Elm documentation compiles"
3+
//MISE sources=["src/**/*.elm", "elm.json"]
4+
//MISE outputs=["docs.json"]
35

46
import { elmMake, log } from "../_lib.ts";
57

.mise/tasks/build/cli.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
#!/usr/bin/env bun
22
//MISE description="Build CLI (Elm compilation)"
33
//MISE depends=["build:check-elm-docs"]
4+
//MISE sources=["src/**/*.elm", "cli/src/**/*.elm", "cli/elm.json"]
5+
//MISE outputs=["cli/Morphir.Elm.CLI.js", "cli/Morphir.Elm.DevCLI.js"]
46

57
import { elmMake, log, PATHS } from "../_lib.ts";
68

.mise/tasks/build/cli2.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
#!/usr/bin/env bun
22
//MISE description="Build CLI2 (TypeScript + Elm in parallel)"
33
//MISE depends=["build:cli"]
4+
//MISE sources=["src/**/*.elm", "cli2/src/**/*.elm", "cli2/elm.json", "cli2/*.ts"]
5+
//MISE outputs=["cli2/Morphir.Elm.CLI.cjs", "cli2/lib/**/*.js"]
46

57
import { elmMake, log, PATHS, join } from "../_lib.ts";
68
import { mkdir, rm } from "fs/promises";

.mise/tasks/build/dev-server.ts

Lines changed: 17 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -1,28 +1,27 @@
11
#!/usr/bin/env bun
22
//MISE description="Build development server Elm components"
33
//MISE depends=["build:cli"]
4+
//MISE sources=["src/**/*.elm", "cli/src/**/*.elm", "cli/elm.json"]
5+
//MISE outputs=["cli/web/index.js", "cli/web/insight.js", "cli/web/insightapp.js"]
46

57
import { elmMake, log, PATHS } from "../_lib.ts";
68

79
log("build:dev-server", "Building dev server Elm components...");
810

9-
// Build all dev server components in parallel
10-
await Promise.all([
11-
// Main dev server app
12-
elmMake(["src/Morphir/Web/DevelopApp.elm"], {
13-
cwd: PATHS.cli,
14-
output: "web/index.js",
15-
}),
16-
// Insight API
17-
elmMake(["src/Morphir/Web/Insight.elm"], {
18-
cwd: PATHS.cli,
19-
output: "web/insight.js",
20-
}),
21-
// Dev server API variant
22-
elmMake(["src/Morphir/Web/DevelopApp.elm"], {
23-
cwd: PATHS.cli,
24-
output: "web/insightapp.js",
25-
}),
26-
]);
11+
// Sequential: concurrent elm make calls against the same elm.json race on
12+
// cli/elm-stuff and ~/.elm/<package>/artifacts.dat, producing
13+
// "PROBLEM BUILDING DEPENDENCIES" failures on Windows.
14+
await elmMake(["src/Morphir/Web/DevelopApp.elm"], {
15+
cwd: PATHS.cli,
16+
output: "web/index.js",
17+
});
18+
await elmMake(["src/Morphir/Web/Insight.elm"], {
19+
cwd: PATHS.cli,
20+
output: "web/insight.js",
21+
});
22+
await elmMake(["src/Morphir/Web/DevelopApp.elm"], {
23+
cwd: PATHS.cli,
24+
output: "web/insightapp.js",
25+
});
2726

2827
log("build:dev-server", "Done");

.mise/tasks/build/morphir-ts.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
#!/usr/bin/env bun
22
//MISE description="Build Morphir TypeScript library"
33
//MISE depends=["build:cli", "build:cli2"]
4+
//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"]
5+
//MISE outputs=["morphir-ir.json", "morphir-ts/dist/**/*.js", "morphir-ts/src/generated/**/*.ts"]
46

57
import { del, morphirMake, morphirGen, exec, log, PATHS, ROOT_DIR, copyGlob, join } from "../_lib.ts";
68

.mise/tasks/build/treeview.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
#!/usr/bin/env bun
22
//MISE description="Build treeview webpack bundle"
33
//MISE depends=["build:check-elm-docs"]
4+
//MISE sources=["cli/treeview/src/*", "cli/treeview/webpack.config.js", "cli/treeview/tsconfig.json"]
5+
//MISE outputs=["cli/treeview/dist/bundle.js", "cli/treeview/dist/index.html"]
46

57
import { exec, log, PATHS, ROOT_DIR } from "../_lib.ts";
68

.mise/tasks/build/try-morphir.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
#!/usr/bin/env bun
22
//MISE description="Build Try Morphir web app"
33
//MISE depends=["build:cli"]
4+
//MISE sources=["src/**/*.elm", "cli/src/**/*.elm", "cli/elm.json"]
5+
//MISE outputs=["cli/web/try-morphir.html"]
46

57
import { elmMake, log, PATHS } from "../_lib.ts";
68

0 commit comments

Comments
 (0)