Build and release
Four scripts do all of the work. Each script stops at the first error, and each script tells you why it stopped.
| Script | Function |
|---|---|
./build.sh |
The gate. It builds, tests and inspects everything. |
./wasm.sh |
It builds the WebAssembly module and compares it with Python. |
./install.sh |
It installs the program and the wheels. |
./publish.sh |
It makes a release. |
The gate
./build.sh
The script does these steps:
- It confirms that all of the necessary files are present.
- It confirms that all of the manifests agree about the version.
- It runs
cargo fmt --check,cargo clippy -D warnings,cargo testand the documentation tests. - It builds the engine again without the default features, which is the form that the browser build uses.
- It builds both wheels, and it inspects the metadata of each one.
- It installs the wheels in a new temporary environment, and then it runs the Python tests there.
- It runs
./wasm.sh, which compares the WebAssembly module with Python. - It confirms that the repository did not change.
The gate needs no model weights. The Rust tests that need a checkpoint skip
themselves when ref/ is empty, and tests/test_parity.py skips itself without
transformers.
Step 8 is important. A release depends on a build that writes into no tracked file. If the check fails, something wrote into the repository.
The log goes to build.log.txt.
To skip the WebAssembly step, for example on a machine without Node:
BANANAMEND_SKIP_WASM=1 ./build.sh
The WebAssembly module
./wasm.sh # build and compare; docs/ does not change
./wasm.sh --refresh # build, compare, and write the new module into docs/
The comparison is the interesting part. The Python extension module writes a reference file with the model facts, the token ids, the chat format, the greedy answers and the top scores. Node then gives the same checkpoint to the WebAssembly module and compares every value. Greedy decoding must give the same tokens, or the browser and your computer do different work.
The test needs the Nano checkpoint. It finds it with bananamendy where nano, or
you can name one:
BANANAMEND_CHECKPOINT=/path/to/checkpoint ./wasm.sh
Without a checkpoint the script builds the module and tests only that it loads.
docs/assets/wasm/ holds the module that the demonstration page uses, and
docs/assets/wasm/VERSION holds its version. ./build.sh compares that version
with the manifests. Run ./wasm.sh --refresh after each change to the Rust code,
and then commit the result.
You need these tools: wasm-pack, node, and the wasm32-unknown-unknown
target.
rustup target add wasm32-unknown-unknown
cargo install wasm-pack
The features of the engine
| Feature | Default | Function |
|---|---|---|
std-fs |
yes | Read a checkpoint from a directory. |
parallel |
yes | Use all of the cores for the projections, through rayon. |
cli |
yes | The bananamendr program. |
onig |
yes | The C library for regular expressions in the tokenizer. |
wasm |
no | The Rust library for regular expressions. A browser needs this one. |
The tokenizer needs exactly one library for regular expressions. Give onig for a
computer, or wasm for a browser. A browser also has no files and one thread, so
a browser build gives default-features = false, features = ["wasm"].
Versions
The git tag is the only source of the version number.
scripts/release-manifests.py is the only program that may change a version in a
tracked file. It keeps these places identical:
Cargo.toml, and its pin on the path dependencycrates/bananamendr-wasm/Cargo.toml, and its pinbananamendy/pyproject.toml, and its pin onbananamendrbananamendy/src/bananamendy/__init__.py
python3 scripts/release-manifests.py check # print the agreed version
python3 scripts/release-manifests.py sync 1.2.3 # write one version everywhere
A release
./publish.sh # a test run: it changes nothing
./publish.sh --real # a release: it cannot be undone
The test run is safe. Do it first, and read the predicted version.
A real release does these steps in this order:
- It confirms the state: branch
main, upstreamorigin/main, no conflicts, and a remote that is not in front of your work. - It predicts the next version. The rule is the same as the rule in
gitnextver: the highestvtag plus one patch, or1.0.0when the repository has no tag. - It commits your work, so that the release points to a commit.
- It runs all of the gates at the version that is in the manifests now.
- It writes the new version into the manifests, it builds the WebAssembly module
again, and then it runs
./build.sh. A change to a file that is not in the allowlist stops the release. - It calls
uvx gitnextver@1.0.1. That program makes one commit, one tag, and one push. - It confirms that the local tag, the remote branch and the remote tag point to the same commit.
- It uploads: first the crate to crates.io, then
bananamendrto PyPI, thenbananamendyto PyPI. After each upload it waits until the registry serves the new version.
Do not commit the version change yourself.
gitnextvermakes a tag only when the repository has a change to commit. The version change is that change.
A real release needs two tokens in the environment:
export CARGO_REGISTRY_TOKEN=...
export UV_PUBLISH_TOKEN=...
crates/bananamendr-wasm and crates/bananamendr-py have publish = false for
crates.io. A WebAssembly module and a Python extension module have no use as a
Rust dependency; both go to PyPI or to the site instead.
If an upload fails, the script prints the artifacts that it published before the failure. You cannot remove them from the registries, so the next release must use a new version.
The documentation site
The site is the docs/ directory. GitHub Pages builds it with Jekyll, and it
loads the Just the Docs theme from GitHub. The
demonstration page loads daisyUI from a CDN. The
repository holds no copy of either one.
To see the site on your computer:
cd docs
bundle init
bundle add jekyll jekyll-remote-theme jekyll-seo-tag
bundle exec jekyll serve --baseurl ""
The demonstration page needs a server, because a browser refuses to load a
WebAssembly module from a file: address. The Jekyll server above gives the
complete page. A plain static server also serves the module and the scripts:
python3 -m http.server --directory docs 8000
That server does not run Jekyll, so it shows the Markdown files as text.