API, ABI, and migration
This is the compatibility contract for the public C headers, the runtime, and
zxsdc output. It does not extend the XSD support contract or claim complete
W3C conformance. The project remains experimental.
Release policy
In
0.MINOR.PATCH, patch releases preserve existing public C source APIs and the three compatibility epochs below. A minor release may introduce incompatible changes, documented inCHANGELOG.mdwith migration steps.During 0.x, rebuild the runtime, generated objects, and application together when upgrading. There is no promise of drop-in replacement of a separately built library, even when release numbers or epochs match.
From 1.0, incompatible public API/ABI changes will require a major release. This is a release rule, not a claim that 1.x compatibility is already tested.
Correctness and security fixes may reject previously accepted invalid XML or XSD. This is not an API break. Validity remains subject to the documented XSD subset and configured resource limits.
Private headers, internal functions, descriptor addresses, generated C text, diagnostic wording, and performance measurements are not stable interfaces. Within a build, generation remains deterministic.
The installed CMake package accepts compatible requests from the same minor
line before 1.0, and the same major line afterwards. Its shared-library ABI
identifier follows the same rule (currently 0.20, not just 0).
An exact package version can always be requested. This selection rule does
not check compiler flags, memory limits, or the separately installed compiler.
Compatibility epochs
include/zxsd_version.h defines independent, monotonically increasing integer
epochs. Their initial value is 1; they are not release numbers.
Identifier |
Covered contract; increment when incompatible |
|---|---|
|
Public function signatures, calling interface, caller-owned state layouts, configuration stamp format, and runtime entry points required by generated code. |
|
Schema descriptor layouts, enum values, and their interpretation by the runtime. |
|
Binding descriptors, generic binding state, scalar/storage conventions, and the generator/runtime representation contract, including streaming bindings. |
A change can require more than one increment. Do not recycle epochs or export an old epoch guard unless the corresponding compatibility adapter is actually implemented and tested. A compatible implementation fix does not require an epoch change. An incompatible epoch change cannot be shipped in a 0.x patch.
zxsdc --version prints the compiler release; zxsdc --abi-info prints its
release and required epochs as JSON. ZXSD_VERSION identifies the including
headers and zxsd_version() identifies the linked runtime. Release strings
are diagnostic metadata, not compatibility predicates.
Generated .zxsd.[ch] files check the runtime and descriptor epochs at
compilation. Generated .bind.[ch] files additionally check the binding epoch.
Checks are present in both headers and sources: replacing just a generated
header cannot make a stale source compatible. Entry points also reference
epoch-specific symbols such as zxsd_descriptor_abi_1. An already compiled
object therefore fails to link against a runtime exporting only another epoch.
With dynamic libraries, symbol resolution can happen at load time or first use.
Do not suppress these errors or edit the checks to force a build through.
These guards are not a schema checksum or a binary loader. Two different XSDs can have the same epochs but incompatible application structures. Regenerate each schema’s complete output set together and do not mix different revisions of its headers, sources, or application objects. Never persist the raw C descriptors as a portable flash, disk, or network format: they contain native pointers, sizes, and alignment.
Important
Output produced before epoch stamping has no such checks. Regenerate all legacy validators and bindings once with the matching runtime/compiler. Matching epochs cannot retroactively certify old, unversioned objects.
Memory configuration and binary boundaries
Epochs do not encode the architecture or configured capacities. In particular,
zxsd_stream_t, zxsd_xml_reader_t, and zxsd_bind_stream_t contain arrays
sized by public resource macros. All participating translation units must use
the same public limits, ZXSD_NO_HEAP mode, architecture, calling convention,
compiler ABI, packing, and enum/alignment options. Rebuild all consumers after
any change. The selected XML backend is private; switching it still requires
rebuilding the runtime and rerunning the relevant tests.
Zephyr propagates Kconfig-derived definitions globally. With CMake, link
zxsd::zxsd to inherit its usage requirements; application-specific capacity
overrides must also be applied to the runtime and every generated source, not
only the application. Generated .limits.h checks prove minimum capacities,
not that two translation units have identical layouts.
For a separately built runtime, check the application’s stamp once before initializing any context or calling a validation/binding API:
#include <zxsd_bind.h>
static const zxsd_bind_runtime_config_t expected = ZXSD_BIND_RUNTIME_CONFIG_INIT;
zxsd_result_t result = zxsd_bind_check_runtime(&expected);
if (result.code != ZXSD_OK) {
/* Refuse to use this runtime. No XML context has been touched. */
return 1;
}
Without bindings, use zxsd_runtime_config_t, ZXSD_RUNTIME_CONFIG_INIT,
and zxsd_check_runtime() instead. A mismatch returns
ZXSD_ERR_INCOMPATIBLE_ABI. The checks allocate nothing and compare exact
epochs, all public resource limit values, heap mode, and recorded public sizes;
the binding check also compares binding sizes and its epoch.
This is an explicit startup check, not an automatic check on every runtime call. Matching stamps are necessary, not sufficient: sizes cannot prove identical member offsets, calling conventions, or all toolchain settings. The stamp itself requires the same basic C ABI and runtime epoch. It is not a safe bridge between arbitrary architectures or incompatible versions of the check function.
Supported upgrade paths
Change |
Required action |
|---|---|
Same epochs, compatible release |
Existing stamped generated sources can be recompiled. Regeneration is recommended to receive generator fixes; rebuild all objects during 0.x. |
Any epoch changes or legacy output |
Upgrade the compiler and runtime together, regenerate all output, adapt application code according to the changelog, then rebuild everything. |
XSD, imports/includes, or generator options change |
Regenerate the schema’s complete output set, review changed application types and validation behavior, and rebuild all consumers. |
Limits, Kconfig, ABI flags, or target change |
Reconfigure and rebuild the runtime, generated objects, and application. Review arenas, stacks, and schema minima; regeneration alone is insufficient. |
Prefer a compiler and runtime from the same release, especially for an upgrade from unversioned output. Different releases with matching epochs can share the generated format; this does not promise that generator fixes or supported XSD features are identical. New generated dependencies require a runtime epoch change if older runtimes with that epoch cannot provide them.
Migration checklist
Record the old runtime/compiler releases, XSD dependency graph, binding roots,
--stream-bindpaths, and configured limits. Read the target release’sCHANGELOG.mdbefore upgrading.Install the chosen compiler and runtime together. Check
--abi-infoand the runtime headers; do not accidentally use anotherzxsdcfromPATH.Regenerate all
.zxsd.[ch],.bind.[ch], and, when requested,.confand.limits.hfiles with the original explicit options. Review the diff.Adapt application types and calls. Merge reviewed configuration recommendations before Zephyr configuration, keeping application budgets explicit.
Use a fresh out-of-tree build with consistent definitions. Run the startup configuration check when crossing a separately built library boundary.
Run host and no-heap tests, the installed-package consumer, and Zephyr Twister tests for relevant targets. Rerun application-specific XML fixtures, resource measurements, and selected optional W3C/HIL gates as appropriate.
Compatibility regression tests are zxsd_compatibility_test (also shared with
Twister) and generated-compatibility. They test matching/mismatched stamps,
header/source rejection, precompiled-object link rejection, release metadata,
and CMake package selection. These establish the new baseline, not compatibility
with historical releases whose output was unversioned.
On Windows, the generated-code test initializes the developer environment using
vcvarsall.bat from the configured compiler’s Visual Studio installation and
toolset, with matching host/target architectures. Its direct compiler/linker
probes therefore have the SDK’s INCLUDE, LIB, and PATH even when
CTest is launched from an ordinary PowerShell prompt after a Visual Studio
CMake build. Environment changes remain local to the test’s child processes;
compilation/link artifacts stay in its temporary directory. The
compatibility-helpers tests exercise this setup and error handling offline.