> ## Documentation Index
> Fetch the complete documentation index at: https://site.aspect.build/llms.txt
> Use this file to discover all available pages before exploring further.

# What's better than a genrule?

> Learn why genrule is the best rule for your Bazel build process, simplifying command execution and enhancing developer experience

export const BlogPost = ({title, date, authors, tags, image, children}) => {
  const tagList = tags ? tags.split(", ").filter(Boolean) : [];
  const tagSlug = t => t.toLowerCase().replace(/&/g, "").replace(/\+/g, "").replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "");
  const formattedDate = date ? new Date(date + "T00:00:00").toLocaleDateString("en-US", {
    year: "numeric",
    month: "long",
    day: "numeric"
  }) : "";
  return <section className="w-full flex justify-center px-4 py-12 md:py-16">
      <div style={{
    maxWidth: "800px",
    width: "100%"
  }}>
        {image && (typeof image === "string" ? <img noZoom src={image} alt={title} className="w-full rounded-xl mb-8" style={{
    maxHeight: "400px",
    objectFit: "cover"
  }} /> : <div className="blog-post-hero-image">{image}</div>)}
        <h1 className="text-3xl md:text-4xl font-bold text-zinc-900 dark:text-white">
          {title}
        </h1>
        <div className="flex flex-wrap items-center gap-3 mt-4 text-sm text-zinc-500 dark:text-zinc-400">
          {authors && <span>{authors}</span>}
          {authors && formattedDate && <span>·</span>}
          {formattedDate && <span>{formattedDate}</span>}
        </div>
        {tagList.length > 0 && <div className="flex flex-wrap gap-2 mt-3">
            {tagList.map(tag => <a key={tag} href={"/blog/tags/" + tagSlug(tag)} className="px-2 py-0.5 rounded-full text-xs bg-zinc-100 dark:bg-zinc-800 text-zinc-600 dark:text-zinc-400 hover:bg-blue-100 dark:hover:bg-blue-900/40 hover:text-blue-700 dark:hover:text-blue-300 transition">
                {tag}
              </a>)}
          </div>}
        <hr className="my-8 border-zinc-200 dark:border-zinc-700" />
        <div className="prose dark:prose-invert max-w-none">{children}</div>
      </div>
    </section>;
};

export const MarketingPage = () => <div className="marketing-page-marker" style={{
  display: "none"
}} />;

export const Section = ({children, className = "", gray = false, dark = false, id}) => <section id={id} className={`w-full flex justify-center px-4 py-16 md:py-24 ${gray ? "bg-gray-50 dark:bg-zinc-900" : dark ? "bg-zinc-900 dark:bg-zinc-950" : ""} ${className}`}>
    <div className="w-full" style={{
  maxWidth: "1140px"
}}>
      {children}
    </div>
  </section>;

<MarketingPage />

<BlogPost title="What's better than a genrule?" date="2022-06-17" authors="Alex Eagle" tags="Bazel">
  <img noZoom src="https://mintcdn.com/aspectbuild/x1L7Iep716jCyJVo/images/blog/stock/blueprint.jpg?fit=max&auto=format&n=x1L7Iep716jCyJVo&q=85&s=9e80970619ab05f3ef6f0a8513dc4907" alt="" className="blog-post-cover" width="800" height="450" data-path="images/blog/stock/blueprint.jpg" />

  Bazel is pretty confusing. One confusion I've seen a lot is, when do I need to write a custom rule? When can I use a macro? When can I use a genrule?

  To make matters worse, Xooglers tend to have a biased answer to this question because of what they saw in google3. Because Google doesn't really use third-party tooling much, there was little usage of genrule to run tools. In a BazelCon talk I once said "we all know genrule is the bestrule" and the Bazel TL gave me a skeptical look.

  > While the Bazel user guide and user manual preach the benefits of giving Bazel full control over your build process by rewriting all build processes using Bazel-native rulesets (as Google reportedly does internally), this is an immense amount of work.
  > [https://www.stevenengelhardt.com/2020/10/21/practical-bazel-start-with-genrules/](https://www.stevenengelhardt.com/2020/10/21/practical-bazel-start-with-genrules/)

  Also the API is kinda lame, and Google hires "only the smartest engineers" so it's not considered a burden to tell someone they must learn Starlark and Bazel's obscure analysis-loading-execution phase semantics.

  In reality, genrule semantics are what you want.

  * Point to an existing binary
  * say what command line flags it requires
  * list inputs, outputs, and dependencies

  This should be doable entirely in a `BUILD` file by a regular developer who isn't interested in a half-day meander through build system internals.

  ## Yeah but genrule is not great?

  Sadly, the "canonical" genrule [https://bazel.build/reference/be/general#genrule](https://bazel.build/reference/be/general#genrule) is pretty lacking, and requires that you give it a bash one-liner or script.

  [`run_binary`](https://github.com/bazelbuild/bazel-skylib/blob/main/docs/run_binary_doc.md) from bazel-skylib is a little better since it supports cmd.exe on Windows and has a smaller sane API. [`run_binary`](https://github.com/aspect-build/bazel-lib/blob/main/docs/run_binary.md) from Aspect bazel-lib is even better: it can output directories, and supports custom progress messages, mnemonics (bazel's action tagging system) and execution requirements (hints about how to spawn).

  The best `genrule` ought to provide a bunch more features. We built one for rules\_js users [`js_run_binary`](https://github.com/bazel-contrib/bazel-lib/blob/main/lib/private/run_binary.bzl) which can do more things:

  * collect stdout or stderr as output files (great for stubborn tools that insist on putting outputs on stdio when Bazel requires files for everything)
  * intercept the exit code and write to an output file (Bazel will immediately fail if any action exits non-zero, even if the tool is just being cute and returning information via exit code when successful)
  * allow both output files and output directories
  * `chdir` to a different folder at the start of the action. Some tools expect users to `cd` into a folder containing some config file and run there.
  * throw away logspam when the action succeeds

  ## Generated Genrules

  Okay so if you have a great "genrule" API, the next logical step is for the package manager rules to get involved. Those rules read the metadata for third-party packages, and can see which ones provide developer tool "binaries".

  rules\_python, rules\_nodejs, and rules\_js all support auto-generated rules for these. For example if you just wanted to use `yamllint` in your BUILD file, you can do it like this:

  [https://github.com/bazelbuild/rules\_python/blob/main/examples/pip\_parse/BUILD#L53-L56](https://github.com/bazelbuild/rules_python/blob/main/examples/pip_parse/BUILD#L53-L56)

  ```python theme={null}
  load("@pypi//:requirements.bzl", "entry_point")

  alias(
      name = "yamllint",
      actual = entry_point("yamllint"),
  )
  ```

  This gives you a genrule-like API for calling the tool. Even better, there's no eager fetch: loading this BUILD file won't make Bazel download the yamllint wheel from pypi. (Same for rules\_js generated bins)

  `@pypi_yamllint//:rules_python_wheel_entry_point_yamllint` is what this alias points to, and that's a regular `py_binary` rule which could be used with the aspect\_bazel\_lib `run_binary` above, and you're already done.

  ## So, genrule, macro, or custom rule?

  So far everything I showed is just genrule. But it can get unwieldy for developers to directly call the CLI of third-party tooling from the BUILD file. It's a pretty nice pattern to wrap these generated `bin` entry points with a macro, which is just a preprocessor definition Bazel will resolve early (in the loading phase). When you run `bazel query`, you're looking at BUILD files after macros expand, so they're really just a bit of syntax sugar.

  > Be careful not to make a mess with macros - they are leaky abstractions. Read [https://docs.bazel.build/versions/main/skylark/bzl-style.html#macros](https://docs.bazel.build/versions/main/skylark/bzl-style.html#macros) before you write your first one.

  Now users BUILD files look just the way you'd wish they did. We still didn't write custom rules. If you've gotten this far, here's my main takeaway:

  ## You should rarely need to write a custom rule

  Here are some good reasons to write your own custom Bazel rule:

  * You need to interoperate with other rules using richer information than the built-in providers like `DefaultInfo` and `OutputGroupInfo` provide.
  * The underlying tool is too slow on a cold start (ahem JVM and Node applications), and everyone works around this with "watch mode", so you need to write a "Bazel persistent worker" binary and wrap that as a rule.
  * You want to write a `toolchain` that is platform-aware, to manage Bazel's fetching that tool or to cross-compile for the target platform.
  * The actions to run depend on what outputs a user requests. For example, you can run a quicker transpiler for TypeScript if the user only wants JavaScript outputs and not "declaration" interface files.
</BlogPost>
