Search code examples
bazelbazel-aspect

Bazel- How to get all transitive sources of a target


I have a need to write a rule which zips up all transitive config files (*.foo) of an executable (can be custom rule, java_binary and docker container_image).
The config files can appear on the srcs attribute of any attribute of the executable (tars, deps, runtime_deps, etc)

This sounds like it should be rather easy to do with an aspect attached to my rule but I lost my way between the various examples.


Solution

  • sources_aspect.bzl:

    SourceFiles = provider(
        fields = {
            "transitive_source_files": "list of transitive source files of a target",
        },
    )
    
    #add 'resources' ? if so _accumulate_transitive_config_files needs to check for dep in deps if ConfigFiles in dep
    SourceAttr = ["tars", "deps", "runtime_deps", "exports"]
    
    def _accumulate_transitive_source_files(accumulated, deps):
        return depset(
            transitive = [dep[SourceFiles].transitive_source_files for dep in deps] + [accumulated],
        )
    
    def _collect_current_source_files(srcs):
        return [file for src in srcs for file in src.files.to_list()]
    
    def _collect_source_files_aspect_impl(target, ctx):
        current_source_files = []
        if hasattr(ctx.rule.attr, "srcs"):
            current_source_files = _collect_current_source_files(ctx.rule.attr.srcs)
        if hasattr(ctx.rule.attr, "resources"):
            current_source_files = current_source_files + _collect_current_source_files(ctx.rule.attr.resources)
    
        accumulated_source_files = depset(current_source_files)
        for attr in SourceAttr:
            if hasattr(ctx.rule.attr, attr):
                accumulated_source_files = _accumulate_transitive_source_files(accumulated_source_files, getattr(ctx.rule.attr, attr))
    
        return [SourceFiles(transitive_source_files = accumulated_source_files)]
    
    collect_source_files_aspect = aspect(
        implementation = _collect_source_files_aspect_impl,
        attr_aspects = SourceAttr,
    )
    

    sources.bzl

    load("//sources/src/main:sources_aspect.bzl", "SourceFiles", "collect_source_files_aspect")
    
    def _owner_to_bazel_file(fileLabel):
        workspace = fileLabel.workspace_root
        package = fileLabel.package
        if 0 < len(workspace):
            workspace = workspace + "/"
        if 0 < len(package):
            package = package + "/"
        return workspace + package + "BUILD.bazel"
    
    def _collect_source_files_rule_impl(ctx):
        metadata = [ctx.attr.group_id, ctx.attr.artifact_id]
        paths = sorted([f.path for f in ctx.attr.main_artifact_name[SourceFiles].transitive_source_files.to_list()])
        owners = sorted(depset([_owner_to_bazel_file(f.owner) for f in ctx.attr.main_artifact_name[SourceFiles].transitive_source_files.to_list()] + [_owner_to_bazel_file(ctx.label)]).to_list())
    
        ctx.actions.write(ctx.outputs.sources, "\n".join(metadata + paths + owners))
        ctx.actions.write(ctx.outputs.source_files, "{\"groupId\": \"%s\", \"artifactId\": \"%s\", \"sources\": %s, \"buildFiles\": %s}" % (ctx.attr.group_id, ctx.attr.artifact_id, paths, owners))
        return DefaultInfo(
            runfiles = ctx.runfiles(files = [ctx.outputs.sources, ctx.outputs.source_files]),
        )
    
    source_files = rule(
        implementation = _collect_source_files_rule_impl,
        attrs = {
            "main_artifact_name": attr.label(aspects = [collect_source_files_aspect]),
            "group_id": attr.string(mandatory = True),
            "artifact_id": attr.string(mandatory = True),
        },
        outputs = {"sources": "%{name}.sources.txt", "source_files": "%{name}.sources.json"},
    )