Skip to content

Dependency cycle involving label_flag and configuration transition #11291

@jayconrod

Description

@jayconrod

Description of the problem / feature request:

In rules_go, we have a customizable static analysis system called nogo. There's a nogo rule that generates a binary that is run alongside the Go compiler when building each go_library. Developers use it to define a binary with a customizable set of static analyzers.

I'd like to migrate nogo to the new Starlark Build Configuration system. Ideally, there should be a label_flag that points to a nogo target to use, and that could be set on the command-line. Currently, each go_binary and go_library implicitly depends on a default nogo target, which is set using some hacks involving repository rules. To break the dependency cycle, nogo targets may only depend on targets defined with go_tool_library, a bootstrap rule similar to go_library except it doesn't depend on nogo in order to break the dependency cycle.

Unfortunately, I'm seeing a different kind of dependency cycle. The workspace below is a minimal, standalone example of what I want to do with nogo.

  • x_binary is analogous to go_binary.
  • x_checker is analogous to nogo.
  • Every x_binary implicitly depends on a label_flag (with cfg = "exec") that points to an x_checker that depends on an x_binary.
  • The x_checker has an incoming transition that sets the flag to a special target with no dependencies to break the cycle.

Any build involving the label_flag reports a dependency cycle. It doesn't seem like the current value of the flag is taken into account when the dependency graph is loaded.

This works if checker_flag defaults to //:null_checker and is set to another value. It just doesn't work when checker_flag defaults to a real x_checker target.

Bugs: what's the simplest, easiest way to reproduce this bug? Please provide a minimal example if possible.

Run bazel build //:x in this workspace:

-- WORKSPACE --
# empty file

-- BUILD.bazel --
load(":def.bzl", "null_checker", "x_binary", "x_checker")

x_binary(
    name = "x",
)

label_flag(
    name = "checker_flag",
    build_setting_default = "//:checker",
)

x_checker(
    name = "checker",
    dep = ":checker_bin",
)

x_binary(
    name = "checker_bin",
)

null_checker(
    name = "null_checker",
)

-- def.bzl --
def _x_binary_impl(ctx):
    pass

x_binary = rule(
    implementation = _x_binary_impl,
    attrs = {
        "_checker": attr.label(
            default = "//:checker_flag",
            cfg = "exec",
        ),
    },
)

def _x_checker_transition(settings, attr):
    settings = dict(settings)
    settings["//:checker_flag"] = "//:null_checker"
    return settings

x_checker_transition = transition(
    implementation = _x_checker_transition,
    inputs = ["//:checker_flag"],
    outputs = ["//:checker_flag"],
)

def _x_checker_impl(ctx):
    pass

x_checker = rule(
    implementation = _x_checker_impl,
    attrs = {
        "dep": attr.label(mandatory = True),
        "_whitelist_function_transition": attr.label(
            default = "@bazel_tools//tools/whitelists/function_transition_whitelist",
        ),
    },
    cfg = x_checker_transition,
)

def _null_checker_impl(ctx):
    pass

null_checker = rule(
    implementation = _null_checker_impl,
)

What operating system are you running Bazel on?

Darwin / amd64 (macOS 10.15.4)

What's the output of bazel info release?

release 3.1.0

cc @juliexxia @gregestren @katre

Metadata

Metadata

Assignees

No one assigned

    Labels

    P2We'll consider working on this in future. (Assignee optional)team-Configurabilityplatforms, toolchains, cquery, select(), config transitionstype: bug

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions