SCUA

How-to

Ship a tool as one file

A tool made of several files — a main.scua, a lib/ folder, a few packages from scua add — can travel as one file: an application bundle, NAME.scuapp. scua runs it directly. The machine running it needs scua and nothing else: no deps/ folder, no project layout, and no scua-pkg.

#Build one

The package manager writes bundles. In the project folder, name the entry script (this needs a scua-pkg newer than 0.3.19, which writes the bundle format this scua reads):

$ scua-pkg bundle --app main.scua
bundled app `tally` (entry main.scua): 2 app file(s), 0 package(s) with 0 file(s) into tally.scuapp (1955 bytes)
needs: fs — grant them when you run it: `scua-pkg run [--allow-…] tally.scuapp`

The bundle holds your own .scua files, every package your lock resolved, the lock itself, and a digest for each of them. tally here is examples/tally, a small wc: the repository also carries the bundle built from it, examples/tally.scuapp.

#Run it

Run it like a .scua file. scua options go before the file and the tool's own arguments after it:

$ scua --allow-fs=. tally.scuapp notes.txt main.scua
     2      5      24  notes.txt
    30    131     879  main.scua

scua knows a bundle by its first bytes, not by its name, so a renamed one still runs as a bundle.

#It gets only the grants you give it

A bundle grants itself nothing. Its capabilities come from your command line, exactly as for a .scua file, and --allow-fs=. means the folder you are in when you type the command, not the folder the bundle sits in.

A run that is missing a grant is refused before anything starts, as it is for a .scua file. The bundle also records which capabilities its code reaches, so the message names every missing grant at once, not only the first one the compiler met:

$ scua tally.scuapp notes.txt
scua: tally.scuapp:main.scua:10: this app needs capabilities this run was not granted: fs. Grant them before the file: `scua --allow-fs tally.scuapp` (or narrower: --allow-fs=DIR)

That exits with status 4, like any refused grant (see Exit status). The list only shapes that message. It never grants anything, and it never refuses a run on its own.

#What is checked before it runs

Before a single line of the tool is compiled, scua checks the whole bundle:

  • it is a bundle format this scua reads, and an application rather than a dependency-only bundle;
  • the whole file matches the digest in its header, which covers every byte: names, paths and the layout as well as the code;
  • every offset and length points inside the file;
  • no path in it is absolute or contains an empty, . or .. part;
  • every file matches its digest, and each package's files are exactly the ones its release record lists, with the same digests;
  • every package's release record matches its release root, and the lock names exactly the packages the bundle carries, at the versions and roots it carries them;
  • the lock, the name table and the list of capabilities match the digest that covers them.

If any check fails, nothing runs, and the message says which one, with a stable code first (E-APP-FORMAT, E-APP-CORRUPT, or the lock and record codes a project's deps/ folder uses):

$ scua --allow-fs=. tally.scuapp notes.txt
scua: tally.scuapp: E-APP-CORRUPT: this application bundle does not match its own digest — it is damaged or was changed after it was built

That exits with status 3: nothing compiled, and the fix is the file, not the command.

A bundle built by scua-pkg 0.3.19 is an older format, and is refused with a message saying how to rebuild it:

$ scua --allow-fs=. tally.scuapp notes.txt
scua: tally.scuapp: E-APP-FORMAT: this application bundle is format 2, and this scua reads format 3 — rebuild it with a current `scua-pkg bundle --app`

These checks find damage. They don't prove who built the bundle. Every digest lives inside the bundle, so someone who changes a file and then recomputes all the digests produces a bundle that passes. To know a bundle is the one its author built, get its SHA-256 from the author through a channel you trust, and compare:

$ shasum -a 256 tally.scuapp

#Imports work as they did on disk

Inside a bundle, import finds the same file it found when you ran the sources:

  • a module's own folder first (lib/fmt.scua importing helper gets lib/helper.scua if it's there);
  • then the entry script's folder;
  • then the packages, which see their own modules as they do under deps/.

A name that both your files and a package provide is refused as ambiguous, as it is on disk. A bundle that runs is the program you tested.

#What doesn't combine with a bundle

A bundle is the whole program, so the options that would add files or settings from beside it are refused rather than mixed in:

  • --mod-path and --mod-override add module folders from disk;
  • --profile reads a scua.toml, and a bundle carries none. Pass -D flags instead;
  • scua debug steps through source files. Debug the tool from its sources.

Everything else works as it does for a .scua file: --fast, --jit, --max-ops, --workers, --output=json, every --allow-… grant, -D flags and script arguments.