3.2.7 Project Specifications & Flag Sets

extra
Bazel with project.scl support

Bazel knows what a target is, what a package is, and what a repository is. It does not know what a project is. That gap matters because projects carry policy: which flags produce a correct build, which configurations are sanctioned, and what "just build it" means for a given set of source files. Without a first-class project concept, that policy lives in .bazelrc layers, team wikis, and tribal knowledge, and the build system cannot enforce it.1

Project Specifications and Flag Sets are an experimental feature that fills this gap. A project.scl file placed in the source tree defines named configurations for a project, and Bazel applies the right flags automatically based on which targets the developer builds.1,2

Why .bazelrc is not enough

The .bazelrc hierarchy covered in 3.2.1 .bazelrc Hierarchy handles the common case well: the workspace rc sets team defaults, named configs separate CI from local builds, and try-import gives each developer a personal override. Three structural limitations remain:1

No per-project scoping in monorepos. .bazelrc applies to the entire workspace. If two projects in the same repository need different flags, developers must remember which flags apply where and unset the others when switching context.1

Adding flags, not defining policy. .bazelrc stacks flags from multiple sources but never declares which combination is canonical. A user rc, a CI wrapper, or an IDE plugin can all inject flags that override project intent. At best this causes cache misses from configuration divergence. At worst it silently produces incorrect builds.1

Opaque to external tools. .bazelrc uses a custom format that is not Starlark, has no formal specification, and cannot be consumed by CI systems, IDEs, or analysis tools without reimplementing the Bazel rc parser.1

project.scl syntax

project.scl uses SCL (Starlark Configuration Language), a strict subset of Starlark designed for declarative configuration. A minimal file looks like this:1

load("//path/to:project_scl.bzl", "project")

project(
    configs = [
        config(
            name = "release",
            flags = ["--compilation_mode=opt"],
            default = True,
        ),
        config(
            name = "dev",
            flags = ["--compilation_mode=dbg"],
        ),
    ],
)

Bazel finds the active project.scl by traversing up the file tree from the target being built and picking the first one it encounters. Different subtrees can have their own project.scl files, so a monorepo can host multiple projects with independent configurations.1

Developers switch between named configs with --scl_config:1

bazel build //foo:bar --scl_config=dev

When no --scl_config is specified, the config marked default = True applies.

Enforcement policies

Each config can declare how strictly Bazel enforces its flags:1

PolicyBehavior
compatibleFails only if user-supplied flags conflict with config flags
warnLogs a warning when non-config flags are present
strictFails the build if any flags outside the config are set
config(
    name = "release",
    flags = ["--compilation_mode=opt"],
    enforcement_policy = "compatible",
)

compatible is the safest starting point: it prevents contradictions while still allowing additive flags. strict is useful in CI pipelines where full cache consistency matters and no flag drift should be tolerated.1

Target patterns

Configs can restrict which targets they apply to:1

config(
    name = "mac_build",
    flags = ["--cpu=darwin"],
    target_patterns = ["//mac/..."],
)

This supports platform-specific builds (different flags for Mac versus Linux subtrees) and different policies for test targets versus production code.1

Current limitations

The feature is still experimental and has three significant constraints:1

Output-affecting flags only. project.scl can set flags that affect compilation output (like --compilation_mode) but not flags that control how Bazel itself runs. Some loosening of this restriction is planned.1

Single configuration per invocation. Bazel requires all targets in the same invocation to share the same flags. If two targets resolve to different project.scl configs with conflicting flags, the build fails. Building one target with a project file and one without works: the config flags apply to both.1

No transitive dependency configs. Configs apply from the top-level project and propagate downward. A library cannot yet express its own required configuration. This means a shared library built as a dependency of two projects with different configs gets whatever flags the top-level project specifies.1

key takeaway

project.scl addresses a real gap: Bazel has never had a canonical way to say "this is how you build this project." .bazelrc sets workspace-wide defaults but cannot scope flags to subtrees, define sanctioned configurations, or enforce policy. project.scl does all three. It is experimental and worth tracking, but not yet a replacement for .bazelrc-based configuration.

Check your understanding · 3 questions

1.What problem does project.scl solve that .bazelrc cannot?

Select one answer

2.Match each project.scl enforcement policy to its behavior:

Drag each answer onto the matching prompt, or click an answer and then click a prompt

Answers
compatible
warn
strict

3.True or false about project.scl limitations:

Choose True or False for each sentence

All targets in one bazel invocation must share the same flags. Two targets resolving to different configs with conflicting flags will cause a build error.
project.scl can set any Bazel flag, including flags that control how Bazel itself runs.
project.scl files are selected by traversing up the file tree from the target being built.
0 of 3 answered

Footnotes

  1. Flagsets - Susan Steinman & Greg Estren, Google — full talk covering project.scl syntax, enforcement policies, target patterns, current limitations, and future plans 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17

  2. State of the Union - Tobias Werth & John Field, Google — initial announcement of Project Specifications and Flag Sets as a shift toward project-centric configuration