From 7b0091d45c355faf9110a1ad939752512acd0910 Mon Sep 17 00:00:00 2001 From: Ian Cottrell Date: Thu, 7 Dec 2017 18:01:05 -0500 Subject: [PATCH] Allow pure go cross compilation within a single build (#1114) This adds goos and goarch attributes to a go_binary If specified they force the binary to cross compile to the specified architecture. It also adds a test that uses this to cross compile and verify the binary was correctly cross compiled. This also required multiple rules for the same binary in different modes, which meant we needed control over the binary file name (so not using the rule name) Fixes #1104 Fixes #1030 --- go/private/actions/action.bzl | 4 +- go/private/actions/binary.bzl | 2 +- go/private/actions/compile.bzl | 2 +- go/private/actions/link.bzl | 2 +- go/private/common.bzl | 14 +++--- go/private/go_toolchain.bzl | 53 +++++++++++------------ go/private/mode.bzl | 53 +++++++++++++++-------- go/private/rules/aspect.bzl | 6 +++ go/private/rules/binary.bzl | 13 +++++- go/private/rules/stdlib.bzl | 2 + tests/cross/BUILD.bazel | 44 +++++++++++++++++++ tests/cross/cross_test.go | 79 ++++++++++++++++++++++++++++++++++ tests/cross/main.go | 24 +++++++++++ 13 files changed, 237 insertions(+), 61 deletions(-) create mode 100644 tests/cross/BUILD.bazel create mode 100644 tests/cross/cross_test.go create mode 100644 tests/cross/main.go diff --git a/go/private/actions/action.bzl b/go/private/actions/action.bzl index 3c533bf667..846d4c326a 100644 --- a/go/private/actions/action.bzl +++ b/go/private/actions/action.bzl @@ -22,8 +22,8 @@ def add_go_env(args, stdlib, mode): "-cgo=" + ("0" if mode.pure else "1"), ]) -def bootstrap_action(ctx, go_toolchain, inputs, outputs, mnemonic, arguments): - stdlib = go_toolchain.stdlib.cgo +def bootstrap_action(ctx, go_toolchain, mode, inputs, outputs, mnemonic, arguments): + stdlib = go_toolchain.stdlib.get(ctx, go_toolchain, mode) ctx.actions.run_shell( inputs = inputs + stdlib.files, outputs = outputs, diff --git a/go/private/actions/binary.bzl b/go/private/actions/binary.bzl index 835f037b3b..bf016b6b25 100644 --- a/go/private/actions/binary.bzl +++ b/go/private/actions/binary.bzl @@ -39,7 +39,7 @@ def emit_binary(ctx, go_toolchain, importpath = importpath, importable = False, ) - executable = declare_file(ctx, ext=go_toolchain.data.extension, mode=mode) + executable = declare_file(ctx, name=name, ext=go_toolchain.data.extension, mode=mode) go_toolchain.actions.link(ctx, go_toolchain = go_toolchain, archive=goarchive, diff --git a/go/private/actions/compile.bzl b/go/private/actions/compile.bzl index ff7b0b233c..32041ea39a 100644 --- a/go/private/actions/compile.bzl +++ b/go/private/actions/compile.bzl @@ -95,7 +95,7 @@ def bootstrap_compile(ctx, go_toolchain, args = ["tool", "compile", "-o", out_lib.path] args.extend(gc_goopts) args.extend([s.path for s in sources]) - bootstrap_action(ctx, go_toolchain, + bootstrap_action(ctx, go_toolchain, mode, inputs = sources, outputs = [out_lib], mnemonic = "GoCompile", diff --git a/go/private/actions/link.bzl b/go/private/actions/link.bzl index 7078b51338..71f73acce5 100644 --- a/go/private/actions/link.bzl +++ b/go/private/actions/link.bzl @@ -138,7 +138,7 @@ def bootstrap_link(ctx, go_toolchain, args = ["tool", "link", "-o", executable.path] args.extend(gc_linkopts) args.append(archive.data.file.path) - bootstrap_action(ctx, go_toolchain, + bootstrap_action(ctx, go_toolchain, mode, inputs = inputs, outputs = [executable], mnemonic = "GoCompile", diff --git a/go/private/common.bzl b/go/private/common.bzl index 5a552441e2..0e3329c2ed 100644 --- a/go/private/common.bzl +++ b/go/private/common.bzl @@ -145,13 +145,13 @@ def to_set(v): fail("Do not pass a depset to to_set") return depset(v) -def declare_file(ctx, path="", ext="", mode=None): - name = "" +def declare_file(ctx, path="", ext="", mode=None, name = ""): + filename = "" if mode: - name += mode_string(mode) + "/" - name += ctx.label.name + filename += mode_string(mode) + "/" + filename += name if name else ctx.label.name if path: - name += "~/" + path + filename += "~/" + path if ext: - name += ext - return ctx.actions.declare_file(name) \ No newline at end of file + filename += ext + return ctx.actions.declare_file(filename) \ No newline at end of file diff --git a/go/private/go_toolchain.bzl b/go/private/go_toolchain.bzl index c26badb352..182b98cfcb 100644 --- a/go/private/go_toolchain.bzl +++ b/go/private/go_toolchain.bzl @@ -23,16 +23,18 @@ load("@io_bazel_rules_go//go/private:actions/library.bzl", "emit_library") load("@io_bazel_rules_go//go/private:actions/link.bzl", "emit_link", "bootstrap_link") load("@io_bazel_rules_go//go/private:actions/pack.bzl", "emit_pack") load("@io_bazel_rules_go//go/private:providers.bzl", "GoStdLib") +load("@io_bazel_rules_go//go/platform:list.bzl", "GOOS_GOARCH") +load("@io_bazel_rules_go//go/private:mode.bzl", "mode_string") def _get_stdlib(ctx, go_toolchain, mode): - if mode.race and mode.pure: - return go_toolchain.stdlib.pure_race - elif mode.pure: - return go_toolchain.stdlib.pure - elif mode.race: - return go_toolchain.stdlib.cgo_race - else: - return go_toolchain.stdlib.cgo + for stdlib in go_toolchain.stdlib.all: + stdlib = stdlib[GoStdLib] + if (stdlib.goos == mode.goos and + stdlib.goarch == mode.goarch and + stdlib.race == mode.race and + stdlib.pure == mode.pure): + return stdlib + fail("No matching standard library for "+mode_string(mode)) def _goos_to_extension(goos): if goos == "windows": @@ -43,13 +45,10 @@ def _go_toolchain_impl(ctx): return [platform_common.ToolchainInfo( name = ctx.label.name, cross_compile = ctx.attr.cross_compile, - goos = ctx.attr.goos, - goarch = ctx.attr.goarch, + default_goos = ctx.attr.goos, + default_goarch = ctx.attr.goarch, stdlib = struct( - cgo = ctx.attr._stdlib_cgo[GoStdLib], - pure = ctx.attr._stdlib_pure[GoStdLib], - cgo_race = ctx.attr._stdlib_cgo_race[GoStdLib], - pure_race = ctx.attr._stdlib_pure_race[GoStdLib], + all = ctx.attr._stdlib_all, get = _get_stdlib, ), actions = struct( @@ -83,17 +82,16 @@ def _go_toolchain_impl(ctx): ), )] -def _stdlib_cgo(goos, goarch): - return Label("@go_stdlib_{}_{}_cgo".format(goos, goarch)) - -def _stdlib_pure(goos, goarch): - return Label("@go_stdlib_{}_{}_pure".format(goos, goarch)) - -def _stdlib_cgo_race(goos, goarch): - return Label("@go_stdlib_{}_{}_cgo_race".format(goos, goarch)) - -def _stdlib_pure_race(goos, goarch): - return Label("@go_stdlib_{}_{}_pure_race".format(goos, goarch)) +def _stdlib_all(): + stdlibs = [] + for goos, goarch in GOOS_GOARCH: + stdlibs.extend([ + Label("@go_stdlib_{}_{}_cgo".format(goos, goarch)), + Label("@go_stdlib_{}_{}_pure".format(goos, goarch)), + Label("@go_stdlib_{}_{}_cgo_race".format(goos, goarch)), + Label("@go_stdlib_{}_{}_pure_race".format(goos, goarch)), + ]) + return stdlibs def _asm(bootstrap): if bootstrap: @@ -150,10 +148,7 @@ _go_toolchain = rule( "_test_generator": attr.label(allow_files = True, single_file = True, executable = True, cfg = "host", default = _test_generator), "_cover": attr.label(allow_files = True, single_file = True, executable = True, cfg = "host", default = _cover), # Hidden internal attributes - "_stdlib_cgo": attr.label(allow_files = True, default = _stdlib_cgo), - "_stdlib_pure": attr.label(allow_files = True, default = _stdlib_pure), - "_stdlib_cgo_race": attr.label(allow_files = True, default = _stdlib_cgo_race), - "_stdlib_pure_race": attr.label(allow_files = True, default = _stdlib_pure_race), + "_stdlib_all": attr.label_list(default = _stdlib_all()), "_crosstool": attr.label(default=Label("//tools/defaults:crosstool")), "_package_list": attr.label(allow_files = True, single_file = True, default="@go_sdk//:packages.txt"), }, diff --git a/go/private/mode.bzl b/go/private/mode.bzl index 62e161c6b5..19312e6eec 100644 --- a/go/private/mode.bzl +++ b/go/private/mode.bzl @@ -59,6 +59,23 @@ def get_mode(ctx, toolchain_flags): force_pure = go_toolchain.cross_compile #TODO: allow link mode selection + static = _ternary( + getattr(ctx.attr, "static", None), + "static" in ctx.features, + ) + race = _ternary( + getattr(ctx.attr, "race", None), + "race" in ctx.features, + ) + msan = _ternary( + getattr(ctx.attr, "msan", None), + "msan" in ctx.features, + ) + pure = _ternary( + getattr(ctx.attr, "pure", None), + force_pure, + "pure" in ctx.features, + ) debug = ctx.var["COMPILATION_MODE"] == "debug" strip_mode = "sometimes" if toolchain_flags: @@ -68,27 +85,25 @@ def get_mode(ctx, toolchain_flags): strip = True elif strip_mode == "sometimes": strip = not debug + goos = getattr(ctx.attr, "goos", None) + if goos == None or goos == "auto": + goos = go_toolchain.default_goos + elif not pure: + fail("If goos is set, pure must be true") + goarch = getattr(ctx.attr, "goarch", None) + if goarch == None or goarch == "auto": + goarch = go_toolchain.default_goarch + elif not pure: + fail("If goarch is set, pure must be true") + return struct( - static = _ternary( - getattr(ctx.attr, "static", None), - "static" in ctx.features, - ), - race = _ternary( - getattr(ctx.attr, "race", None), - "race" in ctx.features, - ), - msan = _ternary( - getattr(ctx.attr, "msan", None), - "msan" in ctx.features, - ), - pure = _ternary( - getattr(ctx.attr, "pure", None), - force_pure, - "pure" in ctx.features, - ), + static = static, + race = race, + msan = msan, + pure = pure, link = LINKMODE_NORMAL, debug = debug, strip = strip, - goos = go_toolchain.goos, - goarch = go_toolchain.goarch, + goos = goos, + goarch = goarch, ) diff --git a/go/private/rules/aspect.bzl b/go/private/rules/aspect.bzl index ddc80e2e3b..7d6ac26bca 100644 --- a/go/private/rules/aspect.bzl +++ b/go/private/rules/aspect.bzl @@ -28,6 +28,10 @@ load("@io_bazel_rules_go//go/private:providers.bzl", "GoArchiveData", "sources", ) +load("@io_bazel_rules_go//go/platform:list.bzl", + "GOOS", + "GOARCH", +) GoAspectProviders = provider() @@ -99,6 +103,8 @@ go_archive_aspect = aspect( "static": attr.string(values=["on", "off", "auto"]), "msan": attr.string(values=["on", "off", "auto"]), "race": attr.string(values=["on", "off", "auto"]), + "goos": attr.string(values=GOOS.keys() + ["auto"], default="auto"), + "goarch": attr.string(values=GOARCH.keys() + ["auto"], default="auto"), }, toolchains = ["@io_bazel_rules_go//go:toolchain"], ) diff --git a/go/private/rules/binary.bzl b/go/private/rules/binary.bzl index 3b3bdc4928..843f7ab4b6 100644 --- a/go/private/rules/binary.bzl +++ b/go/private/rules/binary.bzl @@ -28,6 +28,10 @@ load("@io_bazel_rules_go//go/private:providers.bzl", "GoSourceList", "sources", ) +load("@io_bazel_rules_go//go/platform:list.bzl", + "GOOS", + "GOARCH", +) def _go_binary_impl(ctx): """go_binary_impl emits actions for compiling and linking a go executable.""" @@ -36,8 +40,11 @@ def _go_binary_impl(ctx): else: go_toolchain = ctx.toolchains["@io_bazel_rules_go//go:bootstrap_toolchain"] gosource = collect_src(ctx) + name = ctx.attr.basename + if not name: + name = ctx.label.name golib, goarchive, executable = go_toolchain.actions.binary(ctx, go_toolchain, - name = ctx.label.name, + name = name, importpath = go_importpath(ctx), source = gosource, gc_linkopts = gc_linkopts(ctx), @@ -55,6 +62,7 @@ def _go_binary_impl(ctx): go_binary = rule( _go_binary_impl, attrs = { + "basename": attr.string(), "data": attr.label_list( allow_files = True, cfg = "data", @@ -67,6 +75,8 @@ go_binary = rule( "static": attr.string(values=["on", "off", "auto"], default="auto"), "race": attr.string(values=["on", "off", "auto"], default="auto"), "msan": attr.string(values=["on", "off", "auto"], default="auto"), + "goos": attr.string(values=GOOS.keys() + ["auto"], default="auto"), + "goarch": attr.string(values=GOARCH.keys() + ["auto"], default="auto"), "gc_goopts": attr.string_list(), "gc_linkopts": attr.string_list(), "linkstamp": attr.string(), @@ -82,6 +92,7 @@ go_binary = rule( go_tool_binary = rule( _go_binary_impl, attrs = { + "basename": attr.string(), "data": attr.label_list( allow_files = True, cfg = "data", diff --git a/go/private/rules/stdlib.bzl b/go/private/rules/stdlib.bzl index 6881559411..51c395e387 100644 --- a/go/private/rules/stdlib.bzl +++ b/go/private/rules/stdlib.bzl @@ -95,6 +95,8 @@ def _stdlib_impl(ctx): root_file = root_file, goos = ctx.attr.goos, goarch = ctx.attr.goarch, + race = ctx.attr.race, + pure = not ctx.attr.cgo, libs = [pkg], headers = [pkg], files = files, diff --git a/tests/cross/BUILD.bazel b/tests/cross/BUILD.bazel new file mode 100644 index 0000000000..18d407254f --- /dev/null +++ b/tests/cross/BUILD.bazel @@ -0,0 +1,44 @@ +load("@io_bazel_rules_go//go:def.bzl", "go_binary", "go_test", "go_source") + +go_source( + name = "cross", + srcs = ["main.go"], +) + +go_binary( + name = "windows", + embed = [":cross"], + basename = "cross", + goos = "windows", + goarch = "amd64", + pure = "on", +) + +go_binary( + name = "linux", + embed = [":cross"], + basename = "cross", + goos = "linux", + goarch = "amd64", + pure = "on", +) + +go_binary( + name = "darwin", + embed = [":cross"], + basename = "cross", + goos = "darwin", + goarch = "amd64", + pure = "on", +) + +go_test( + name = "cross_test", + size = "small", + srcs = ["cross_test.go"], + data = [ + ":linux", + ":darwin", + ":windows", + ], +) diff --git a/tests/cross/cross_test.go b/tests/cross/cross_test.go new file mode 100644 index 0000000000..b0c155bb22 --- /dev/null +++ b/tests/cross/cross_test.go @@ -0,0 +1,79 @@ +/* Copyright 2017 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 cross_test + +import ( + "os" + "os/exec" + "path/filepath" + "strings" + "testing" +) + +type check struct { + file string + info []string +} + +var checks = []check{ + {"darwin_amd64_pure_stripped/cross", []string{ + "Mach-O", + "64-bit", + "executable", + "x86_64", + }}, + {"linux_amd64_pure_stripped/cross", []string{ + "ELF", + "64-bit", + "executable", + "x86-64", + }}, + {"windows_amd64_pure_stripped/cross", []string{ + "PE32+", + "Windows", + "executable", + "console", + "x86-64", + }}, +} + +func TestCross(t *testing.T) { + for _, c := range checks { + if _, err := os.Stat(c.file); os.IsNotExist(err) { + t.Fatalf("Missing binary %v", c.file) + } + file, err := filepath.EvalSymlinks(c.file) + if err != nil { + t.Fatalf("Invalid filename %v", file) + } + cmd := exec.Command("file", file) + cmd.Stderr = os.Stderr + res, err := cmd.Output() + if err != nil { + t.Fatalf("failed running 'file': %v", err) + } + output := string(res) + if index := strings.Index(output, ":"); index >= 0 { + output = output[index+1:] + } + output = strings.TrimSpace(output) + for _, info := range c.info { + if !strings.Contains(output, info) { + t.Errorf("incorrect type for %v\nExpected %v\nGot %v", file, info, output) + } + } + } +} diff --git a/tests/cross/main.go b/tests/cross/main.go new file mode 100644 index 0000000000..7b2317cbb6 --- /dev/null +++ b/tests/cross/main.go @@ -0,0 +1,24 @@ +/* Copyright 2016 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 main + +import ( + "fmt" +) + +func main() { + fmt.Println("Goodbye") +}