Search code examples
bazelbazel-rulesbazel-aspect

Is there a way to change private attribute of an Aspect progammatically?


Say I have something like the following.

def _foo_aspect_impl(target, ctx):
    # operations

    return FooInfo(...)

foo_aspect = aspect(implementation = _foo_aspect_impl,
    attr_aspects = ['deps'],
    attrs = dict(
        _tool = attr.Label(
            # defs
        ),
    )
)

def _foo_rule_impl(ctx):
    for dep in ctx.attr.deps:
        # do something with `dep[FooInfo]`

    return DefaultInfo(...)

foo_rule = rule(
    implementation = _foo_rule_impl,
    attrs = dict(
        "deps": attr.label_list(
            aspects = [foo_aspect],
        )
    )
)

Is there a way to change the value of foo_aspect.attr._tool, either in WORKSPACE, or at the invocation of foo_rule? Former is much preferable.

The use case being that version and repository origin of _tool might change from project to project. When aspect resides in a repository shared by two projects, it does not make sense to create two branches for these two projects just for versioning of _tool.


Solution

  • After a lot of head scratching I found a rather complicated way of doing it.

    Since the only thing that seems to be configuarable in WORKSPACE.bazel during loading phase is other workspaces / repositories, one could actually use target aliasing together with repository loading to mutiplex configuarable targets.

    Here is how it works.

    First, define a new repository rule new_virtual_repository, which creates repositories that does nothing but loading the BUILD.bazel and WORKSPACE.bazel files.

    # repo.bzl
    
    load("@bazel_tools//tools/build_defs/repo:utils.bzl", "workspace_and_buildfile")
    
    def _new_virtual_repo_impl(ctx):
        # Create build file
        workspace_and_buildfile(ctx)
        return ctx.attr
    
    new_virtual_repository = repository_rule(
        implementation = _new_virtual_repo_impl,
        attrs = dict(
            build_file = attr.label(allow_single_file = True),
            build_file_content = attr.string(),
            workspace_file = attr.label(allow_single_file = True),
            workspace_file_content = attr.string(),
        ),
        local = True,
    )
    

    Then, create an extension file config.bzl which implements a function that generates the BUILD.bazel file and load the virtual repository:

    # config.bzl
    
    load(":repo.bzl", "new_virtual_repository")
    def config(tool):
        build_file_content = """
    alias(
        name = "tool",
        actual = "%s",
    """ % (tool)
    
        new_virtual_repository(
            name = "config_repo",
            build_file_content = build_file_content,
        )
    

    Now in the aspect specification:

    # aspect.bzl
    
    foo_aspect = aspect(
        ...
        attrs = dict(
            _tool = attr.Label("@config_repo//:tool"),
        )
    )
    

    Finally, configure the actual tool in WORKSPACE.bazel:

    # WORKSPACE.bazel
    
    load("//:config.bzl", "config")
    config(tool="<actual_tool_label>")