blob: d56d7babc88542e730910421e7800d919b969611 [file] [view]
# Tooling Overview
This document explains the high-level structure and philosophy of this
repository tooling. It is intended for developers who want to contribute to
the tooling.
## Guiding Principles
* **Dart tooling is better than shell scripts.** Non-trivial tooling should be
written and maintained in the language the team is most familiar with. Dart
is also safer and more testable than shell scripts.
* **Everything should be runnable locally.** It should be trivial to run the
same tests locally as are run in CI. This makes the development cycle much
simpler, as contributors don't need to rely on CI cycles to get feedback, and
also makes investigating failures faster and easier.
* **Auditability is critical.** It should be trivial to determine which tests
ran on which packages, and what the outcome of each was. When they don't run
for a given package, it should be clear that they didn't, and why.
* **False negatives are the worst kind of failure.** In general, it's much
better for the default to be to fail than to silently pass. If most packages
should run a certain kind of test, then not having that test without explicit
opt-out should fail.
* **Tooling is better at enforcing things than humans.** If a repo standard or
best practice can be encoded in the tooling, it should be. Reviewers should
not need to remember to actively check for every possible violation. When
there's judgment involved, consider defaulting to failing, and having an
`override: *` label to allow reviewers to intentionally skip a check in
presubmit.
* **Collecting failures is better than stopping immediately.** Unless a failure
would prevent continuing, it is better to add the failure to a list and
continue, then report all failures. If a package has N unrelated failures,
it's much better to report all of them in one run, rather than requiring N
fix/re-run cycles.
## Structure
* Most commands should be subclasses of `PackageLoopingCommand`, which handles
all of the logic of iterating over packages and summarizing the results.
Unless a failure is systemic, failures should be returned rather than thrown
so that the command can continue to run for the remaining packages.
* Overriding `packageLoopingType` allows controlling which packages are
included (e.g., top-level only vs all sub-packages).
* Overriding `initializeRun()` allows for setup that should happen once, such
as reading/parsing repo-level files needed for all packages.
* Overriding `completeRun()` allows for final one-time checks based on the
results of all package runs.
* Overriding `shouldIgnoreFile()` allows for skipping commands when the only
files touched cannot possibly impact the result of the command (e.g.,
`README.md` changes for a native unit test command). `file_filters.dart` has
several common classes of files that can be useful for filtering.
* Keep in mind the principle that false negatives are the worst kind of
failure, so err on the side of *not* skipping unless you're very sure
it's safe.
* Commands that are not package-level (e.g., the license check should run on
every file in the repository, whether part of a package or not) should instead
subclass the more generic `PackageCommand`.
* Very fast and simple checks should be new classes in `lib/src/validators/`
rather than new commands, and added to `ValidateCommand`. While this is
slightly worse for looking at failures in CI, it is much easier for
contributors (and their agents) to run one command to do most of the checks
rather than remembering the entire suite of commands to run.
## Types of Commands
Commands generally fall into two categories: checks and developer utilities.
* Checks are intended to be run by CI as well as locally. They generally
validate that some desired condition is met (unit tests pass, pubspecs are
well structured, etc.) and have a pass/fail result.
* Developer utilities are not intended to be used in CI, but instead to make
things easier for developers. These should be documented in `README.md` for
easier discovery.
There is some overlap between these two categories. For example, `format`
is primarily a developer utility, but with the `--fail-on-change` flag
can be used as a check in CI.