Releasing: compiled packages, release assets and the installer¶
Building for this machine from source is a different job: see Install and build (setup.sh). This page is for
maintainers who package releases for other computers, and for install.py.
build.sh (macOS / Linux) and build.bat (Windows) compile UltrafastEngine and lay out release folders you can
copy to another computer: the programs and libraries you choose, the plugins you choose, no source code. Both call
build/package.py.
./build.sh # CLI for this machine, all plugins
./build.sh --cli --python --cpp --csharp --plugins fitting zemax gpu hitran --platform osx-arm64 win-x64 linux-x64
./build.sh --all --plugins none --platform host # everything, core only
./build.sh --list # platforms, plugins, what this machine can build
build.bat --cli --python --plugins transform zemax --platform win64 x86 --zos-api-dir "C:\Program Files\Ansys Zemax OpticStudio 2025 R1"
Requirements: the .NET 10 SDK and Python 3.9+ (only the standard library; the wheel is written directly). The first build for a platform other than this machine's downloads that platform's .NET runtime pack from nuget.org.
A release is the whole repository, in pieces you can take separately¶
A release on GitHub has three kinds of file: the source archive (the full repository), one asset per component and platform
(cli-2.2.0-osx-arm64.tar.gz, python-2.2.0.tar.gz, examples.Branched-2.2.0.tar.gz ...), and manifest.json (every asset with its size,
SHA-256, the platform and what it needs) with install.py. GitHub cannot serve part of an asset, so the pieces are separate assets;
for source folders install.py can instead use git's partial clone (below). Maintainers build the assets; users run install.py.
Components¶
A name selects the component and all its children (examples is every examples.<Group>; plugins every plugin). all is everything built for
the platform except media and python-native, which must be named. Dependencies come along (shown by --list).
| name | contents | needs |
|---|---|---|
cli (per platform) |
the ultrafast program, self-contained, in bin/ |
|
plugins.<id> (fitting, zemax, gpu, ao, twin, hitran) |
the plugin folder in bin/plugins/<id> |
cli; plugins.twin also plugins.fitting |
python |
the hosted-engine wheel, pip installed by the installer |
.NET 10 runtime at run time |
python-native (per platform) |
the wheel with the NativeAOT engine (no plugins) | conflicts with python; counts as python for dependencies |
cpp (per platform) |
C API: header, native library, CMake example | |
csharp |
UltrafastEngine.Core.dll for net10.0 and net48, plugin assemblies |
|
zemax (win-x64) |
the OpticStudio extension, built only where your ZOS-API is found | |
schemas, docs |
JSON schemas; the Markdown documentation (not examples, papers, design notes) | |
examples.<Group>, examples.shared |
docs/examples/<Group>/; the helper files in docs/examples/ that every group imports |
examples.shared, python |
media |
the figures and animations (png, gif, svg) under docs/examples and docs/papers |
|
tests, component-tests |
test sources and data (C#, Python), no binaries; tests/components |
python |
papers |
docs/papers (replications; never the licensed PDFs) |
|
source |
every tracked file of the repository |
A name deeper than a component picks part of it: examples.Branched.pump-probe installs that example, plus the files that sit directly in
docs/examples/Branched/ (the group's helpers) and, through examples.shared, the shared ones. Source components use the repository paths
(docs/examples/...), so a release install and a git checkout give the same tree.
install.py¶
Standard library only; it is in the repository and attached to every release.
python install.py --list # components, sizes for this platform, what is installed
python install.py --include python tests examples.Branched --dest ~/uf # only these (and what they need)
python install.py --include examples.Branched.pump-probe --dest ~/uf # adds to the same folder
python install.py --include all --exclude media --version 2.2.0
python install.py --preset cli-plugins # ready-made selections: cli, cli-plugins, python, python-native, cpp, full
python install.py --include docs examples.Fibres --source git # source folders by git partial clone (see below)
python install.py --uninstall --include tests --dest ~/uf # remove part ...
python install.py --uninstall --dest ~/uf # ... or all
| option | meaning |
|---|---|
--include, --exclude |
component names; no --include is an error (the installer never guesses) |
--version X, --latest |
the release (default: latest); --base-url reads manifest.json and the assets from another folder or URL (file:// too) |
--platform auto |
win-x64, win-arm64, linux-x64, linux-arm64, osx-arm64, osx-x64; a component without an asset for it is skipped under all, an error when you name it |
--dest DIR |
default ./UltrafastEngine |
--source release\|git |
release assets (default), or git clone --filter=blob:none --sparse --branch v<version> and a cone-mode sparse-checkout of the folders of the selected source components |
--no-pip, --python EXE |
wheels are installed with EXE -m pip install --find-links <dest>/python ultrafast-engine[hosted] (never from PyPI; the wheel's dependencies are); --no-pip only unpacks them |
--dry-run, --force |
show what would happen; overwrite or remove files even if changed |
What it does: it downloads only the chosen assets and checks each SHA-256 before anything is written (a mismatch installs nothing), unpacks them,
and writes <dest>/.ultrafast-install.json with every file, its hash, the component and the version. Therefore:
- Uninstall removes exactly those files and the folders it emptied; a file you changed is left in place with a warning, a file it did not install is never touched
(an existing file that is not in the record is not overwritten either, without
--force). Removing a component that another installed one needs is refused. - A second
--includeadds to an install; installing a newer--versionof a component upgrades it (the old unmodified files are removed first). - The ZOS-API is never installed, removed or packaged; an archive that holds one is refused.
- With
--source gitthe tag pins the version. Cone mode brings the files in the parent folders of a selected folder (that is how one example gets its shared helpers), and figures cannot be separated from their folders, somediahas no effect there. Uninstall shrinks the sparse set; git keeps files with local changes. Built components (cli, wheels,cpp...) are not in git: ask for them from the release.
Building the assets¶
build/package.py takes the same names; the old flags are aliases (--cli = --components cli, also --python --cpp --csharp --zemax --examples --docs).
./build.sh --components cli python plugins schemas docs examples tests papers --platform linux-x64 osx-arm64 win-x64
./build.sh --components cpp python-native --platform osx-arm64 # NativeAOT: on the target OS (the owner's Mac for macOS)
./build.sh --components all media source --exclude papers --dry-run # the plan only
python build/release_components.py merge dist/release-2.2.0/manifest.json dist/a/manifest.json dist/b/manifest.json # manifests of several jobs
python build/release_components.py check-zosapi dist/release-2.2.0 # fails if any archive or wheel holds a ZOSAPI file
Output: dist/release-<version>/ with <component>-<version>[-<platform>].tar.gz (reproducible: sorted, fixed timestamps), manifest.json, install.py.
Source components are built from git ls-files, so untracked files, results folders and the licensed paper PDFs can not get in. The writer refuses a
ZOSAPI* file (the tracked ZosApiStubs sources are ours and allowed), and the packager checks every archive and every wheel inside one again at the end.
Built components reuse the steps of the single-folder packages above, so a platform-independent component is built once.
The release workflow¶
.github/workflows/release.yml runs on a v* tag (or by hand, which only builds and tests). Job managed builds everything that is plain files or pure .NET, and cross-publishes
the CLI for win-x64, linux-x64, linux-arm64, osx-arm64 and osx-x64; job native builds cpp and python-native on Linux x64 / arm64 and Windows and runs the C example;
job publish merges the manifests, runs the ZOS-API gate, installs a selection into a temporary folder (cli, schemas, examples.Branched), runs the CLI and an example
in strict mode, uninstalls and checks the folder is empty, installs the wheel in a fresh virtual environment (ultrafast.devices(), the quickstart), and attaches the assets
to a draft release. The NativeAOT components for macOS are built on the owner's Mac and added with gh release upload. A tag is created only when the owner asks.
The workflow has not run on GitHub yet. Rehearsed by hand on macOS: the asset build, the manifest merge, the ZOS-API gate, and the installer install / run / uninstall steps. Not run: the Windows and Linux NativeAOT builds, the CMake example, the wheel in a fresh environment, the upload (see session-f).
Single-folder packages (--cli --python ...)¶
The original mode: one folder per platform with the components you choose, no source.
Output¶
dist/UltrafastEngine-<version>-<platform>/ plus .zip (Windows, portable) or .tar.gz (macOS, Linux, keeps the
executable bits):
| folder | contents | needs on the target computer |
|---|---|---|
bin/ |
ultrafast command-line program, bin/plugins/<id>/ |
nothing (self-contained); --framework-dependent: the .NET 10 runtime |
python/ |
ultrafast_engine-<ver>-py3-none-any.whl (hosted engine) or a platform wheel (native engine) |
Python ≥ 3.10 + NumPy; hosted: the .NET 10 runtime and clr_loader (pip install "...whl[hosted]") |
cpp/ |
include/ultrafast.h, lib/ (ultrafast.dll + ultrafast.lib / libultrafast.so / libultrafast.dylib), example/ (CMake) |
nothing (NativeAOT library) |
csharp/ |
lib/net10.0/ and lib/net48/UltrafastEngine.Core.dll (+ XML docs), plugins/ |
.NET 10 or .NET Framework 4.8 |
zemax/ |
OpticStudio extension UltrafastEngine.Zemax.exe (Windows x64), install-zemax.bat |
OpticStudio; run install-zemax.bat once |
| top level | README.md (what is inside, how to start), LICENSE, NOTICE, CITATION.cff, CHANGELOG.md, schemas/, manifest.json (every file with SHA-256), optional examples/, docs/ |
Options¶
| option | meaning |
|---|---|
--cli, --python, --cpp, --csharp, --zemax, --all |
components (default: --cli) |
--plugins ID ... |
fitting (aliases transform, fit), zemax, gpu (aliases cuda, opencl), all (default), none |
--platform RID ... |
win-x64 (win64), win-x86 (x86, win32), win-arm64, linux-x64 (linux), linux-arm64, linux-musl-x64, osx-arm64 (mac), osx-x64 (mac-intel), portable (framework-dependent, any OS), host (this machine; default) |
--framework-dependent |
the CLI uses an installed .NET 10 runtime (about 8 MB instead of about 75 MB) |
--single-file |
the CLI as one executable |
--python-engine hosted\|native |
hosted (default): one wheel for every OS, loads plugins, needs the .NET runtime. native: NativeAOT library in a platform wheel, nothing else needed, no plugins |
--zos-api-dir DIR |
your OpticStudio folder, to build the extension (the ZOS-API assemblies are never packaged) |
--examples, --docs |
include docs/examples and the documentation |
--examples-wheel |
build only ultrafast_examples-<ver>-py3-none-any.whl: the examples as package data and the ultrafast-examples command (list, copy, run). No .NET needed. Install it next to the engine wheel with pip install --find-links <folder> ultrafast-examples (also the extra ultrafast-engine[examples]); it is never uploaded to PyPI |
--symbols |
keep .pdb debug symbols |
--test |
run the C# tests first |
--out DIR, --no-archive, --version LABEL, --dry-run, -v |
output folder, no zip/tar, version label, print commands only, full build output |
What can be built where¶
| component | built on | for |
|---|---|---|
| CLI, C#, hosted Python wheel | any OS | every platform (pure .NET; the self-contained CLI takes the target's runtime pack) |
C API (--cpp), native Python wheel |
the target OS | NativeAOT does not cross-compile between operating systems; build the macOS package on the Mac, the Windows one on Windows. Cross-architecture on the same OS works with the matching C toolchain. Windows needs Visual Studio's "Desktop development with C++"; Linux / macOS need clang |
| OpticStudio extension | Windows (or anywhere with your ZOS-API folder) | Windows x64 |
Steps that cannot run are skipped with a warning (listed at the end and in manifest.json); the rest of the
package is still built.
Plugins in packages¶
The selected plugins go into bin/plugins/, the hosted wheel (ultrafast/bin/hosted/plugins/) and
csharp/plugins/. Users add others with ultrafast plugins install <folder or zip> (copies into
~/.ultrafast/plugins). The NativeAOT library (C API, native wheel) cannot load plugins at run time; the gpu,
hitran and ao plugins are compiled into it when selected (-p:UF_WITH_GPU=false, UF_WITH_HITRAN=false, UF_WITH_AO=false otherwise; the twin plugin is Python plus a CLI command and needs nothing). The GPU back-ends load the vendor
libraries (CUDA driver, NVRTC, OpenCL) from the target computer at run time; nothing NVIDIA-owned is packaged.
The ZOS-API rule¶
ZOSAPI.dll, ZOSAPI_Interfaces.dll and ZOSAPI_NetHelper.dll belong to Ansys. The packager never copies any
ZOSAPI* file into a package. The extension finds ZOSAPI.dll / ZOSAPI_Interfaces.dll in the user's OpticStudio
installation at run time; install-zemax.bat copies the ZOSAPI_NetHelper.dll of the user's own installation next to
the program. Check a package with find dist -iname "ZOSAPI*" (no output).
Checking a package¶
dist/UltrafastEngine-*/bin/ultrafast plugins list
dist/UltrafastEngine-*/bin/ultrafast run docs/examples/GettingStarted/spm-plate/plate.ufs docs/examples/GettingStarted/spm-plate/run.json -o /tmp/r
python3 -m venv /tmp/v && /tmp/v/bin/pip install "dist/UltrafastEngine-*/python/*.whl[hosted]" && /tmp/v/bin/python -c "import ultrafast; print(ultrafast.version())"
cmake -S dist/UltrafastEngine-*/cpp/example -B /tmp/c -DULTRAFAST_DIR=$PWD/dist/UltrafastEngine-*/cpp && cmake --build /tmp/c
In Claude Code, /release <options> runs the packager and these checks.