Command-line Tools Rubric

Overview

This document is for command line interface (CLI) tools. Graphical User Interfaces (GUI) are out of scope.

When developing tools for Fuchsia there are specific features and styles that will be used to create consistency. This document walks through those requirements.

The goal is to maintain a uniform fit and finish for Fuchsia developer tools so that developers can know what to expect. They can most easily see how to accomplish common tasks and there is a well lit path to discover rarer used tools.

Guide

The experience developers have writing software for Fuchsia will impact their general feelings toward writing for the platform and our tools are a significant part of that experience. Providing tools that are inconsistent (with one another) creates a poor developer experience.

This guide provides a rubric that Fuchsia tools must follow.

IDK

Some sections have an "IDK" call-out, like this one. These detail specific rules that apply to tools included with the Fuchsia Integrator Development Kit distribution.

Considerations

Before embarking on the creation of a new tool, consider these factors to determine if the tool is a good fit for Fuchsia or the Fuchsia SDK.

IDK

IDK tools are specific to Fuchsia in some way. Generic tools or tools that are widely available should not be part of Fuchsia and will not be included in the Fuchsia IDK. For example, a tool that verifies generic JSON files would not be a good addition. However a tool that verifies Fuchsia .cml files, which happen to use the JSON format, would be okay.

ffx

ffx is Fuchsia's unified CLI tool platform for host to target interactions. It provides a logical subcommand based grouping that maps to high-level Fuchsia workflows. It also provides a plugin framework to allow contributors to expand the ffx command surface. ffx is distributed as part of the Fuchsia IDK.

Audience

Tools may be used for different development tasks. On a large team these roles may be separate people. Some categories are:

  • Component development
  • Driver development (DDK)
  • Fuchsia development (SDK)
  • Build integration (GN, etc.)
  • Quality assurance (QA)
  • System integrators (e.g., on-device network tools)
  • Publishing (from dev host to server)
  • Deployment (from server to customers)

Consider which users may use a tool and cater the tool to the audience.

Tools may have different integration expectations. For example, a developer doing component development may expect tools to integrate with their Integrated Development Environment (IDE), while a build integration tool may be called from a script.

Prefer to put related commands under a common tool, such as ffx. As an example, git, ffx, or fx present many features (or, "sub-tools") under a single user-facing command. This helps encourage the team toward a shared workflow and provides a single point of discovery.

Prefer subcommands to multiple tools. E.g. don't create tools with hyphenated names like package-create and package-publish, instead create a package command that accepts create and publish subcommands.

Keep the number of commands under a tool organized and reasonable. I.e. avoid adding unrelated commands to a tool and provide sensible organization of the commands in the help and documentation.

Scope of Work

Command line tools can be divided into two groups: simple single purpose tools and larger more featureful tools. Create tools that are ergonomic for their purpose. Simple tools should be quick to start up, while more complex tools will lean toward the more featureful.

Larger tools will encompass an entire task at the user (developer) level. Avoid making a tool that accomplishes one small step of a task; instead make a tool that will perform a complete task.

For example, when:

  • developing a C++ application: run the preprocessor, run the compiler, run the linker, start the built executable.
  • working on a unit test: build the tests and run the tests being worked on
  • developing a mod: compile the code, move the code and resources to the device, start the mod (or hot-reload)

Lean toward a tool that will accomplish all the steps needed by default, but allow for an advanced user to do a partial step (for example, passing an argument to ask the C++ compiler to only run the preprocessor).

IDK

For development environment integrators and EngProd teams, separate tools. The build integrators will learn each and piece them together to make a working system.

ffx

ffx introduces many subgroupings and related subcommands. In general for tools that fall in the categories such as host to target interaction, system integration, and publishing, prefer extending the existing ffx service instead of a new standalone tool. This can be accomplished by extending ffx via additional flags, options or subcommands to take advantage of shared code and functionality. For considerations and additional details refer to the ffx development overview.

Sharing common functionality

If a small step of a task will be needed by several tools, it doesn't make sense to duplicate that code. Consider making a small support tool or create a library to share the code.

Making a small tool that performs one step of the task can make sense to promote code reuse. If the user is not expected to run this small tool individually, place the support tool in a directory that is not added to the $PATH. I.e. avoid polluting the environment path unnecessarily.

Providing a library to share code may be preferable, so that a subprocess isn't needed.

Implementation

Here is some guidance for the nuts and bolts of creating a tool. We'll cover which language to write the tool in, what style to use in that language, and so on.

ffx

ffx follows the rubric and conventions laid out below and provides a reference implementation for the outlined recommendations.

Naming

The following applies to names of binaries, tools, sub-commands, and long parameter flags.

Use well-known US English terms or nouns for names. Well-known nouns includes those in common use for the subject matter, or the names of whole subsystems. If a name does not appear in documentation, it is likely not well-known. If it does not appear in any implementation, it is definitely not well-known.

Only use lower-case letters (a-z) in the US-ASCII character set and hyphens. A single hyphen (-) is used to separate words in a name. A Platform required extension is an exception (such as .exe).

Name CLI tools with more than three characters. Keep the short file names available for user shortcuts (aliases). If you believe a tool should have a very short name, request approval from the Fuchsia API Council.

Keeping the points above in mind:

  • Prefer whole words rather than abbreviations.
  • Prefer shorter names where a user is expected type the name frequently. For less frequently typed names bias to more explicit names.
  • Prefer a single word to multiple words
  • Prefer subcommands to multiple tools that are hyphenated (e.g. avoid foo-start, foo-stop, foo-reset; instead have foo that accepts commands start|stop|reset).
  • Prefer symmetry (particularly in verbs) with other similar commands or sub-systems, unless that introduces a broken metaphor.

Programming Languages

Tools may be written in C++, Rust, and Go. For clarity, here are some languages not approved: Bash, Python, Perl, JavaScript, and Dart (see exceptions below).

No language is preferred between C++, Rust, and Go. The choice between these languages is up to the author of the tool.

IDK

If a SDK that is an integration of the Fuchsia IDK includes a specific language (e.g. Dart), that language may be used for tools that are distributed with that SDK. In other words, do not include a Dart tool in a SDK that wouldn't otherwise include the Dart runtime, but if it's already there, that's okay.

Style Guides

Follow the corresponding style guide for the language and area of Fuchsia being developed. For example, if the tool is included with Zircon and written in C++, use the style guide for C++ in Zircon. Specifically, avoid creating a separate style guide for tools.

Try to minimize runtime link dependencies (statically link dependencies instead). On Linux it is acceptable to runtime link against the glibc suite of libraries (libm, etc.); other runtime link dependencies are not allowed.

Building from Source

Keep in mind that some developers will want to build the tools from source. Use the same build and dependency structure as the code in the Platform Source Tree. Do not make a separate system to build tools.

Host Platforms

Keep an eye on how resource heavy a tool becomes and what OSes it will be expected to operate on.

Run on a Variety of Hardware

Developer machines may range from a few CPU cores and moderate amount of RAM to dozens of CPU cores and huge amounts of RAM. Don't assume that host machines are very powerful or that a server cluster is available to offload work to.

Supported OSes

This section is for the convenience of the reader. This document is not authoritative on which platforms are supported.

We currently support

  • Debian-based Linux distributions (other Linux distributions may work but are not officially supported)
  • macOS (on x86, M1 is currently not officially supported)

Tools written for developers must run on those platforms. There are other platforms to consider, and while these are not required at this time, it's good to keep the platforms listed below in mind.

Tools should be built in a way that makes them easy to port to the following platforms:

  • Fuchsia (self-hosted)
  • Windows

This is not an exhaustive list, we may support others.

Case Insensitive File Systems

Don't rely on case sensitivity in file paths. E.g. don't expect that src/BUILD and src/build are different files. Conversely, don't rely on case insensitivity since some platforms are case sensitive.

Development Hosts Using a Non-English Locale

There are several aspects to consider for non-English developers:

  • Whether the tool itself can be localized
  • Whether the documentation for the tool can be localized
  • Whether the tool can work with path names and data that include non-ASCII
  • Whether the tool works correctly on non-English OSes

Tools are provided in US English. It's not required that a tool be localized. (This may change in the future.)

The documentation for a tool will support non-ASCII characters. Both HTML and Markdown can support Unicode (UTF-8) characters, so these are both good choices for documentation. Doing the translation is not required, merely allow for the possibility.

Tools will function properly with file paths that contain binary sequences and white space. Use a library to work with file paths rather than manipulating paths as strings. (e.g. path.Join in Go.)

Tools will operate correctly on non-English platforms (e.g. Japanese or French). This means handling binary (e.g. UTF-8) data without corrupting it. E.g. don't assume a text file is just ASCII characters.

Execution

At runtime (or execution time) consider how the tool should behave.

Optimize for No Work Needed

When appropriate, such as with a build tool, have the tool exit quickly if there is no work to do. If possible, go one step better by providing information to the caller about the dependencies so that the caller can accurately determine whether the tool needs to be called at all.

Command Line Arguments

There are three types of command line arguments:

  • exact text
  • arguments
  • options (i.e. switches and keys)

Exact text

Exact text is placed as-is on the command line. A piece of exact text may be required or optional. Parsing exact text arguments should be restricted to cases where they are needed for disambiguation (i.e. for correctly parsing other arguments). For example if a copy command accepted multiple source and destination arguments, an exact text argument may be used to clarify which is which: copy a b c may be ambiguous; while copy a to b c may indicate that 'a' is copied to two destinations.

Arguments

Arguments are like function parameters or slots for data to fit into. Often, their order matters. In the example copy <from> <destination>, both <from> and <destination> are ordered arguments. In cases where a single logical argument is repeated the order may not matter, such as remove <files>... where the tool might process the <files> in an arbitrary order.

Options

Some arguments are known as options. Both switches and keyed (key/value pairs) are options. Options tend to modify the behavior of the tool or how the tool processes parameter arguments. Options consist of a dash prefixed letter or word.

Options must start with either one ('-') or two ('--') dashes followed by an alphanumeric label. In the case of a single dash, the length of the label must be 1. If the length of the label is two or more, then two dashes must be used. For example: -v or --help are correct; -help is not valid.

For option names with more than one word (for example, "foo bar"), you must use a single dash ('-') between words. For example, "foo bar" becomes --foo-bar.

All choices are required to have a (--) option. Providing single character shorthand (-) is optional. E.g. it's okay to provide just --output, or both -o and --output, but it's not ok to only provide an -o option without a long option as well.

Do not create numeric options, such as -1 or -2. E.g. rather than having -1 mean to do something once, add a --once option. If a numeric value is needed, make a keyed option, like --repeat <number>.

One (-) or two (--) dashes on their own are special cases and are not allowed as a key or switch.

Switches

The presence of a switch means the feature it represents is 'on' while its absence means that it is 'off'. Switches default to 'off'. Unlike keyed options, a switch does not accept a value. E.g. -v is a common switch meaning verbose; it doesn't take a value, making it switch rather than a keyed value.

All switches must be documented (hidden switches are not allowed).

Running switches together is not allowed. E.g. -xzf or -vv, each must be separate: "-x -z -f" or "-v -v".

Keyed Options

Keyed options consist of a key and a value. Keys are similar in syntax to switches except that a keyed option expects a value for the key. E.g. -o <my_output_file> has a key '-o' and a value of 'my_output_file'.

Do not use an equals punctuation (or similar) to separate the key and value. E.g. do not do -o=<my_output_file>.

Note about a rare case: Avoid making optional keys (where the value appears without its key) or optional values (where the key appears without its value). It's clearer to consider the key/value pair optional, but inseparable. I.e. if the key is present a value is required and vice versa. Consider making an argument instead of a keyed option with an optional key. E.g. rather than "do-something [--config [<config_file>]]" where not passing [<config_file>] means don't use a config file; instead do "do-something [--config <config_file>|--no-config]" where passing --no-config means don't load a config file.

Mutually Exclusive Options

Some options don't make sense with other options. We call the options mutually exclusive.

Passing mutually exclusive options is considered a user error. When this occurs the tool will do one of the following:

  • Write an error message explaining the issue and exit with a non-zero result code; doing no work (i.e. there was no data changed as a result of the call). This is the expected handling, so no further documentation or notes are required.
  • Prioritize one option over another. E.g. "passing -z will override -y". In this case the handling will be documented in the --help output.
  • Other handling is possible (first takes precedence or last takes precedence or something else) though this is discouraged. In this case the handling will be documented in the Description, Options, and Notes; though "See Notes" may be used in Description and Options with the full write-up in Notes.
Grouping Options

There is no specific syntax to indicate when enabling one option will also affect another option. When an option implies that another option is enabled or disabled, specify that in the Options. E.g. "passing -e implies -f" means that if -e is enabled, -f will be enabled as if it were passed on the command line (regardless of whether -f was explicitly passed). The redundant passing of the implied value is harmless (not an error).

Option Delimiter

Two dashes ('--') on their own indicates the end of argument options. All subsequent values are given to the tool as-is. For example, with "Usage: foo [-a] <file>", the command line "foo -- -a" may interpret -a as a file name rather than a switch. Further, "foo -a -- -a" enables the switch -a (the first -a, before the --) and passes the literal text -a (the second -a).

Repeating Options

Repeating switches may be used to apply more emphasis (what more emphasis means is up to the tool, the description here is intentionally vague). A common example is increasing verbosity by passing more -v switches.

Repeating keyed options may be used to pass multiple values to the same command. Often this is done to avoid calling the same command multiple times. Common commands that accept repeating options are cp, rm, cat. Care must be taken to ensure that repeating commands are unambiguous and clear. E.g. cp always interprets the last argument as the destination; if cp accepted multiple source and destination arguments the parsing would become ambiguous or unclear.

Standard Input Alias

In Fuchsia tools a single dash (-) is not interpreted as an alias to stdin. Use pipes to direct data into stdin or use /dev/stdin as an alias for stdin. (Note: /dev/stdin is not available on Fuchsia or Windows).

Single Dash

A single dash ('-') on its own is reserved for future use.

Subcommands

Tools may contain sub-command that accept independent command line arguments. (Similar to the git tool). Subcommands do not begin with any dashes. E.g. in fx build the build argument is a subcommand.

When a tool has many subcommands, it should also have a help subcommand that display help about other subcommands. E.g. "fx help build" will provide help on the build subcommand.

Subcommands may have their own arguments that are not handled by the main tool. Arguments between the tool name and the subcommand are handled by the tool and arguments that follow the subcommand are handled by the subcommand. E.g. in fx -a build -b the -a is an argument for the fx tool, while the -b argument is handled by the build subcommand.

Common Features

Command line tools are expected to support some common switches:

  • --help
  • --quiet
  • --verbose
  • --version

Interactive Help (--help)

A tool must accept a --help switch and provide usage information to the command line in that case. The layout and syntax of the help text is described in CLI tool help requirements.

The tool must not do other work (i.e. have side effects) when displaying help.

Use a library that can parse the arguments as well as present help information from the same source. Doing so keeps the two in sync. I.e. avoid writing command line help as an independent paragraph of text.

Keep the interactive help reasonably concise. Plan for a skilled reader, i.e. someone looking for a reminder on how to use the tool or a developer experienced in reading interactive help. For the novice, provide a note referring them to the Markdown documentation.

Provide an option to generate machine parsable output.

Verbosity (--quiet and --verbose)

The --quiet and --verbose switches decrease or increase informational output to the user. Their implementation is optional, but all tools will accept them as arguments and must not use those terms for other purposes, e.g. don't use --quiet to turn off the audio output (use --silence or --volume 0 or some other synonym).

Interactive Version (--version)

A tool must accept a --version switch and provide an indication of the code used to build the tool in that case. The layout and syntax is not specified, but the version will include a version number of some kind.

The tool must not do other work (have side effects) when reporting its version.

Logging

Logging is distinct from normal output. The audience for logging is normally the tool developer or a power user trying to debug an issue. Logging may go to stdout in special cases, such as when --verbose output is requested.

Logging from multiple threads will not interlace words within a line, i.e. the minimum unit of output is a full text line. Each line will be prefixed with an indication of the severity of the line. The severity will be one of: detail, info, warning, error, fatal.

Metrics

Every tool must file a Privacy Design Document (PDD) in order to collect usage metrics.

Metrics are important to drive quality and business decisions. Questions we want to answer with metrics include:

  • Which OS are our users using? - so we know how to prioritize work for various platforms
  • Which tools are they using? - so we know how to prioritize investments, and to learn which workflows are currently being used so we can prioritize investments or identify weak spots
  • How often do they use a tool? - so we know how to prioritize investments, and to learn which workflows are currently being used so we can prioritize investments or identify weak spots
  • Do our tools crash in the wild? How often? - so we know how to prioritize maintenance of tools
  • How do they use a tool? - assuming that a tool can do one or more things, we'd like to learn how to prioritize investments in particular workflows of a tool

The type and content of the metrics collected must be carefully chosen. We will go through the Google-standard PDD review process to ensure we are compliant with Google's practices and policies. Tools must get approval on which metrics are collected before collection.

Configuration and Environment

Tools often need to know something about the context they are running. Let's look at how that context should be gathered or stored.

Reading Information

Tools should not attempt to gather or intuit settings or other state directly from the environment. Information such as an attached target's IP address, the out directory for build products, or a directory for writing temporary files will be gathered from a platform independent source. Separating out the code that performs platform-specific work will allow tools to remain portable between disparate platforms.

Where practical, configuration information should be stored in a way familiar to the user of the host machine (e.g. on Windows, use the registry). Tools should gather information from SDK files or platform-specific tools that encapsulate the work of reading from the Windows registry, Linux environment, or Mac settings.

Tools will be unbiased towards any build system or environment as well. Accessing a common file such as build input dependency file is okay.

Writing Information

Tools will not modify configuration or environment settings, except when the tool is clearly for the purpose of modifying an expected portion of the environment.

If modifying the environment outside of the tool's normal scope may help the user, the tool may do so with the express permission of the user.

Execution Success and Failure

Command line tools return an integer value in the range [0..127] when they exit. A zero represents success (no error) and 1-127 are various forms of error. The value 1 is used as a general error. Any values other than 0 and 1 that may be returned must be documented for the user.

Succeed with Grace

If there were no errors encountered, return a result code of zero.

Avoid producing unnecessary output on success. Don't print "succeeded" (unless the user is asking for verbose output).

If Something is Unclear, Stop

If the tool encounters an ambiguous situation or is in danger of corrupting data, do not continue. E.g. if the path to the directory you're being asked to delete comes back as just "/", there was likely an error trying to get that configuration information, avoid 'soldiering on' and removing everything under "/".

Do Not Fail Silently

Tools must clearly indicate failure by returning a non-zero error code. If appropriate (if it makes sense for the tool or if the user explicitly asked for verbose output) print an error message explaining what went wrong.

Provide Direction on Failure

When a tool execution fails, be clear about whether the error came from bad inputs, missing dependencies, or bugs within the tool. Make error reports comprehensible and actionable.

If the error came from bad inputs

  1. If the user gave the tool bad data, give context about the error and guide the user toward fixing the input, for example, by printing the input file (and line number if that's appropriate for the input) where the input error occurred.
    • Prefer output that follows this format (for easy regex use): file_name:line:column:description. This is a common format used by many tools. Other formats are acceptable, but try to use something that is easy for both humans and tools to parse.
  2. Provide a reference to further information. If documentation is available, provide a link to documentation about the tool in general or to documentation regarding the specific error. If the tool has the capacity to provide more details, describe that (like how gn can explain how to run the tool to get more help).

If the error came from missing dependencies

  1. Be clear that the error is from missing dependencies. Don't leave the user trying to debug their input data if that is not the issue.
  2. Provide instruction on how to satisfy the dependencies. This can be an example command to run (apt-get install foo) or a link to further instructions (see: http:example.com/how-to-install-foo).

If the error came from an unexpected state (i.e. a bug) in the tool

  1. Apologize. Explain that the tool got into an unexpected state. Don't leave the user trying to guess whether their input data was bad or they were missing dependencies.
  2. Suggest a mailing list or forum to get help. Help the user find out if the bug is fixed in the next tool version; or someone has found a workaround.
  3. Invite the user to enter a bug report and make that as easy as possible. Provide a link that goes to the bug database with the tool and platform information prepopulated.

Include Tests

Tools must include tests that guarantee its correct behavior. Include both unit tests and integration tests with each tool. Tests will run in Fuchsia continuous integration.

IDK

It's especially important that IDK tools imported from the Fuchsia build (pm, etc.) have tests that run in Fuchsia continuous integration because the IDK bot does not currently prevent breaking changes.

ffx The ffx platform provides a framework for introducing tests that are run automatically in Fuchsia continuous integration. Contributors can see examples of plugin tests and end-to-end self-tests in the ffx source.

Documentation

The Markdown documentation is the right place to put more verbose usage examples and explanations.

IDK

All tools included in the IDK and intended to be executed directly by an end user must have a corresponding Markdown documentation file.

User vs. Programmatic Interaction

A tool may be run interactively by a human user or programmatically via a script (or other tool).

While each tool will default to interactive or non-interactive mode if they can glean which is sensible, they must also accept explicit instruction to run in a given mode (e.g. allow the user to execute the programmatic interface even if they are running in an interactive shell).

Stdin

For tools that are not normally interactive, avoid requesting user input e.g. readline or linenoise). Don't suddenly put up an unexpected prompt to ask the user a question.

For interactive tools (e.g. zxdb) prompting the user for input is expected.

Stdout

When sending output to the user on stdout use proper spelling, grammar, and avoid unusual abbreviations. If an unusual abbreviation is used, be sure it has an entry in the glossary.

Try to check for output to terminal, i.e. see if a user is there or whether the receiver is a program.

ANSI Color

Use of color is allowed, with the following caveats:

  • Suppressing color:
    • When possible, check whether the terminal supports color, and suppress color output if not.
    • Always allow the user to manually suppress color output, e.g. with a --no-color flag and/or by setting the NO_COLOR environment variable (no-color.org).
  • When using color, be sure to use colors that are distinct for readers who may not be able to see a full range of color (e.g. color-blindness).
    • The best way to do this is to stick to the standard 8/16 colors. It's easy for users to remap these, unlike the 256 colors.
  • Never rely solely on color to convey information. Only use color as an enhancement. Seeing the color must not be needed for correct interpretation of the output.

Stderr

Use stderr for reporting invalid operation (diagnostic output) i.e. when the tool is misbehaving. If the tool's purpose is to report issues (like a linter, where the tool is not failing) output those results to stdout instead of stderr.

See Success and Failure for more information on reporting errors.

Full-Screen

Avoid creating full-screen terminal applications. Use a GUI application for such a tool.

Non-interactive (Programmatic)

Include a programmatic interface where reasonable to allow for automation.

If there is an existing protocol for that domain, try to follow suit (or have a good reason not to). Otherwise consider using manifest or JSON files for machine input.

IDE (Semi-Programmatic)

Allow for tools to be used by an Integrated Development Environment. This generally involves accepting a manifest for input and generating a manifest.

Interactive (User)

Interacting with the user while the tool is running is an uncommon case for many tools. Some tools may run interactively as an option, e.g. rm -i will prompt the user before each removal.

State Files

State files encode information for data sharing between tools. PID file and lock files are examples of state files.

Avoid using a PID file to contain the process ID of a running executable.

Avoid using a lock file to manage mutual exclusion of resource access (i.e. a mutex).