Skip to content

feat: publish shared models from Java, Kotlin and C# projects - #540

Merged
dmealing merged 5 commits into
mainfrom
fm/mo-deps-p2-s8s9
Nov 6, 2026
Merged

dmealing merged 5 commits into
mainfrom
fm/mo-deps-p2-s8s9

Conversation

@dmealing

@dmealing dmealing commented Nov 6, 2026 •

Copy link
Copy Markdown
Member

What Changed

  • Adds a shared-model generator to the Java codegen-base module and the C# MetaObjects.Codegen package. Each writes <name>.metaobjects.json and metaobjects.pkg.json, byte-identical to the TypeScript sharedModelFile() and gated by fixtures/shared-model-conformance/. The engines (SharedModel in Java metadata, MetaObjects.Config.SharedModel in C#) stay in the package and out of the ejected copy.
  • Adds KotlinSharedModelGenerator, a thin Kotlin wrapper over the Java engine. Java and Kotlin run it through metaobjects:generate (version from <version>) and eject it with mvn metaobjects:eject. C# declares SharedModelGenerator (version from <Version>) and ejects it with dotnet meta eject shared-model.
  • generator-registry-conformance lists java, kotlin and csharp under shared-model. The sweep tests skip the Java/Kotlin shared-model generator because it writes no code and needs file arguments. Also updates the CHANGELOG Unreleased section, docs/ports/{csharp,java,kotlin}.md, and the Java/Kotlin/C# rows in metadata-dependencies.md and CONFORMANCE.md.
Validation

Risk Assessment

✅ Low: The fix round is small and correct: the C# generator now rejects MSBuild $(...) versions, and the new Maven mojo tests cover files and version handling. The changed generators are thin and delegate artifact bytes to the in-package engines. I did not read the engines or the other changed files, so low risk covers only what I reviewed.

Testing

Baseline ts-fast/ts-unit given. Targeted Java and C# shared-model tests pass. Live driving: Java SharedModelGenerator and C# SharedModelGenerator both publish the acme-common model with bytes matching fixtures/shared-model-conformance (artifact exact, manifest exact after placeholder substitution). Adversarial refusals verified live: C# rejects MSBuild-expression version (the review fix), both ports refuse a missing version and a missing include. Full C# solution and full Java reactor suites pass. Not live: Maven mojo default-version and relative-files resolution (unit tests only; running the plugin needs installing into the local Maven repository, outside the worktree), Kotlin KotlinSharedModelGenerator (unit tests only), and dotnet/mvn eject round trips (contract tests only). Overall: pass.

  • Live validation: ✅ go - 8 of 11 scenarios driven live against the product
Scenario Result Live Evidence
Java publish: artifact bytes equal the shared-model corpus artifact ✅ pass live Java driver a scratch driver over fixtures/shared-model-conformance; cmp equals fixtures/dependency-conformance/artifacts/acme-common-v1.json
Java publish: manifest bytes equal the pinned manifest ✅ pass live cmp of out-version-ok/metaobjects.pkg.json vs expected manifest with @@METAMODEL_VERSION@@ replaced by 1.3
Java refuses publish with no include scope ✅ pass live REFUSED GeneratorException: set 'include' ... (Drive no-include)
Java refuses publish with no version ✅ pass live REFUSED GeneratorException: no version available (Drive no-version)
C# publish: artifact bytes equal the shared-model corpus artifact ✅ pass live Harness a scratch directory, out-100 cmp equals acme-common-v1.json
C# publish: manifest version taken from project <Version> ✅ pass live Harness with <Version>1.0.0</Version> produced manifest matching pinned manifest; <Version>2.3.4</Version> recorded 2.3.4
C# adversarial: MSBuild-expression <Version> refused, not recorded as literal ✅ pass live REFUSED InvalidOperationException: no version available (proj-msb with $(VersionPrefix)-beta)
C# refuses publish when project has no <Version> ✅ pass live REFUSED InvalidOperationException (proj-none)
Maven mojo supplies project version and resolves relative files list against basedir ⏸️ untested no Live run needs the metaobjects-maven-plugin installed into the local Maven repository (outside the worktree boundary). Unit tests cover the code path, not the live plugin run.
Kotlin shared-model generator publishes the corpus bytes ⏸️ untested no Driving the Kotlin generator live needs a kotlinc classpath assembled outside the worktree. Only unit tests ran.
Eject produces a thin owned copy that still runs (Java and C#) ⏸️ untested no Live eject needs running mvn metaobjects:eject and dotnet meta eject in a disposable project. Only contract tests ran.
  • Evidence: Java live publish output (artifact + manifest) (kept in the gate run's local evidence)
  • Evidence: C# live publish output (artifact + manifest) (kept in the gate run's local evidence)
Evidence: C# MSBuild-version harness project (refused live)
<Project Sdk="Microsoft.NET.Sdk"><PropertyGroup><Version>$(VersionPrefix)-beta</Version></PropertyGroup></Project>
Evidence: Java targeted test log

The raw log is left out of this description: it only repeated local scratch paths. The result is in the summary above.

  • Evidence: Java full regression log (kept in the gate run's local evidence)
  • Outcome: ⚠️ 1 info across 1 run (27m52s)

Pipeline

Updates from git push no-mistakes

✅ **Intent** - passed

✅ No issues found.

✅ **Rebase** - passed

✅ No issues found.

🔧 **Review** - 4 issues found → auto-fixed ✅
  • ⚠️ server/csharp/MetaObjects.Codegen/Generators/SharedModelGenerator.cs:115 - SharedModelGenerator.VersionIn takes the raw text of <Version> with no MSBuild evaluation. A project that writes <Version>$(VersionPrefix)-beta</Version> or <Version>$(Major).$(Minor).0</Version> gets the literal string "$(VersionPrefix)-beta" in metaobjects.pkg.json. No error is raised, so the manifest records a wrong version silently. Fix: treat a value that contains "$(" as not a version (return null). The existing "no version available" error then asks for an explicit Version.
  • ⚠️ server/java/maven-plugin/src/main/java/com/metaobjects/mojo/AbstractMetaDataMojo.java:599 - The real Maven path depends on two new behaviours in AbstractMetaDataMojo, and no test covers either one. First, mergeAndOverwriteArgs defaults version from ${project.version} with putIfAbsent (line 599). Second, files is resolved element by element against the module basedir through PATH_LIST_ARGS (line 626). The generator tests pass version and absolute files by hand, so they skip both. RelativeGeneratorArgPathsTest exists but was not extended. If the putIfAbsent is lost, every metaobjects:generate publish fails with "no version available". If the list resolution regresses, a relative files entry resolves against the shell's working directory, and the generator reads the wrong files or none. Fix: add mojo-level cases that use a stub project. Assert that a relative comma-separated files resolves under basedir. Assert that version defaults to the project version, and that an explicit arg overrides it.
  • ℹ️ server/csharp/MetaObjects.Codegen.Tests/SharedModelGeneratorTests.cs:73 - Three SharedModelGeneratorTests cases check the version with a raw substring over the emitted manifest JSON: Assert.Contains(&#34;\&#34;version\&#34;: \&#34;2.3.4\&#34;&#34;, manifest) at lines 73, 112 and 123. This is the substring-over-JSON pattern that the test-quality rule flags. A version that appears elsewhere in the manifest would also pass, and a formatting change would fail with no change in meaning. Fix: parse the manifest (JsonDocument) and assert that the version property equals the expected value.
  • ℹ️ server/java/codegen-kotlin/src/test/kotlin/com/metaobjects/generator/kotlin/EjectContractTest.kt:231 - In the Kotlin EjectContractTest, modelFiles() was inserted between the existing KDoc "Loaded once and shared by both sides. A loader's name is an identifier." and the loader property that KDoc documents. That KDoc is now orphaned in front of modelFiles' own KDoc. Fix: move the new function above the loader KDoc.

🔧 Fix applied.
✅ Re-checked - no issues remain.

⚠️ **Test** - 1 info
  • ℹ️ server/java/codegen-base/src/main/java/com/metaobjects/generator/SharedModelGenerator.java:65 - Java shared-model accepts an explicit version arg such as "$(VersionPrefix)-beta" and writes it verbatim. The review fix that rejects MSBuild-expression versions landed only in the C# generator, which is the port the finding named. The Maven path is unaffected because ${project.version} is already interpolated by Maven.
  • Live validation: ✅ go - 8 of 11 scenarios driven live against the product
Scenario Result Live Evidence
Java publish: artifact bytes equal the shared-model corpus artifact ✅ pass live Java driver a scratch driver over fixtures/shared-model-conformance; cmp equals fixtures/dependency-conformance/artifacts/acme-common-v1.json
Java publish: manifest bytes equal the pinned manifest ✅ pass live cmp of out-version-ok/metaobjects.pkg.json vs expected manifest with @@METAMODEL_VERSION@@ replaced by 1.3
Java refuses publish with no include scope ✅ pass live REFUSED GeneratorException: set 'include' ... (Drive no-include)
Java refuses publish with no version ✅ pass live REFUSED GeneratorException: no version available (Drive no-version)
C# publish: artifact bytes equal the shared-model corpus artifact ✅ pass live Harness a scratch directory, out-100 cmp equals acme-common-v1.json
C# publish: manifest version taken from project <Version> ✅ pass live Harness with <Version>1.0.0</Version> produced manifest matching pinned manifest; <Version>2.3.4</Version> recorded 2.3.4
C# adversarial: MSBuild-expression <Version> refused, not recorded as literal ✅ pass live REFUSED InvalidOperationException: no version available (proj-msb with $(VersionPrefix)-beta)
C# refuses publish when project has no <Version> ✅ pass live REFUSED InvalidOperationException (proj-none)
Maven mojo supplies project version and resolves relative files list against basedir ⏸️ untested no Live run needs the metaobjects-maven-plugin installed into the local Maven repository (outside the worktree boundary). Unit tests cover the code path, not the live plugin run.
Kotlin shared-model generator publishes the corpus bytes ⏸️ untested no Driving the Kotlin generator live needs a kotlinc classpath assembled outside the worktree. Only unit tests ran.
Eject produces a thin owned copy that still runs (Java and C#) ⏸️ untested no Live eject needs running mvn metaobjects:eject and dotnet meta eject in a disposable project. Only contract tests ran.
  • scripts/ci-local.sh --only ts-fast --only ts-unit --strict-toolchains
  • Baseline (given): scripts/ci-local.sh --only ts-fast --only ts-unit --strict-toolchains
  • cd server/csharp && dotnet test --filter FullyQualifiedName~SharedModel (9 passed)
  • cd server/java && mvn -o -pl metadata,codegen-base,maven-plugin,codegen-kotlin -am test -Dtest='SharedModel*,RelativeGeneratorArgPathsTest,EjectContractTest,EjectRuntimeRoundTripTest,NoMagicPhysicalNamesTest,ReportingInertTest,GeneratorRegistry*' (all pass)
  • LIVE Java: javac a scratch driver against codegen-base/target/classes; ran SharedModelGenerator.execute via a MetaDataLoader over fixtures/shared-model-conformance with version-ok, version-msbuild, no-include, no-version. Artifact cmp == fixtures/dependency-conformance/artifacts/acme-common-v1.json; manifest cmp == expected manifest after @@METAMODEL_VERSION@@ -> 1.3.
  • LIVE C#: disposable console harness a scratch directory referencing MetaObjects.Codegen.csproj; ran SharedModelGenerator.Generate with project <Version>1.0.0</Version> (artifact and manifest byte-match fixtures), <Version>$(VersionPrefix)-beta</Version> (refused: no version available), and no <Version> (refused).
  • cd server/csharp && dotnet test (full solution: Render 364, Conformance 2331, Cli 370, Codegen 1315 passed, 1 skipped, 0 failed)
  • cd server/java && mvn -o test (full reactor, excluding nothing; BUILD SUCCESS, 0 failures; integration-tests not in reactor so not run)
  • Evidence is kept in the gate run's local evidence directory (java-out, csharp-out, the MSBuild-version harness project, mvn logs)
⚠️ **Document** - 1 info
  • ℹ️ agent-context/skills/metaobjects-codegen/references/dependencies-and-shared-models.md - Still says only the TypeScript toolchain publishes a shared model. Skills/agent-context close-out is S10, which the user placed out of scope for this change.
✅ **Lint** - passed

✅ No issues found.

✅ **Push** - passed

✅ No issues found.

no-mistakes gate: 1 run (review fixes applied in-run; CI step skipped, local CI is the result).

Adds a shared-model generator to the Java/Kotlin Maven goals and to the C# CLI,
each ejectable, byte-identical to the TypeScript publisher and gated by the
shared-model publish corpus. The registry now lists java, kotlin and csharp.
C#: read the project <Version> property only (not a PackageReference's), resolve Files against an input root so verify --codegen matches gen, and classify any re-load failure as non-core-vocabulary. Java/Kotlin: a blank version arg falls back to the project version.
The Maven mojo supplies the project version as a default 'version' arg, so the ProjectVersionAware hook goes away; path resolution and closure check tidied.
@dmealing
dmealing merged commit 0bfe483 into main Nov 6, 2026
9 checks passed
@dmealing
dmealing deleted the fm/mo-deps-p2-s8s9 branch November 6, 2026 05:06
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.

1 participant