Project: Fuchsia Infra Recipes

Background

This repository uses the recipes framework which lives at https://chromium.googlesource.com/infra/luci/recipes-py and is documented at https://chromium.googlesource.com/infra/luci/recipes-py/+/HEAD/doc/user_guide.md.

The entrypoint to a recipe is its RunSteps function. Each recipe also has a GenTests function that generates unit tests, each of which runs RunSteps with all subprocess results mocked out.

Recipes are not allowed to run any Python code that makes syscalls; instead, any interactions with the filesystem or the internet must be done by calling api.step(name, [<command>]) to run a subprocess.

Aside from the Python standard library and third party packages, recipes don‘t share code via imports. Instead, shared recipe code is defined in a recipe module under the recipe_modules directory. A recipe can import a recipe module by listing the module in its DEPS list. Then the module will be accessible via the api object that’s passed to RunSteps.

For example, a call to a api.foo.bar() function in a recipe‘s RunSteps function calls the bar method of the recipe_api.RecipeApi subclass defined in recipe_modules/foo/api.py. Any recipe module that a recipe calls must also be listed in the recipe’s DEPS variable, e.g. DEPS = ["fuchsia/foo"].

In a recipe module, dependencies are injected via the self.m object instead of api, but it works the same way - self.m.foo.bar() calls the foo recipe module's bar method. The dependencies of recipe module foo are listed in the DEPS variable in recipe_modules/foo/__init__.py.

On the other hand, a call to api.foo.bar() in a GenTests function refers to the bar method in recipe_modules/foo/test_api.py. test_api.py files provide helper functions for mocking results of various recipe steps.

Running tests

After making a code change, you can run unit tests by running ./recipes.py test train -x.

Note that that command also automatically updates the “expectation” golden files. For pure refactorings with no expected behavior changes, such as function renamings, use ./recipes.py test run -x instead, which not only runs the tests but also asserts that the expectation files are unaffected by the code changes.

Formatting

After making changes, run:

  1. ./scripts/cleanup_deps.py to remove unused DEPS.
  2. shac fmt to auto-format code.

These don't need to be run after every modification, only after completing a series of modifications before exiting.

Type annotations.

Use Python type annotations where possible.

  • Use ‘list’ and ‘dict’ instead of ‘typing.List’ and ‘typing.Dict’
  • Use ‘from typing import ...’ and ‘from collections.abc import ...’ instead of ‘import typing’ and ‘import collections.abc’
  • Prefer importing from ‘collections.abc’ over ‘typing’
  • Use ‘from future import annotations’
  • Function and method arguments should use generic types if possible, like ‘Sequence’
  • Return values should use specific types, like ‘list’
  • In most cases, don't hide type annotation imports under ‘if TYPE_CHECKING:’
    • Yes: from typing import Callable
    • No: from typing import TYPE_CHECKING ... if TYPE_CHECKING: from typing import Callable
  • Make sure any “if TYPE_CHECKING:” lines that do exist have a “# pragma: no cover” comment

Commit workflow

Do not automatically create or amend commits. You may run git read operations to query the repository history, but do not modify or append to the Git history.