Files
soleprint/docs/data/en/export.md
2026-09-24 00:43:18 -03:00

6.9 KiB

Export / Compile

Soleprint's source tree is not what runs. build.py compiles the framework plus a room's configuration into a self-contained instance under gen/<room>/, and that directory is what a container boots, what deploy.sh rsyncs, and what the cluster manifests point at.

Everything below is a make target, and every target is one script in ctrl/. The logic lives in the scripts, never in the Makefile.

make build            # cfg/standalone -> gen/standalone
make build sample     # cfg/sample     -> gen/sample
make start            # run it

The targets

Command Runs Does
make build [<room>|all|models] ctrl/build.sh compile a room into gen/
make dist [NAME|FILE] ctrl/dist.sh compile the plexus UIs to single files — nothing built first
make docs [serve|graphs] ctrl/docs.sh serve the docs, or re-render the diagrams
make start [<room>] [-d] ctrl/start.sh run a built room's compose stack
make stop [<room>] ctrl/stop.sh stop it
make cluster [up|down|status] ctrl/cluster.sh the shared kind cluster
make component [list|publish|sync|watch|diff] ctrl/spr.py publish a distributable component
make deploy ctrl/deploy.sh rsync gen/standalone to the server and restart

Bare words pass straight through, so make build sample becomes ctrl/build.sh sample. Anything starting with a dash would be eaten by make itself, so those go through ARGS:

make deploy    ARGS="--build"
make component ARGS="publish soleprint-ui /tmp/out --dist"

What a build does

python build.py --cfg <room> runs these in order:

  1. Clean gen/<room>/. A build is not incremental — a stale file left behind is worse than a slow build.
  2. Copy the framework. main.py, run.py, index.html, Dockerfile, requirements.txt, dataloader/, common/, and the three systems (artery/, atlas/, station/).
  3. Merge the room (copy_cfg). cfg/<room>/config.json lands in cfg/, data/*.json in data/, and anything under cfg/<room>/soleprint/artery|atlas|station/ is merged over the framework copy — which is how a room adds its own vein, shunt, tool or generator without forking the tree.
  4. Compose cabinets. The dependency containers the room declared in data/cabinets.json are merged into its docker-compose.yml. See Cabinets.
  5. Generate models. modelgen reads the room's config.json and writes models/pydantic/__init__.py.
  6. Render k8s (optional). When the room's config enables it, soleprint/ctrl/k8s/ writes manifests and lifecycle scripts.

What comes out

A standalone room is a flat instance:

gen/standalone/
  run.py  main.py  Dockerfile  docker-compose.yml
  artery/  atlas/  station/  common/
  cfg/config.json
  data/*.json
  models/pydantic/

A managed room — one that wraps an existing application — is three folders instead, because soleprint sits beside the app rather than containing it:

gen/<room>/
  <app>/        the application's repos, plus its ctrl scripts
  link/         bridge code between the two
  soleprint/    the instance, exactly as above

build.py picks between them on whether the room's config.json has a managed block. gen/ is gitignored in full: it is an artifact, and the way to change it is to change cfg/<room>/ and rebuild.

Distributing components

Rooms are compiled; components are published. registry.json lists what can be shipped out of this repo on its own:

make component                                            # list
make component ARGS="publish soleprint-ui /tmp/out --dist"
make component ARGS="watch soleprint-ui ../unt/ui/framework"

--dist copies only the built bundle — dist/** plus package.json, README.md and LICENSE — rather than the source. It refuses to publish an empty dist/, because a component whose bundle was never built is the failure that shows up later as a container that starts and renders nothing:

no build at soleprint/common/ui
build it first: cd soleprint/common/ui && pnpm build

Each publish leaves a .spr stamp in the destination recording name, version, type, source and mode, so a copy can say where it came from.

Slices: only what a consumer uses

publish hands over the whole component. A slice hands over the part one consumer actually uses, as readable source, computed rather than picked:

make ui                                  # list: every export and part, and what it pulls in
make ui scan ../mpr/ui/detection-app     # what an app uses; its framework copy against spr
./ctrl/ui.sh slice path/to/ui.toml       # write the slices a run file lists
./ctrl/ui.sh check path/to/ui.toml       # the same, writing nothing — exit 1 if stale

A run file names the consumer, where the slice goes, and whether it is read from the consumer's own imports (scan) or named (use). target = "vue" takes files from common/ui/src — Panel alone is one file and vue; TimeSeriesRenderer brings uplot and nothing else does. target = "plain" takes parts from common/theme/parts as one ui.css and one ui.js. See soleprint/common/slice/ui.example.toml.

Two deliveries:

delivery for what changes
dependency your own projects nothing: spr's names stay, and a .spr stamp says where the copy came from
vendored a repository that is not spr's names go through a mapping profile, headers are stripped, no stamp is written, a scrub fails on any spr word, and notice must be stated

Every run writes a manifest beside its run file recording the hash of each file it wrote. That is what makes drift visible both ways: when spr moves on, check says the slice is stale; when someone edits the copy, the next run names the file and refuses to overwrite it without --force — so an improvement made in a consumer can be brought back instead of lost.

Deploying

make deploy ARGS="--build"      # rebuild, sync, restart
make deploy ARGS="--sync-only"  # sync, leave it running

deploy.sh rsyncs gen/standalone/ and runs docker compose up -d --build on the far side. .env is excluded, so server secrets stay on the server.

Running without building

python run.py from soleprint/ serves every subsystem on one port (12000 by default) straight from the source tree. It is for developing the framework itself; a room's cfg/config.json does not exist there, so the landing pages fall back to their defaults. Rooms use docker.

Diagrams

The .dot sources under docs/graphs/ carry structure; the palette lives in docs/graphs/themes/*.gvpr and is applied at render time, so one source renders in every theme.

make docs graphs           # every graph, every theme
make docs graphs lucid     # one theme

<name>.svg is the dark default the docs link to; other themes write <name>.<theme>.svg. See Themes.