From da4ad1ca4dd1c704f6ce69ac6912c52580d3ba67 Mon Sep 17 00:00:00 2001 From: Thomas Date: Fri, 19 Nov 2021 21:39:09 +0000 Subject: [PATCH] Transition container image target platform 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 https://github.com/bazelbuild/rules_docker/issues/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`. --- container/image.bzl | 31 ++++++------ container/layer.bzl | 48 +++++++++++++----- container/transition.bzl | 18 +++++++ contrib/repro_test.bzl | 1 + docker/package_managers/apt_key.bzl | 8 +-- .../toolchain_container.bzl | 2 + docs/container.md | 1 + java/image.bzl | 4 ++ lang/image.bzl | 2 + nodejs/image.bzl | 2 + platforms/BUILD | 49 +++++++++++++++++++ 11 files changed, 137 insertions(+), 29 deletions(-) create mode 100644 container/transition.bzl diff --git a/container/image.bzl b/container/image.bzl index b40c07620..2dd3456aa 100644 --- a/container/image.bzl +++ b/container/image.bzl @@ -324,6 +324,7 @@ def _impl( executable = True, outputs = _container.image.outputs, implementation = _impl, + cfg = _container.image.cfg, ) Args: @@ -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( @@ -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( @@ -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 = { @@ -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], @@ -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( @@ -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`.""", ), @@ -767,14 +768,14 @@ _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). @@ -803,15 +804,17 @@ image = struct( attrs = _attrs, outputs = _outputs, implementation = _impl, + cfg = _layer.cfg, ) 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 diff --git a/container/layer.bzl b/container/layer.bzl index 38e978395..03172700a 100644 --- a/container/layer.bzl +++ b/container/layer.bzl @@ -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 @@ -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 @@ -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.""", ), @@ -326,7 +326,7 @@ _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, @@ -334,8 +334,8 @@ _layer_attrs = dicts.add({ env = { "FOO": "bar", ... - }, - + }, + The values of this field support make variables (e.g., `$(FOO)`) and stamp variables; keys support make variables as well.""", ), @@ -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", ... @@ -369,29 +369,53 @@ _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.""", ), + "_allowlist_function_transition": attr.label( + default = "@bazel_tools//tools/allowlists/function_transition_allowlist", + ), }, _hash_tools, _layer_tools, _zip_tools) _layer_outputs = { "layer": "%{name}-layer.tar", } +def _layer_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), + }) + +_layer_transition = transition( + implementation = _layer_transition_impl, + inputs = [], + outputs = [ + "//command_line_option:platforms", + ], +) + layer = struct( attrs = _layer_attrs, outputs = _layer_outputs, implementation = _impl, toolchains = ["@io_bazel_rules_docker//toolchains/docker:toolchain_type"], + cfg = _layer_transition, ) 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"], + cfg = layer.cfg, ) def container_layer(**kwargs): diff --git a/container/transition.bzl b/container/transition.bzl new file mode 100644 index 000000000..1c4c30bb1 --- /dev/null +++ b/container/transition.bzl @@ -0,0 +1,18 @@ +def _layer_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), + }) + +layer_transition = transition( + implementation = _layer_transition_impl, + inputs = [], + outputs = [ + "//command_line_option:platforms", + ], +) diff --git a/contrib/repro_test.bzl b/contrib/repro_test.bzl index d4e1d512c..82337c4fa 100644 --- a/contrib/repro_test.bzl +++ b/contrib/repro_test.bzl @@ -328,4 +328,5 @@ container_repro_test = rule( }), test = True, toolchains = ["@io_bazel_rules_docker//toolchains/docker:toolchain_type"], + cfg = _container.image.cfg, ) diff --git a/docker/package_managers/apt_key.bzl b/docker/package_managers/apt_key.bzl index 37595833f..8a7da87ab 100644 --- a/docker/package_managers/apt_key.bzl +++ b/docker/package_managers/apt_key.bzl @@ -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, ) diff --git a/docker/toolchain_container/toolchain_container.bzl b/docker/toolchain_container/toolchain_container.bzl index f8347d315..d93baf5aa 100644 --- a/docker/toolchain_container/toolchain_container.bzl +++ b/docker/toolchain_container/toolchain_container.bzl @@ -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): @@ -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): diff --git a/docs/container.md b/docs/container.md index ee339ba33..049212f82 100644 --- a/docs/container.md +++ b/docs/container.md @@ -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, ) diff --git a/java/image.bzl b/java/image.bzl index 87b220ae4..c7b5a1be3 100644 --- a/java/image.bzl +++ b/java/image.bzl @@ -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): @@ -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( @@ -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): @@ -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): diff --git a/lang/image.bzl b/lang/image.bzl index c2db8f495..216ed9306 100644 --- a/lang/image.bzl +++ b/lang/image.bzl @@ -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( @@ -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 diff --git a/nodejs/image.bzl b/nodejs/image.bzl index 7c7259651..88a1169d4 100644 --- a/nodejs/image.bzl +++ b/nodejs/image.bzl @@ -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): @@ -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( diff --git a/platforms/BUILD b/platforms/BUILD index 50b8593ed..5d05829c4 100644 --- a/platforms/BUILD +++ b/platforms/BUILD @@ -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]