NAME

App::FuguBench - the dispatcher of the fugubench program

SYNOPSIS

use App::FuguBench;

exit App::FuguBench->new->run(@ARGV);

DESCRIPTION

App::FuguBench is the dispatcher of fugubench. Every verb shares it, and it holds the parts that every verb repeats: the option parse, the sandbox entry, the checkout discovery, and the child command.

The dispatcher runs over Fugu::CLI. That module parses the global options, finds the verb, parses the options of the verb, and calls it. Its exit codes are the codes of the program: 0 for success, 1 for a failure, 2 for a usage error, and 3 for a configuration error. No verb defines another code.

Output has two channels. Standard output carries the result line of a verb, and nothing else, because a hook reads it. Every diagnostic goes to the logger of Fugu::Log, which writes to standard error. A child command writes its own standard error there too, in full.

new

new builds the dispatcher. The global options are -C <dir>, which names the start of the checkout walk, and --verbose, which traces each child command on standard error.

The command table holds one entry for each verb module of this distribution. Each module has one class method, command($verb), that returns the entry of the Fugu::CLI table: summary, usage, options and run. A module that holds one verb ignores the name. The body of the entry receives the dispatcher and the arguments of the verb.

run

run(@argv) parses the command line, enters the sandbox row of the verb, dispatches, and returns the exit code.

A command line with no verb is a usage error: the method prints the usage on standard error and returns 2. A global option in front of no verb gives the same error. --help, -h, and the word help ask for the help, and the method prints it on standard output with the code 0.

cli

cli returns the Fugu::CLI dispatcher. A verb body reads its options with $app->cli->option($name), and it reports with $app->cli->log.

start

start returns the start directory of the run: the -C value, or the current directory. A verb that reads a path relative to the start, and that walks up to no checkout, reads it here. deps is that verb: it reads deps/<OS>.txt there, and a guest runs it out of an extracted tarball that holds no .toolingrc.

checkout

checkout returns the App::FuguBench::Checkout of the run. The walk runs on the first call, from the directory of start. A verb that reads no checkout never starts it, so fugubench version runs in a home with no .toolingrc.

The method returns undef when no .toolingrc sits above the start. The call of the verb names the start directory in the log, and the verb then returns the configuration code.

The walk runs one time, and a failed walk stays failed. A sandbox row reads the checkout before the verb does, and that row reports nothing. So the report waits for the call of the verb, and it comes one time. A verb that rejects its argument list reports the usage, and no configuration error.

checkout($checkout) sets the checkout, for a verb that reads its start from a payload. The argument drops the report of a failed walk, so no later call writes it.

command

command(\@cmd, %args) runs one child command through Fugu::Process->run. The command is an argument list, and it never reaches a shell. Every other argument reaches Fugu::Process->run, so a caller of the plain form names cwd, stdin, env or timeout there.

The method writes the command line to standard error under --verbose, before the child runs. After the child exits, it writes the captured standard error of the child to standard error, in full.

command(\@cmd, group => 1) runs the child as the leader of its own session, so the child leads its own process group too. The method keeps the pid of the child in child while it waits. After the exit it writes the output of the child to standard error, and it returns as the plain form does. A verb that must stop a whole process tree uses this form.

The two streams of that child arrive on two files of one temporary directory, because the start opens each stream by its path. One path for both streams would truncate the file twice.

Every other argument of the group form reaches Fugu::Process->spawn_command, which takes stdin, env and inherit. It takes neither cwd nor timeout, and it drops each one in silence. A caller that needs one of those two uses the plain form.

stdin carries one name and two meanings. The plain form takes a string, and it writes that string to the child. The group form takes a path, and it opens that path as the standard input of the child. The group form reads /dev/null without the argument.

child

child returns the pid of the running child of the group form, or undef. A signal handler reads it, and it stops the group with Fugu::Process->terminate($app->child, group => 1) before the handler starts its cleanup.

error

error returns the reason of the last failure of command, or undef.

THE SANDBOX

One table in this module holds the sandbox row of each verb. A row names the pledge promises of the verb, and the unveil paths of a verb that opens a file of its own. The dispatcher resolves the row after the option parse, it unveils and pledges, and then the body runs. On a platform other than OpenBSD the calls change nothing.

The unveil comes in front of the pledge. unveil(2) needs the unveil promise, no row of the table holds that promise, and a pledge in front of the call would stop the program. A row with no list unveils nothing, and the whole filesystem stays in view.

A row can name subcommands. The dispatcher then pledges the promises of the named subcommand, and the promises of the row for every other one.

version opens no file, so its row holds stdio alone. stdio denies open(2), and the row names no rpath.

wiki and worktree run git, and no row can name each file that a child opens. So each row unveils nothing. git pushes, so the row of wiki adds the network promises. The list subcommand of worktree writes no file, so that subcommand drops the write promises.

traces opens its files itself and runs no child, so its row unveils. The list comes from the verb, because a path of it comes from an option and a path of it comes from the checkout. App::FuguBench::Traces holds the list and the reason of each path.

hook runs the other verbs in its own process, so its row pledges the promises of wiki and of worktree together. Those verbs run git, so the row unveils nothing. The install subcommand writes one file of its own, and the write promises of the row cover that write. The row names no unveil list, so no walk runs in front of the verb.

doctor runs git for the library check and for the fix, so its row unveils nothing too. It reads the settings file itself, and rpath covers that read. It writes no file of its own: git writes every byte of the fix.

The install of deps runs a package manager, cpanm, and the commands of an archive, and each one writes outside every path of a row. So its row unveils nothing either. The file promises cover the manifest read and the digest file, proc exec covers each child, and inet dns covers each download.

fetch runs the downloader of Fugu::Curl as a child, which writes its file beside the destination and renames it. So its row holds the promises of deps, and it unveils nothing.

RETURN VALUES

new returns a dispatcher. run returns an exit code. cli returns the Fugu::CLI dispatcher. start returns a directory path. checkout returns a checkout or undef.

command returns the captured standard output of the child, and undef with the reason in error. A child that writes nothing gives the empty string, so a caller tests the return value with defined. The group form writes every stream of the child to standard error, so it gives the empty string after a successful run. child returns a pid or undef.

SEE ALSO

App::FuguBench::Checkout, App::FuguBench::Deps, App::FuguBench::Doctor, App::FuguBench::Fetch, App::FuguBench::Hook, App::FuguBench::Traces, App::FuguBench::Wiki, App::FuguBench::Worktree, Fugu::CLI, Fugu::Curl, Fugu::File, Fugu::Log, Fugu::Process, Fugu::Sandbox

AUTHORS

Dick Olsson <hi@senzilla.io>