Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add go_cross_binary rule for cross-compilation. #3261

Merged
merged 3 commits into from
Aug 25, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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_binary = "go_cross_binary")

go_library = _go_library
go_binary = _go_binary
go_test = _go_test
go_source = _go_source
go_path = _go_path
go_cross_binary = _go_cross_binary
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_binary"></a>

## go_cross_binary

<pre>
go_cross_binary(<a href="#go_cross_binary-name">name</a>, <a href="#go_cross_binary-platform">platform</a>, <a href="#go_cross_binary-sdk_version">sdk_version</a>, <a href="#go_cross_binary-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_binary-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_binary-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_binary-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_binary-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_binary = "go_cross_binary",
)

# 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_binary for full documentation.
go_cross_binary = _go_cross_binary

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 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.

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 == ";"
fmeum marked this conversation as resolved.
Show resolved Hide resolved

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 implementation 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,
]
fmeum marked this conversation as resolved.
Show resolved Hide resolved
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.
""",
JamesMBartlett marked this conversation as resolved.
Show resolved Hide resolved
),
"_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_binary = 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_binary", "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",
fmeum marked this conversation as resolved.
Show resolved Hide resolved
deps = [":platform_lib"],
)

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

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

go_cross_binary(
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_binary_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_binary: /docs/go/core/rules.md#go_cross_binary
.. _#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_binary`_ 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_binary`_ rule, with the ``sdk_version`` attribute set, produces an executable built with the correct Go SDK version.

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

Expand Down
Loading