Skip to content

docker build (and therefore docker run / mxcli test) silently converts MPRv2 projects to MPRv1 via mx update-widgets, deleting mprcontents — same root cause as #763, different call site #808

Description

@fabric-ian

Summary

mxcli docker build silently converts an MPRv2 project to the self-contained MPRv1
storage format, the same way mxcli docker check did before #764. It inlines every
unit into the .mpr (adds the Unit.Contents column) and deletes the entire
mprcontents/ directory. A command whose job is to build a deployment package should
not rewrite the source project's on-disk storage format.

The cause is the same mx update-widgets step described in #763 — but docker build
has its own, separate call site for it (cmd/mxcli/docker/build.go, distinct from
cmd/mxcli/docker/check.go), which PR #764 did not touch. Since docker build is what
docker run and mxcli test actually invoke, this is arguably the more commonly hit
path in practice: routine use of mxcli test or mxcli docker run against an MPRv2
project silently converts it to MPRv1 and deletes mprcontents/.

On a project under Git this is damaging in exactly the way #763 described: after the
conversion the working tree no longer matches the mprcontents/*.mxunit files tracked
in the repository. We hit this twice on a real ~700-entity / 1,300-microflow production
project in the same working session, purely from running mxcli test against it.

Environment

  • Reproduced and fix-verified directly against mendixlabs/mxcli main @ d86fb696
    (current tip as of this writing), linux/amd64 (Dev Container) — not against our fork's
    own integration branch, so this isn't stale relative to anything that's landed since
    fix(docker): preserve MPRv2 storage format across docker check #764.
  • mx/mxbuild 11.12.0 (Mendix Studio Pro-bundled binaries, resolved the same way
    docker check resolves them)
  • Project version 11.12.0, MPRv2 storage format (mprcontents/ present, 3,447 unit
    files)

Reproduction

Same format oracle as #763 (1 = MPRv1 self-contained, 0 = MPRv2):

sqlite3 App.mpr "SELECT COUNT(*) FROM pragma_table_info('Unit') WHERE name='Contents';"

Starting from a clean MPRv2 project (oracle returns 0, mprcontents/ populated):

mxcli docker build -p App.mpr
# ...
#   Checking project for errors...
#   Updating widget definitions...
# ...
# BUILD SUCCEEDED

sqlite3 App.mpr "SELECT COUNT(*) FROM pragma_table_info('Unit') WHERE name='Contents';"
# 1   <- now MPRv1; mprcontents/ is gone

mxcli test and mxcli docker run both go through Build() in build.go (mxcli test via its own build step before restarting the runtime), so both are affected
identically — you don't need to call docker build directly to hit this.

Impact

Relationship to #763 / PR #764

Confirmed by reading cmd/mxcli/docker/build.go: it has its own
exec.Command(mxPath, "update-widgets", ...) call (around the "Pre-build check" step,
right before mx check), structurally identical to the one #764 fixed in check.go,
but never wrapped in the snapshot/restore protection that PR added. check.go's fix is
scoped to Check() only; Build() was untouched.

Suggested fix

We already have a working fix, branched directly from and verified against current
main @ d86fb696 (not our own fork's diverged integration branch, to keep this
independent of anything else we've got in flight) — happy to share/PR once you've had a
look:

Verified three ways, all on the branch built directly from main @ d86fb696:

  • go build ./... and go test ./... (unit) pass with no regressions, run fresh
    against current main.
  • go test -tags integration ./cmd/mxcli/docker/... passes, including a new
    TestBuild_PreservesMPRv2StorageFormat mirroring TestCheck_PreservesMPRv2StorageFormat:
    scaffolds a real MPRv2 project via mx create-project, runs a full real Build()
    (actual mxbuild PAD package build, not a stub), asserts the project is still MPRv2
    with mprcontents/ intact afterward. Fails without the fix (project ends MPRv1,
    mprcontents/ deleted).
  • Separately, ran the same fix (same commit, cherry-picked) via docker build against
    the real project mentioned above, which had been corrupted this exact way twice
    already: .mpr stayed at its original 475KB (previously ballooned to ~196MB) and all
    3,447 mprcontents/ files survived.

Workaround

Confirmed in build.go: the update-widgets call is nested entirely inside the
if !opts.SkipCheck block, alongside mx check itself. So --skip-check (the flag
docker build/docker run already expose) skips update-widgets too and avoids the
conversion:

mxcli docker build -p App.mpr --skip-check
mxcli docker run   -p App.mpr --skip-check

Trade-off: you lose the pre-build mx check validation and the CE0463
widget-definition-drift suppression that update-widgets provides, so real project
errors would only surface later, from mxbuild itself. mxcli test does not currently
expose a way to pass --skip-check through to the docker build it runs internally, so
this workaround isn't available for that path without a flag added there.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions