Search code examples
bazel

How to create a rule from within another rule in Bazel


Situation

I have two Skylark extension rules: blah_library and blah_binary. All of a blah_library's transitive dependencies are propagated by returning a provider(transitive_deps=...), and are handled appropriately by any ultimate dependent blah_binary target.

What I want to do

I want each blah_library to also create a filegroup with all the transitive dependencies mentioned above, so that I can access them separately. E.g., I'd like to be able to pass them in as data dependencies to a cc_binary. In other words:

# Somehow have this automatically create a target named `foo__trans_deps`?
blah_library(
    name = "foo",
    srcs = [...],
    deps = [...],
)

cc_binary(
    ...,
    data = [":foo__trans_deps"],
)

How should I do this? Any help would be appreciated!

What I've tried

Make a macro

I tried making a macro like so:

_real_blah_library = rule(...)

def blah_library(name, *args, **kwargs):
    native.filegroup(
        name = name + "__trans_deps",
        srcs = ???,
    )
    _real_blah_library(name=name, *args, **kwargs)

But I'm not sure how to access the provider provided by _real_blah_library from within the macro, so I don't know how to populate the filegroup's srcs field...

Modify the blah_library rule's implementation

Right now I have something like:

_blah_provider = provider(fields=['transitive_deps'])

def _blah_library_impl(ctx):
    ...

    trans_deps = []
    for dep in ctx.attr.deps:
        trans_deps += dep[_blah_provider].trans_deps

    return _blah_provider(trans_deps=trans_deps)

blah_library = rule(impl=_blah_library_impl, ...)

I tried adding the following to _blah_library_impl, but it didn't work because apparently native.filegroup can't be called within a rule's implementation ("filegroup() cannot be called during the analysis phase"):

def _blah_library_impl(ctx):
    ...

    trans_deps = []
    for dep in ctx.attr.deps:
        trans_deps += dep[_blah_provider].trans_deps

    native.filegroup(
        name = ctx.attr.name + "__trans_deps",
        srcs = trans_deps,
    )

    return _blah_provider(trans_deps=trans_deps)

Solution

  • I ended up making my own special filegroup-like rule, as discussed in the comments under @Laszlo's answer. Here's the raw code in case it's a useful starting point for anyone:

    def _whl_deps_filegroup_impl(ctx):
        input_wheels = ctx.attr.src[_PyZProvider].transitive_wheels
        output_wheels = []
        for wheel in input_wheels:
            file_name = wheel.basename
            output_wheel = ctx.actions.declare_file(file_name)
            # TODO(josh): Use symlinks instead of copying. Couldn't figure out how
            # to do this due to issues with constructing absolute paths...
            ctx.actions.run(
                outputs=[output_wheel],
                inputs=[wheel],
                arguments=[wheel.path, output_wheel.path],
                executable="cp",
                mnemonic="CopyWheel")
            output_wheels.append(output_wheel)
    
        return [DefaultInfo(files=depset(output_wheels))]
    
    whl_deps_filegroup = rule(
        _whl_deps_filegroup_impl,
        attrs = {
            "src": attr.label(),
        },
    )