diff --git a/WORKSPACE b/WORKSPACE index 89316d0ca0..96bbe5e6c8 100644 --- a/WORKSPACE +++ b/WORKSPACE @@ -6,7 +6,9 @@ load("@io_bazel_rules_go//go:deps.bzl", "go_register_toolchains", "go_rules_depe go_rules_dependencies() -go_register_toolchains(version = "1.18.3") +go_register_toolchains( + version = "1.19.4", +) http_archive( name = "com_google_protobuf", diff --git a/go/private/BUILD.sdk.bazel b/go/private/BUILD.sdk.bazel index 720918f999..8ad8768dae 100644 --- a/go/private/BUILD.sdk.bazel +++ b/go/private/BUILD.sdk.bazel @@ -41,6 +41,7 @@ go_sdk( libs = [":libs"], package_list = ":package_list", root_file = "ROOT", + boringcrypto = {boringcrypto}, tools = [":tools"], ) diff --git a/go/private/actions/compilepkg.bzl b/go/private/actions/compilepkg.bzl index 93f34a8b87..a2336f4474 100644 --- a/go/private/actions/compilepkg.bzl +++ b/go/private/actions/compilepkg.bzl @@ -117,6 +117,8 @@ def emit_compilepkg( outputs.append(out_cgo_export_h) if testfilter: args.add("-testfilter", testfilter) + if go.sdk.boringcrypto: + args.add("-boringcrypto") gc_flags = list(gc_goopts) gc_flags.extend(go.mode.gc_goopts) diff --git a/go/private/actions/link.bzl b/go/private/actions/link.bzl index 8a2c1f4739..4bf9120277 100644 --- a/go/private/actions/link.bzl +++ b/go/private/actions/link.bzl @@ -175,6 +175,8 @@ def emit_link( builder_args.add("-p", archive.data.importmap) tool_args.add_all(gc_linkopts) tool_args.add_all(go.toolchain.flags.link) + if go.sdk.boringcrypto: + builder_args.add("-boringcrypto") # Do not remove, somehow this is needed when building for darwin/arm only. tool_args.add("-buildid=redacted") diff --git a/go/private/actions/stdlib.bzl b/go/private/actions/stdlib.bzl index ee467f4004..8c3707184f 100644 --- a/go/private/actions/stdlib.bzl +++ b/go/private/actions/stdlib.bzl @@ -49,6 +49,7 @@ def _should_use_sdk_stdlib(go): not go.mode.race and # TODO(jayconrod): use precompiled race not go.mode.msan and not go.mode.pure and + not go.sdk.boringcrypto and go.mode.link == LINKMODE_NORMAL) def _build_stdlib_list_json(go): @@ -79,6 +80,8 @@ def _build_stdlib(go): args.add("-out", pkg.dirname) if go.mode.race: args.add("-race") + if go.sdk.boringcrypto: + args.add("-boringcrypto") args.add_all(link_mode_args(go.mode)) env = go.env if go.mode.pure: diff --git a/go/private/providers.bzl b/go/private/providers.bzl index 8be46d4e47..d9b93ccb40 100644 --- a/go/private/providers.bzl +++ b/go/private/providers.bzl @@ -41,6 +41,7 @@ GoSDK = provider( fields = { "goos": "The host OS the SDK was built for.", "goarch": "The host architecture the SDK was built for.", + "boringcrypto": "Whether to build for boringcrypto", "root_file": "A file in the SDK root directory", "libs": ("List of pre-compiled .a files for the standard library " + "built for the execution platform."), diff --git a/go/private/rules/sdk.bzl b/go/private/rules/sdk.bzl index fcb9dd74a5..ac53cb5dd9 100644 --- a/go/private/rules/sdk.bzl +++ b/go/private/rules/sdk.bzl @@ -25,6 +25,7 @@ def _go_sdk_impl(ctx): return [GoSDK( goos = ctx.attr.goos, goarch = ctx.attr.goarch, + boringcrypto = ctx.attr.boringcrypto, root_file = ctx.file.root_file, package_list = package_list, libs = ctx.files.libs, @@ -45,6 +46,10 @@ go_sdk = rule( mandatory = True, doc = "The host architecture the SDK was built for", ), + "boringcrypto": attr.bool( + mandatory = False, + doc = "Whether the toolchain should be built with boringcrypto support enabled", + ), "root_file": attr.label( mandatory = True, allow_single_file = True, diff --git a/go/private/sdk.bzl b/go/private/sdk.bzl index 34f2520fad..028a260010 100644 --- a/go/private/sdk.bzl +++ b/go/private/sdk.bzl @@ -26,18 +26,20 @@ load( ) MIN_SUPPORTED_VERSION = (1, 14, 0) +MIN_BORINGCRYPTO_VERSION = (1, 19, 0) def _go_host_sdk_impl(ctx): goroot = _detect_host_sdk(ctx) platform = _detect_sdk_platform(ctx, goroot) version = _detect_sdk_version(ctx, goroot) - _sdk_build_file(ctx, platform, version) + _sdk_build_file(ctx, platform, version, ctx.attr.boringcrypto) _local_sdk(ctx, goroot) _go_host_sdk = repository_rule( implementation = _go_host_sdk_impl, environ = ["GOROOT"], attrs = { + "boringcrypto": attr.bool(), "version": attr.string(), }, ) @@ -112,7 +114,7 @@ def _go_download_sdk_impl(ctx): _remote_sdk(ctx, [url.format(filename) for url in ctx.attr.urls], ctx.attr.strip_prefix, sha256) detected_version = _detect_sdk_version(ctx, ".") - _sdk_build_file(ctx, platform, detected_version) + _sdk_build_file(ctx, platform, detected_version, ctx.attr.boringcrypto) if not ctx.attr.sdks and not ctx.attr.version: # Returning this makes Bazel print a message that 'version' must be @@ -134,6 +136,7 @@ _go_download_sdk = repository_rule( "goos": attr.string(), "goarch": attr.string(), "sdks": attr.string_list_dict(), + "boringcrypto": attr.bool(), "urls": attr.string_list(default = ["https://dl.google.com/go/{}"]), "version": attr.string(), "strip_prefix": attr.string(default = "go"), @@ -223,7 +226,7 @@ def _go_local_sdk_impl(ctx): goroot = ctx.attr.path platform = _detect_sdk_platform(ctx, goroot) version = _detect_sdk_version(ctx, goroot) - _sdk_build_file(ctx, platform, version) + _sdk_build_file(ctx, platform, version, ctx.attr.boringcrypto) _local_sdk(ctx, goroot) _go_local_sdk = repository_rule( @@ -231,6 +234,7 @@ _go_local_sdk = repository_rule( attrs = { "path": attr.string(), "version": attr.string(), + "boringcrypto": attr.bool(), }, ) @@ -263,7 +267,7 @@ def _go_wrap_sdk_impl(ctx): goroot = str(ctx.path(root_file).dirname) platform = _detect_sdk_platform(ctx, goroot) version = _detect_sdk_version(ctx, goroot) - _sdk_build_file(ctx, platform, version) + _sdk_build_file(ctx, platform, version, ctx.attr.boringcrypto) _local_sdk(ctx, goroot) _go_wrap_sdk = repository_rule( @@ -278,6 +282,7 @@ _go_wrap_sdk = repository_rule( doc = "A set of mappings from the host platform to a file in the SDK's root directory", ), "version": attr.string(), + "boringcrypto": attr.bool(), }, ) @@ -334,7 +339,7 @@ def _local_sdk(ctx, path): for entry in ["src", "pkg", "bin", "lib", "misc"]: ctx.symlink(path + "/" + entry, entry) -def _sdk_build_file(ctx, platform, version): +def _sdk_build_file(ctx, platform, version, boringcrypto): ctx.file("ROOT") goos, _, goarch = platform.partition("_") @@ -347,6 +352,7 @@ def _sdk_build_file(ctx, platform, version): "{goarch}": goarch, "{exe}": ".exe" if goos == "windows" else "", "{rules_go_repo_name}": "io_bazel_rules_go", + "{boringcrypto}": str(boringcrypto), }, ) @@ -427,7 +433,7 @@ def _detect_sdk_version(ctx, goroot): output_parts = result.stdout.split(" ") if len(output_parts) > 2 and output_parts[2].startswith("go"): version = output_parts[2][len("go"):] - if len(output_parts) > 3 and output_parts[2] == "devel" and output_parts[3].startswith("go"): + elif len(output_parts) > 3 and output_parts[2] == "devel" and output_parts[3].startswith("go"): version = output_parts[3][len("go"):] else: fail("Could not parse SDK version from '%s version' output: %s" % (go_binary_path, result.stdout)) @@ -519,7 +525,7 @@ def _version_string(v): v = v[:-1] return ".".join([str(n) for n in v]) + suffix -def go_register_toolchains(version = None, nogo = None, go_version = None): +def go_register_toolchains(version = None, nogo = None, go_version = None, boringcrypto = None): """See /go/toolchains.rst#go-register-toolchains for full documentation.""" if not version: version = go_version # old name @@ -537,16 +543,19 @@ def go_register_toolchains(version = None, nogo = None, go_version = None): if not version: fail('go_register_toolchains: version must be a string like "1.15.5" or "host"') elif version == "host": - go_host_sdk(name = "go_sdk") + go_host_sdk(name = "go_sdk", boringcrypto = boringcrypto) else: pv = _parse_version(version) if not pv: fail('go_register_toolchains: version must be a string like "1.15.5" or "host"') if _version_less(pv, MIN_SUPPORTED_VERSION): print("DEPRECATED: Go versions before {} are not supported and may not work".format(_version_string(MIN_SUPPORTED_VERSION))) + if boringcrypto and _version_less(pv, MIN_BORINGCRYPTO_VERSION): + fail("go_register_toolchains: boringcrypto is only supported for versions 1.19.0 and above") go_download_sdk( name = "go_sdk", version = version, + boringcrypto = boringcrypto, ) if nogo: diff --git a/go/tools/builders/compilepkg.go b/go/tools/builders/compilepkg.go index 6dfbc073f5..f927ed4963 100644 --- a/go/tools/builders/compilepkg.go +++ b/go/tools/builders/compilepkg.go @@ -56,6 +56,7 @@ func compilePkg(args []string) error { var testFilter string var gcFlags, asmFlags, cppFlags, cFlags, cxxFlags, objcFlags, objcxxFlags, ldFlags quoteMultiFlag var coverFormat string + var boringcrypto bool fs.Var(&unfilteredSrcs, "src", ".go, .c, .cc, .m, .mm, .s, or .S file to be filtered and compiled") fs.Var(&coverSrcs, "cover", ".go file that should be instrumented for coverage (must also be a -src)") fs.Var(&embedSrcs, "embedsrc", "file that may be compiled into the package with a //go:embed directive") @@ -71,6 +72,7 @@ func compilePkg(args []string) error { fs.Var(&cxxFlags, "cxxflags", "C++ compiler flags") fs.Var(&objcFlags, "objcflags", "Objective-C compiler flags") fs.Var(&objcxxFlags, "objcxxflags", "Objective-C++ compiler flags") + fs.BoolVar(&boringcrypto, "boringcrypto", false, "Build stdlib with boringcrypto") fs.Var(&ldFlags, "ldflags", "C linker flags") fs.StringVar(&nogoPath, "nogo", "", "The nogo binary. If unset, nogo will not be run.") fs.StringVar(&packageListPath, "package_list", "", "The file containing the list of standard library packages") @@ -130,6 +132,10 @@ func compilePkg(args []string) error { return fmt.Errorf("invalid test filter %q", testFilter) } + if boringcrypto { + os.Setenv("GOEXPERIMENT", "boringcrypto") + } + return compileArchive( goenv, importPath, diff --git a/go/tools/builders/link.go b/go/tools/builders/link.go index 78b24688ec..b2bcc3d58b 100644 --- a/go/tools/builders/link.go +++ b/go/tools/builders/link.go @@ -62,6 +62,7 @@ func link(args []string) error { flags.Var(&archives, "arc", "Label, package path, and file name of a dependency, separated by '='") packageList := flags.String("package_list", "", "The file containing the list of standard library packages") buildmode := flags.String("buildmode", "", "Build mode used.") + boringcrypto := flags.Bool("boringcrypto", false, "set boringcrypto GOEXPERIMENT") flags.Var(&xdefs, "X", "A string variable to replace in the linked binary (repeated).") flags.Var(&stamps, "stamp", "The name of a file with stamping values.") conflictErrMsg := flags.String("conflict_err", "", "Error message about conflicts to report if there's a link error.") @@ -158,6 +159,10 @@ func link(args []string) error { } goargs = append(goargs, "-o", *outFile) + if *boringcrypto { + os.Setenv("GOEXPERIMENT", "boringcrypto") + } + // add in the unprocess pass through options goargs = append(goargs, toolArgs...) goargs = append(goargs, *main) diff --git a/go/tools/builders/stdlib.go b/go/tools/builders/stdlib.go index 8c03630c80..a4f0ae6877 100644 --- a/go/tools/builders/stdlib.go +++ b/go/tools/builders/stdlib.go @@ -33,6 +33,7 @@ func stdlib(args []string) error { race := flags.Bool("race", false, "Build in race mode") shared := flags.Bool("shared", false, "Build in shared mode") dynlink := flags.Bool("dynlink", false, "Build in dynlink mode") + boringcrypto := flags.Bool("boringcrypto", false, "Build stdlib with boringcrypto") if err := flags.Parse(args); err != nil { return err } @@ -109,6 +110,10 @@ You may need to use the flags --cpu=x64_windows --compiler=mingw-gcc.`) } os.Setenv("CGO_LDFLAGS_ALLOW", b.String()) + if *boringcrypto { + os.Setenv("GOEXPERIMENT", "boringcrypto") + } + // Build the commands needed to build the std library in the right mode // NOTE: the go command stamps compiled .a files with build ids, which are // cryptographic sums derived from the inputs. This prevents us from diff --git a/tests/core/boringcrypto/BUILD.bazel b/tests/core/boringcrypto/BUILD.bazel new file mode 100644 index 0000000000..9de03bb09f --- /dev/null +++ b/tests/core/boringcrypto/BUILD.bazel @@ -0,0 +1,6 @@ +load("@io_bazel_rules_go//go/tools/bazel_testing:def.bzl", "go_bazel_test") + +go_bazel_test( + name = "boringcrypto_test", + srcs = ["boringcrypto_test.go"], +) diff --git a/tests/core/boringcrypto/README.rst b/tests/core/boringcrypto/README.rst new file mode 100644 index 0000000000..cd084f333d --- /dev/null +++ b/tests/core/boringcrypto/README.rst @@ -0,0 +1,11 @@ +Boringcrypto +=========== + +Tests to ensure that support for building with boringcrypto is working as expected. + +boringcrypto_test +-------------- + +Test that the build is failed if a non-local Go version less than 1.19 is requested to be built with +boringcrypto. Test that binaries built with boringcrypto stdlib have X:boringcrypto in version +information. \ No newline at end of file diff --git a/tests/core/boringcrypto/boringcrypto_test.go b/tests/core/boringcrypto/boringcrypto_test.go new file mode 100644 index 0000000000..803c033acb --- /dev/null +++ b/tests/core/boringcrypto/boringcrypto_test.go @@ -0,0 +1,137 @@ +// Copyright 2022 The Bazel Authors. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package boringcrypto_test + +import ( + "bytes" + "os" + "os/exec" + "strings" + "testing" + + "github.com/bazelbuild/rules_go/go/tools/bazel_testing" +) + +func TestMain(m *testing.M) { + bazel_testing.TestMain(m, bazel_testing.Args{ + Main: ` +-- BUILD.bazel -- +load("@io_bazel_rules_go//go:def.bzl", "go_binary", "go_library") + +go_binary( + name = "program", + srcs = ["main.go"], + deps = [":library"], + visibility = ["//visibility:public"], +) + +go_library( + name = "library", + srcs = ["library.go"], + importpath = "example.com/library" +) +-- main.go -- +package main + +import "example.com/library" + +func main() { + library.F() +} +-- library.go -- +package library + +func F() {} +`, + }) +} + +const origWrapSDK = `go_wrap_sdk( + name = "go_sdk", + root_file = "@local_go_sdk//:ROOT", +)` + +const wrapSDKBoringcrypto = `go_wrap_sdk( + name = "go_sdk", + root_file = "@local_go_sdk//:ROOT", + boringcrypto = True, +)` + +func TestBoringcryptoExperimentPresent(t *testing.T) { + mustReplaceInFile(t, "WORKSPACE", origWrapSDK, wrapSDKBoringcrypto) + defer mustReplaceInFile(t, "WORKSPACE", wrapSDKBoringcrypto, origWrapSDK) + + if _, err := exec.LookPath("go"); err != nil { + t.Skip("go command is necessary to evaluate if boringcrypto experiment is present") + } + + cmd := bazel_testing.BazelCmd("build", "//:program") + cmd.Stdout = os.Stdout + cmd.Stderr = os.Stdout + if err := cmd.Run(); err != nil { + t.Fatal("failed to run bazel build: ", err) + } + + out, err := exec.Command("go", "version", "bazel-bin/program_/program").CombinedOutput() + if err != nil { + t.Fatalf("failed to run go version command: %v\noutput was:\n%v", err, string(out)) + } + + if !strings.Contains(string(out), "X:boringcrypto") { + t.Fatalf(`version of binary: got %q, want string containing "X:boringcrypto"`, string(out)) + } +} + +func TestGoRegisterToolchainsChecksVersion(t *testing.T) { + const ( + from = `go_wrap_sdk( + name = "go_sdk", + root_file = "@local_go_sdk//:ROOT", +) + +go_register_toolchains()` + to = `go_register_toolchains(version = "1.18.0", boringcrypto = True)` + ) + mustReplaceInFile(t, "WORKSPACE", from, to) + defer mustReplaceInFile(t, "WORKSPACE", to, from) + + out, err := bazel_testing.BazelCmd("build", "//:program").CombinedOutput() + if err == nil { + t.Fatal("bazel build succeeded; expected command failure\n output:", string(out)) + } + + wantMsg := "go_register_toolchains: boringcrypto is only supported for versions 1.19.0 and above" + if !strings.Contains(string(out), wantMsg) { + t.Fatalf("output of bazel build: expected to contain %q\ngot %v", wantMsg, string(out)) + } +} + +func mustReplaceInFile(t *testing.T, path, old, new string) { + t.Helper() + if old == new { + return + } + data, err := os.ReadFile(path) + if err != nil { + t.Fatal(err) + } + if !bytes.Contains(data, []byte(old)) { + t.Fatalf("bytes to replace %q not found in file %q with contents, %q", old, path, data) + } + data = bytes.ReplaceAll(data, []byte(old), []byte(new)) + if err := os.WriteFile(path, data, 0666); err != nil { + t.Fatal(err) + } +}