Workflows Launcher#
pw_build: Integrations for Bazel, GN, and CMake
The workflows launcher is a command-line tool for running tools, builds, and
other workflows defined in a workflows.json
file. It provides a centralized
and discoverable way to execute common development tasks.
Note
b/425973227: This tool is currently under active development, and will change significantly as the experience is refined.
Getting Started#
bazelisk run //pw_build/py:workflows_launcher
python -m pw_build.workflows.launcher
The launcher will automatically find and load a workflows.json
file in the
current directory or any parent directory.
Commands#
The Workflows launcher provides a set of built-in commands, and will later
support commands generated from the workflows.json
file.
Built-in Commands#
describe
#
The describe
command prints a human-readable description of a Tool
,
Build
, BuildConfig
, or TaskGroup
from the workflows.json
file.
This is useful for inspecting the configuration of a specific workflow, and is
particularly helpful for debugging programmatically-generated configurations.
Example:
bazelisk run //pw_build/py:workflows_launcher -- describe format
python -m pw_build.workflows.launcher describe format
This will print the configuration for the format
tool:
▒█████▄ █▓ ▄███▒ ▒█ ▒█ ░▓████▒ ░▓████▒ ▒▓████▄
▒█░ █░ ░█▒ ██▒ ▀█▒ ▒█░ █ ▒█ ▒█ ▀ ▒█ ▀ ▒█ ▀█▌
▒█▄▄▄█░ ░█▒ █▓░ ▄▄░ ▒█░ █ ▒█ ▒███ ▒███ ░█ █▌
▒█▀ ░█░ ▓█ █▓ ░█░ █ ▒█ ▒█ ▄ ▒█ ▄ ░█ ▄█▌
▒█ ░█░ ░▓███▀ ▒█▓▀▓█░ ░▓████▒ ░▓████▒ ▒▓████▀
name: "format"
description: "Find and fix code formatting issues"
use_config: "bazel_default"
target: "@pigweed//:format"
analyzer_friendly_args: "--check"
build
#
The build
command launches the build for the requested build name.
Example:
bazelisk run //pw_build/py:workflows_launcher -- build all_host
python -m pw_build.workflows.launcher build all_host
Note
b/425973227: This doesn’t offer any configurability or additional argument handling today.
Generated Commands#
The workflows launcher generates commands from the tools
and groups
defined in the workflows.json
configuration file.
Tools#
For each tool in the tools
list, a command is created with the same name.
Running this command will execute the tool’s specified command.
From the example workflows.json, the following command is created:
bazelisk run //pw_build/py:workflows_launcher -- format
python -m pw_build.workflows.launcher format
This will launch a bazel run
invocation of Pigweed’s code formatter tool.
Groups#
For each group in the groups
list, a command is created with the same name.
Running this command will execute all the workflows in the group in sequence.
From the example workflows.json, the following command is created:
bazelisk run //pw_build/py:workflows_launcher -- presubmit
python -m pw_build.workflows.launcher presubmit
This will launch a series of builds followed by code health check tooling as
enumerated by the group named presubmit
.
Configuration#
The Workflows launcher is configured via
ProtoJSON. The schema
lives in workflows.proto
:
Configuration schema
package pw.build.proto;
import "google/protobuf/any.proto";
// A distinct build configuration.
//
// Build configurations are expected to be hermetic, meaning that if the
// same build configuration is reused across multiple builds it should be safe
// to reuse the same build directory.
message BuildConfig {
// Descriptive name for this build configuration.
// This is used as an ID and must be unique across a workflow configuration.
string name = 1;
// A help string that describes this configuration.
string description = 2;
// The name of the driver that should be used for this build configuration.
string build_type = 3;
// Arguments that should be passed to underlying build system.
repeated string args = 4;
// Environment variables unique to this build configuration.
map<string, string> env = 5;
}
// A discrete build.
message Build {
// A human-readable name for this build.
// This is the name that users will write out to trigger this build.
string name = 1;
// A help string that describes this build.
string description = 2;
// The configuration that should be used when building the targets specified
// by this build.
oneof config {
// The ID/name of a shared BuildConfig.
string use_config = 3;
// A unique one-off config.
BuildConfig build_config = 4;
}
// Target patterns that should be built/tested by this configuration.
repeated string targets = 5;
}
// A runnable tool.
message Tool {
// The name that is used to launch this tool.
string name = 1;
// A descriptive help string for this tool.
string description = 2;
// The configuration that this tool will be built under.
oneof config {
// The ID/name of a shared BuildConfig.
string use_config = 3;
// A unique one-off config.
BuildConfig build_config = 4;
}
// Target to build and launch.
string target = 5;
enum Type {
// A tool that may produce output, mutate the world, require realtime user
// interaction, or have side-effects that affect hermeticity of subsequent
// actions.
// By default, all tools are assumed to be of this kind.
GENERAL = 0;
// A runnable tool that exists *purely* to surface information.
// e.g. linters, code style checks, etc.
// Theses tools may NOT:
// - Modify files.
// - Upload/download arbitrary files.
// - Modify local system state.
// - Interact with attached devices.
ANALYZER = 1;
}
// The type of tool.
Type type = 6;
// Arguments to inject that make a Tool safe to run as an ANALYZER.
// These are not injected when this tool is normally launched, only when
// the tool is launched in a way that requires behavior that doesn't affect
// hermeticity.
repeated string analyzer_friendly_args = 7;
}
// A series of builds and analyzers that are launchable as a group to
// accelerate workflows.
//
// The builds and analyzers listed in this workflow *may* be ran in order,
// but no guarantee is provided. Builds and analyzers listed here should
// behave predictably and deterministically when run out-of-order.
// Having order-based dependencies in a workflow is considered an antipattern.
message TaskGroup {
// The name used to launch this workflow.
string name = 1;
// A descriptive help string for this tool.
string description = 2;
// The name of builds that should be performed as part of this workflow.
repeated string builds = 3;
// The name of analyzers that should be run as part of this workflow.
//
// Note: Only ANALYZER tools, or tools that have analyzer_friendly_args
// may be listed here.
repeated string analyzers = 4;
}
// These options are used when loading ProtoJSON files since JSON doesn't
// have any inherent mechanism for sharing/reusing snippets from other files.
message ConfigLoaderOptions {
message ImportedConfigFragment {
// The file to import entries from.
// This path is the result of strip/import_prefix of any dependent
// configurations.
string config_path = 1;
// The `name`s that should be imported into the current config.
// Note that all transitive requirements are also imported. i.e.
// importing a LaunchableWorkflow will import all referenced builds,
// analyzers, build configs, etc.
repeated string imported_ids = 2;
}
// Config fragments that should be imported from another externally-provided
// file.
repeated ImportedConfigFragment imports = 1;
}
// The launcher config.
message WorkflowSuite {
ConfigLoaderOptions loader_options = 1;
repeated BuildConfig configs = 2;
repeated Build builds = 3;
repeated Tool tools = 4;
repeated TaskGroup groups = 5;
}
The workflows launcher searches for a workflows.json
file from the current
working directory, traversing through parent directories as needed. When a
workflows.json
file is found, it is loaded as a WorkflowSuite
Protobuf
message. This configuration is then used to drive the rest of the launcher’s
behavior.
Example workflows.json
{
"configs": [
{
"name": "bazel_default",
"description": "Default bazel build configuration (host)",
"build_type": "bazel",
"args": [],
"env": {}
}
],
"builds": [
{
"name": "all_host",
"use_config": "bazel_default",
"targets": [
"//..."
]
},
{
"name": "all_host_cpp20",
"build_config": {
"name": "bazel_default_cpp20",
"description": "Host C++20 build",
"build_type": "bazel",
"args": [
"--//pw_toolchain/cc:cxx_standard=20"
]
},
"targets": [
"//..."
]
},
{
"name": "docs",
"use_config": "bazel_default",
"targets": [
"//docs"
]
},
{
"name": "build_rp2040_tests",
"build_config":{
"name": "pico_rp2040",
"description": "Default Pi Pico build (rp2040)",
"build_type": "bazel",
"args": [
"--config=rp2040",
"--build_tests_only"
]
},
"targets": [
"//..."
]
},
{
"name": "build_rp2350_tests",
"build_config": {
"name": "pico_rp2350",
"description": "Default Pi Pico build (rp2350)",
"build_type": "bazel",
"args": [
"--config=rp2350",
"--build_tests_only"
]
},
"targets": [
"//..."
]
}
],
"tools": [
{
"name": "format",
"description": "Find and fix code formatting issues",
"use_config": "bazel_default",
"analyzer_friendly_args": ["--check"],
"target": "@pigweed//:format"
},
{
"name": "owners_lint",
"description": "Identify OWNERS file issues",
"use_config": "bazel_default",
"target": "@pigweed//pw_presubmit/py:owners_lint",
"type": "ANALYZER"
}
],
"groups": [
{
"name": "presubmit",
"builds": [
"all_host",
"docs",
"rp2040",
"rp2350"
],
"analyzers": [
"format",
"owners_lint"
]
}
]
}
Build programming#
The definition of how build configurations become a series of actions is defined by Workflows build drivers.