> ## 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.

# Bazel: Avoiding eager fetches

> Learn how to identify and prevent eager fetches in Bazel builds, optimizing dependency management for efficient development workflows

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="Bazel: Avoiding eager fetches" date="2022-03-30" authors="Alex Eagle" tags="Bazel, Performance">
  <img noZoom src="https://mintcdn.com/aspectbuild/x1L7Iep716jCyJVo/images/blog/hashnode/hn-ba2d1c6e40.jpeg?fit=max&auto=format&n=x1L7Iep716jCyJVo&q=85&s=3c44ad139b20e6f98f33d33ba3911cd8" alt="" className="blog-post-cover" width="1600" height="1068" data-path="images/blog/hashnode/hn-ba2d1c6e40.jpeg" />

  Bazel manages your dependencies, and fetches them to a users machine when they are needed for a build. That's great, as it ensures all developers on the project have the same dependencies installed without having to think about it. When working well, these fetches are lazy and fine-grained: users only download what's needed for the specific targets they requested to build or test.

  However it's easy to de-optimize by introducing an "eager fetch". This is when Bazel downloads some dependencies which aren't actually needed for the current build, just because they are referenced during the analysis phase when the BUILD files are read. These fetches are only a problem on the first build, since the resulting "external repositories" are reused by Bazel for subsequent re-builds. However they are still annoying that first time, and if the repository gets invalidated (maybe because the user switches branches to one that's rebased before some change to the dependency listing) then they have to wait again. They are extra-annoying when the "fetch" includes some subsequent slow install steps, like compiling a program that was just downloaded.

  > Bazel has a ["repository cache"](https://bazel.build/docs/build#repository-cache) but this is often misunderstood. It does *not* cache the external repository that Bazel installed on the disk - rather it only caches certain network fetches which had a sha256 sum or integrity hash and were fetched by the Bazel built-in downloader. Tools like `npm` and `pip` do their own fetches, so those aren't cached by Bazel (though they might be cached somewhere else on disk by those tools). Even if you avoid a network fetch, any computation performed to "install" those dependencies is never cached by Bazel and has to be re-done if the external repository is invalidated. If you change branches back to the original one, the invalidation is just as expensive; there's no re-use of the prior state in a X -> Y -> X sequence.

  This article explains how these eager fetches get triggered, how to remediate them and how to prevent regressions.

  ## WORKSPACE eager fetches

  These are the worst kind, because they happen for every single build regardless of the dependency graph or which targets the user requests. Bazel must evaluate the complete `WORKSPACE` file to understand what third-party dependencies exist for the build. Let's say the WORKSPACE file contains this content:

  ```python theme={null}
  load("@rules_python//python:pip.bzl", "pip_parse")

  pip_parse(
     name = "my_deps",
     requirements_lock = "//path/to:requirements_lock.txt",
  )

  load("@my_deps//:requirements.bzl", "install_deps")
  install_deps()
  ```

  The penultimate line loads from the `@my_deps` repository, which means that repository must be eagerly fetched. Whatever work happens in `pip_parse` will happen for every single build, even for developers who aren't doing anything Python-related. In this case, `pip_parse` does need to fetch metadata about Python dependencies, so this isn't free. Use the Bazel profile to help you determine whether an eager fetch is a problem in your builds.

  ## Mitigating

  In some cases, you can refactor the WORKSPACE to remove the fetch. One approach is to "vendor" - check in the result of the expensive computation rather than perform it on-the-fly, and add a test to the repo ensuring it stays up-to-date. That test will still need to fetch the external repository, but other builds won't.

  Continuing the example above, I added documentation for `pip_parse` showing how you could `load` the `requirements.bzl` file from within your repo: [https://github.com/bazelbuild/rules\\\_python/blob/main/docs/pip.md#vendoring-the-requirementsbzl-file](https://github.com/bazelbuild/rules\\_python/blob/main/docs/pip.md#vendoring-the-requirementsbzl-file) If you do this, there will no longer be a `load` statement from `@my_deps` in the WORKSPACE, which should fix the eager fetch.

  Another approach is to defer the work from a repository rule to an action that runs later in the BUILD graph. This generally requires changes to the rules you're using, so I won't try to give an example for end-users to follow.

  Again, you should first profile your build to understand which eager fetches are really a problem in practice. External repositories are locally cached by Bazel and shouldn't be invalidated often.

  ## BUILD eager fetches

  BUILD files also contain `load` statements, causing the external repository being loaded to be fetched. Unlike the WORKSPACE case above, the behavior depends on whether Bazel needs to analyze the BUILD file, which is the case if it is transitively referenced from targets the user requests to build or test.

  For example, in this BUILD file, we load from under the `@npm` repository:

  ```python theme={null}
  # Content of //pkg1:BUILD
  load("@npm//@bazel/typescript:index.bzl", "ts_project")

  package(default_visibility = ["//visibility:public"])

  ts_project(
      name = "a",
      srcs = glob(["*.ts"]),
      declaration = True,
      tsconfig = "//:tsconfig.json",
      deps = [
          "@npm//@types/node",
          "@npm//tslib",
      ],
  )

  filegroup(name = "b")
  ```

  As a result, if the user asks to build `a`, or *any target* in the `pkg1` package such as `b`, then the full fetch of `@npm` will be eager. This also happens if the user asks for a target which directly or indirectly load's *any target* in this package, such as `//pkg2:c` shown here:

  ```python theme={null}
  # Content of //pkg2:BUILD
  filegroup(name = "c", srcs = ["//pkg1:b"])
  ```

  Another example comes from using the `requirements` helper provided by rules\_python. If you use the suggested pattern

  ```python theme={null}
  load("@pip//:requirements.bzl", "requirement")
  py_library(
      name = "foo",
      ...
      deps = [
         requirement("requests"),
      ],
  )
  ```

  this also causes an eager-fetch of whatever is in `@pip` which might cause all Python dependencies to be downloaded!

  I added this warning to the [documentation for pip\_install](https://github.com/bazelbuild/rules_python/blob/main/docs/pip.md#pip_install):

  > Note that this convenience comes with a cost. Analysis of any BUILD file which loads the requirements helper in this way will cause an eager-fetch of all the pip dependencies, even if no python targets are requested to be built. In a multi-language repo, this may cause developers to fetch dependencies they don't need, so consider using the long form for dependencies if this happens.

  ## Mitigating

  For BUILD fetches, the shape of the BUILD file graph matters. As with any programming language, it's a design smell when your imports come from many different unrelated places. Try to avoid BUILD files that load from external repositories and *also* contain other targets which don't use those repositories.

  Another mitigation is to reduce the size of the external repository being fetched. In the npm example example, we fetched `@npm` which might have a large number of packages, and if it uses `npm_install` or `yarn_install` from `build_bazel_rules_nodejs`, then *all* of those packages had to be installed just to get the `@bazel/typescript` one actually needed by this build. You could have a separate `package.json` listing file with a small number of dependencies loaded by BUILD files, with another `npm_install` repository rule fetching a repo like `@npm_bazel_deps`. This would still be eager-fetched, but it would be much faster.

  In some cases the eager fetch is just for syntax sugar. In the rules\_python `requirement` example, we could have just used `@pypi__requests//:pkg` in the `deps`, with no `load` statement at all.

  You can file issues as well. This takes longer to resolve, but it's healthier for the ecosystem to report these problems. Often the maintainers of the ruleset you use don't know about the problem, because they only build targets that require fetching the repo and have a smaller project, so they just don't observe the negative effects. [https://github.com/bazelbuild/rules\\\_nodejs/issues/3193](https://github.com/bazelbuild/rules\\_nodejs/issues/3193) is the issue for the example above.

  ## Preventing regression

  Eager fetches are subtle, and easy to introduce into your Bazel build. You only notice them when the external repository is invalidated, and are only outraged enough to file issues when you're building something you feel is unrelated. "Why am I re-compiling a python interpreter from source just to run my Go test??"

  You can't write a test within Bazel to catch this condition (as far as I know). But you can formulate something outside of Bazel, and then run this as a separate step/pipeline on CI. There are a couple methods.

  The first is clumsy but reproduces exactly what you observe: write a test to the effect of "If I do a clean Bazel build of target `//:foo`, then look in the external directory, I should not observe the presence of the unrelated repo `bar`." See [https://github.com/aspect-build/bazel-examples/tree/main/eager-fetch](https://github.com/aspect-build/bazel-examples/tree/main/eager-fetch) for both the working example, and a sample `test_no_eager_fetch.sh` script which makes the assertion.

  The second is to use a `bazel query` to detect a path through the dependency graph from a target to an undesired external repo. We'll just query for all the packages that some targets depend on, then grep for those in a repo, and if the result is non-empty then we found an eager fetch:

  ```plaintext theme={null}
  bazel query --output=package 'let targets = set(//some:target //some/other:target) in buildfiles(deps($targets))' | uniq | sort | grep @slow_repo
  ```
</BlogPost>
