stale_project example¶
Upgrading a project an old just-makeit generated. tree/ is a real
project, generated by just-makeit 0.33.14 and never touched since except
by its author, who wrote a kernel into it. This example upgrades it to the
jm you are running, one step at a time, and proves the result: jm status
--check clean, built and tested on Linux, macOS and Windows, the author's
code intact, and a bug the old version shipped now fixed.
Use it as the recipe for your own project. Every step below is the same command you would run, and the order matters: first make the project build with today's jm, then take the fixes its generated code is missing.
TL;DR — see it work first¶
What you start with¶
A project with a standalone object (gain), a module dsp holding an
object fir with a variable-output method, an array method, a writable
property and a module function — the shapes a real library has. FREEZE.toml
records the exact commands that made it.
Its author changed one thing jm generated: the body of fir_step in the
sacred header native/inc/fir/fir_core.h.
An upgrade must leave that alone, and this one does.
1. Read before you write¶
It exits 1 and sorts what is behind into kinds, each with what to do about
it: files the manifest implies that are missing or stale, a binding
fragment that is unreconciled, create-only files that are outdated,
C cores that are unwired from the C library, and fixes to the root
CMakeLists.txt template that yours lacks (root cmake). Keep this output — every
step below answers one of its sections.
2. jm apply¶
It writes everything the manifest implies that is missing and reconciles the
wiring jm owns, then stamps just-makeit.toml with the version you ran. It
also warns, loudly, about a fragment it will not rewrite:
warning ~: native/src/dsp/dsp_ext_fir.c: binding no longer matches the
manifest [...; steps: jm's out= guard tests C-contiguity and this fragment
does not, so a strided out= is filled through a copy and silently ignored]
Hold that thought — step 5 shows what it means, and step 6 fixes it.
3. The root CMakeLists.txt, which jm maintains only in part¶
apply keeps the root CMakeLists.txt's marked blocks current (the
components, modules and external dependencies) and never touches the rest,
because the rest is where your own targets go. So no fix jm has made to that
template since your project was generated has arrived. jm status names each
one under ROOT CMAKE, with what breaks without it:
ROOT CMAKE (8) — fixes jm's root CMakeLists.txt template carries that yours lacks:
↑ libm (gh-1452): the combined library does not link libm PUBLIC, so a C consumer built through find_package() fails with undefined math symbols
↑ static-name (gh-1368) [Windows]: the static library shares the shared library's OUTPUT_NAME, so both produce <name>.lib: `ninja: error: multiple rules generate`
...
Most of them are Windows fixes, and without them this project does not build
under clang-cl. jm status --diff prints your file against today's render,
so you can see what to merge. This project's file was never edited, so take
today's whole, from a scratch project of the same name, and let apply put
the managed blocks back:
jm status --diff # what to merge
(cd /tmp && jm new stale)
cp /tmp/stale/CMakeLists.txt CMakeLists.txt # here: never edited, so whole
jm apply
A fix you have decided against goes in [project] status_allow as
CMakeLists.txt:<fix>, e.g. CMakeLists.txt:complex-range.
If you added your own targets to it, merge by hand instead of copying — or
move them to a native/src/<dir>/<dir>_extra.cmake, which jm includes and
never writes.
4. jm upgrade, and the create-only files jm ships newer¶
These go together. jm upgrade runs the migrations the schema carries — for
this project, respelling complex as _Complex in four files (the same type
and ABI). Only the new clib_common.h reads that spelling on every
compiler, so adopt it in the same step: both or neither (see
Upgrading). clang-cl rejects the old spelling outright.
jm upgrade # also renames jb.toml -> bootstrap.toml, edits and all
jm status # lists the create-only files jm ships newer as OUTDATED
jb.toml is what jm used to call bootstrap.toml. apply does not create a
fresh bootstrap.toml beside it, because a default there would silently stand
in for anything you added to yours; it says so on every run until upgrade
renames it.
apply never rewrites a create-only file, because your edits may be in it.
Diff each first. None was edited here, so each takes jm's render the same
way — delete it, and apply writes today's:
rm .gitignore Makefile Doxyfile bootstrap.toml \
native/benchmarks/jm_bench.h native/inc/clib_common.h
jm apply
A file you did edit: merge jm's version into yours by hand instead.
The packaging templates are handed to jm rather than deleted.
cmake/stale.pc.in and cmake/stale-config.cmake.in are what a C consumer
reads through pkg-config and find_package, and today's jm owns them:
status lists the two under PACKAGING, and once adopted apply keeps them
current by itself. Every line in them is one a released jm rendered --
0.33.14's -- so adopting drops nothing of yours, and --check says so. (A
root CMakeLists.txt you did not replace whole in step 4 is adopted the same
way: its install section becomes jm's managed block.)
Your C benchmarks too, if you never touched them. Once scaffolded,
native/benchmarks/bench_<obj>_core.c is yours, so status cannot call it
behind. But the ones 0.33.14 wrote time with POSIX clock_gettime, which
Windows does not have, and today's time through jm_bench.h on every
platform. If yours are still the scaffold, take today's the same way:
5. See the bug the warning named¶
The project builds everywhere now. Build it and pass a strided out=
buffer — every other element of a longer array:
strided = np.zeros(16, np.float32)[::2]
f.steps(np.ones(8, np.float32), out=strided) # returns normally...
strided # ...and it is still all zeros
0.33.14's binding fills a temporary copy and hands your buffer back
untouched. That is what step 2's warning meant, and apply alone cannot fix
it.
6. Receive the fix the binding fragment is missing¶
A module object's binding fragment (<mod>_ext_<obj>.c) is sacred by
default: apply only ever adds members to it, so a fix to a member it
already has never arrives. Ask which parts differ from today's render:
jm adopt --check
# needs acknowledgement fir
# native/src/dsp/dsp_ext_fir.c
# differs: fn:Fir_steps
# ...
jm cannot tell a hand-written body from a render that predates a fix, so it asks. Look at the diff. Here nobody wrote code in that file, so take today's render — delete it and apply:
Then make it jm's for good, so the next fix to a wrapper arrives on the next
apply by itself. Under the [fir] table in objects/fir.toml:
Put it under [fir], not at the end of the file: appended, it lands in
the last [[fir.properties]] table, and jm tells you it has no effect there.
Run jm apply once more. If you had written code in the fragment, move it
to dsp_ext_fir_extra.c beside it first — jm includes that file and never
writes it. See
Who owns a module's binding fragment.
7. Prove it¶
jm status --check # exit 0
cmake -S . -B ../build && cmake --build ../build && ctest --test-dir ../build
and from Python:
from stale.dsp import Fir
import numpy as np
f = Fir()
f.scale = 2.0
f.steps(np.ones(8, np.float32)) # [2.0, ...]: your kernel, intact
strided = np.zeros(16, np.float32)[::2]
f.steps(np.ones(8, np.float32), out=strided)
# TypeError: out must be a writable, C-contiguous ndarray of the output dtype
The strided out= that 0.33.14's binding silently ignored is now refused.
For jm maintainers: it is also a regression gate¶
A fresh scaffold cannot show what goes wrong when a file an older jm
rendered meets a guard added later — it is rendered by the jm under test.
This project can. Everything each step prints is compared with
upgrade.golden, and the run first proves tree/ is still the frozen tree
(its manifest's jm_version and every file's hash in tree.sha256).
A release that changes what upgrading prints on purpose regenerates the golden in the same PR, and the diff is what gets reviewed:
Never regenerate tree/. The golden growing is the record of every guard
added since 0.33.14; if jm ever stops applying this project on purpose,
freeze a second one beside it and keep both
(gh-1443).
It has already found one:
gh-1471, the
root-CMakeLists.txt gap in step 3, on its first Windows run.