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

# Preventing production code depending on experiments

> Learn how to manage experimental code in Bazel monorepos by restricting dependencies and ensuring faster iteration without risking production builds.

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="Preventing production code depending on experiments" date="2024-03-15" authors="Alex Eagle" tags="Bazel, Monorepo">
  <img noZoom src="https://mintcdn.com/aspectbuild/x1L7Iep716jCyJVo/images/blog/hashnode/hn-128f5e6ec9.jpeg?fit=max&auto=format&n=x1L7Iep716jCyJVo&q=85&s=df982f37e2715325a0cabd8f9f21f73c" alt="" className="blog-post-cover" width="1600" height="1065" data-path="images/blog/hashnode/hn-128f5e6ec9.jpeg" />

  At Google we had an `/experimental` folder in source control. This is a nice way to be able to check in code in a monorepo that you're just "spiking" on, with all the usual developer ergonomics and access to shared library code. There's also no expectation that you have to maintain code in this folder.

  These could go on an `experimental` feature branch. However, having this on the `main` branch lets you get CI feedback and easily point co-workers at your experiments, and collaborate with others on them, without having anyone forced to rebase the `experimental` branch on the latest `main` and deal with merge conflicts.

  You can go a step further and configure the code review and merging requirements to be relaxed for commits that only modify `experimental/` so that this folder has much faster iteration times, with correspondingly lower quality expectations.

  The danger of having this low-quality code in the `main` branch of a Bazel-built monorepo is that it's so easy for a production service to take a (transitive) dependency on it. This article presents a simple way to prevent that.

  ### Bazel can disallow dependency edges

  We'll build on top of a Bazel feature called `testonly`. Bazel enforces that production code cannot depend on tests. It also prints a good error message when a developer violates the policy. So our first step is to mark all our experimental code as `testonly` - that is, you're free to use that code for your testing, but it won't be allowed to depend on it from anything that's *not* marked with `testonly`.

  There's a convenient tool to machine-edit Bazel's BUILD files, `buildozer`. We'll use that to mechanically set all packages beneath `/experimental` to default their targets to be `testonly`. We'll also add a helpful comment, since developers might not be used to seeing this configuration.

  ```bash theme={null}
  # Download buildozer if needed: https://github.com/bazelbuild/buildtools/releases/
  buildozer 'set default_testonly True' //experimental/...:__pkg__
  buildozer 'comment code\ in\ experimental\ may\ only\ be\ used\ for\ testing' //experimental/...:__pkg__
  ```

  As a result, all BUILD files under experimental will now contain

  ```python theme={null}
  # code in experimental may only be used for testing
  package(default_testonly = True)
  ```

  Now we can check what happens when production code tries to add a `dep` on something in this folder:

  ```bash theme={null}
  $ bazel build --nobuild //myservice/...
  ERROR: proj/myservice/BUILD.bazel:3:18:
   in js_binary rule server: 
  non-test target '//myservice:server' depends on testonly target '//experimental/subdir:proto_test' 
  and doesn't have testonly attribute set
  ```

  ### Making sure it stays this way

  That's a good start, but our setup is brittle for a couple reasons:

  * we set the default\_testonly property for each package, but individual targets can override that with `testonly=False`

  * new packages can be introduced under `/experimental` and the authors won't know to apply this again

  As a fix, we should setup a CI task. It simply needs to query for any non-`testonly` targets under `/experimental` like so:

  `bazel query 'attr(testonly, 0, //experimental/...)'`

  If there's any stdout from this command, then we report that as a failure in CI.

  Also, let's give developers an easy way to repair a red build if they get one. We can just print those same buildozer commands we used above - they are idempotent and safe to re-run on all the packages to update newly-added `BUILD` files.
</BlogPost>
