I stated earlier in another thread that I think that providers could remove about half of the issues I have with GN, and I got some interest in going into more detail, so I'm starting a new thread here.Coming from bazel, my biggest complaints about GN are:
- A user cannot think purely in terms of labels, they must think in terms of files
- Eg. Any time you have to use rebase_path
- Eg. Any time you have to rely on naming conventions
- Eg. Any time you have to distinguish between root_build_dir, root_gen_dir, etc.
- Modifying toolchains requires modifying GN itself (sort of - GN has "toolchains", but they're pretty weak by comparison - clang modules, for example, require cooperation from GN itself)
- There is no way to write good quality reliable rules.
- Build files feel like code, rather than config
A quick overview of bazel, for context (with some minor complexities simplified a little). Bazel has a few really fundamental concepts to it that work extremely well together:
- A label is a way of referring to a target. It can be absolute or relative. GN has this already (//foo/bar:baz and :baz).
- Every target has associated metadata in the form of providers
- Each different provider is a different topic of information (eg. CcInfo, RustInfo, etc.)
- They are basically just structs
- They usually contain files, but may contain arbitrary information
- A rule is a function that creates targets.
- It outputs providers
- It can consume providers from other rules
- It can declare files
- And if it does, it must declare an action which generates it
- An action is:
- A list of input files
- A list of output files
- A command-line to run
- DefaultInfo is a provider that almost every rule outputs. It contains:
- Files (set of files that are built whenever you run `bazel build //path/to:target`)
- Runfiles (set of files that are built whenever you want to run this target, either as `bazel run //path/to:target`, or in another build action)
Providers can solve or mitigate all of the problems I mentioned above. Consider the following rule:CcInfo = provider(fields = ["headers", "transitive_libs"]
CcToolchainInfo = provider(fields = ["ccflags", "ldflags"])
def cc_binary_impl(ctx):
deps = [dep[CcInfo] for dep in ctx.attr.deps]
toolchain = ctx.toolchain[CcToolchainInfo]
compiled = ctx.actions.declare_file(ctx.label.name + ".o")
ctx.actions.run(
inputs = [...] + [dep.headers for dep in deps],
outputs = [compiled],
args = ... + [dep.headers for dep in deps] + toolchain.ccflags
)
binary = ctx.actions.declare_file(ctx.label.name)
ctx.actions.run(
inputs = [...] + [dep.transitive_libs for dep in deps],
outputs = [binary],
args = ... + [dep.transitive_libs for dep in deps] + toolchain.ldflags
)
return DefaultInfo(files = [binary], runfiles = [dep.runfiles for dep in deps])cc_binary = rule(implementation = cc_binary_impl,attrs = {"srcs": attr.label_list(allow_files = True),"headers": attr.label_list(allow_files = True),"deps": attr.label_list(providers = [CcInfo]),"toolchain": attr.label(default = "//toolchains:cpp", providers = [CcToolchainInfo]),},)This rule:
- Depends on .o files declared by other rules without ever mentioning their file paths, or following any sort of naming conventions
- Has complete control over the toolchain (this isn't quite how bazel does it, but I've simplified things, and this would work), and complete control over how the command-line gets created
- Can depend on any rule that outputs CcInfo. This could be static_library, shared_library, prebuilt_shared_library, or any rule that you create
- Puts the complexities in the rule definition, allowing the build files to be trivial.
- Completely removes the need for complexities such as "public deps" or "public config". Instead, you can just add a field "transitive_deps", and then put your direct dependencies' transitive_deps into your CcInfo (this is performant via the use of a depset type).
This also allow you to create rules like:
- Alias - eg. foo is an alias to foo_linux on linux, and foo_windows on windows
- Filegroup - Group a bunch of sources together (which can be rules)
- Config-only rules
- Eg. I wrote rules_toolchains for bazel, which allows users to specify arbitrary toolchains via build files (example)
--Thanks, Matt.