Is it possible to choose one file from multiple files in "outs" of a genrule?

3,417 views
Skip to first unread message

zhao...@gmail.com

unread,
Aug 31, 2017, 3:32:13 AM8/31/17
to bazel-discuss
To wrap a library build by cmake, I tried different ways.

One idea is, a `genrule` triggers cmake to do all dirty work, and a `cc_library` makes a bazel target.

`genrule` creates different kinds of files altogether. Is it possible to cherrypick some of them in another rule?


Here is an approach:

```
cc_library(
    name = "foo",
    srcs = [
        f
        for f in ":cmake_build_foo"
        if f.endswith(".a")
    ],  # only ".a" files
    hdrs = [
        f
        for f in ":cmake_build_foo"
        if f.endswith(".h")
    ],  # only headers
    linkstatic = 1,
    deps = [
        ":cmake_build_foo",
    ],
)

genrule(
    name = "cmake_build_foo",
    outs = [
        "libfoo_static.a",
        "foo.h",
    ],
    cmd = "use cmake to build the whole library",
)

```

Thanks.

Brian Silverman

unread,
Aug 31, 2017, 6:22:53 PM8/31/17
to zhao...@gmail.com, bazel-discuss
Yes, there are various ways to do that, with different tradeoffs. The ones I think make sense:
  1. Put the output filenames into a separate variable and then filter that in the BUILD file. The main disadvantage I can think of is this doesn't work with globs. Something like this:
    _files = [
         "libfoo_static.a",
         "foo.h",
     ]

  1. cc_library(
        name = "foo",
  1.     srcs = [f for f in _files if f.endswith(".a")],
        hdrs = [f for f in files if f.endswith(".h")],
        ...

  1. )
    genrule(
        name = "cmake_build_foo",
  1.     outs = _files,
        cmd = ...
    )
    If you do this same pattern of filtering out .a files and .h files, it might make sense to put it all in a macro.
  2. Duplicate the filenames. This is generally only a good idea if there are just a few output files that are easy to keep track of. Something like this:

  1. cc_library(
        name = "foo",
  1.     srcs = ["libfoo_static.a"],
        hdrs = ["foo.h"],
        ...

  1. )
    genrule(
        name = "cmake_build_foo",
  1.     ...
    )
  2. Write a custom rule that filters filenames. This lets you work with globs (and any kind of rule which puts a set of files you want to filter in any provider, not just DefaultInfo.files), but it has a lot more pieces to deal with. Something like this:
    In filter_files.bzl:
    def _filter_hdrs_impl(ctx):
        return DefaultInfo(files=depset([f for f in ctx.files.srcs if f.endswith(".h")]))
    filter_hdrs = rule(
        implementation = _filter_hdrs_impl,
        attrs = {
            "srcs": ctx.label_list(allow_files=True, mandatory=True),
        },
    )

    def _filter_srcs_impl(ctx):
        return DefaultInfo(files=depset([f for f in ctx.files.srcs if f.endswith(".a")]))
    filter_srcs = rule(
        implementation = _filter_srcs_impl,
        attrs = {
            "srcs": ctx.label_list(allow_files=True, mandatory=True),
        },
    )

    In BUILD (probably should wrap a lot of this up in a macro in filter_files.bzl to keep it more contained):
    load("//:filter_files.bzl", "filter_hdrs", "filter_srcs")


  1. cc_library(
        name = "foo",
  1.     srcs = [":foo_srcs"],
        hdrs = [":foo_hdrs"],
        ...
    )

    filter_srcs(
        name = "foo_srcs",
        srcs = [":cmake_build_foo"],
    )
    filter_hdrs(
        name = "foo_hdrs",
        srcs = [":cmake_build_foo"],

  1. )

    genrule(
        name = "cmake_build_foo",
  1.     ...
    )

    Alternatively, you could have a single filter_all_outputs rule which returns the headers and static libraries in separate file groups. You'd then use multiple filegroup rules with filegroup.output_group to pull them apart, and put the labels of those filegroup rules in srcs/hdrs. I don't think it makes much difference in the end, although all the logic is contained in a single custom rule that way.

    filter_files.bzl could use some deduplicating if you want to write a lot of these filter rules. I've found reusing the same implementation function but passing information in via private string attrs (like which extension to filter out) a useful technique in similar situations.
Also note that you don't want ":cmake_build_foo" in the cc_library's deps. deps is for things which get linked in, aka other cc_library rules (and similarly for other languages), not plain files.

Another relevant tidbit: when you put a rule's label into a label/label_list attr which is looking for files, it (mostly) uses DefaultInfo.files. You can also write the name of one of those outputs directly in another rule (or on the command line) if that file is an output of the rule. The distinction can be a bit confusing because many common rules (such as genrule) have the same set of files both places, but this is where it starts mattering.

--
You received this message because you are subscribed to the Google Groups "bazel-discuss" group.
To unsubscribe from this group and stop receiving emails from it, send an email to bazel-discuss+unsubscribe@googlegroups.com.
To view this discussion on the web visit https://groups.google.com/d/msgid/bazel-discuss/251a9b2c-cf5a-4cde-a55d-803b76b46a53%40googlegroups.com.
For more options, visit https://groups.google.com/d/optout.

Reply all
Reply to author
Forward
0 new messages