This is what Swoop does, and what any client writing to a calculator should do.
Ask what has to come first
curl -s https://roost.ceagle.cc/api/v1/programs/snake/requiresThe answer is already flattened and already ordered: deepest dependency first, the program itself not included.
Do not walk the graph yourself. It is flattened server-side precisely so that a client on a calculator does not have to.
Skip what is already there
Every entry carries a
checksum. Anything on the calculator at the same checksum is already correct, whatever its version string says.for each requirement: if calculator has requirement.var_name and checksum(calculator copy) == requirement.checksum: skipWrite each requirement, in order
Fetch
raw_url, then write it using the metadata from the same entry:Field Use var_namethe name to create on the calculator var_typeprogramorappvarsizehow many bytes to expect archivedwhether to move it to archive memory checksumwhat to verify after writing Write the program last
Same procedure, using the program's own record.
The response#
{
"slug": "snake",
"programs": [
{
"slug": "usb-driver",
"title": "USB Driver",
"version": "2.1.0",
"checksum": "a3f1…",
"size": 8412,
"size_text": "8.2 KB",
"var_name": "USBDRVCE",
"var_type": "appvar",
"archived": true,
"language": "lib",
"raw_url": "/d/usb-driver/raw"
}
]
}The same array rides along on the single-program endpoint as requires, so a client that is already
fetching the program page does not need a second request.
Why the order matters#
LibLoad resolves a program's imports when the program runs, against whatever appvars are present. A program written before its library will fail to run rather than fail to install, which is a worse failure because it happens later and away from the thing that caused it.
Writing deepest-first means the calculator is never in a state where a program is present and its library is not.
Free space#
The list gives you every size before you write anything, so a client can add them up and refuse early rather than failing halfway through.
needed = sum(requirement.size for requirement in skipped_none) + program.sizeWhen a dependency has gone#
Archiving a project suspends its program, and a suspended program is filtered out of the dependency graph rather than returned with a flag.
So a program whose library was archived comes back with a shorter requires list than its
dependencies array. A careful client can compare the two and warn:
declared = program.dependencies # what the manifest said
resolved = program.requires # what Roost will serve
missing = declared - {r.slug for r in resolved}Limits#
direct dependenciesat most 8 per programcyclesrefused at build timeSo the flattened list always terminates.
published firstenforced at build timeA dependency that does not exist fails the build that names it, rather than producing a broken install later.
The publishing side of this.