| # 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. |
| |
| """Definitions of language_tool_layer and toolchain_container rules.""" |
| |
| load("@io_bazel_rules_docker//container:container.bzl", _container = "container") |
| load("@base_images_docker//package_managers:download_pkgs.bzl", _download = "download") |
| load("@base_images_docker//package_managers:install_pkgs.bzl", _install = "install") |
| load("@base_images_docker//package_managers:apt_key.bzl", _key = "key") |
| load(":debian_pkg_tar.bzl", _generate_deb_tar = "generate") |
| |
| def _input_validation(kwargs): |
| if "debs" in kwargs: |
| fail("debs is not supported.") |
| |
| if "packages" in kwargs and "installables_tar" in kwargs: |
| fail("'packages' and 'installables_tar' cannot be specified at the same time.") |
| |
| has_no_packages = "packages" not in kwargs or kwargs["packages"] == [] |
| if has_no_packages and "additional_repos" in kwargs: |
| fail("'additional_repos' can only be specified when 'packages' is not empty.") |
| if has_no_packages and "keys" in kwargs: |
| fail("'keys' can only be specified when 'packages' is not empty.") |
| |
| has_no_tar = "installables_tar" not in kwargs or kwargs["installables_tar"] == "" |
| has_no_layer = "language_layers" not in kwargs or kwargs["language_layers"] == [] |
| |
| if has_no_packages and has_no_tar and has_no_layer and "installation_cleanup_commands" in kwargs: |
| fail("'installation_cleanup_commands' can only be specified when at least " + |
| "one of 'packages', 'installables_tar' or 'language_layers' " + |
| "(if 'toolchain_container' rule) is not empty.") |
| |
| def _language_tool_layer_impl( |
| ctx, |
| symlinks = None, |
| env = None, |
| tars = None, |
| files = None, |
| packages = None, |
| additional_repos = None, |
| keys = None, |
| installables_tars = None, |
| installation_cleanup_commands = ""): |
| """Implementation for the language_tool_layer rule. |
| |
| Args: |
| ctx: ctx of container_image rule |
| (https://github.com/bazelbuild/rules_docker#container_image-1) + |
| ctx of download_pkgs rule |
| (https://github.com/GoogleCloudPlatform/base-images-docker/blob/master/package_managers/download_pkgs.bzl) + |
| ctx of install_pkgs rule |
| (https://github.com/GoogleCloudPlatform/base-images-docker/blob/master/package_managers/install_pkgs.bzl) + |
| some overrides. |
| symlinks: str Dict, overrides ctx.attr.symlinks |
| env: str Dict, overrides ctx.attr.env |
| tars: File list, overrides ctx.files.tars |
| files: File list, overrides ctx.files.files |
| packages: str List, overrides ctx.attr.packages |
| additional_repos: str List, overrides ctx.attr.additional_repos |
| keys: File list, overrides ctx.files.keys |
| installables_tars: File list, overrides [ctx.file.installables_tar] |
| installation_cleanup_commands: str, overrides ctx.attr.installation_cleanup_commands |
| |
| TODO(ngiraldo): add validations to restrict use of any other attrs. |
| """ |
| |
| symlinks = symlinks or ctx.attr.symlinks |
| env = env or ctx.attr.env |
| tars = tars or ctx.files.tars |
| files = files or ctx.files.files |
| packages = packages or ctx.attr.packages |
| additional_repos = additional_repos or ctx.attr.additional_repos |
| keys = keys or ctx.files.keys |
| installables_tars = installables_tars or [] |
| installation_cleanup_commands = installation_cleanup_commands or ctx.attr.installation_cleanup_commands |
| |
| # If ctx.file.installables_tar is specified, ignore 'packages' and other tars in installables_tars. |
| if ctx.file.installables_tar: |
| installables_tars = [ctx.file.installables_tar] |
| # Otherwise, download packages if packages list is not empty, and add the tar of downloaded |
| # debs to installables_tars. |
| |
| elif packages != []: |
| download_pkgs_output_tar = ctx.attr.name + "-download_pkgs_output_tar.tar" |
| download_pkgs_output_script = ctx.attr.name + "-download_pkgs_output_script.sh" |
| |
| aggregated_debian_tar = _generate_deb_tar.implementation( |
| ctx, |
| packages = packages, |
| additional_repos = additional_repos, |
| keys = keys, |
| download_pkgs_output_tar = download_pkgs_output_tar, |
| download_pkgs_output_script = download_pkgs_output_script, |
| ) |
| |
| installables_tars.append(aggregated_debian_tar.providers[0].installables_tar) |
| |
| # Prepare new base image for the container_image rule. |
| new_base = ctx.files.base[0] |
| |
| # Install debian packages in the base image. |
| if installables_tars != []: |
| # Create a list of paths of installables_tars. |
| installables_tars_paths = [] |
| for tar in installables_tars: |
| if tar: |
| installables_tars_paths.append(tar.path) |
| |
| # Declare file for final tarball of debian packages to install. |
| final_installables_tar = ctx.actions.declare_file(ctx.attr.name + "-packages.tar") |
| |
| # Combine all installables_tars into one tar. install_pkgs only takes a |
| # single installables_tar as input. |
| ctx.actions.run_shell( |
| inputs = installables_tars, |
| outputs = [final_installables_tar], |
| command = "tar cvf {output_tar} --files-from /dev/null && \ |
| for i in {input_tars}; do tar A --file={output_tar} $i; done".format( |
| output_tar = final_installables_tar.path, |
| input_tars = " ".join(installables_tars_paths), |
| ), |
| ) |
| |
| # Declare intermediate output file generated by install_pkgs rule. |
| install_pkgs_out = ctx.actions.declare_file(ctx.attr.name + "-with-packages.tar") |
| |
| # install_pkgs rule consumes 'final_installables_tar' and 'installation_cleanup_commands'. |
| _install.implementation( |
| ctx, |
| image_tar = ctx.files.base[0], |
| installables_tar = final_installables_tar, |
| installation_cleanup_commands = installation_cleanup_commands, |
| output_image_name = ctx.attr.name + "-with-packages", |
| output_tar = install_pkgs_out, |
| ) |
| |
| # Set the image with packages installed to be the new base. |
| new_base = install_pkgs_out |
| |
| # Install tars and configure env, symlinks using the container_image rule. |
| result = _container.image.implementation(ctx, base = new_base, symlinks = symlinks, env = env, tars = tars, files = files) |
| |
| if hasattr(result.providers[1], "runfiles"): |
| result_runfiles = result.providers[1].runfiles |
| else: |
| result_runfiles = result.providers[1].default_runfiles |
| |
| return struct( |
| runfiles = result_runfiles, |
| files = result.providers[1].files, |
| container_parts = result.container_parts, |
| tars = tars, |
| input_files = files, |
| env = env, |
| symlinks = symlinks, |
| packages = packages, |
| additional_repos = additional_repos, |
| keys = keys, |
| installables_tar = ctx.file.installables_tar, |
| installation_cleanup_commands = installation_cleanup_commands, |
| ) |
| |
| language_tool_layer_attrs = _container.image.attrs + _key.attrs + _download.attrs + _install.attrs + { |
| # Redeclare following attributes as non-mandatory. |
| "image_tar": attr.label( |
| allow_files = True, |
| single_file = True, |
| ), |
| "image": attr.label( |
| allow_files = True, |
| single_file = True, |
| ), |
| "packages": attr.string_list(), |
| "keys": attr.label_list( |
| allow_files = True, |
| ), |
| "installables_tar": attr.label( |
| allow_files = True, |
| single_file = True, |
| ), |
| "output_image_name": attr.string(), |
| } |
| |
| language_tool_layer_ = rule( |
| attrs = language_tool_layer_attrs, |
| executable = True, |
| outputs = _container.image.outputs, |
| toolchains = ["@io_bazel_rules_docker//toolchains/docker:toolchain_type"], |
| implementation = _language_tool_layer_impl, |
| ) |
| |
| def language_tool_layer(**kwargs): |
| """A wrapper around attrs in container_image, download_pkgs and install_pkgs rules. |
| |
| Downloads and installs debian packages using |
| https://github.com/GoogleCloudPlatform/base-images-docker/tree/master/package_managers, |
| and configures the rest using https://github.com/bazelbuild/rules_docker#container_image-1. |
| |
| Args: |
| Same args as https://github.com/bazelbuild/rules_docker#container_image-1 |
| minus: |
| debs: debian packages should be listed in 'packages', or be included in |
| 'installables_tar' as .deb files. |
| plus: |
| packages: list of packages to fetch and install in the base image. |
| additional_repos: list of additional debian package repos to use, |
| in sources.list format. |
| keys: list of labels of additional gpg keys to use while downloading |
| packages. |
| installables_tar: a tar of debian packages to install in the base image. |
| installation_cleanup_commands: cleanup commands to run after package |
| installation. |
| |
| Note: |
| - 'additional_repos' can only be specified when 'packages' is speficified. |
| - 'installation_cleanup_commands' can only be specified when at least one of |
| 'packages' or 'installables_tar' is specified. |
| |
| Experimental rule. |
| """ |
| |
| _input_validation(kwargs) |
| |
| language_tool_layer_(**kwargs) |
| |
| def _toolchain_container_impl(ctx): |
| """Implementation for the toolchain_container rule. |
| |
| toolchain_container rule composes all attrs from itself and language_tool_layer(s), |
| and generates container using container_image rule. |
| |
| Args: |
| ctx: ctx as the same as for container_image + list of language_tool_layer(s) |
| https://github.com/bazelbuild/rules_docker#container_image |
| """ |
| |
| tars = [] |
| files = [] |
| env = {} |
| symlinks = {} |
| packages = [] |
| additional_repos = [] |
| keys = [] |
| installables_tars = [] |
| installation_cleanup_commands = "cd ." |
| |
| # TODO(ngiraldo): we rewrite env and symlinks if there are conficts, |
| # warn the user of conflicts or error out. |
| for layer in ctx.attr.language_layers: |
| tars.extend(layer.tars) |
| files.extend(layer.input_files) |
| env.update(layer.env) |
| symlinks.update(layer.symlinks) |
| packages.extend(layer.packages) |
| additional_repos.extend(layer.additional_repos) |
| keys.extend(layer.keys) |
| if layer.installables_tar: |
| installables_tars.append(layer.installables_tar) |
| if layer.installation_cleanup_commands: |
| installation_cleanup_commands += (" && " + layer.installation_cleanup_commands) |
| tars.extend(ctx.files.tars) |
| env.update(ctx.attr.env) |
| symlinks.update(ctx.attr.symlinks) |
| packages.extend(ctx.attr.packages) |
| additional_repos.extend(ctx.attr.additional_repos) |
| keys.extend(ctx.files.keys) |
| if ctx.attr.installation_cleanup_commands: |
| installation_cleanup_commands += (" && " + ctx.attr.installation_cleanup_commands) |
| |
| files = depset(files).to_list() |
| packages = depset(packages).to_list() |
| additional_repos = depset(additional_repos).to_list() |
| keys = depset(keys).to_list() |
| installables_tars = depset(installables_tars).to_list() |
| |
| return _language_tool_layer_impl( |
| ctx, |
| symlinks = symlinks, |
| env = env, |
| tars = tars, |
| files = files, |
| packages = packages, |
| additional_repos = additional_repos, |
| keys = keys, |
| installables_tars = installables_tars, |
| installation_cleanup_commands = installation_cleanup_commands, |
| ) |
| |
| toolchain_container_ = rule( |
| attrs = language_tool_layer_attrs + { |
| "language_layers": attr.label_list(), |
| }, |
| executable = True, |
| outputs = _container.image.outputs, |
| toolchains = ["@io_bazel_rules_docker//toolchains/docker:toolchain_type"], |
| implementation = _toolchain_container_impl, |
| ) |
| |
| def toolchain_container(**kwargs): |
| """Composes multiple language_tool_layers into a single resulting image. |
| |
| Args: |
| Same args as https://github.com/bazelbuild/rules_docker#container_image-1 |
| minus: |
| debs: debian packages should be listed in 'packages', or be included in |
| 'installables_tar' as .deb files. |
| plus: |
| language_layers: a list of language_tool_layer. |
| installables_tar: a tar of debian packages to install in the base image. |
| packages: list of packages to fetch and install in the base image. |
| additional_repos: list of additional debian package repos to use, |
| in sources.list format. |
| keys: list of labels of additional gpg keys to use while downloading |
| packages. |
| installation_cleanup_commands: cleanup commands to run after package |
| installation. |
| |
| If 'installables_tar' is specified in the 'toolchain_container' rule, then |
| 'packages' or 'installables_tar' specified in any of the 'language_layers' |
| passed to this 'toolchain_container' rule will be ignored. |
| |
| Experimental rule. |
| """ |
| |
| _input_validation(kwargs) |
| |
| toolchain_container_(**kwargs) |