SoFunction
Updated on 2025-03-02

Detailed explanation of how to build a Golang program using Bazel

Building a Golang program using Bazel

In this short article, we will cover how to use Golang with the Bazel build system.

Specifically, we will discuss three scenarios:

  • Start a Golang project from scratch;
  • Convert an existing Golang project to a Bazel build;
  • and bringing a third-party Golang project to your Bazel build system.

Start a Golang project from scratch

Let's start with the basics of using Go with Bazel.

To do this, we need to/bazelbuild/…Get the official build rules for Go language.

In the configuration section, you will find: We need to include the following sectionStarlarkLanguage code, put in the nameWORKSPACEIn the configuration file:

load("@bazel_tools//tools/build_defs/repo:", "http_archive")
http_archive(
    name = "io_bazel_rules_go",
    sha256 = "8e968b5fcea1d2d64071872b12737bbb5514524ee5f0a4f54f5920266c261acb",
    urls = [
        "//bazelbuild/rules_go/releases/download/v0.28.0/rules_go-v0.28.",
        "/bazelbuild/rules_go/releases/download/v0.28.0/rules_go-v0.28.",
    ],
)
load("@io_bazel_rules_go//go:", "go_register_toolchains", "go_rules_dependencies")
go_rules_dependencies()
go_register_toolchains(version = "1.16.5")

Let's take a step-by-step look at what this code does.

First, we useloadDirective to load and extract new features to be able to use this feature in Bazel files. We called twiceloadDirective, the first time is to import the ability to download HTTP code bases, and the second time is to load Go-specific commands from the code base you just downloaded.

For the import itself, we need to provide an import name. The usual naming scheme is: reverse domain name, followed by namespace and project name; and/and.All converted to underscore_. For example:/user/projectbecomecom_github_user_projectio_bazel_rules_goSince this project is part of Bazel's official project, it usesInstead

If you are not familiar with Bazel, then you need to understand that the actual build configuration is throughBUILDThe file is completed. We can think of Go as any other language and use rules that follow the same structure:go_binarygo_libraryandgo_test. I've prepared a minimization example on my Github:/HappyCerber…. You will notice that we need to import fromio_bazel_rules_goThese rules are loaded in the code base to make them inBUILDAvailable in the file.

Convert an existing project to a Bazel build

Now we know that it is easy to start fresh from scratch. But what if you already have a Golang project and need to convert it to a build using Bazel? To do this, we need to use another tool provided in the official Bazel projectGazelle ( /bazelbuild/… )。

For demonstration, I will use a third-party project (/aler9/rtsp-…), I am currently revising the project for the upcoming system design course.

First, we need to create aWORKSPACEFile and fromGazelleCopy and paste the code in the settings section of the code base.

load("@bazel_tools//tools/build_defs/repo:", "http_archive")
http_archive(
    name = "io_bazel_rules_go",
    sha256 = "8e968b5fcea1d2d64071872b12737bbb5514524ee5f0a4f54f5920266c261acb",
    urls = [
        "//bazelbuild/rules_go/releases/download/v0.28.0/rules_go-v0.28.",
        "/bazelbuild/rules_go/releases/download/v0.28.0/rules_go-v0.28.",
    ],
)
http_archive(
    name = "bazel_gazelle",
    sha256 = "62ca106be173579c0a167deb23358fdfe71ffa1e4cfdddf5582af26520f1c66f",
    urls = [
        "//bazelbuild/bazel-gazelle/releases/download/v0.23.0/bazel-gazelle-v0.23.",
        "/bazelbuild/bazel-gazelle/releases/download/v0.23.0/bazel-gazelle-v0.23.",
    ],
)
load("@io_bazel_rules_go//go:", "go_register_toolchains", "go_rules_dependencies")
load("@bazel_gazelle//:", "gazelle_dependencies")
go_rules_dependencies()
go_register_toolchains(version = "1.16.5")
gazelle_dependencies()

You will notice that the above code also imports the rules mentioned in the previous section.

Now, to actually run Gazelle, we need to add the following code to our mainBUILDIn the file:

load("@bazel_gazelle//:", "gazelle")
# gazelle:prefix /aler9/rtsp-simple-server 
gazelle(name = "gazelle")

gazelle:prefixThe path after the comment is the Go import path used by the entire project. For example,There are imports of the following packages:

import (
    "os"
    "/aler9/rtsp-simple-server/internal/core"
)

At this point, we can finally really run Gazelle and make itBUILDGenerate files for our project.

bazel run //:gazelle

After that, we shouldBUILDAutomatically generate all files for the project:

git status
On branch main
Your branch is up to date with 'origin/main'.
Untracked files:
  (use "git add <file>..." to include in what will be committed)
 BUILD
 WORKSPACE
 bazel-bin
 bazel-out
 bazel-test
 bazel-testlogs
 internal/aac/
 internal/conf/
 internal/confenv/
 internal/confwatcher/
 internal/core/
 internal/externalcmd/
 internal/h264/
 internal/hls/
 internal/logger/
 internal/rlimit/
 internal/rtcpsenderset/
 internal/rtmp/
nothing added to commit but untracked files present (use "git add" to track)

However, if you trybazel build //...Command build the project, you will actually see a lot of errors about undefined code bases. This is because we are still missing the definition of project dependencies. However, Gazelle can even do this for us (to_macroParameters are optional):

bazel run //:gazelle -- update-repos -from_file= -to_macro=%go_dependencies

This command will generate a file namedNew file (if we are inWORKSPACEUsed inrepository_macroThe command has been defined, so we omit thisto_macrodirective), load the file to import all code bases needed to build the project.

load("@bazel_gazelle//:", "go_repository")
def go_dependencies():
    go_repository(
        name = "com_github_alecthomas_template",
        importpath = "/alecthomas/template",
        sum = "h1:JYp7IbQjafoB+tBA3gMyHYHrpOtNuDiK/uB5uXxq5wM=",
        version = "v0.0.0-20190718012654-fb15b899a751",
    )
    go_repository(
        name = "com_github_alecthomas_units",
        importpath = "/alecthomas/units",
        sum = "h1:UQZhZ2O0vMHr2cI+DC1Mbh0TJxzA3RcLoMsFw+aXw7E=",
        version = "v0.0.0-20190924025748-f65c72e2690d",
    )
    go_repository(
        name = "com_github_aler9_gortsplib",
        importpath = "/aler9/gortsplib",
        sum = "h1:Bf0hzdN1jUWsb5Mzezq1pd18EIBeKXxk5clIpHZJ1Lk=",
        version = "v0.0.0-20210724151831-dae5a1f04033",
    )
    go_repository(
...

In this code base, I actually had a small problem. The build is still failing because it importsorg_golang_x_toolsBeing incorrectly inferred as a dependency (bydelete it to fix this problem). You can branch in my project:/HappyCerber…See abovertsp-simple-serverThe final result.

You can continue to use Gazelle to manage dependencies later, which is how you can introduce your code base to a Bazel-based project without actually converting it:

bazel run //:gazelle -- update-repos /some/repo

Seal tests

The last problem you may encounter is seal testing. If you see a test failing due to access denied, file not found, or operation not allowed to fail, it is because Bazel enforces sealing tests. This means that each test must be completely independent and independent of any other test.

For unit tests, any file needs to be provided as a dependency for the test and accessed through the run file mechanism (/bazelbuild/…)。

The temporary directory for each test is provided in the environment variable, which you will useTEST_TMPDIRInstead of traditional()function.

Sealing integration and system testing require careful design from the start, so switching existing such tests can be tricky. Unfortunately, I don't have universal advice here.

While converting your tests to seal tests can be annoying, it's a worthwhile effort and will bring you better test repeatability and less brittleness.

Original text Translated from:Golang With Bazel

The above is a detailed explanation of how to use Bazel to build Golang programs. For more information about Bazel to build Golang programs, please follow my other related articles!