# `exudev` — the Exudyn maintainer driver One command for building, testing and documenting the repository. It replaces the sixteen batch files that used to live in `tools/buildAndGenerate/` (issue #2503). ``` exudev --help the commands exudev --help the options of one command exudev -n print the commands it would run, and run nothing ``` On Windows type `exudev` from anywhere in the tree (`exudev.bat` sits in the repository root); everywhere else — WSL, linux, macOS — type `python tools/exudev`. Every command plans the same steps on the three platforms (#2644); what differs is the build directories `clean` removes, the program `docs --open` calls, and the shell that starts the manylinux container. `python/testing/test_exudev.py` pins those three from any platform. **Quiet is the default**; `-v/--verbose` turns the tools' own output back on. ## Commands | command | what it runs | |---|---| | `exudev generate [--check] [--no-run] [--all-checks]` | `tools/regenerate.py`; with `--all-checks` also every `tools/check*.py` - `checkTocs` among them, which holds `index.md` and `pdfIndex.md` together - and `gen_sources`, each with `--check` | | `exudev scripts [...] [--check] [--run [--timeout 120]]` | `tools/checkUserScripts.py`: every `.py` under the folders that imports exudyn, checked for what changed in Exudyn - names a star import no longer provides, removed and deprecated names, settings and arguments, submodules used without their import, files written beside the script - with the line to write instead. It parses; with `--run` it also runs each script in a copy of its folder, without windows, with a timeout and a solver timeout of 2 s, and reports what failed, timed out or needs a package - a script that names a path outside its folder (`C:/...`, `../data`) is not run (#2713); `--check` fails if anything was found or failed | | `exudev notebooks [names] [--check]` | `tools/runNotebooks.py`: runs the notebooks of `python/Notebooks/` without a window and stores their outputs in them, which the documentation shows; the environment needs what the notebooks import (`--env venvP313`: networkx, ngsolve). `--check` runs nothing and lists the notebooks whose code changed after their outputs were stored; `exudev release` refuses them | | `exudev figures [--all]` | `tools/buildFigures.py`: the TikZ flow charts of `docs/figures/tikz/` that changed, compiled into `docs/figures/.pdf` (the PDF documentation) and `.svg` (html); needs pdflatex and pdftocairo. `exudev generate --all-checks` fails for a chart changed and not compiled | | `exudev build [--py P313] [--fast] [--complete]` | `pip wheel . -w dist --no-deps`, then installs exactly that wheel. **No clean, no regeneration, no docs, no tests** unless `--complete` | | `exudev test [--py] [--fast] [--parallel [N]]` | `runTestSuite.py`, always with `--exit-code` | | `exudev examples [--py] [--timeout S]` | `runTestExamples.py` (slow; default `venvP312`) | | `exudev perf [--py] [--fast]` | `runPerformanceTests.py` | | `exudev perf --mini [--full] [--processes N] [--only A,B] [--compare LOG1 LOG2]` | `runMiniExamplePerformance.py`: every MiniExample solved once more with a small step, the solver timers recorded; the regular module only | | `exudev pytest [--graphics] [--gate] [--record] [-k EXPR] [--processes N]` | pytest over `python/testing/` in `venvExuP313` (the environment with pytest), 8 processes; `--graphics` the graphics tests only, `--gate` the commit-gate subset, `--record` writes the graphics references anew and lists what changed | | `exudev docs [--keep-cache] [--open] [--pdf]` | `sphinx-build -b html . _build -E`; `--pdf` adds the printable documentation in `dist/` (LaTeX needed, release only) | | `exudev linux [--manylinux \| --wsl-conda]` | the linux wheels: through WSL on Windows, in this shell on linux; refused on macOS | | `exudev release [--dev] [--no-linux]` | `build --complete` over every version, with the guards a release needs | | `exudev clean [--dist] [--linux] [--all]` | the build directories and eggs | | `exudev env [--py]` | python, exudyn, numpy, scipy and matplotlib per environment | | `exudev issue ` | the issue tracker: `raise`, `extend`, `remark`, `resolve`, `close`, `show`, `list`, `modify`, `triage`, `plot`, `html`, `serve`, `mode`. Runs in the current interpreter, without conda; `resolve` and `abandon` rewrite the version files | | `exudev issue bump` | start a new release: `--minor` (1.11 → 1.12), `--major` (1.11 → 2.0) or `--to `; appends it to `releases.json` and restarts the micro version | | `exudev issue serve` | the same tracker as a local web page, for a backlog pass: list, search, filter, and raise/extend/remark/resolve through the same API | ## The three things worth knowing **`-n/--dry-run` is the documentation.** It prints the real command lines — the conda call, the environment variables it overrides, the working directory — and runs nothing. When you want to know what a release does, `exudev -n release` answers in one screen and cannot be out of date, because the printed line and the executed line are built by the same code. **`--fast` is opt-in, and it turns the repository default OFF.** `pyproject.toml` has `compileExudynFast = true`, so a plain `pip wheel .` builds `exudynCPPfast` as well; `exudev build` sets `EXUDYN_COMPILE_EXUDYN_FAST=0` unless you ask for `--fast`. Two silent gates remain, and the driver warns about both: on a `.dev` version `setup.py` builds the fast module for **Python 3.13 only**, and on macOS not at all. **`exudev env` is the first thing to run when a test fails "only here".** A stale exudyn install or a different numpy changes results — the two failing models of issues #2501 and #2502 differ between `venvP313` (numpy 2.2.4) and `venvExuP313` (numpy 2.4.6) with a byte-identical binary. ## How the environment is chosen `--py 313` / `P313` / `3.13` / `all` / `310,313` selects the `venvP3xx` environments; `--env NAME` names one directly (not for `build`, which needs to know which `cp3xx` wheel belongs to it). Generation and documentation default to `venvExuP313`. Dispatch is `conda run -n --no-capture-output …` — one process, the exit code propagated, no activate/deactivate dance and no way to leave the shell in the wrong environment. The conda base is found through `EXUDYN_CONDA_ROOT`, then `CONDA_EXE`, then `PATH`; `--no-conda` runs everything in the current environment instead, which is what you want inside WSL. ## Passing something through `test`, `examples` and `perf` forward everything after `--` to the runner, and say so: ``` exudev test --py P313 -- --timeout=5 ``` Nothing is forwarded implicitly. The runners scan `sys.argv` by hand and only *print* a complaint about an option they do not know, so a silently forwarded typo would run a full suite against the wrong thing and report success. ## How a step is judged Every step is judged by its **exit code**; the driver adds `--exit-code` to all three runners and offers no way to turn that off, so the driver never reads a summary line out of a log to decide whether a run failed (#2504). Two of the exit codes mean "nothing NEW broke", not "everything passed": - `runTestSuite.py` excludes `SensitiveTests()` and, on Linux, `UnresolvedOnLinux()`; - `runTestExamples.py` excludes `testRunnerTools.KnownExampleFailures()` - five examples today, three of them missing an optional package (#2507). A known failure that starts passing is reported as a **dead exclusion** by name, so the list shrinks rather than rots. `runPerformanceTests.py` has no exclusions: every performance test passes, and one that does not is worth going red for. One thing the driver still cannot do: attribute a run if two `exudev` processes overlap. Don't run two at once. ## The files | file | what is in it | |---|---| | `__main__.py` | every option, every spelling, every help text; dispatch | | `commands.py` | one function per subcommand — **read this to find out what a command does**. It builds `Step` objects and runs nothing | | `runner.py` | the only file that knows about processes, conda and the file system; executes or prints the steps | | `probe.py` | runs *inside* an environment and reports what is installed; the only part that imports exudyn | Nothing except `probe.py` imports exudyn — the driver selects the environment, so it must run in any interpreter. Standard library only, Python 3.8 syntax. `manylinuxBuild.sh` and `buildManylinux.sh` stay in `tools/ci/`: they run inside the manylinux docker image and have to be shell.