Skip to content

Commit

Permalink
Add go_cross rule for cross-compilation.
Browse files Browse the repository at this point in the history
- Adds a `go_cross` rule that wraps a `go_binary` to generate a
  cross-compiled version of the binary.
- Supports compiling for a different platform, and/or a different golang
  SDK version.
- Adds docs for the new `go_cross` rule.
- Adds testing in `tests/core/cross` for the new `go_cross` rule.

Signed-off-by: James Bartlett <jamesbartlett@newrelic.com>
  • Loading branch information
JamesMBartlett committed Aug 16, 2022
1 parent d0c6770 commit dbae7c9
Show file tree
Hide file tree
Showing 11 changed files with 526 additions and 1 deletion.
1 change: 1 addition & 0 deletions docs/go/core/BUILD.bazel
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ bzl_library(
deps = [
"//go/private:rpath",
"//go/private/rules:binary",
"//go/private/rules:cross",
"//go/private/rules:library",
"//go/private/rules:library.bzl",
"//go/private/rules:source",
Expand Down
2 changes: 2 additions & 0 deletions docs/go/core/rules.bzl
Original file line number Diff line number Diff line change
Expand Up @@ -119,9 +119,11 @@ load("//go/private/rules:binary.bzl", _go_binary = "go_binary")
load("//go/private/rules:test.bzl", _go_test = "go_test")
load("//go/private/rules:source.bzl", _go_source = "go_source")
load("//go/private/tools:path.bzl", _go_path = "go_path")
load("//go/private/rules:cross.bzl", _go_cross = "go_cross")

go_library = _go_library
go_binary = _go_binary
go_test = _go_test
go_source = _go_source
go_path = _go_path
go_cross = _go_cross
33 changes: 33 additions & 0 deletions docs/go/core/rules.md
Original file line number Diff line number Diff line change
Expand Up @@ -177,6 +177,39 @@ This builds an executable from a set of source files,



<a id="#go_cross"></a>

## go_cross

<pre>
go_cross(<a href="#go_cross-name">name</a>, <a href="#go_cross-platform">platform</a>, <a href="#go_cross-sdk_version">sdk_version</a>, <a href="#go_cross-target">target</a>)
</pre>

This wraps an executable built by `go_binary` to cross compile it
for a different platform, and/or compile it using a different version
of the golang SDK.<br><br>
**Providers:**
<ul>
<li>[GoLibrary]</li>
<li>[GoSource]</li>
<li>[GoArchive]</li>
</ul>


### **Attributes**


| Name | Description | Type | Mandatory | Default |
| :------------- | :------------- | :------------- | :------------- | :------------- |
| <a id="go_cross-name"></a>name | A unique name for this target. | <a href="https://bazel.build/docs/build-ref.html#name">Name</a> | required | |
| <a id="go_cross-platform"></a>platform | The platform to cross compile the <code>target</code> for. If unspecified, the <code>target</code> will be compiled with the same platform as it would've with the original <code>go_binary</code> rule. | <a href="https://bazel.build/docs/build-ref.html#labels">Label</a> | optional | None |
| <a id="go_cross-sdk_version"></a>sdk_version | The golang SDK version to use for compiling the <code>target</code>. Supports specifying major, minor, and/or patch versions, eg. <code>"1"</code>, <code>"1.17"</code>, or <code>"1.17.1"</code>. The first Go SDK provider installed in the repo's workspace (via <code>go_download_sdk</code>, <code>go_wrap_sdk</code>, etc) that matches the specified version will be used for compiling the given <code>target</code>. If unspecified, the <code>target</code> will be compiled with the same SDK as it would've with the original <code>go_binary</code> rule. Transitions <code>target</code> by changing the <code>--@io_bazel_rules_go//go/toolchain:sdk_version</code> build flag to the value provided for <code>sdk_version</code> here. | String | optional | "" |
| <a id="go_cross-target"></a>target | Go binary target to transition to the given platform and/or sdk_version. | <a href="https://bazel.build/docs/build-ref.html#labels">Label</a> | required | |





<a id="#go_library"></a>

## go_library
Expand Down
7 changes: 7 additions & 0 deletions go/def.bzl
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,10 @@ load(
"//go/private/rules:nogo.bzl",
_nogo = "nogo_wrapper",
)
load(
"//go/private/rules:cross.bzl",
_go_cross = "go_cross",
)

# TOOLS_NOGO is a list of all analysis passes in
# golang.org/x/tools/go/analysis/passes.
Expand Down Expand Up @@ -164,6 +168,9 @@ go_source = _go_source
# See docs/go/core/rules.md#go_path for full documentation.
go_path = _go_path

# See docs/go/core/rules.md#go_cross for full documentation.
go_cross = _go_cross

def go_vet_test(*args, **kwargs):
fail("The go_vet_test rule has been removed. Please migrate to nogo instead, which supports vet tests.")

Expand Down
14 changes: 14 additions & 0 deletions go/private/rules/BUILD.bazel
Original file line number Diff line number Diff line change
Expand Up @@ -151,6 +151,19 @@ bzl_library(
],
)

bzl_library(
name = "cross",
srcs = ["cross.bzl"],
visibility = [
"//docs:__subpackages__",
"//go:__subpackages__",
],
deps = [
"//go/private:providers",
"//go/private/rules:transition",
],
)

bzl_library(
name = "wrappers",
srcs = ["wrappers.bzl"],
Expand All @@ -161,6 +174,7 @@ bzl_library(
deps = [
"//go/private/rules:binary",
"//go/private/rules:cgo",
"//go/private/rules:cross",
"//go/private/rules:library",
"//go/private/rules:test",
"//go/private/rules:transition",
Expand Down
141 changes: 141 additions & 0 deletions go/private/rules/cross.bzl
Original file line number Diff line number Diff line change
@@ -0,0 +1,141 @@
# Copyright 2014 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.

load(
"//go/private/rules:transition.bzl",
"go_cross_transition",
)
load(
"//go/private:providers.bzl",
"GoArchive",
"GoLibrary",
"GoSource",
)

def _is_windows(ctx):
return ctx.configuration.host_path_separator == ";"

WINDOWS_ERR_SCRIPT = """
@echo off
>&2 echo {}
exit /b 1
"""
UNIX_ERR_SCRIPT = """
>&2 echo '{}'
exit 1
"""

def _error_script(ctx):
errmsg = 'cannot run go_cross target "{}": underlying target "{}" is not executable'.format(
ctx.attr.name,
ctx.attr.target.label,
)
if _is_windows(ctx):
error_script = ctx.actions.declare_file("error.bat")
ctx.actions.write(error_script, WINDOWS_ERR_SCRIPT.format(errmsg), is_executable = True)
return error_script

error_script = ctx.actions.declare_file("error")
ctx.actions.write(error_script, UNIX_ERR_SCRIPT.format(errmsg), is_executable = True)
return error_script

def _go_cross_impl(ctx):
old_default_info = ctx.attr.target[DefaultInfo]
old_executable = old_default_info.files_to_run.executable

new_default_info = None
if old_executable:
# Bazel requires executable rules to created the executable themselves,
# so we create a symlink in this rule so that it appears this rule created its executable.
new_executable = ctx.actions.declare_file(ctx.attr.name)
ctx.actions.symlink(output = new_executable, target_file = old_executable)
new_default_info = DefaultInfo(
files = depset([new_executable]),
runfiles = old_default_info.default_runfiles,
executable = new_executable,
)
else:
# There's no way to determine if the underlying `go_binary` target is executable at loading time
# so we must set the `go_cross` rule to be always executable. If the `go_binary` target is not
# executable, we set the `go_cross` executable to a simple script that prints an error message
# when executed. This way users can still run a `go_cross` target using `bazel run` as long as
# the underlying `go_binary` target is executable.
error_script = _error_script(ctx)

# See the implementaion of `go_binary` for an explanation of the need for default vs data runfiles here.
new_default_info = DefaultInfo(
files = depset([error_script] + old_default_info.files.to_list()),
default_runfiles = old_default_info.default_runfiles,
data_runfiles = old_default_info.data_runfiles.merge(ctx.runfiles([error_script])),
executable = error_script,
)

providers = [
ctx.attr.target[provider]
for provider in [
GoLibrary,
GoSource,
GoArchive,
OutputGroupInfo,
CcInfo,
]
if provider in ctx.attr.target
]
return [new_default_info] + providers

_go_cross_kwargs = {
"implementation": _go_cross_impl,
"attrs": {
"target": attr.label(
doc = """Go binary target to transition to the given platform and/or sdk_version.
""",
providers = [GoLibrary, GoSource, GoArchive],
mandatory = True,
),
"platform": attr.label(
doc = """The platform to cross compile the `target` for.
If unspecified, the `target` will be compiled with the
same platform as it would've with the original `go_binary` rule.
""",
),
"sdk_version": attr.string(
doc = """The golang SDK version to use for compiling the `target`.
Supports specifying major, minor, and/or patch versions, eg. `"1"`,
`"1.17"`, or `"1.17.1"`. The first Go SDK provider installed in the
repo's workspace (via `go_download_sdk`, `go_wrap_sdk`, etc) that
matches the specified version will be used for compiling the given
`target`. If unspecified, the `target` will be compiled with the same
SDK as it would've with the original `go_binary` rule.
Transitions `target` by changing the `--@io_bazel_rules_go//go/toolchain:sdk_version`
build flag to the value provided for `sdk_version` here.
""",
),
"_allowlist_function_transition": attr.label(
default = "@bazel_tools//tools/allowlists/function_transition_allowlist",
),
},
"cfg": go_cross_transition,
"doc": """This wraps an executable built by `go_binary` to cross compile it
for a different platform, and/or compile it using a different version
of the golang SDK.<br><br>
**Providers:**
<ul>
<li>[GoLibrary]</li>
<li>[GoSource]</li>
<li>[GoArchive]</li>
</ul>
""",
}

go_cross = rule(executable = True, **_go_cross_kwargs)
22 changes: 22 additions & 0 deletions go/private/rules/transition.bzl
Original file line number Diff line number Diff line change
Expand Up @@ -400,3 +400,25 @@ def _set_ternary(settings, attr, name):
label = filter_transition_label("@io_bazel_rules_go//go/config:{}".format(name))
settings[label] = value == "on"
return value

_SDK_VERSION_BUILD_SETTING = filter_transition_label("@io_bazel_rules_go//go/toolchain:sdk_version")
TRANSITIONED_GO_CROSS_SETTING_KEYS = [
_SDK_VERSION_BUILD_SETTING,
"//command_line_option:platforms",
]

def _go_cross_transition_impl(settings, attr):
settings = dict(settings)
if attr.sdk_version != None:
settings[_SDK_VERSION_BUILD_SETTING] = attr.sdk_version

if attr.platform != None:
settings["//command_line_option:platforms"] = str(attr.platform)

return settings

go_cross_transition = transition(
implementation = _go_cross_transition_impl,
inputs = TRANSITIONED_GO_CROSS_SETTING_KEYS,
outputs = TRANSITIONED_GO_CROSS_SETTING_KEYS,
)
58 changes: 57 additions & 1 deletion tests/core/cross/BUILD.bazel
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
load("@io_bazel_rules_go//go:def.bzl", "go_binary", "go_library", "go_test")
load("@io_bazel_rules_go//go:def.bzl", "go_binary", "go_cross", "go_library", "go_test")
load("@io_bazel_rules_go//go/tools/bazel_testing:def.bzl", "go_bazel_test")
load(":def.bzl", "no_context_info")

Expand Down Expand Up @@ -33,6 +33,31 @@ go_binary(
deps = [":platform_lib"],
)

go_binary(
name = "native_bin",
srcs = ["main.go"],
pure = "on",
deps = [":platform_lib"],
)

go_cross(
name = "windows_go_cross",
platform = "@io_bazel_rules_go//go/toolchain:windows_amd64",
target = ":native_bin",
)

go_cross(
name = "linux_go_cross",
platform = "@io_bazel_rules_go//go/toolchain:linux_amd64",
target = ":native_bin",
)

go_cross(
name = "darwin_go_cross",
platform = "@io_bazel_rules_go//go/toolchain:darwin_amd64",
target = ":native_bin",
)

go_library(
name = "platform_lib",
srcs = select({
Expand Down Expand Up @@ -64,6 +89,27 @@ go_test(
deps = ["//go/tools/bazel:go_default_library"],
)

go_test(
name = "go_cross_test",
size = "small",
srcs = ["cross_test.go"],
args = [
"-darwin",
"$(location :darwin_go_cross)",
"-linux",
"$(location :linux_go_cross)",
"-windows",
"$(location :windows_go_cross)",
],
data = [
":darwin_go_cross",
":linux_go_cross",
":windows_go_cross",
],
rundir = ".",
deps = ["//go/tools/bazel:go_default_library"],
)

go_bazel_test(
name = "ios_select_test",
srcs = ["ios_select_test.go"],
Expand All @@ -74,6 +120,16 @@ go_bazel_test(
srcs = ["proto_test.go"],
)

go_bazel_test(
name = "sdk_version_test",
srcs = ["sdk_version_test.go"],
)

go_bazel_test(
name = "non_executable_test",
srcs = ["non_executable_test.go"],
)

no_context_info(
name = "no_context_info",
)
10 changes: 10 additions & 0 deletions tests/core/cross/README.rst
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ Cross compilation

.. _go_binary: /docs/go/core/rules.md#go_binary
.. _go_library: /docs/go/core/rules.md#go_library
.. _go_cross: /docs/go/core/rules.md#go_cross
.. _#2523: https://github.com/bazelbuild/rules_go/issues/2523

Tests to ensure that cross compilation is working as expected.
Expand Down Expand Up @@ -30,6 +31,15 @@ files have a ``goos`` suffix, so they will only be built on the right platform.
If the wrong source file is used or if all files are filtered out, the
`go_binary`_ will not build.

go_cross_test
-------------

Indentical test to ``cross_test`` except tests using a `go_cross`_ rule wrapping a `go_binary`_ instead of the ``goos`` and ``goarch`` attributes on a `go_binary`_.

sdk_version_test
----------------
Tests that a `go_binary`_ wrapped in a `go_cross`_ rule, with the ``sdk_version`` attribute set, produces an executable built with the correct Go SDK version.

ios_select_test
---------------

Expand Down
Loading

0 comments on commit dbae7c9

Please sign in to comment.