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 sectionStarlark
Language code, put in the nameWORKSPACE
In 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 useload
Directive to load and extract new features to be able to use this feature in Bazel files. We called twiceload
Directive, 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/project
becomecom_github_user_project
。io_bazel_rules_go
Since 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 throughBUILD
The file is completed. We can think of Go as any other language and use rules that follow the same structure:go_binary
、go_library
andgo_test
. I've prepared a minimization example on my Github:/HappyCerber…. You will notice that we need to import fromio_bazel_rules_go
These rules are loaded in the code base to make them inBUILD
Available 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 aWORKSPACE
File and fromGazelle
Copy 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 mainBUILD
In the file:
load("@bazel_gazelle//:", "gazelle") # gazelle:prefix /aler9/rtsp-simple-server gazelle(name = "gazelle")
gazelle:prefix
The 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 itBUILD
Generate files for our project.
bazel run //:gazelle
After that, we shouldBUILD
Automatically 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_macro
Parameters are optional):
bazel run //:gazelle -- update-repos -from_file= -to_macro=%go_dependencies
This command will generate a file namedNew file (if we are in
WORKSPACE
Used inrepository_macro
The command has been defined, so we omit thisto_macro
directive), 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_tools
Being incorrectly inferred as a dependency (bydelete it to fix this problem). You can branch in my project:/HappyCerber…See above
rtsp-simple-server
The 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_TMPDIR
Instead 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!