Search code examples
node.jsbuildbazel

Building bazel rule that depends on another rule


I'm trying to build a bazel rule for codegen'ing GraphQL types, using the graphql-codegen library.

The implementation of the gqlgen rule is straightforward:

load("@bazel_skylib//lib:paths.bzl", "paths")
load("@build_bazel_rules_nodejs//:defs.bzl", "nodejs_binary")

_SCHEMA = """
  {types}:
    schema: {schema}
    plugins:
      - "typescript"
  {meta}:
    schema: {schema}
    plugins:
      - "introspection"
"""

_CONFIG = """
overwrite: true
generates:
  {instructions}
"""

def _gqlgen_impl(ctx):
    config_file = ctx.actions.declare_file(ctx.label.name + ".yml")

    out_files = []
    instructions = []
    for s in ctx.files.srcs:
        name = paths.basename(s.path)[:-len(".graphql")]
        types = ctx.actions.declare_file("types/{}.ts".format(name))
        meta = ctx.actions.declare_file("meta/{}.json".format(name))
        out_files.append(types)
        out_files.append(meta)
        instructions.append(_SCHEMA.format(schema = s.path, types = types.path, meta = meta.path))
    instructions = "\n".join(instructions)

    ctx.actions.write(
        content = _CONFIG.format(instructions = instructions),
        output = config_file,
    )

    ctx.actions.run_shell(
        tools = [ctx.executable._codegen],
        inputs = depset(ctx.files.srcs + [config_file]),
        outputs = out_files,
        command = "{gqlgen} --config {cfg}".format(
            gqlgen = ctx.executable._codegen.path,
            cfg = config_file.path,
        ),
    )

The input is a set of *.graphql files and the output is a corresponding set of *.json and *.ts files.

I'm using the nodejs rules, and since graphql-codegen is a node module, I'm declaring a rule for it in the gqlgen rule:

def gqlgen(name, **kwargs):
    nodejs_binary(
        name = "gqlgen",
        data = ["@npm//:node_modules"],
        entry_point = "@npm//:node_modules/@graphql-codegen/cli/bin.js",
        install_source_map_support = False,
        visibility = ["//visibility:public"],
    )

    _gqlgen(
        name = name,
        **kwargs
    )

My issue lies in tying these two things together. I have the following:

_gqlgen = rule(
    implementation = _gqlgen_impl,
    attrs = {
        "srcs": attr.label_list(
            allow_files = [".graphql"],
        ),
        "_codegen": attr.label(
            cfg = "host",
            default = "//schemas:gqlgen",
            executable = True,
        ),
    },
)

Note, however, that for the _codegen attribute I'm specifying the executable as //schemas:gqlgen, which is wrong, since I should be able to use the gqlgen rule from any package.

Is there a way to refer to the nodejs_binary from the call to rule()?


Solution

  • To answer my question, I can simply do the following:

    _gqlgen = rule(
        implementation = _gqlgen_impl,
        attrs = {
            "srcs": attr.label_list(
                allow_files = [".graphql"],
            ),
            "_codegen": attr.label(
                cfg = "host",
                executable = True,
            ),
        },
    )
    
    def gqlgen(name, **kwargs):
        nodejs_binary(
            name = "gqlgen",
            data = ["@npm//:node_modules"],
            entry_point = "@npm//:node_modules/@graphql-codegen/cli/bin.js",
            install_source_map_support = False,
            visibility = ["//visibility:public"],
        )
    
        _gqlgen(
            name = name,
            _codegen = "gqlgen",
            **kwargs
        )