Skip to content

Commit

Permalink
Transition container image target platform
Browse files Browse the repository at this point in the history
Transition to the target platform associated with the `architecture` and
`operating_system` attributes on container image rules.

This change allows for container image rules to build the correct binary for
the target platform, regardless of the host platform. Container image rules
would require the use of the `target_compatible_with` attribute to prevent
mismatching host and target platforms building dependencies incorrectly.
Additionally, hosts which did not match the target platform had to explicitly
specify the target platform with the `--platforms` command-line option.

This change fixes the aforementioned issues and
bazelbuild#690.

Massive thank you to @joneshf for the initial source. It has been adapted to
automatically select the target platform associated with the container image,
as opposed to always using `@io_bazel_rules_go//go/toolchain:linux_amd64`.
  • Loading branch information
uhthomas committed Nov 23, 2021
1 parent 8f6a2aa commit ecf7f7e
Show file tree
Hide file tree
Showing 10 changed files with 117 additions and 29 deletions.
53 changes: 39 additions & 14 deletions container/image.bzl
Original file line number Diff line number Diff line change
Expand Up @@ -324,6 +324,7 @@ def _impl(
executable = True,
outputs = _container.image.outputs,
implementation = _impl,
cfg = _container.image.cfg,
)
Args:
Expand Down Expand Up @@ -625,7 +626,7 @@ _attrs = dicts.add(_layer.attrs, {
Acceptable formats: Integer or floating point seconds since Unix Epoch, RFC 3339 date/time.
This field supports stamp variables.
If not set, defaults to {BUILD_TIMESTAMP} when stamp = True, otherwise 0""",
),
"docker_run_flags": attr.string(
Expand All @@ -637,14 +638,14 @@ _attrs = dicts.add(_layer.attrs, {
doc = """List of entrypoints to add in the image.
See https://docs.docker.com/engine/reference/builder/#entrypoint
Set `entrypoint` to `None`, `[]` or `""` will set the `Entrypoint` of the image
to be `null`.
The behavior between using `""` and `[]` may differ.
Please see [#1448](https://github.com/bazelbuild/rules_docker/issues/1448)
for more details.
This field supports stamp variables.""",
),
"experimental_tarball_format": attr.string(
Expand All @@ -667,12 +668,12 @@ _attrs = dicts.add(_layer.attrs, {
),
"labels": attr.string_dict(
doc = """Dictionary from custom metadata names to their values.
See https://docs.docker.com/engine/reference/builder/#label
You can also put a file name prefixed by '@' as a value.
Then the value is replaced with the contents of the file.
Example:
labels = {
Expand All @@ -699,7 +700,7 @@ _attrs = dicts.add(_layer.attrs, {
),
"layers": attr.label_list(
doc = """List of `container_layer` targets.
The data from each `container_layer` will be part of container image,
and the environment variable will be available in the image as well.""",
providers = [LayerInfo],
Expand Down Expand Up @@ -731,7 +732,7 @@ _attrs = dicts.add(_layer.attrs, {
# Starlark doesn't support int_list...
"ports": attr.string_list(
doc = """List of ports to expose.
See https://docs.docker.com/engine/reference/builder/#expose""",
),
"repository": attr.string(
Expand All @@ -741,7 +742,7 @@ _attrs = dicts.add(_layer.attrs, {
Images generated by `container_image` are tagged by default to
`bazel/package_name:target` for a `container_image` target at
`//package/name:target`.
Setting this attribute to `gcr.io/dummy` would set the default tag to
`gcr.io/dummy/package_name:target`.""",
),
Expand All @@ -767,19 +768,22 @@ _attrs = dicts.add(_layer.attrs, {
),
"volumes": attr.string_list(
doc = """List of volumes to mount.
See https://docs.docker.com/engine/reference/builder/#volumes""",
),
"workdir": attr.string(
doc = """Initial working directory when running the Docker image.
See https://docs.docker.com/engine/reference/builder/#workdir
Because building the image never happens inside a Docker container,
this working directory does not affect the other actions (e.g., adding files).
This field supports stamp variables.""",
),
"_allowlist_function_transition": attr.label(
default = "@bazel_tools//tools/allowlists/function_transition_allowlist",
),
"_digester": attr.label(
default = "//container/go/cmd/digester",
cfg = "host",
Expand All @@ -799,19 +803,40 @@ _outputs["config_digest"] = "%{name}.json.sha256"

_outputs["build_script"] = "%{name}.executable"

def _image_transition_impl(settings, attr):
# Architecture aliases.
architecture = {
"386": "x86_32",
"amd64": "x86_64",
"ppc64le": "ppc",
}.get(attr.architecture, attr.architecture)
return dicts.add(settings, {
"//command_line_option:platforms": "//platforms:{}_{}".format(attr.operating_system, architecture),
})

_image_transition = transition(
implementation = _image_transition_impl,
inputs = [],
outputs = [
"//command_line_option:platforms",
],
)

image = struct(
attrs = _attrs,
outputs = _outputs,
implementation = _impl,
cfg = _image_transition,
)

container_image_ = rule(
attrs = _attrs,
attrs = image.attrs,
doc = "Called by the `container_image` macro with **kwargs, see below",
executable = True,
outputs = _outputs,
outputs = image.outputs,
toolchains = ["@io_bazel_rules_docker//toolchains/docker:toolchain_type"],
implementation = _impl,
implementation = image.implementation,
cfg = image.cfg,
)

# This validates the two forms of value accepted by
Expand Down
24 changes: 12 additions & 12 deletions container/layer.bzl
Original file line number Diff line number Diff line change
Expand Up @@ -294,7 +294,7 @@ _layer_attrs = dicts.add({
"compression_options": attr.string_list(),
"data_path": attr.string(
doc = """Root path of the files.
The directory structure from the files is preserved inside the
Docker image, but a prefix path determined by `data_path`
is removed from the directory structure. This path can
Expand All @@ -307,7 +307,7 @@ _layer_attrs = dicts.add({
"debs": attr.label_list(
allow_files = deb_filetype,
doc = """Debian packages to extract.
Deprecated: A list of debian packages that will be extracted in the Docker image.
Note that this doesn't actually install the packages. Installation needs apt
or apt-get which need to be executed within a running container which
Expand All @@ -316,7 +316,7 @@ _layer_attrs = dicts.add({
"directory": attr.string(
default = "/",
doc = """Target directory.
The directory in which to expand the specified files, defaulting to '/'.
Only makes sense accompanying one of files/tars/debs.""",
),
Expand All @@ -326,16 +326,16 @@ _layer_attrs = dicts.add({
"enable_mtime_preservation": attr.bool(default = False),
"env": attr.string_dict(
doc = """Dictionary from environment variable names to their values when running the Docker image.
See https://docs.docker.com/engine/reference/builder/#env
For example,
env = {
"FOO": "bar",
...
},
},
The values of this field support make variables (e.g., `$(FOO)`)
and stamp variables; keys support make variables as well.""",
),
Expand All @@ -357,9 +357,9 @@ _layer_attrs = dicts.add({
"portable_mtime": attr.bool(default = False),
"symlinks": attr.string_dict(
doc = """Symlinks to create in the Docker image.
For example,
symlinks = {
"/path/to/link": "/path/to/target",
...
Expand All @@ -369,7 +369,7 @@ _layer_attrs = dicts.add({
"tars": attr.label_list(
allow_files = tar_filetype,
doc = """Tar file to extract in the layer.
A list of tar files whose content should be in the Docker image.""",
),
}, _hash_tools, _layer_tools, _zip_tools)
Expand All @@ -387,10 +387,10 @@ layer = struct(

container_layer_ = rule(
doc = _DOC,
attrs = _layer_attrs,
attrs = layer.attrs,
executable = False,
outputs = _layer_outputs,
implementation = _impl,
outputs = layer.outputs,
implementation = layer.implementation,
toolchains = ["@io_bazel_rules_docker//toolchains/docker:toolchain_type"],
)

Expand Down
1 change: 1 addition & 0 deletions contrib/repro_test.bzl
Original file line number Diff line number Diff line change
Expand Up @@ -328,4 +328,5 @@ container_repro_test = rule(
}),
test = True,
toolchains = ["@io_bazel_rules_docker//toolchains/docker:toolchain_type"],
cfg = _container.image.cfg,
)
8 changes: 5 additions & 3 deletions docker/package_managers/apt_key.bzl
Original file line number Diff line number Diff line change
Expand Up @@ -155,12 +155,14 @@ key = struct(
attrs = _attrs,
outputs = _outputs,
implementation = _impl,
cfg = _container.image.cfg,
)

add_apt_key = rule(
attrs = _attrs,
outputs = _outputs,
implementation = _impl,
attrs = key.attrs,
outputs = key.outputs,
implementation = key.implementation,
toolchains = ["@io_bazel_rules_docker//toolchains/docker:toolchain_type"],
executable = True,
cfg = key.cfg,
)
2 changes: 2 additions & 0 deletions docker/toolchain_container/toolchain_container.bzl
Original file line number Diff line number Diff line change
Expand Up @@ -243,6 +243,7 @@ language_tool_layer_ = rule(
outputs = _container.image.outputs,
toolchains = ["@io_bazel_rules_docker//toolchains/docker:toolchain_type"],
implementation = _language_tool_layer_impl,
cfg = _container.image.cfg,
)

def language_tool_layer(**kwargs):
Expand Down Expand Up @@ -350,6 +351,7 @@ toolchain_container_ = rule(
outputs = _container.image.outputs,
toolchains = ["@io_bazel_rules_docker//toolchains/docker:toolchain_type"],
implementation = _toolchain_container_impl,
cfg = _container.image.cfg,
)

def toolchain_container(**kwargs):
Expand Down
1 change: 1 addition & 0 deletions docs/container.md
Original file line number Diff line number Diff line change
Expand Up @@ -414,6 +414,7 @@ You can write a customized container_image rule by writing something like:
executable = True,
outputs = _container.image.outputs,
implementation = _impl,
cfg = _container.image.cfg,
)


Expand Down
4 changes: 4 additions & 0 deletions java/image.bzl
Original file line number Diff line number Diff line change
Expand Up @@ -163,6 +163,7 @@ jar_dep_layer = rule(
outputs = lang_image.outputs,
toolchains = lang_image.toolchains,
implementation = _jar_dep_layer_impl,
cfg = lang_image.cfg,
)

def _jar_app_layer_impl(ctx):
Expand Down Expand Up @@ -255,6 +256,7 @@ jar_app_layer = rule(
outputs = _container.image.outputs,
toolchains = ["@io_bazel_rules_docker//toolchains/docker:toolchain_type"],
implementation = _jar_app_layer_impl,
cfg = _container.image.cfg,
)

def java_image(
Expand Down Expand Up @@ -362,6 +364,7 @@ _war_dep_layer = rule(
outputs = _container.image.outputs,
toolchains = ["@io_bazel_rules_docker//toolchains/docker:toolchain_type"],
implementation = _war_dep_layer_impl,
cfg = _container.image.cfg,
)

def _war_app_layer_impl(ctx):
Expand Down Expand Up @@ -400,6 +403,7 @@ _war_app_layer = rule(
outputs = _container.image.outputs,
toolchains = ["@io_bazel_rules_docker//toolchains/docker:toolchain_type"],
implementation = _war_app_layer_impl,
cfg = _container.image.cfg,
)

def war_image(name, base = None, deps = [], layers = [], **kwargs):
Expand Down
2 changes: 2 additions & 0 deletions lang/image.bzl
Original file line number Diff line number Diff line change
Expand Up @@ -272,6 +272,7 @@ image = struct(
outputs = _container.image.outputs,
toolchains = ["@io_bazel_rules_docker//toolchains/docker:toolchain_type"],
implementation = _app_layer_impl,
cfg = _container.image.cfg,
)

_app_layer = rule(
Expand All @@ -280,6 +281,7 @@ _app_layer = rule(
outputs = image.outputs,
toolchains = image.toolchains,
implementation = image.implementation,
cfg = image.cfg,
)

# Convenience function that instantiates the _app_layer rule and returns
Expand Down
2 changes: 2 additions & 0 deletions nodejs/image.bzl
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,7 @@ _dep_layer = rule(
outputs = lang_image.outputs,
toolchains = lang_image.toolchains,
implementation = _dep_layer_impl,
cfg = lang_image.cfg,
)

def _npm_deps_runfiles(dep):
Expand All @@ -107,6 +108,7 @@ _npm_deps_layer = rule(
outputs = lang_image.outputs,
toolchains = lang_image.toolchains,
implementation = _npm_deps_layer_impl,
cfg = lang_image.cfg,
)

def nodejs_image(
Expand Down
49 changes: 49 additions & 0 deletions platforms/BUILD
Original file line number Diff line number Diff line change
Expand Up @@ -56,3 +56,52 @@ platform(
],
parents = ["@buildkite_config//config:platform"],
)

# All OSs known to Bazel.
#
# curl -Lfs https://raw.githubusercontent.com/bazelbuild/platforms/main/os/BUILD | grep -Eo 'name = "\w+"' | grep -Eo '"\w+"' | grep -Ev 'srcs|os|none' | tr -d '"' | sort -u -f | xargs -I '{}' echo '"@platforms//os:{}",'
_OS_CONSTRAINTS = (
"@platforms//os:android",
"@platforms//os:freebsd",
"@platforms//os:linux",
"@platforms//os:netbsd",
"@platforms//os:openbsd",
"@platforms//os:qnx",
"@platforms//os:wasi",
"@platforms//os:windows",
)

# All CPUs known to Bazel.
#
# curl -Lfs https://raw.githubusercontent.com/bazelbuild/platforms/main/cpu/BUILD | grep -Eo 'name = "\w+"' | grep -Eo '"\w+"' | grep -Ev 'cpu|srcs' | tr -d '"' | sort -u -f | xargs -I '{}' echo '"@platforms//cpu:{}",'
_CPU_CONSTRAINTS = (
"@platforms//cpu:aarch64",
"@platforms//cpu:arm",
"@platforms//cpu:arm64",
"@platforms//cpu:arm64e",
"@platforms//cpu:arm64_32",
"@platforms//cpu:armv7",
"@platforms//cpu:armv7k",
"@platforms//cpu:i386",
"@platforms//cpu:mips64",
"@platforms//cpu:ppc",
"@platforms//cpu:riscv32",
"@platforms//cpu:riscv64",
"@platforms//cpu:s390x",
"@platforms//cpu:wasm32",
"@platforms//cpu:wasm64",
"@platforms//cpu:x86_32",
"@platforms//cpu:x86_64",
)

# Register all known Bazel platform combinations for use with transitions.
[platform(
name = "{}_{}".format(
os.rsplit(":", 1)[1],
cpu.rsplit(":", 1)[1],
),
constraint_values = [
os,
cpu,
],
) for os in _OS_CONSTRAINTS for cpu in _CPU_CONSTRAINTS]

0 comments on commit ecf7f7e

Please sign in to comment.