Skip to content

JAR dependencies never reached the classpath; $Total = 5; now parses (formula1 findings §11–§13) - #111

Merged
ako merged 4 commits into
mainfrom
claude/mxbuild-diagnostics-spike-emta6h
Aug 7, 2026
Merged

JAR dependencies never reached the classpath; $Total = 5; now parses (formula1 findings §11–§13)#111
ako merged 4 commits into
mainfrom
claude/mxbuild-diagnostics-spike-emta6h

Conversation

@ako

@ako ako commented Aug 7, 2026

Copy link
Copy Markdown
Owner

Follow-up to #110, covering the sections added to the formula1 findings after that PR was opened (§11 DuckDB read path, §12 JAR dependencies, §13 MDL papercuts). Verified against real MxBuild on Mendix 11.12.1.

The silent one: a declared JAR dependency never reached the classpath

ALTER MODULE X ADD JAR DEPENDENCY (…) wrote the coordinate, list jar dependencies reported it, the build went green — and the runtime threw SQLException: No JDBC driver found in app for URL. deployment/build.gradle had no dependencies block and the jar was nowhere in the project.

The write was never wrong. Declaring and resolving are separate steps: mx sync-java-dependencies <project.mpr> downloads into vendorlib/, Studio Pro runs it when you edit Module Settings, and nothing ran it headless.

That answers the open question in the report — it is neither "mxbuild skips Maven resolution" nor "mxcli writes somewhere MxBuild cannot read". Measured both halves on 11.12.1: a full mxbuild --target=deploy resolves nothing, and the sync command then puts commons-lang3-3.14.0.jar in vendorlib/. Found by strings mx.dll | grep -i dependenc surfacing ISyncJavaDependenciesRunner / SkipManagedDependencySync, then confirming in mx --help.

Wired at three levels so the gap cannot stay silent again:

  • the executor says so the moment it writes a coordinate that is not in vendorlib/, naming the command to run;
  • mxcli sync-java-deps resolves on demand, and --check reports what is missing and exits non-zero, so it works as a build gate;
  • run --local vendors anything missing before boot, so the warm loop works from a fresh clone.

Resolution needs network, so every call site is best-effort and prints the retry command rather than failing the run. It sits before the database check, since resolving jars has nothing to do with a database.

A test caught a second problem while I was writing it: resolution silently fell back to a different Mendix version's mx. That fallback is still there (a newer mx reads an older project fine) but is now stated — otherwise the mismatch surfaces as an mpr-format complaint that reads like a corrupt project.

$Total = 5; now parses

It failed with no viable alternative at input '$Total=5' while DECLARE $Total Integer = 0; worked — and so did $X = HEAD($List), $X = create M.E (…) and $X = execute database query …. Assignment existed only as a prefix on specific activity statements, plus a SET $Var = expression statement, so a plain value needed a keyword that nothing in the error or the surrounding syntax suggested.

SET is now optional. Both spellings produce the same MfSetStmt and the same ChangeVariableAction; the keyword form is untouched.

Regressions were checked with a control binary rather than by reading the grammar: stash the .g4, regenerate, build bin/mxcli-control, and sweep every script in mdl-examples with both. Thirteen fail — the same thirteen, all pre-existing. ANTLR's adaptive prediction picks the activity-prefixed alternatives on its own, so no reordering was needed. Executed against a real .mpr, mxbuild reports 0 errors.

Smaller ones

  • mxcli test tests/ -p app/App.mpr no longer fails for a tests/ sitting next to the .mpr. The fallback to project-relative applies only when the CWD-relative path does not exist — a tests/ in both places still resolves to the one you are standing in, since silently preferring the project's copy would run the wrong suite, and a path that exists in neither is passed through so the error names what was typed.
  • type 'BYOD' is confirmed at runtime, not just in the editor (§11), so the skill no longer hedges. Added the two dynamic query rules that cost time: a dynamic override still requires a value for every parameter the query definition declares, including ones the replacement SQL ignores; and a {param} placeholder concatenates into a path, which is what keeps an absolute path out of the model.

Nothing to do

mxcli lint's MDL044 message was singled out as the best error text in the tool. Left exactly as it is.

Verification

  • Unit tests for each change: cmd/mxcli/docker/javadeps_test.go, mdl/visitor/visitor_microflow_bare_assign_test.go (the bare and keyword forms must agree on the AST, not merely both parse), cmd/mxcli/cmd_test_run_paths_test.go.
  • JAR path verified end to end: declare via MDL → run --local --setup → jar in vendorlib/; and --check exits 1 when one is missing, 0 when not.
  • mdl-examples/bug-tests/f1-13-bare-variable-assignment.mdl executes against a real .mpr and builds 0 errors.
  • Rebased onto main after Published OData services could not build from MDL; JAR dependencies never resolved; init/settings/grammar fixes (formula1 findings) #110 merged; grammar regenerated, go build ./... clean, full go test ./... green.
  • Symptom rows appended to .claude/skills/fix-issue.md.

🤖 Generated with Claude Code

https://claude.ai/code/session_01JXnEgoc2NQP1Y2TWMCMXC4


Generated by Claude Code

claude added 4 commits August 7, 2026 20:00
…y skipping

`ALTER MODULE X ADD JAR DEPENDENCY (…)` wrote the coordinate, `list jar
dependencies` reported it, the build went green — and the runtime threw "No JDBC
driver found in app for URL". Nothing between the author and the runtime said
the jar was never fetched.

The write was never wrong. Declaring and resolving are separate steps:
`mx sync-java-dependencies <project.mpr>` downloads into vendorlib/, Studio Pro
runs it when you edit Module Settings, and nothing ran it headless. Measured on
11.12.1: a full `mxbuild --target=deploy` emits a build.gradle with no
dependencies block and downloads nothing; the sync command then fetches the jar.
(The open question in the report was whether mxbuild skips Maven resolution or
mxcli writes somewhere MxBuild cannot read — neither.)

Wired at three levels so the gap cannot stay silent: the executor says so the
moment it writes a coordinate that is not in vendorlib/, `mxcli sync-java-deps`
does the resolution on demand (`--check` exits non-zero as a build gate), and
`run --local` vendors anything missing before boot, so the warm loop works from
a fresh clone. Resolution needs network, so each call site is best-effort and
names the command to retry.

Falling back to another version's mx is now stated rather than silent — a
version mismatch otherwise surfaces as an mpr-format complaint that reads like a
corrupt project.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01JXnEgoc2NQP1Y2TWMCMXC4
`$Total = 5;` failed with "no viable alternative at input '$Total=5'" while
`DECLARE $Total Integer = 0;` worked — and so did every other assignment in MDL:
`$X = HEAD($List)`, `$X = create M.E (…)`, `$X = execute database query …`.
Assignment existed only as a prefix on specific activity statements, plus a
`SET $Var = expression` statement, so a plain value needed a keyword that
nothing in the error or the surrounding syntax suggested.

Rather than improve the error, make the form people reach for work. SET is now
optional; both spellings produce the same MfSetStmt and the same
ChangeVariableAction, and the keyword form is untouched.

Checked for regressions with a control binary rather than by reading the
grammar: stash the .g4, regenerate, build bin/mxcli-control, and sweep every
script in mdl-examples with both. Thirteen fail — the same thirteen, all
pre-existing. ANTLR's adaptive prediction picks the activity-prefixed
alternatives on its own; no reordering was needed. Executed against a real .mpr,
mxbuild reports 0 errors.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01JXnEgoc2NQP1Y2TWMCMXC4
`mxcli test tests/ -p app/App.mpr` failed with "no such file or directory" for a
tests/ sitting right next to the .mpr. Resolving against the process CWD is
defensible on its own, but mxcli otherwise encourages naming the project rather
than standing in its directory, so the two conventions collide and the failure
reads as a missing directory.

The fallback applies only when the CWD-relative path does not exist. A tests/ in
both places still resolves to the one the user is standing in — silently
preferring the project's copy would run the wrong suite — and a path that exists
in neither is passed through unchanged, so the error names what was typed.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01JXnEgoc2NQP1Y2TWMCMXC4
A `dynamic` SQL override still requires a value for every parameter the query
*definition* declares, including ones the replacement SQL never mentions — the
parameter list belongs to the definition, not to the string. And a `{param}`
placeholder can be concatenated into a path, which is what keeps an absolute
path out of the model: bind the data directory as a constant and build the file
name around it.

Both come from a DuckDB read path verified end to end on Mendix 11.13, which
also settles the open question about `type 'BYOD'` — the runtime accepts it, not
just the editor.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01JXnEgoc2NQP1Y2TWMCMXC4
@ako
ako merged commit 45ae6a6 into main Aug 7, 2026
1 of 3 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants