Import gazelle

This patch imports gazelle as a linter. It automatically generates
BUILD file entries for Go code and at the same time keeps BUILD files
formatted.

The `tools/lint:run-ci` target is set up to automatically add new Go
repositories as well.

I added a tool at `//tools/go:mirror_go_repos` that needs to be run
before anyone can merge code that uses third-party Go libraries.

Change-Id: I1fbf6761439d45893f5be88d294ccc3c567840ca
Signed-off-by: Philipp Schrader <philipp.schrader@gmail.com>
diff --git a/BUILD b/BUILD
index 4509a20..62ee0d1 100644
--- a/BUILD
+++ b/BUILD
@@ -1 +1,15 @@
+load("@bazel_gazelle//:def.bzl", "gazelle")
+
 exports_files(["tsconfig.json"])
+
+# gazelle:prefix github.com/frc971/971-Robot-Code
+# gazelle:build_file_name BUILD
+# gazelle:proto disable
+# gazelle:go_generate_proto false
+# gazelle:exclude third_party
+# gazelle:exclude external
+
+gazelle(
+    name = "gazelle",
+    visibility = ["//tools/lint:__subpackages__"],
+)
diff --git a/WORKSPACE b/WORKSPACE
index a464a6d..f4e3191 100644
--- a/WORKSPACE
+++ b/WORKSPACE
@@ -129,6 +129,10 @@
     "//tools/go:noop_go_toolchain",
 )
 
+load("//tools/ci:repo_defs.bzl", "ci_configure")
+
+ci_configure(name = "ci_configure")
+
 http_archive(
     name = "platforms",
     sha256 = "3c4057c53b64dd3f2c753e0a80bbb6ccb29fb437910200c911dd51454baf619b",
@@ -884,8 +888,28 @@
     ],
 )
 
+http_archive(
+    name = "bazel_gazelle",
+    sha256 = "de69a09dc70417580aabf20a28619bb3ef60d038470c7cf8442fafcf627c21cb",
+    urls = [
+        "https://mirror.bazel.build/github.com/bazelbuild/bazel-gazelle/releases/download/v0.24.0/bazel-gazelle-v0.24.0.tar.gz",
+        "https://github.com/bazelbuild/bazel-gazelle/releases/download/v0.24.0/bazel-gazelle-v0.24.0.tar.gz",
+    ],
+)
+
 load("@io_bazel_rules_go//go:deps.bzl", "go_register_toolchains", "go_rules_dependencies")
 
 go_rules_dependencies()
 
 go_register_toolchains(version = "1.17.1")
+
+load("@bazel_gazelle//:deps.bzl", "gazelle_dependencies")
+load("//:go_deps.bzl", "go_dependencies")
+load("//tools/go:mirrored_go_deps.bzl", "mirrored_go_dependencies")
+
+mirrored_go_dependencies()
+
+# gazelle:repository_macro go_deps.bzl%go_dependencies
+go_dependencies()
+
+gazelle_dependencies()
diff --git a/build_tests/BUILD b/build_tests/BUILD
index 6563297..5d9c3bf 100644
--- a/build_tests/BUILD
+++ b/build_tests/BUILD
@@ -1,6 +1,6 @@
 load("@com_google_protobuf//:protobuf.bzl", "cc_proto_library")
 load("@com_github_google_flatbuffers//:build_defs.bzl", "flatbuffer_py_library")
-load("@io_bazel_rules_go//go:def.bzl", "go_binary")
+load("@io_bazel_rules_go//go:def.bzl", "go_binary", "go_library")
 
 cc_test(
     name = "gflags_build_test",
@@ -100,6 +100,15 @@
 
 go_binary(
     name = "hello_go",
-    srcs = ["hello.go"],
+    embed = [":build_tests_lib"],
     target_compatible_with = ["@platforms//cpu:x86_64"],
+    visibility = ["//visibility:public"],
+)
+
+go_library(
+    name = "build_tests_lib",
+    srcs = ["hello.go"],
+    importpath = "github.com/frc971/971-Robot-Code/build_tests",
+    target_compatible_with = ["@platforms//cpu:x86_64"],
+    visibility = ["//visibility:private"],
 )
diff --git a/go.mod b/go.mod
new file mode 100644
index 0000000..fa311ec
--- /dev/null
+++ b/go.mod
@@ -0,0 +1,20 @@
+module frc971.org/971-Robot-Code
+
+go 1.17
+
+require (
+	github.com/buildkite/go-buildkite v2.2.0+incompatible
+	github.com/golang/protobuf v1.5.2
+	github.com/google/flatbuffers v2.0.5+incompatible
+	google.golang.org/grpc v1.43.0
+)
+
+require (
+	github.com/cenkalti/backoff v2.2.1+incompatible // indirect
+	github.com/google/go-querystring v1.1.0 // indirect
+	golang.org/x/net v0.0.0-20200822124328-c89045814202 // indirect
+	golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd // indirect
+	golang.org/x/text v0.3.0 // indirect
+	google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013 // indirect
+	google.golang.org/protobuf v1.26.0 // indirect
+)
diff --git a/go.sum b/go.sum
new file mode 100644
index 0000000..0db981f
--- /dev/null
+++ b/go.sum
@@ -0,0 +1,129 @@
+cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
+cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
+github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
+github.com/antihax/optional v1.0.0/go.mod h1:uupD/76wgC+ih3iEmQUL+0Ugr19nfwCT1kdvxnR2qWY=
+github.com/buildkite/go-buildkite v2.2.0+incompatible h1:yEjSu1axFC88x4dbufhgMDsEnJztPWlLiZzEvzJggXc=
+github.com/buildkite/go-buildkite v2.2.0+incompatible/go.mod h1:WTV0aX5KnQ9ofsKMg2CLUBLJNsQ0RwOEKPhrXXZWPcE=
+github.com/cenkalti/backoff v2.2.1+incompatible h1:tNowT99t7UNflLxfYYSlKYsBpXdEet03Pg2g16Swow4=
+github.com/cenkalti/backoff v2.2.1+incompatible/go.mod h1:90ReRw6GdpyfrHakVjL/QHaoyV4aDUVVkXQJJJ3NXXM=
+github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
+github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
+github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
+github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc=
+github.com/cncf/udpa/go v0.0.0-20201120205902-5459f2c99403/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk=
+github.com/cncf/udpa/go v0.0.0-20210930031921-04548b0d99d4/go.mod h1:6pvJx4me5XPnfI9Z40ddWsdw2W/uZgQLFXToKeRcDiI=
+github.com/cncf/xds/go v0.0.0-20210805033703-aa0b78936158/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs=
+github.com/cncf/xds/go v0.0.0-20210922020428-25de7278fc84/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs=
+github.com/cncf/xds/go v0.0.0-20211011173535-cb28da3451f1/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs=
+github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
+github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
+github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
+github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98=
+github.com/envoyproxy/go-control-plane v0.9.9-0.20201210154907-fd9021fe5dad/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk=
+github.com/envoyproxy/go-control-plane v0.9.10-0.20210907150352-cf90f659a021/go.mod h1:AFq3mo9L8Lqqiid3OhADV3RfLJnjiw63cSpi+fDTRC0=
+github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=
+github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=
+github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
+github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
+github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
+github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
+github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw=
+github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8=
+github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA=
+github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs=
+github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w=
+github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0=
+github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8=
+github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
+github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
+github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk=
+github.com/golang/protobuf v1.5.2 h1:ROPKBNFfQgOUMifHyP+KYbvpjbdoFNs+aK7DXlji0Tw=
+github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY=
+github.com/google/flatbuffers v2.0.5+incompatible h1:ANsW0idDAXIY+mNHzIHxWRfabV2x5LUEEIIWcwsYgB8=
+github.com/google/flatbuffers v2.0.5+incompatible/go.mod h1:1AeVuKshWv4vARoZatz6mlQ0JxURH0Kv5+zNeJKJCa8=
+github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
+github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
+github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
+github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
+github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
+github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
+github.com/google/go-cmp v0.5.5 h1:Khx7svrCpmxxtHBq5j2mp/xVjsi8hQMfNLvJFAlrGgU=
+github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
+github.com/google/go-querystring v1.1.0 h1:AnCroh3fv4ZBgVIf1Iwtovgjaw/GiKJo8M8yD/fhyJ8=
+github.com/google/go-querystring v1.1.0/go.mod h1:Kcdr2DB4koayq7X8pmAG4sNG59So17icRSOU623lUBU=
+github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
+github.com/grpc-ecosystem/grpc-gateway v1.16.0/go.mod h1:BDjrQk3hbvj6Nolgz8mAMFbcEtjT1g+wF4CSlocrBnw=
+github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
+github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
+github.com/rogpeppe/fastuuid v1.2.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6LYCDYWNEvQ=
+github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
+github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=
+github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
+go.opentelemetry.io/proto/otlp v0.7.0/go.mod h1:PqfVotwruBrMGOCsRd/89rSnXhoiJIqeYNgFYFoEGnI=
+golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
+golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
+golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
+golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
+golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU=
+golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
+golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
+golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
+golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
+golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
+golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
+golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
+golang.org/x/net v0.0.0-20200822124328-c89045814202 h1:VvcQYSHwXgi7W+TpUR6A9g6Up98WAHf3f/ulnJ62IyA=
+golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
+golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
+golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
+golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
+golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
+golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
+golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
+golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
+golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
+golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd h1:xhmwyvizuTgC2qz7ZlMluP20uW+C3Rm0FD/WLDX8884=
+golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/text v0.3.0 h1:g61tztE5qeGQ89tm6NTjjM9VPIm088od1l6aSorWRWg=
+golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
+golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
+golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY=
+golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
+golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
+golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
+golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE=
+golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
+google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
+google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
+google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
+google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=
+google.golang.org/genproto v0.0.0-20200513103714-09dca8ec2884/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
+google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013 h1:+kGHl1aib/qcwaRi1CbqBZ1rk19r85MNUf8HaBghugY=
+google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo=
+google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
+google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg=
+google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY=
+google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
+google.golang.org/grpc v1.33.1/go.mod h1:fr5YgcSWrqhRRxogOsw7RzIpsmvOZ6IcH4kBYTpR3n0=
+google.golang.org/grpc v1.36.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU=
+google.golang.org/grpc v1.43.0 h1:Eeu7bZtDZ2DpRCsLhUlcrLnvYaMK1Gz86a+hMVvELmM=
+google.golang.org/grpc v1.43.0/go.mod h1:k+4IHHFw41K8+bbowsex27ge2rCb65oeWqe4jJ590SU=
+google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8=
+google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0=
+google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM=
+google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE=
+google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo=
+google.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
+google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
+google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
+google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c=
+google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
+google.golang.org/protobuf v1.26.0 h1:bxAC2xTBsZGibn2RTntX0oH50xLsqy1OxA9tTL3p/lk=
+google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
+gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
+gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
+gopkg.in/yaml.v2 v2.2.3/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
+gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
+honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
+honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
diff --git a/go_deps.bzl b/go_deps.bzl
new file mode 100644
index 0000000..5e215ef
--- /dev/null
+++ b/go_deps.bzl
@@ -0,0 +1,274 @@
+# This file is auto-generated. Do not edit.
+load("//tools/go:mirrored_go_deps.bzl", "maybe_override_go_dep")
+
+def go_dependencies():
+    maybe_override_go_dep(
+        name = "co_honnef_go_tools",
+        importpath = "honnef.co/go/tools",
+        sum = "h1:/hemPrYIhOhy8zYrNj+069zDB68us2sMGsfkFJO0iZs=",
+        version = "v0.0.0-20190523083050-ea95bdfd59fc",
+    )
+    maybe_override_go_dep(
+        name = "com_github_antihax_optional",
+        importpath = "github.com/antihax/optional",
+        sum = "h1:xK2lYat7ZLaVVcIuj82J8kIro4V6kDe0AUDFboUCwcg=",
+        version = "v1.0.0",
+    )
+    maybe_override_go_dep(
+        name = "com_github_buildkite_go_buildkite",
+        importpath = "github.com/buildkite/go-buildkite",
+        sum = "h1:yEjSu1axFC88x4dbufhgMDsEnJztPWlLiZzEvzJggXc=",
+        version = "v2.2.0+incompatible",
+    )
+    maybe_override_go_dep(
+        name = "com_github_burntsushi_toml",
+        importpath = "github.com/BurntSushi/toml",
+        sum = "h1:WXkYYl6Yr3qBf1K79EBnL4mak0OimBfB0XUf9Vl28OQ=",
+        version = "v0.3.1",
+    )
+    maybe_override_go_dep(
+        name = "com_github_cenkalti_backoff",
+        importpath = "github.com/cenkalti/backoff",
+        sum = "h1:tNowT99t7UNflLxfYYSlKYsBpXdEet03Pg2g16Swow4=",
+        version = "v2.2.1+incompatible",
+    )
+    maybe_override_go_dep(
+        name = "com_github_census_instrumentation_opencensus_proto",
+        importpath = "github.com/census-instrumentation/opencensus-proto",
+        sum = "h1:glEXhBS5PSLLv4IXzLA5yPRVX4bilULVyxxbrfOtDAk=",
+        version = "v0.2.1",
+    )
+    maybe_override_go_dep(
+        name = "com_github_cespare_xxhash_v2",
+        importpath = "github.com/cespare/xxhash/v2",
+        sum = "h1:6MnRN8NT7+YBpUIWxHtefFZOKTAPgGjpQSxqLNn0+qY=",
+        version = "v2.1.1",
+    )
+    maybe_override_go_dep(
+        name = "com_github_client9_misspell",
+        importpath = "github.com/client9/misspell",
+        sum = "h1:ta993UF76GwbvJcIo3Y68y/M3WxlpEHPWIGDkJYwzJI=",
+        version = "v0.3.4",
+    )
+    maybe_override_go_dep(
+        name = "com_github_cncf_udpa_go",
+        importpath = "github.com/cncf/udpa/go",
+        sum = "h1:hzAQntlaYRkVSFEfj9OTWlVV1H155FMD8BTKktLv0QI=",
+        version = "v0.0.0-20210930031921-04548b0d99d4",
+    )
+    maybe_override_go_dep(
+        name = "com_github_cncf_xds_go",
+        importpath = "github.com/cncf/xds/go",
+        sum = "h1:zH8ljVhhq7yC0MIeUL/IviMtY8hx2mK8cN9wEYb8ggw=",
+        version = "v0.0.0-20211011173535-cb28da3451f1",
+    )
+    maybe_override_go_dep(
+        name = "com_github_davecgh_go_spew",
+        importpath = "github.com/davecgh/go-spew",
+        sum = "h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8=",
+        version = "v1.1.0",
+    )
+    maybe_override_go_dep(
+        name = "com_github_envoyproxy_go_control_plane",
+        importpath = "github.com/envoyproxy/go-control-plane",
+        sum = "h1:fP+fF0up6oPY49OrjPrhIJ8yQfdIM85NXMLkMg1EXVs=",
+        version = "v0.9.10-0.20210907150352-cf90f659a021",
+    )
+    maybe_override_go_dep(
+        name = "com_github_envoyproxy_protoc_gen_validate",
+        importpath = "github.com/envoyproxy/protoc-gen-validate",
+        sum = "h1:EQciDnbrYxy13PgWoY8AqoxGiPrpgBZ1R8UNe3ddc+A=",
+        version = "v0.1.0",
+    )
+    maybe_override_go_dep(
+        name = "com_github_ghodss_yaml",
+        importpath = "github.com/ghodss/yaml",
+        sum = "h1:wQHKEahhL6wmXdzwWG11gIVCkOv05bNOh+Rxn0yngAk=",
+        version = "v1.0.0",
+    )
+    maybe_override_go_dep(
+        name = "com_github_golang_glog",
+        importpath = "github.com/golang/glog",
+        sum = "h1:VKtxabqXZkF25pY9ekfRL6a582T4P37/31XEstQ5p58=",
+        version = "v0.0.0-20160126235308-23def4e6c14b",
+    )
+    maybe_override_go_dep(
+        name = "com_github_golang_mock",
+        importpath = "github.com/golang/mock",
+        sum = "h1:G5FRp8JnTd7RQH5kemVNlMeyXQAztQ3mOWV95KxsXH8=",
+        version = "v1.1.1",
+    )
+    maybe_override_go_dep(
+        name = "com_github_golang_protobuf",
+        importpath = "github.com/golang/protobuf",
+        sum = "h1:ROPKBNFfQgOUMifHyP+KYbvpjbdoFNs+aK7DXlji0Tw=",
+        version = "v1.5.2",
+    )
+    maybe_override_go_dep(
+        name = "com_github_google_go_cmp",
+        importpath = "github.com/google/go-cmp",
+        sum = "h1:Khx7svrCpmxxtHBq5j2mp/xVjsi8hQMfNLvJFAlrGgU=",
+        version = "v0.5.5",
+    )
+    maybe_override_go_dep(
+        name = "com_github_google_go_querystring",
+        importpath = "github.com/google/go-querystring",
+        sum = "h1:AnCroh3fv4ZBgVIf1Iwtovgjaw/GiKJo8M8yD/fhyJ8=",
+        version = "v1.1.0",
+    )
+    maybe_override_go_dep(
+        name = "com_github_google_uuid",
+        importpath = "github.com/google/uuid",
+        sum = "h1:EVhdT+1Kseyi1/pUmXKaFxYsDNy9RQYkMWRH68J/W7Y=",
+        version = "v1.1.2",
+    )
+    maybe_override_go_dep(
+        name = "com_github_grpc_ecosystem_grpc_gateway",
+        importpath = "github.com/grpc-ecosystem/grpc-gateway",
+        sum = "h1:gmcG1KaJ57LophUzW0Hy8NmPhnMZb4M0+kPpLofRdBo=",
+        version = "v1.16.0",
+    )
+    maybe_override_go_dep(
+        name = "com_github_pmezard_go_difflib",
+        importpath = "github.com/pmezard/go-difflib",
+        sum = "h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=",
+        version = "v1.0.0",
+    )
+    maybe_override_go_dep(
+        name = "com_github_prometheus_client_model",
+        importpath = "github.com/prometheus/client_model",
+        sum = "h1:gQz4mCbXsO+nc9n1hCxHcGA3Zx3Eo+UHZoInFGUIXNM=",
+        version = "v0.0.0-20190812154241-14fe0d1b01d4",
+    )
+    maybe_override_go_dep(
+        name = "com_github_rogpeppe_fastuuid",
+        importpath = "github.com/rogpeppe/fastuuid",
+        sum = "h1:Ppwyp6VYCF1nvBTXL3trRso7mXMlRrw9ooo375wvi2s=",
+        version = "v1.2.0",
+    )
+    maybe_override_go_dep(
+        name = "com_github_stretchr_objx",
+        importpath = "github.com/stretchr/objx",
+        sum = "h1:4G4v2dO3VZwixGIRoQ5Lfboy6nUhCyYzaqnIAPPhYs4=",
+        version = "v0.1.0",
+    )
+    maybe_override_go_dep(
+        name = "com_github_stretchr_testify",
+        importpath = "github.com/stretchr/testify",
+        sum = "h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY=",
+        version = "v1.7.0",
+    )
+    maybe_override_go_dep(
+        name = "com_google_cloud_go",
+        importpath = "cloud.google.com/go",
+        sum = "h1:eOI3/cP2VTU6uZLDYAoic+eyzzB9YyGmJ7eIjl8rOPg=",
+        version = "v0.34.0",
+    )
+    maybe_override_go_dep(
+        name = "in_gopkg_check_v1",
+        importpath = "gopkg.in/check.v1",
+        sum = "h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=",
+        version = "v0.0.0-20161208181325-20d25e280405",
+    )
+    maybe_override_go_dep(
+        name = "in_gopkg_yaml_v2",
+        importpath = "gopkg.in/yaml.v2",
+        sum = "h1:fvjTMHxHEw/mxHbtzPi3JCcKXQRAnQTBRo6YCJSVHKI=",
+        version = "v2.2.3",
+    )
+    maybe_override_go_dep(
+        name = "in_gopkg_yaml_v3",
+        importpath = "gopkg.in/yaml.v3",
+        sum = "h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo=",
+        version = "v3.0.0-20200313102051-9f266ea9e77c",
+    )
+    maybe_override_go_dep(
+        name = "io_opentelemetry_go_proto_otlp",
+        importpath = "go.opentelemetry.io/proto/otlp",
+        sum = "h1:rwOQPCuKAKmwGKq2aVNnYIibI6wnV7EvzgfTCzcdGg8=",
+        version = "v0.7.0",
+    )
+    maybe_override_go_dep(
+        name = "org_golang_google_appengine",
+        importpath = "google.golang.org/appengine",
+        sum = "h1:/wp5JvzpHIxhs/dumFmF7BXTf3Z+dd4uXta4kVyO508=",
+        version = "v1.4.0",
+    )
+    maybe_override_go_dep(
+        name = "org_golang_google_genproto",
+        importpath = "google.golang.org/genproto",
+        sum = "h1:+kGHl1aib/qcwaRi1CbqBZ1rk19r85MNUf8HaBghugY=",
+        version = "v0.0.0-20200526211855-cb27e3aa2013",
+    )
+    maybe_override_go_dep(
+        name = "org_golang_google_grpc",
+        importpath = "google.golang.org/grpc",
+        sum = "h1:Eeu7bZtDZ2DpRCsLhUlcrLnvYaMK1Gz86a+hMVvELmM=",
+        version = "v1.43.0",
+    )
+    maybe_override_go_dep(
+        name = "org_golang_google_protobuf",
+        importpath = "google.golang.org/protobuf",
+        sum = "h1:bxAC2xTBsZGibn2RTntX0oH50xLsqy1OxA9tTL3p/lk=",
+        version = "v1.26.0",
+    )
+    maybe_override_go_dep(
+        name = "org_golang_x_crypto",
+        importpath = "golang.org/x/crypto",
+        sum = "h1:psW17arqaxU48Z5kZ0CQnkZWQJsqcURM6tKiBApRjXI=",
+        version = "v0.0.0-20200622213623-75b288015ac9",
+    )
+    maybe_override_go_dep(
+        name = "org_golang_x_exp",
+        importpath = "golang.org/x/exp",
+        sum = "h1:c2HOrn5iMezYjSlGPncknSEr/8x5LELb/ilJbXi9DEA=",
+        version = "v0.0.0-20190121172915-509febef88a4",
+    )
+    maybe_override_go_dep(
+        name = "org_golang_x_lint",
+        importpath = "golang.org/x/lint",
+        sum = "h1:XQyxROzUlZH+WIQwySDgnISgOivlhjIEwaQaJEJrrN0=",
+        version = "v0.0.0-20190313153728-d0100b6bd8b3",
+    )
+    maybe_override_go_dep(
+        name = "org_golang_x_net",
+        importpath = "golang.org/x/net",
+        sum = "h1:VvcQYSHwXgi7W+TpUR6A9g6Up98WAHf3f/ulnJ62IyA=",
+        version = "v0.0.0-20200822124328-c89045814202",
+    )
+    maybe_override_go_dep(
+        name = "org_golang_x_oauth2",
+        importpath = "golang.org/x/oauth2",
+        sum = "h1:TzXSXBo42m9gQenoE3b9BGiEpg5IG2JkU5FkPIawgtw=",
+        version = "v0.0.0-20200107190931-bf48bf16ab8d",
+    )
+    maybe_override_go_dep(
+        name = "org_golang_x_sync",
+        importpath = "golang.org/x/sync",
+        sum = "h1:8gQV6CLnAEikrhgkHFbMAEhagSSnXWGV915qUMm9mrU=",
+        version = "v0.0.0-20190423024810-112230192c58",
+    )
+    maybe_override_go_dep(
+        name = "org_golang_x_sys",
+        importpath = "golang.org/x/sys",
+        sum = "h1:xhmwyvizuTgC2qz7ZlMluP20uW+C3Rm0FD/WLDX8884=",
+        version = "v0.0.0-20200323222414-85ca7c5b95cd",
+    )
+    maybe_override_go_dep(
+        name = "org_golang_x_text",
+        importpath = "golang.org/x/text",
+        sum = "h1:g61tztE5qeGQ89tm6NTjjM9VPIm088od1l6aSorWRWg=",
+        version = "v0.3.0",
+    )
+    maybe_override_go_dep(
+        name = "org_golang_x_tools",
+        importpath = "golang.org/x/tools",
+        sum = "h1:5Beo0mZN8dRzgrMMkDp0jc8YXQKx9DiJ2k1dkvGsn5A=",
+        version = "v0.0.0-20190524140312-2c0ae7006135",
+    )
+    maybe_override_go_dep(
+        name = "org_golang_x_xerrors",
+        importpath = "golang.org/x/xerrors",
+        sum = "h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE=",
+        version = "v0.0.0-20200804184101-5ec99f83aff1",
+    )
diff --git a/motors/fet12/BUILD b/motors/fet12/BUILD
index be744b2..bf94f52 100644
--- a/motors/fet12/BUILD
+++ b/motors/fet12/BUILD
@@ -84,10 +84,10 @@
     srcs = [
         "calib_sensors.py",
     ],
+    target_compatible_with = ["@platforms//os:linux"],
     deps = [
         "@python_repo//:scipy",
     ],
-    target_compatible_with = ["@platforms//os:linux"],
 )
 
 py_binary(
@@ -95,11 +95,11 @@
     srcs = [
         "current_equalize.py",
     ],
+    target_compatible_with = ["@platforms//os:linux"],
     deps = [
         ":calib_sensors",
         "@python_repo//:scipy",
     ],
-    target_compatible_with = ["@platforms//os:linux"],
 )
 
 genrule(
diff --git a/tools/ci/BUILD b/tools/ci/BUILD
new file mode 100644
index 0000000..7fa13d5
--- /dev/null
+++ b/tools/ci/BUILD
@@ -0,0 +1,17 @@
+load("@io_bazel_rules_go//go:def.bzl", "go_binary", "go_library")
+
+go_library(
+    name = "buildkite_gerrit_trigger_lib",
+    srcs = ["buildkite_gerrit_trigger.go"],
+    importpath = "github.com/frc971/971-Robot-Code/tools/ci",
+    target_compatible_with = ["@platforms//cpu:x86_64"],
+    visibility = ["//visibility:private"],
+    deps = ["@com_github_buildkite_go_buildkite//buildkite:go_default_library"],
+)
+
+go_binary(
+    name = "buildkite_gerrit_trigger",
+    embed = [":buildkite_gerrit_trigger_lib"],
+    target_compatible_with = ["@platforms//cpu:x86_64"],
+    visibility = ["//visibility:public"],
+)
diff --git a/tools/ci/buildkite.yaml b/tools/ci/buildkite.yaml
index 55288f9..9ccb606 100644
--- a/tools/ci/buildkite.yaml
+++ b/tools/ci/buildkite.yaml
@@ -1,6 +1,6 @@
 env:
   STARTUP: --max_idle_secs=0 --watchfs
-  COMMON: -c opt --stamp=no --curses=yes --symlink_prefix=/ --disk_cache=~/.cache/bazel/disk_cache/
+  COMMON: -c opt --stamp=no --curses=yes --symlink_prefix=/ --disk_cache=~/.cache/bazel/disk_cache/ --repo_env=FRC971_RUNNING_IN_CI=1
   TARGETS: //... @com_github_google_glog//... @com_google_ceres_solver//... @com_github_rawrtc_rawrtc//... @com_google_googletest//...
   M4F_TARGETS: //...
   # Sanity check that we are able to build the y2020 roborio code, which confirms
diff --git a/tools/ci/repo_defs.bzl b/tools/ci/repo_defs.bzl
new file mode 100644
index 0000000..b490f79
--- /dev/null
+++ b/tools/ci/repo_defs.bzl
@@ -0,0 +1,19 @@
+def _ci_configure_impl(repository_ctx):
+    """This repository rule tells other rules whether we're running in CI.
+
+    Other rules can use this knowledge to make decisions about enforcing certain
+    things on buildkite while relaxing restrictions during local development.
+    """
+    running_in_ci = repository_ctx.os.environ.get("FRC971_RUNNING_IN_CI", "0") == "1"
+    repository_ctx.file("ci.bzl", """\
+RUNNING_IN_CI = {}
+""".format(running_in_ci))
+    repository_ctx.file("BUILD", "")
+
+ci_configure = repository_rule(
+    implementation = _ci_configure_impl,
+    environ = [
+        # This is set in CI via tools/ci/buildkite.yaml.
+        "FRC971_RUNNING_IN_CI",
+    ],
+)
diff --git a/tools/go/BUILD b/tools/go/BUILD
index ec8aa26..1895ffe 100644
--- a/tools/go/BUILD
+++ b/tools/go/BUILD
@@ -1,5 +1,36 @@
-# This file exists to create a NOOP toolchain for Go on platforms that don't
-# support Go. We can probably get rid of this once
+py_library(
+    name = "mirror_lib",
+    srcs = [
+        "mirror_lib.py",
+    ],
+)
+
+py_binary(
+    name = "tweak_gazelle_go_deps",
+    srcs = [
+        "tweak_gazelle_go_deps.py",
+    ],
+    visibility = ["//visibility:public"],
+    deps = [
+        ":mirror_lib",
+    ],
+)
+
+py_binary(
+    name = "mirror_go_repos",
+    srcs = [
+        "mirror_go_repos.py",
+    ],
+    data = [
+        "@go_sdk//:bin/go",
+    ],
+    deps = [
+        ":mirror_lib",
+    ],
+)
+
+# The remainder of this file exists to create a NOOP toolchain for Go on
+# platforms that don't support Go. We can probably get rid of this once
 # https://github.com/bazelbuild/bazel/issues/12897 is fixed.
 #
 # For platforms that do support Go, we use go_register_toolchain() in
diff --git a/tools/go/go_mirrors.bzl b/tools/go/go_mirrors.bzl
new file mode 100644
index 0000000..bee4c5b
--- /dev/null
+++ b/tools/go/go_mirrors.bzl
@@ -0,0 +1,318 @@
+# This file is auto-generated. Do not edit.
+GO_MIRROR_INFO = {
+  "co_honnef_go_tools": {
+    "filename": "co_honnef_go_tools__v0.0.0-20190523083050-ea95bdfd59fc.zip",
+    "importpath": "honnef.co/go/tools",
+    "sha256": "eeaa82700e96ac5e803d7a9c32363332504beff8fbb1202492b4d43d5a5e7360",
+    "strip_prefix": "honnef.co/go/tools@v0.0.0-20190523083050-ea95bdfd59fc",
+    "version": "v0.0.0-20190523083050-ea95bdfd59fc"
+  },
+  "com_github_antihax_optional": {
+    "filename": "com_github_antihax_optional__v1.0.0.zip",
+    "importpath": "github.com/antihax/optional",
+    "sha256": "15ab4d41bdbb72ee0ac63db616cdefc7671c79e13d0f73b58355a6a88219c97f",
+    "strip_prefix": "github.com/antihax/optional@v1.0.0",
+    "version": "v1.0.0"
+  },
+  "com_github_buildkite_go_buildkite": {
+    "filename": "com_github_buildkite_go_buildkite__v2.2.0+incompatible.zip",
+    "importpath": "github.com/buildkite/go-buildkite",
+    "sha256": "1871115c8c6db004e4b6e57cee927043bfc9ea0c56e7b8f8336021bd8bf588c4",
+    "strip_prefix": "github.com/buildkite/go-buildkite@v2.2.0+incompatible",
+    "version": "v2.2.0+incompatible"
+  },
+  "com_github_burntsushi_toml": {
+    "filename": "com_github_burntsushi_toml__v0.3.1.zip",
+    "importpath": "github.com/BurntSushi/toml",
+    "sha256": "815c6e594745f2d8842ff9a4b0569c6695e6cdfd5e07e5b3d98d06b72ca41e3c",
+    "strip_prefix": "github.com/BurntSushi/toml@v0.3.1",
+    "version": "v0.3.1"
+  },
+  "com_github_cenkalti_backoff": {
+    "filename": "com_github_cenkalti_backoff__v2.2.1+incompatible.zip",
+    "importpath": "github.com/cenkalti/backoff",
+    "sha256": "f8196815a1b4d25e5b8158029d5264801fc8aa5ff128ccf30752fd169693d43b",
+    "strip_prefix": "github.com/cenkalti/backoff@v2.2.1+incompatible",
+    "version": "v2.2.1+incompatible"
+  },
+  "com_github_census_instrumentation_opencensus_proto": {
+    "filename": "com_github_census_instrumentation_opencensus_proto__v0.2.1.zip",
+    "importpath": "github.com/census-instrumentation/opencensus-proto",
+    "sha256": "b3c09f3e635d47b4138695a547d1f2c7138f382cbe5a8b5865b66a8e08233461",
+    "strip_prefix": "github.com/census-instrumentation/opencensus-proto@v0.2.1",
+    "version": "v0.2.1"
+  },
+  "com_github_cespare_xxhash_v2": {
+    "filename": "com_github_cespare_xxhash_v2__v2.1.1.zip",
+    "importpath": "github.com/cespare/xxhash/v2",
+    "sha256": "5baa031c72e73e42617f0fd74e7d813344850bd6a6073381b6a4160a5e9cb59e",
+    "strip_prefix": "github.com/cespare/xxhash/v2@v2.1.1",
+    "version": "v2.1.1"
+  },
+  "com_github_client9_misspell": {
+    "filename": "com_github_client9_misspell__v0.3.4.zip",
+    "importpath": "github.com/client9/misspell",
+    "sha256": "a3af206372e131dd10a68ac470c66a1b18eaf51c6afacb55b2e2a06e39b90728",
+    "strip_prefix": "github.com/client9/misspell@v0.3.4",
+    "version": "v0.3.4"
+  },
+  "com_github_cncf_udpa_go": {
+    "filename": "com_github_cncf_udpa_go__v0.0.0-20210930031921-04548b0d99d4.zip",
+    "importpath": "github.com/cncf/udpa/go",
+    "sha256": "a449fa94e58117a79c17577e39f72f695c4876f74cbd9142d512278192ca90aa",
+    "strip_prefix": "github.com/cncf/udpa/go@v0.0.0-20210930031921-04548b0d99d4",
+    "version": "v0.0.0-20210930031921-04548b0d99d4"
+  },
+  "com_github_cncf_xds_go": {
+    "filename": "com_github_cncf_xds_go__v0.0.0-20211011173535-cb28da3451f1.zip",
+    "importpath": "github.com/cncf/xds/go",
+    "sha256": "eb86281e1e9cf6f83d25edbec0e3fd690570efa3414866d14c6a32b44931375d",
+    "strip_prefix": "github.com/cncf/xds/go@v0.0.0-20211011173535-cb28da3451f1",
+    "version": "v0.0.0-20211011173535-cb28da3451f1"
+  },
+  "com_github_davecgh_go_spew": {
+    "filename": "com_github_davecgh_go_spew__v1.1.0.zip",
+    "importpath": "github.com/davecgh/go-spew",
+    "sha256": "0b5a691aeb8b6af31bd2bb640973ea7e8bf1ed9bc5889da220bf44dc06d9692c",
+    "strip_prefix": "github.com/davecgh/go-spew@v1.1.0",
+    "version": "v1.1.0"
+  },
+  "com_github_envoyproxy_go_control_plane": {
+    "filename": "com_github_envoyproxy_go_control_plane__v0.9.10-0.20210907150352-cf90f659a021.zip",
+    "importpath": "github.com/envoyproxy/go-control-plane",
+    "sha256": "41dc70a8e658cb8945fa0de289d25dd7a608e99929bae144776781401dec507a",
+    "strip_prefix": "github.com/envoyproxy/go-control-plane@v0.9.10-0.20210907150352-cf90f659a021",
+    "version": "v0.9.10-0.20210907150352-cf90f659a021"
+  },
+  "com_github_envoyproxy_protoc_gen_validate": {
+    "filename": "com_github_envoyproxy_protoc_gen_validate__v0.1.0.zip",
+    "importpath": "github.com/envoyproxy/protoc-gen-validate",
+    "sha256": "ec5261f3bbc426d71e2be4c76063ba12460c5d27845d630763e9e911ec4768af",
+    "strip_prefix": "github.com/envoyproxy/protoc-gen-validate@v0.1.0",
+    "version": "v0.1.0"
+  },
+  "com_github_ghodss_yaml": {
+    "filename": "com_github_ghodss_yaml__v1.0.0.zip",
+    "importpath": "github.com/ghodss/yaml",
+    "sha256": "c3f295d23c02c0b35e4d3b29053586e737cf9642df9615da99c0bda9bbacc624",
+    "strip_prefix": "github.com/ghodss/yaml@v1.0.0",
+    "version": "v1.0.0"
+  },
+  "com_github_golang_glog": {
+    "filename": "com_github_golang_glog__v0.0.0-20160126235308-23def4e6c14b.zip",
+    "importpath": "github.com/golang/glog",
+    "sha256": "36b3c522c8102dfe74ca96e474c4c361750bf2bb85bc3cefe4f074c07d6825a9",
+    "strip_prefix": "github.com/golang/glog@v0.0.0-20160126235308-23def4e6c14b",
+    "version": "v0.0.0-20160126235308-23def4e6c14b"
+  },
+  "com_github_golang_mock": {
+    "filename": "com_github_golang_mock__v1.1.1.zip",
+    "importpath": "github.com/golang/mock",
+    "sha256": "636fd21575ebdfbebd53045802a40c780fdab33c6130cea9279346898286f1ca",
+    "strip_prefix": "github.com/golang/mock@v1.1.1",
+    "version": "v1.1.1"
+  },
+  "com_github_golang_protobuf": {
+    "filename": "com_github_golang_protobuf__v1.5.2.zip",
+    "importpath": "github.com/golang/protobuf",
+    "sha256": "5d1c817bebc1202ab3b42a418e584e0008e8027baf212ce69c2ae3e9e7b8c64b",
+    "strip_prefix": "github.com/golang/protobuf@v1.5.2",
+    "version": "v1.5.2"
+  },
+  "com_github_google_go_cmp": {
+    "filename": "com_github_google_go_cmp__v0.5.5.zip",
+    "importpath": "github.com/google/go-cmp",
+    "sha256": "0ee90a7194c025d849699f897d97641b8676ceca9215c96e00eaf1f0e6e953ad",
+    "strip_prefix": "github.com/google/go-cmp@v0.5.5",
+    "version": "v0.5.5"
+  },
+  "com_github_google_go_querystring": {
+    "filename": "com_github_google_go_querystring__v1.1.0.zip",
+    "importpath": "github.com/google/go-querystring",
+    "sha256": "a6aafc01f5602e6177928751074e325792a654e1d92f0e238b8e8739656dd72b",
+    "strip_prefix": "github.com/google/go-querystring@v1.1.0",
+    "version": "v1.1.0"
+  },
+  "com_github_google_uuid": {
+    "filename": "com_github_google_uuid__v1.1.2.zip",
+    "importpath": "github.com/google/uuid",
+    "sha256": "5d52fee1f44cf85dbba03f1bd6098a15a131e32c0e45839b352d69aceb7babfc",
+    "strip_prefix": "github.com/google/uuid@v1.1.2",
+    "version": "v1.1.2"
+  },
+  "com_github_grpc_ecosystem_grpc_gateway": {
+    "filename": "com_github_grpc_ecosystem_grpc_gateway__v1.16.0.zip",
+    "importpath": "github.com/grpc-ecosystem/grpc-gateway",
+    "sha256": "377b03aef288b34ed894449d3ddba40d525dd7fb55de6e79045cdf499e7fe565",
+    "strip_prefix": "github.com/grpc-ecosystem/grpc-gateway@v1.16.0",
+    "version": "v1.16.0"
+  },
+  "com_github_pmezard_go_difflib": {
+    "filename": "com_github_pmezard_go_difflib__v1.0.0.zip",
+    "importpath": "github.com/pmezard/go-difflib",
+    "sha256": "de04cecc1a4b8d53e4357051026794bcbc54f2e6a260cfac508ce69d5d6457a0",
+    "strip_prefix": "github.com/pmezard/go-difflib@v1.0.0",
+    "version": "v1.0.0"
+  },
+  "com_github_prometheus_client_model": {
+    "filename": "com_github_prometheus_client_model__v0.0.0-20190812154241-14fe0d1b01d4.zip",
+    "importpath": "github.com/prometheus/client_model",
+    "sha256": "5d4719be47f4f69ab5bf36a04c75eb078a0f69b43a335f400c2d688ac9e61795",
+    "strip_prefix": "github.com/prometheus/client_model@v0.0.0-20190812154241-14fe0d1b01d4",
+    "version": "v0.0.0-20190812154241-14fe0d1b01d4"
+  },
+  "com_github_rogpeppe_fastuuid": {
+    "filename": "com_github_rogpeppe_fastuuid__v1.2.0.zip",
+    "importpath": "github.com/rogpeppe/fastuuid",
+    "sha256": "f9b8293f5e20270e26fb4214ca7afec864de92c73d03ff62b5ee29d1db4e72a1",
+    "strip_prefix": "github.com/rogpeppe/fastuuid@v1.2.0",
+    "version": "v1.2.0"
+  },
+  "com_github_stretchr_objx": {
+    "filename": "com_github_stretchr_objx__v0.1.0.zip",
+    "importpath": "github.com/stretchr/objx",
+    "sha256": "1fa10dab404ed7fc8ed2a033f8784187d5df3513ced3841ce39e46d37850eb1d",
+    "strip_prefix": "github.com/stretchr/objx@v0.1.0",
+    "version": "v0.1.0"
+  },
+  "com_github_stretchr_testify": {
+    "filename": "com_github_stretchr_testify__v1.7.0.zip",
+    "importpath": "github.com/stretchr/testify",
+    "sha256": "5a46ccebeff510df3e2f6d3842ee79d3f68d0e7b1554cd6ee93390d68b6c6b34",
+    "strip_prefix": "github.com/stretchr/testify@v1.7.0",
+    "version": "v1.7.0"
+  },
+  "com_google_cloud_go": {
+    "filename": "com_google_cloud_go__v0.34.0.zip",
+    "importpath": "cloud.google.com/go",
+    "sha256": "27c1190dcd33c594838487544d8c3d17df4f7916fb542f5c2c9ca729ef1d9fe6",
+    "strip_prefix": "cloud.google.com/go@v0.34.0",
+    "version": "v0.34.0"
+  },
+  "in_gopkg_check_v1": {
+    "filename": "in_gopkg_check_v1__v0.0.0-20161208181325-20d25e280405.zip",
+    "importpath": "gopkg.in/check.v1",
+    "sha256": "4e1817f964ca34e545b81afda0325a5e89cf58de2e413d8207c0afddd0fdc15c",
+    "strip_prefix": "gopkg.in/check.v1@v0.0.0-20161208181325-20d25e280405",
+    "version": "v0.0.0-20161208181325-20d25e280405"
+  },
+  "in_gopkg_yaml_v2": {
+    "filename": "in_gopkg_yaml_v2__v2.2.3.zip",
+    "importpath": "gopkg.in/yaml.v2",
+    "sha256": "213403de27ae981b118ba199a3a1ddc64a82d0c9cf7534b762dc9ee5d79c5316",
+    "strip_prefix": "gopkg.in/yaml.v2@v2.2.3",
+    "version": "v2.2.3"
+  },
+  "in_gopkg_yaml_v3": {
+    "filename": "in_gopkg_yaml_v3__v3.0.0-20200313102051-9f266ea9e77c.zip",
+    "importpath": "gopkg.in/yaml.v3",
+    "sha256": "acf19ccb4fca983b234a39ef032faf9ab70e759680673bb3dff077e77fee20fe",
+    "strip_prefix": "gopkg.in/yaml.v3@v3.0.0-20200313102051-9f266ea9e77c",
+    "version": "v3.0.0-20200313102051-9f266ea9e77c"
+  },
+  "io_opentelemetry_go_proto_otlp": {
+    "filename": "io_opentelemetry_go_proto_otlp__v0.7.0.zip",
+    "importpath": "go.opentelemetry.io/proto/otlp",
+    "sha256": "a7db0590bc4c5f0b9b99cc958decf644f1e5cc11e0b995dc20b3583a2215259b",
+    "strip_prefix": "go.opentelemetry.io/proto/otlp@v0.7.0",
+    "version": "v0.7.0"
+  },
+  "org_golang_google_appengine": {
+    "filename": "org_golang_google_appengine__v1.4.0.zip",
+    "importpath": "google.golang.org/appengine",
+    "sha256": "d4dba839844a92bd9355812a53f086ff3301aa8f59b29a3e2f799d27be8db71f",
+    "strip_prefix": "google.golang.org/appengine@v1.4.0",
+    "version": "v1.4.0"
+  },
+  "org_golang_google_genproto": {
+    "filename": "org_golang_google_genproto__v0.0.0-20200526211855-cb27e3aa2013.zip",
+    "importpath": "google.golang.org/genproto",
+    "sha256": "50336913c7325576ed9c3587fdcacd39094013af95c8c11736f335969136527d",
+    "strip_prefix": "google.golang.org/genproto@v0.0.0-20200526211855-cb27e3aa2013",
+    "version": "v0.0.0-20200526211855-cb27e3aa2013"
+  },
+  "org_golang_google_grpc": {
+    "filename": "org_golang_google_grpc__v1.43.0.zip",
+    "importpath": "google.golang.org/grpc",
+    "sha256": "19fa6e227e62e3ae9791ab81b8a784e93cc68860b7fe0e85dd8d3cfbc1b24398",
+    "strip_prefix": "google.golang.org/grpc@v1.43.0",
+    "version": "v1.43.0"
+  },
+  "org_golang_google_protobuf": {
+    "filename": "org_golang_google_protobuf__v1.26.0.zip",
+    "importpath": "google.golang.org/protobuf",
+    "sha256": "d7bc5de329bd4e803f7a2acfcbe8f2eba4ef1579485056ef569a4b245bee1208",
+    "strip_prefix": "google.golang.org/protobuf@v1.26.0",
+    "version": "v1.26.0"
+  },
+  "org_golang_x_crypto": {
+    "filename": "org_golang_x_crypto__v0.0.0-20200622213623-75b288015ac9.zip",
+    "importpath": "golang.org/x/crypto",
+    "sha256": "aeaac2684d693207a90f491b0303e881e58c014e96d27b0ebb7fe6937dfc1e76",
+    "strip_prefix": "golang.org/x/crypto@v0.0.0-20200622213623-75b288015ac9",
+    "version": "v0.0.0-20200622213623-75b288015ac9"
+  },
+  "org_golang_x_exp": {
+    "filename": "org_golang_x_exp__v0.0.0-20190121172915-509febef88a4.zip",
+    "importpath": "golang.org/x/exp",
+    "sha256": "c3a7dcc2e8117e749c6badc91d492de3ee2f5b440b30481a0a75f9d63db6fe0b",
+    "strip_prefix": "golang.org/x/exp@v0.0.0-20190121172915-509febef88a4",
+    "version": "v0.0.0-20190121172915-509febef88a4"
+  },
+  "org_golang_x_lint": {
+    "filename": "org_golang_x_lint__v0.0.0-20190313153728-d0100b6bd8b3.zip",
+    "importpath": "golang.org/x/lint",
+    "sha256": "5c7bb9792bdc4ec4cf1af525cf9998f8a958daf6495852c9a7dbb71738f2f10a",
+    "strip_prefix": "golang.org/x/lint@v0.0.0-20190313153728-d0100b6bd8b3",
+    "version": "v0.0.0-20190313153728-d0100b6bd8b3"
+  },
+  "org_golang_x_net": {
+    "filename": "org_golang_x_net__v0.0.0-20200822124328-c89045814202.zip",
+    "importpath": "golang.org/x/net",
+    "sha256": "22ea306ad6c9100c636c165dc05a64265d0f985452a9f1edf32b561b620ae762",
+    "strip_prefix": "golang.org/x/net@v0.0.0-20200822124328-c89045814202",
+    "version": "v0.0.0-20200822124328-c89045814202"
+  },
+  "org_golang_x_oauth2": {
+    "filename": "org_golang_x_oauth2__v0.0.0-20200107190931-bf48bf16ab8d.zip",
+    "importpath": "golang.org/x/oauth2",
+    "sha256": "661e4c30b15e488b434b19085567e581eb7bde892f04a0a3ab7ef94c0028f133",
+    "strip_prefix": "golang.org/x/oauth2@v0.0.0-20200107190931-bf48bf16ab8d",
+    "version": "v0.0.0-20200107190931-bf48bf16ab8d"
+  },
+  "org_golang_x_sync": {
+    "filename": "org_golang_x_sync__v0.0.0-20190423024810-112230192c58.zip",
+    "importpath": "golang.org/x/sync",
+    "sha256": "dc105c2b4d6c7ab48e54946ce2f624e8d1f5d47270eff1e88fed06cc65f91fb4",
+    "strip_prefix": "golang.org/x/sync@v0.0.0-20190423024810-112230192c58",
+    "version": "v0.0.0-20190423024810-112230192c58"
+  },
+  "org_golang_x_sys": {
+    "filename": "org_golang_x_sys__v0.0.0-20200323222414-85ca7c5b95cd.zip",
+    "importpath": "golang.org/x/sys",
+    "sha256": "d00ccd67cf0f2dd8622ed2721b8824fcbaf4f5730f47387fd5e39adbd5f6972e",
+    "strip_prefix": "golang.org/x/sys@v0.0.0-20200323222414-85ca7c5b95cd",
+    "version": "v0.0.0-20200323222414-85ca7c5b95cd"
+  },
+  "org_golang_x_text": {
+    "filename": "org_golang_x_text__v0.3.0.zip",
+    "importpath": "golang.org/x/text",
+    "sha256": "ea3068395503d3c7ef8ce16a286f75c8c93882c25a66c2aa6c8e2ad4da7a9ae0",
+    "strip_prefix": "golang.org/x/text@v0.3.0",
+    "version": "v0.3.0"
+  },
+  "org_golang_x_tools": {
+    "filename": "org_golang_x_tools__v0.0.0-20190524140312-2c0ae7006135.zip",
+    "importpath": "golang.org/x/tools",
+    "sha256": "86687e8cd5adccf8809ba031e59146d0c89047b6267aacc785ffc20b0ce6b735",
+    "strip_prefix": "golang.org/x/tools@v0.0.0-20190524140312-2c0ae7006135",
+    "version": "v0.0.0-20190524140312-2c0ae7006135"
+  },
+  "org_golang_x_xerrors": {
+    "filename": "org_golang_x_xerrors__v0.0.0-20200804184101-5ec99f83aff1.zip",
+    "importpath": "golang.org/x/xerrors",
+    "sha256": "380aaa3368ae165628bb487cf4e695a55c1420072b8b7a6f59c122f6a23bb255",
+    "strip_prefix": "golang.org/x/xerrors@v0.0.0-20200804184101-5ec99f83aff1",
+    "version": "v0.0.0-20200804184101-5ec99f83aff1"
+  }
+}
diff --git a/tools/go/mirror_go_repos.py b/tools/go/mirror_go_repos.py
new file mode 100644
index 0000000..dc160fa
--- /dev/null
+++ b/tools/go/mirror_go_repos.py
@@ -0,0 +1,144 @@
+"""This script mirrors the dependencies from go_deps.bzl as Build-Dependencies.
+
+We use "go mod download" to manually download each Go dependency. We then tar
+up all the dependencies and copy them to the Build-Dependencies server for
+hosting.
+"""
+
+import argparse
+import hashlib
+import json
+import os
+from pathlib import Path
+import subprocess
+import sys
+import tarfile
+from typing import List, Dict
+import urllib.request
+
+import tools.go.mirror_lib
+
+GO_DEPS_WWWW_DIR = "/var/www/html/files/frc971/Build-Dependencies/go_deps"
+
+def compute_sha256(filepath: str) -> str:
+    """Computes the SHA256 of a file at the specified location."""
+    with open(filepath, "rb") as file:
+        contents = file.read()
+    return hashlib.sha256(contents).hexdigest()
+
+def get_existing_mirrored_repos(ssh_host: str) -> Dict[str, str]:
+    """Gathers information about the libraries that are currently mirrored."""
+    run_result = subprocess.run(["ssh", ssh_host, f"bash -c 'sha256sum {GO_DEPS_WWWW_DIR}/*'"], check=True, stdout=subprocess.PIPE)
+
+    existing_mirrored_repos = {}
+    for line in run_result.stdout.decode("utf-8").splitlines():
+        sha256, fullpath = line.split()
+        existing_mirrored_repos[Path(fullpath).name] = sha256
+
+    return existing_mirrored_repos
+
+def download_repos(
+        repos: Dict[str, str],
+        existing_mirrored_repos: Dict[str, str],
+        tar: tarfile.TarFile) -> Dict[str, str]:
+    """Downloads the not-yet-mirrored repos into a tarball."""
+    cached_info = {}
+
+    for repo in repos:
+        print(f"Downloading file for {repo['name']}")
+        importpath = repo["importpath"]
+        version = repo["version"]
+        module = f"{importpath}@{version}"
+
+        download_result = subprocess.run(
+            ["external/go_sdk/bin/go", "mod", "download", "-json", module],
+            check=True, stdout=subprocess.PIPE)
+        if download_result.returncode != 0:
+            print("Failed to download file.")
+            return 1
+
+        module_info = json.loads(download_result.stdout.decode("utf-8"))
+
+        name = repo["name"]
+        zip_path = Path(module_info["Zip"])
+        mirrored_name = f"{name}__{zip_path.name}"
+        if mirrored_name not in existing_mirrored_repos:
+            # We only add the Go library to the tarball if it's not already
+            # mirrored. We don't want to overwrite files.
+            tar.add(zip_path, arcname=mirrored_name)
+            sha256 = compute_sha256(zip_path)
+        else:
+            # Use the already-computed checksum for consistency.
+            sha256 = existing_mirrored_repos[mirrored_name]
+
+        cached_info[name] = {
+            "strip_prefix": module,
+            "filename": mirrored_name,
+            "sha256": sha256,
+            "version": version,
+            "importpath": importpath,
+        }
+
+    return cached_info
+
+def copy_to_host_and_unpack(filename: str, ssh_host: str) -> None:
+    subprocess.run(["scp", filename, f"{ssh_host}:"], check=True)
+
+    # Be careful not to use single quotes in these commands to avoid breaking
+    # the subprocess.run() invocation below.
+    command = " && ".join([
+        f"tar -C {GO_DEPS_WWWW_DIR} --no-same-owner -xvaf {filename}",
+        # Change the permissions so other users can read them (and checksum
+        # them).
+        f"find {GO_DEPS_WWWW_DIR}/ -type f -exec chmod 644 {{}} +",
+    ])
+
+    print("You might be asked for your sudo password shortly.")
+    subprocess.run(["ssh", "-t", ssh_host, f"sudo -u www-data bash -c '{command}'"], check=True)
+
+def main(argv):
+    parser = argparse.ArgumentParser()
+    parser.add_argument(
+        "--ssh_host",
+        type=str,
+        help=("The SSH host to copy the downloaded Go repositories to. This "
+              "should be software.971spartans.net where all the "
+              "Build-Dependencies files live. Only specify this if you have "
+              "access to the server."))
+    parser.add_argument("--go_deps_bzl", type=str, default="go_deps.bzl")
+    parser.add_argument("--go_mirrors_bzl", type=str, default="tools/go/go_mirrors.bzl")
+    args = parser.parse_args(argv[1:])
+
+    os.chdir(os.environ["BUILD_WORKSPACE_DIRECTORY"])
+
+    repos = tools.go.mirror_lib.parse_go_repositories(args.go_deps_bzl)
+
+    if args.ssh_host:
+        existing_mirrored_repos = get_existing_mirrored_repos(args.ssh_host)
+    else:
+        existing_mirrored_repos = {}
+
+    with tarfile.open("go_deps.tar", "w") as tar:
+        cached_info = download_repos(repos, existing_mirrored_repos, tar)
+        num_not_already_mirrored = len(tar.getnames())
+
+    print(f"Found {num_not_already_mirrored}/{len(cached_info)} libraries "
+          "that need to be mirrored.")
+
+    # Only mirror the deps if we've specified an SSH host and we actually have
+    # something to mirror.
+    if args.ssh_host and num_not_already_mirrored:
+        copy_to_host_and_unpack("go_deps.tar", args.ssh_host)
+    else:
+        print("Skipping mirroring because of lack of --ssh_host or there's "
+              "nothing to actually mirror.")
+
+    with open(args.go_mirrors_bzl, "w") as file:
+        file.write("# This file is auto-generated. Do not edit.\n")
+        file.write("GO_MIRROR_INFO = ")
+        json.dump(cached_info, file, indent=2, sort_keys=True)
+        file.write("\n")
+
+
+if __name__ == "__main__":
+    sys.exit(main(sys.argv))
diff --git a/tools/go/mirror_lib.py b/tools/go/mirror_lib.py
new file mode 100644
index 0000000..faad896
--- /dev/null
+++ b/tools/go/mirror_lib.py
@@ -0,0 +1,41 @@
+"""Provides helper functions for mirroring Go repositories."""
+
+import unittest.mock
+from typing import List, Dict
+
+
+def read_file(filepath: str) -> str:
+    """Reads an entire file by returning its contents as a string."""
+    with open(filepath, "r") as file:
+        return file.read()
+
+def parse_go_repositories(filepath: str) -> List[Dict[str, str]]:
+    """Parses the top-level go_deps.bzl file.
+
+    This function can parse both the original version of the file generated by
+    gazelle as well as the tweaked version generated by
+    tweak_gazelle_go_deps.py. The two versions are identical other than what function they call.
+    """
+    global_functions = {
+        "load": unittest.mock.MagicMock(),
+        # The gazelle generated version uses go_repository().
+        "go_repository": unittest.mock.MagicMock(),
+        # The tweak_gazelle_go_deps.py generated version uses
+        # maybe_override_go_dep().
+        "maybe_override_go_dep": unittest.mock.MagicMock()
+    }
+    compiled_code = compile(read_file(filepath), filepath, "exec")
+    eval(compiled_code, global_functions)
+
+    # Extract the repositories defined in the go_dependencies() function from
+    # go_deps.bzl.
+    global_functions["go_dependencies"]()
+
+    repositories = []
+    for repo_kind in ("go_repository", "maybe_override_go_dep"):
+        for repo in global_functions[repo_kind].mock_calls:
+            _, _, kwargs = repo
+            repositories.append(kwargs)
+
+    return repositories
+
diff --git a/tools/go/mirrored_go_deps.bzl b/tools/go/mirrored_go_deps.bzl
new file mode 100644
index 0000000..d77ce5c
--- /dev/null
+++ b/tools/go/mirrored_go_deps.bzl
@@ -0,0 +1,47 @@
+load("//tools/go:go_mirrors.bzl", "GO_MIRROR_INFO")
+load("@bazel_gazelle//:deps.bzl", "go_repository")
+load("@ci_configure//:ci.bzl", "RUNNING_IN_CI")
+
+def maybe_override_go_dep(name, importpath, sum, version):
+    """This macro selects between our dependency mirrors and upstream sources.
+
+    We want to use the mirrored version whenever possible. In CI we are required
+    to use the mirrored version. For local development we only use the mirrored
+    version if it's available. Otherwise we download from the upstream sources.
+    """
+    if not RUNNING_IN_CI:
+        override_go_dep = not (name in GO_MIRROR_INFO and GO_MIRROR_INFO[name]["version"] == version)
+    else:
+        override_go_dep = False
+        if name not in GO_MIRROR_INFO or GO_MIRROR_INFO[name]["version"] != version:
+            fail(("The repo {} is not properly mirrored. " +
+                  "Please ask someone with mirroring access for help." +
+                  "They need to 'bazel run //tools/go:mirror_go_repos -- " +
+                  "--ssh_host <software.971spartans.net>'.").format(name))
+
+    # If we want to use the upstream version and we've already imported a
+    # mirrored version via mirrored_go_dependencies(), then we override it here
+    # by giving the upstream version the same name.
+    if override_go_dep:
+        go_repository(
+            name = name,
+            importpath = importpath,
+            sum = sum,
+            version = version,
+        )
+
+
+def mirrored_go_dependencies():
+    """Sets up the Go dependencies we've mirrored."""
+    for name in GO_MIRROR_INFO:
+        info = GO_MIRROR_INFO[name]
+        go_repository(
+            name = name,
+            strip_prefix = info["strip_prefix"],
+            type = "zip",
+            urls = [
+                "https://www.frc971.org/Build-Dependencies/go_deps/" + info["filename"],
+            ],
+            sha256 = info["sha256"],
+            importpath = info["importpath"],
+        )
diff --git a/tools/go/tweak_gazelle_go_deps.py b/tools/go/tweak_gazelle_go_deps.py
new file mode 100644
index 0000000..7189d43
--- /dev/null
+++ b/tools/go/tweak_gazelle_go_deps.py
@@ -0,0 +1,42 @@
+"""Tweaks the gazelle-generated go_deps.bzl to work with our mirrors.
+
+This script changes all invocations of go_repository() in go_deps.bzl to use
+maybe_override_go_dep(). That lets us more easily switch between upstream
+sources and our mirrored versions of the code.
+
+The motivation is to let folks use upstream sources during local development.
+For CI runs, however, we have to restrict ourselves to mirrored dependencies.
+"""
+
+import argparse
+import sys
+import textwrap
+
+import tools.go.mirror_lib
+
+def main(argv):
+    parser = argparse.ArgumentParser()
+    parser.add_argument("go_deps_bzl", type=str)
+    args = parser.parse_args(argv[1:])
+
+    repos = tools.go.mirror_lib.parse_go_repositories(args.go_deps_bzl)
+
+    with open(args.go_deps_bzl, "w") as file:
+        file.write(textwrap.dedent("""\
+            # This file is auto-generated. Do not edit.
+            load("//tools/go:mirrored_go_deps.bzl", "maybe_override_go_dep")
+
+            def go_dependencies():
+            """))
+        for repo in repos:
+            file.write(textwrap.indent(textwrap.dedent(f"""\
+                maybe_override_go_dep(
+                    name = "{repo['name']}",
+                    importpath = "{repo['importpath']}",
+                    sum = "{repo['sum']}",
+                    version = "{repo['version']}",
+                )
+                """), " " * 4))
+
+if __name__ == "__main__":
+    sys.exit(main(sys.argv))
diff --git a/tools/lint/BUILD b/tools/lint/BUILD
index 30fc7c9..80b4aa1 100644
--- a/tools/lint/BUILD
+++ b/tools/lint/BUILD
@@ -1,3 +1,5 @@
+load("@ci_configure//:ci.bzl", "RUNNING_IN_CI")
+
 sh_binary(
     name = "gofmt",
     srcs = ["gofmt.sh"],
@@ -16,7 +18,16 @@
     ],
     data = [
         ":gofmt",
+        "//:gazelle-runner",
+        "//tools/go:tweak_gazelle_go_deps",
+        "@go_sdk//:bin/go",
     ],
+    env = {
+        # Prevent CI errors like:
+        #   failed to initialize build cache at
+        #   /var/lib/buildkite-agent/.cache/go-build: permission denied
+        "RUNNING_IN_CI": "1" if RUNNING_IN_CI else "0",
+    },
     deps = [
         "@bazel_tools//tools/bash/runfiles",
     ],
diff --git a/tools/lint/run-ci.sh b/tools/lint/run-ci.sh
index a1bc07c..48c0960 100755
--- a/tools/lint/run-ci.sh
+++ b/tools/lint/run-ci.sh
@@ -14,10 +14,42 @@
   { echo>&2 "ERROR: cannot find $f"; exit 1; }; f=; set -e
 # --- end runfiles.bash initialization v2 ---
 
+set -o nounset
+
+# Redirect the Go cache on buildkite. Otherwise we run into errors like:
+# "failed to initialize build cache at /var/lib/buildkite-agent/.cache/go-build"
+# due to permission errors.
+if ((RUNNING_IN_CI == 1)); then
+    export GOCACHE=/tmp/lint_go_cache
+fi
+
 gofmt() {
     ./tools/lint/gofmt
 }
 
+gomod() {
+    local -r go="$(readlink -f external/go_sdk/bin/go)"
+    cd "${BUILD_WORKSPACE_DIRECTORY}"
+    "${go}" mod tidy -e
+}
+
+update_repos() {
+    ./gazelle-runner.bash update-repos \
+        -from_file=go.mod \
+        -to_macro=go_deps.bzl%go_dependencies \
+        -prune
+}
+
+gazelle() {
+    ./gazelle-runner.bash
+}
+
+tweak_gazelle_go_deps() {
+    local -r tweaker="$(readlink -f tools/go/tweak_gazelle_go_deps)"
+    cd "${BUILD_WORKSPACE_DIRECTORY}"
+    "${tweaker}" ./go_deps.bzl
+}
+
 git_status_is_clean() {
     cd "${BUILD_WORKSPACE_DIRECTORY}"
     if ! git diff --quiet; then
@@ -29,6 +61,10 @@
 # All the linters that we are going to run.
 readonly -a LINTERS=(
     gofmt
+    gomod
+    update_repos
+    gazelle
+    tweak_gazelle_go_deps
     git_status_is_clean  # This must the last linter.
 )
 
diff --git a/tools/python/BUILD b/tools/python/BUILD
index 32093d3..9939653 100644
--- a/tools/python/BUILD
+++ b/tools/python/BUILD
@@ -18,11 +18,11 @@
 
 toolchain(
     name = "python_toolchain",
-    target_compatible_with = [
+    exec_compatible_with = [
         "@platforms//cpu:x86_64",
         "@platforms//os:linux",
     ],
-    exec_compatible_with = [
+    target_compatible_with = [
         "@platforms//cpu:x86_64",
         "@platforms//os:linux",
     ],
diff --git a/y2020/actors/BUILD b/y2020/actors/BUILD
index b2a5121..85d8882 100644
--- a/y2020/actors/BUILD
+++ b/y2020/actors/BUILD
@@ -86,15 +86,15 @@
     ],
     target_compatible_with = ["@platforms//os:linux"],
     deps = [
-        "//aos/events:shm_event_loop",
         "//aos:init",
+        "//aos/events:shm_event_loop",
         "//frc971/autonomous:base_autonomous_actor",
         "//frc971/control_loops:control_loops_fbs",
         "//frc971/control_loops:profiled_subsystem_fbs",
         "//y2020:constants",
         "//y2020/control_loops/drivetrain:drivetrain_base",
+        "//y2020/control_loops/superstructure:superstructure_goal_fbs",
         "//y2020/control_loops/superstructure/shooter:shooter_tuning_params_fbs",
         "//y2020/control_loops/superstructure/shooter:shooter_tuning_readings_fbs",
-        "//y2020/control_loops/superstructure:superstructure_goal_fbs",
     ],
 )
diff --git a/y2020/control_loops/superstructure/BUILD b/y2020/control_loops/superstructure/BUILD
index 375e4b0..e5ff8dc 100644
--- a/y2020/control_loops/superstructure/BUILD
+++ b/y2020/control_loops/superstructure/BUILD
@@ -83,9 +83,9 @@
         "//frc971/control_loops:control_loops_fbs",
         "//frc971/control_loops/drivetrain:drivetrain_status_fbs",
         "//y2020:constants",
+        "//y2020/control_loops/superstructure/hood:hood_encoder_zeroing_estimator",
         "//y2020/control_loops/superstructure/shooter",
         "//y2020/control_loops/superstructure/turret:aiming",
-        "//y2020/control_loops/superstructure/hood:hood_encoder_zeroing_estimator",
     ],
 )
 
diff --git a/y2020/control_loops/superstructure/hood/BUILD b/y2020/control_loops/superstructure/hood/BUILD
index 3035a55..437c67a 100644
--- a/y2020/control_loops/superstructure/hood/BUILD
+++ b/y2020/control_loops/superstructure/hood/BUILD
@@ -43,7 +43,7 @@
     ],
     target_compatible_with = ["@platforms//os:linux"],
     deps = [
-        "//frc971/zeroing:zeroing",
+        "//frc971/zeroing",
         "//y2020:constants",
     ],
 )
diff --git a/y2021_bot3/control_loops/superstructure/BUILD b/y2021_bot3/control_loops/superstructure/BUILD
index e4dd6a2..d1d2684 100644
--- a/y2021_bot3/control_loops/superstructure/BUILD
+++ b/y2021_bot3/control_loops/superstructure/BUILD
@@ -113,4 +113,4 @@
         "//aos/network/www:colors",
         "//aos/network/www:proxy",
     ],
-)
\ No newline at end of file
+)