Skip to content

Backend resolution

The registry, sh/backend/init.lua, is the only module that knows which backends exist. Its rule, from section 6.6: resolve once, look up, never re-detect.

When a backend is selected

Three moments call select:

  1. require("sh"), inside pcall, so that a host with no usable backend still loads the module and the first command raises the recorded reason (current);
  2. sh.set_backend(choice, opts);
  3. an assignment sh.config = { ... }, which apply_config turns into a selection of the backend the table names.

A command or a cd never probes anything: run, chdir, getcwd and capabilities go straight to the backend in use.

What a choice means

resolve dispatches on the type of the choice through RESOLVE:

The order is one table, ORDER, keyed by sys.platform(): posix, luv, popen on POSIX systems, luv, popen on Windows, and luv, posix, popen on Android, where fork inside an app process is unsupported.

What "works" means

candidate accepts a backend when three checks pass:

  1. the module loads (load); a failed load is tried again at the next selection, so a library installed later is found;
  2. it is complete: a string name and the six functions of CONTRACT, checked by incomplete, which names what is missing;
  3. ready: configure(opts) is called, then available() must return true.

configure runs before available because popen's answer depends on its strategy: minimal works almost anywhere, shmem only with a writable /dev/shm. The options are the popen options of sh.config with those of the call on top (popen_options); configuration replaces a strategy of "auto" with LUASH_POPEN_STRATEGY. Both environment variables are read once, when the registry loads (ENV), and exist for the test matrix and operators.

All three checks run through sys.load_pcall, so that a library loading while sh.install() is active still sees undefined globals as nil.

Failure

A named backend that does not work is an error naming it and the reason, such as sh.backend: luv is unavailable: module 'luv' not found; with "auto" and nothing available, the error lists every candidate's reason. In both cases the backend in use stays as it was: select configures it again with its previous options, since the failed attempt may have reconfigured popen. sh.config is updated only after a selection succeeded (set_backend).

Listing and preloading

available reports the platform's backends that pass the checks with the options of the backend in use, computed once per selection; sh.available_backends() returns it. preload loads every backend of the platform without selecting one. sh.install() calls it first, so that a script's own require("posix") after install() gets the loaded module, as with the 1.x library.

How to

See what is in use

print(sh.backend_name())                           -- posix
print(table.concat(sh.available_backends(), " "))  -- posix luv popen
print(sh.capabilities().separate_stderr)           -- true

Where nothing was available at load, sh.backend_name() and the first command raise the recorded reason: sh.backend: no backend in use: sh.backend: luv is unavailable: module 'luv' not found.

Choose a backend

sh.set_backend("luv") selects by name, and sh.set_backend("popen", { strategy = "standard" }) configures popen at the same time. Both raise when the choice is unavailable and leave the backend in use as it was, so a preference with a fallback is a pcall:

if not pcall(sh.set_backend, "luv") then
    sh.set_backend("auto")                         -- whatever works here
end

For one run of a script, from the environment, without a change to the code:

LUASH_BACKEND=popen LUASH_POPEN_STRATEGY=minimal lua script.lua

The variables replace a value of "auto" only: a script that calls sh.set_backend("posix") is not overridden.

Get a faster backend

luaposix and luv are weak dependencies: nothing requires them, and whichever is present is resolved when sh loads or a backend is selected. Install one for the interpreter in use, and the next require("sh") runs commands on pipes alone, with stderr apart and signals reported:

luarocks install luaposix      # or: luarocks install luv

A load that failed is tried again at the next selection, so a running program picks up a library installed meanwhile with sh.set_backend("auto").

Require a capability

Check the capability, not the name: a popen strategy changes what the same backend can do.

if not sh.capabilities().separate_stderr then
    sh.set_backend("popen", { strategy = "standard" })
end

The booleans are stdin, separate_stderr, streaming, exitcode and signals (section 5), next to name and, under popen, strategy.

Read an "unavailable" error

  • sh.backend: luv is unavailable: module 'luv' not found: the library is not on this interpreter's package.cpath. Rocks are installed per Lua version (luarocks --lua-version 5.1 install luv).
  • sh.backend: popen is unavailable: strategy shmem unavailable: /dev/shm is not a writable directory: the strategy asked for cannot work here. standard names its temp root the same way, and tmpdir points it elsewhere (configuration).
  • sh.backend: no backend available on posix: posix (...); luv (...); popen (...): "auto" found nothing, and the reasons follow in the order tried.
  • sh.backend: the backend table has no available(): a table of your own lacks part of the contract (testing).