尝试编写自定义规则

I'm trying to write a custom rule for gqlgen. The idea is to run it to generate Go code from a GraphQL schema.

My intended usage is:

gqlgen(
    name = "gql-gen-foo",
    schemas = ["schemas/schema.graphql"],
    visibility = ["//visibility:public"],
)

"name" is the name of the rule, on which I want other rules to depend; "schemas" is the set of input files.

So far I have:

load(
    "@io_bazel_rules_go//go:def.bzl",
    _go_context = "go_context",
    _go_rule = "go_rule",
)

def _gqlgen_impl(ctx):
    go = _go_context(ctx)
    args = ["run github.com/99designs/gqlgen --config"] + [ctx.attr.config]
    ctx.actions.run(
        inputs = ctx.attr.schemas,
        outputs = [ctx.actions.declare_file(ctx.attr.name)],
        arguments = args,
        progress_message = "Generating GraphQL models and runtime from %s" % ctx.attr.config,
        executable = go.go,
    )

_gqlgen = _go_rule(
    implementation = _gqlgen_impl,
    attrs = {
        "config": attr.string(
            default = "gqlgen.yml",
            doc = "The gqlgen filename",
        ),
        "schemas": attr.label_list(
            allow_files = [".graphql"],
            doc = "The schema file location",
        ),
    },
    executable = True,
)

def gqlgen(**kwargs):
    tags = kwargs.get("tags", [])
    if "manual" not in tags:
        tags.append("manual")
        kwargs["tags"] = tags
    _gqlgen(**kwargs)

My immediate issue is that Bazel complains that the schemas are not Files:

expected type 'File' for 'inputs' element but got type 'Target' instead

What's the right approach to specify the input files?

Is this the right approach to generate a rule that executes a command?

Finally, is it okay to have the output file not exist in the filesystem, but rather be a label on which other rules can depend?

Instead of:

ctx.actions.run(
        inputs = ctx.attr.schemas,

Use:

ctx.actions.run(
        inputs = ctx.files.schemas,

Is this the right approach to generate a rule that executes a command?

This looks right, as long as gqlgen creates the file with the correct output name (outputs = [ctx.actions.declare_file(ctx.attr.name)]).

generated_go_file = ctx.actions.declare_file(ctx.attr.name + ".go")

# ..

ctx.actions.run(
    outputs = [generated_go_file],
    args = ["run", "...", "--output", generated_go_file.short_path],
    # ..
)

Finally, is it okay to have the output file not exist in the filesystem, but rather be a label on which other rules can depend?

The output file needs to be created, and as long as it's returned at the end of the rule implementation in a DefaultInfo provider, other rules will be able to depend on the file label (e.g. //my/package:foo-gqlgen.go).