Versioning and releases¶
Scaly is in the 0.x.y series. Patch releases preserve the public interfaces of that release line,
while minor releases may contain breaking changes. This policy covers the Python API, generated C, and
official solver plugins.
Initial versions¶
The first non-prerelease version is 0.1.0. There is no separate 0.0.x series to signal
that the project is young.
The project stays in the 0.x.y range while its public interfaces are still expected to change
substantially. 1.0.0 comes when we are ready to maintain the documented public API through
explicit deprecations with a migration period. Feature completeness by itself is not the criterion.
Within the pre-1.0 series, patch releases stay within a compatible release line. A new minor version may open a new compatibility line and contain breaking changes.
Core and plugin versions¶
The core package and each solver plugin are versioned independently. A release of one package does not require publishing unchanged packages to keep version numbers aligned. Their initial versions may happen to be equal, and a coordinated compatibility change may update several versions in the same commit, but equality has no compatibility meaning.
Compatibility is enforced in two places:
- Each plugin declares the supported core range in its package dependencies,
scaly>=0.1.0,<0.2for the0.1.xcompatibility line. The exclusive upper bound also excludes every0.2pre-release. - The solver registry checks the plugin protocol version at runtime. A breaking change to the plugin contract bumps that protocol version and requires coordinated releases of the affected official plugins.
What a release may change¶
Generated code has three compatibility concerns.
Exported C symbols¶
The stable interface is the pointer signature of the exported entry <name>
and, for solver modules, the <name>_stats accessor, together with the derived-output
names {kind}_{of}_{wrt} such as f_spjac_y_x. The signature follows the
data types of the inputs and outputs by one rule: const double** arg, double** res, double* w
when every input and output is float64, const float** arg, float** res,
float* w when every one is float32, and const void** arg, void** res,
void* w otherwise, with SZ_W counted in bytes and w aligned to 8 bytes.
Each input and output buffer holds values of its own C type, and iw and mem
never change. A patch release changes none of these. A minor
release may rename, reorder or remove them, and the release notes list the change. The typed C
structs, the C++ Buffer aliases and the CasADi query functions are sugar over the pointer
signature and follow the same rule. The static _raw bodies
are internal and may change in any release.
A fully declared Function keeps its declared name. @sc.function() on a body without parameters
counts as fully declared, so its body runs at the decorator and its instance takes the plain name.
An instance of a function with open input shapes is named <name>_<shapes>_t<nesting>, where the shapes are the bound open shapes in
declaration order, written 3x4 for a matrix and s for a scalar, and the nesting is a six-digit
hexadecimal digest of the parameter tree. A bare function includes all of its input shapes. The
name never contains __, which C++ reserves. The declaration alone decides whether an instance
name carries this suffix, never the order of calls, and a derivative given an explicit name
inherits the suffix of its source instance. Two instances of one function that would share a name
raise an error, as do two distinct graphs in one module whose names sanitize to the same C
identifier. Give independently declared functions distinct names.
Sparsity tables¶
The <prefix>_NNZ, _NROW and _NCOL macros and the index tables in the
generated header take their prefix from the input or output name, a derived output's included, so
their names follow the symbol rule above. A sparse output's values follow its declared layout, and
the header publishes that layout's tables, as
the generated interface describes. The
order rule is part of the interface. The tables' contents are not: a sharper derivative analysis
may drop an entry from a derivative's pattern, so a consumer reads the tables rather than copying
them. Whenever generated output changes,
_JIT_CACHE_VERSION in src/scaly/codegen/jit.py is bumped so a cached library from an older
version is never reused.
Cached solver modules¶
Cached solver modules depend on the names and contents of readable files in their native include
and library directories. These come from installed plugins, SCALY_SOLVER_INCLUDE_DIR,
SCALY_SOLVER_LIB_DIR, and the parent directories of SCALY_<NAME>_LIB overrides. Scaly reads
each directory once per process, across all solver backends. A changed file selects a new cache
entry even when the generated C and plugin version stay the same. Pointing an override at a system
directory such as /usr/lib reads every readable file beneath it.
Keep native solver files fixed while Python runs, and stop Python before replacing them. Replacing them in a live process can store a new build under the old cache key, leaving an incorrect entry on disk after that process exits.
Plugin protocol¶
SOLVER_PLUGIN_PROTOCOL_VERSION and SCALY_SOLVER_STATS_VERSION version the
contract between core and solver plugins. Solver plugins lists what
each covers and its history. A protocol bump is a minor release of scaly and a coordinated release
of every official plugin, which raise their lower bound on scaly in the same commit. A patch
release never bumps either constant.
Git tags¶
Every published package version gets exactly one package-qualified Git tag, for example:
There is no additional repository-wide release tag. Several package tags may point to the same commit when that commit releases several packages, and packages that are not released receive no new tag.