Migrate to new package manager.

Change-Id: I82c65a7734eb0118b8a95309434eaded86d5c284
diff --git a/.bazelci/presubmit.yml b/.bazelci/presubmit.yml
index b4e6134..9b6d97e 100644
--- a/.bazelci/presubmit.yml
+++ b/.bazelci/presubmit.yml
@@ -2,29 +2,29 @@
 platforms:
   ubuntu1404:
     test_targets:
-    - "//test:debian-jessie-autoconfig_test"
-    - "//test:debian-jessie-bazel-head-autoconfig_test"
-    - "//test:debian-jessie-custom-bazel-version-autoconfig_test"
-    - "//test:debian-jessie-custom-bazel-rc-version-autoconfig_test"
-    - "//test:ubuntu-xenial-autoconfig_test"
-    - "//test:ubuntu-xenial-bazel-head-autoconfig_test"
-    - "//test:ubuntu-xenial-custom-bazel-version-autoconfig_test"
-    - "//test:ubuntu-xenial-custom-bazel-rc-version-autoconfig_test"
-    - "//test:debian8_clang_autoconfig_test"
+    - "//test/configs:debian-jessie-autoconfig_test"
+    - "//test/configs:debian-jessie-bazel-head-autoconfig_test"
+    - "//test/configs:debian-jessie-custom-bazel-version-autoconfig_test"
+    - "//test/configs:debian-jessie-custom-bazel-rc-version-autoconfig_test"
+    - "//test/configs:ubuntu-xenial-autoconfig_test"
+    - "//test/configs:ubuntu-xenial-bazel-head-autoconfig_test"
+    - "//test/configs:ubuntu-xenial-custom-bazel-version-autoconfig_test"
+    - "//test/configs:ubuntu-xenial-custom-bazel-rc-version-autoconfig_test"
+    - "//test/configs:debian8_clang_autoconfig_test"
     - "//rules:debian8-clang-0.2.0-bazel_0.9.0-autoconfig_test"
     - "//rules:debian8-clang-0.3.0-bazel_0.10.0-autoconfig_test"
     - "//container/debian8-clang-fully-loaded:fl-toolchain-test"
   ubuntu1604:
     test_targets:
-    - "//test:debian-jessie-autoconfig_test"
-    - "//test:debian-jessie-bazel-head-autoconfig_test"
-    - "//test:debian-jessie-custom-bazel-version-autoconfig_test"
-    - "//test:debian-jessie-custom-bazel-rc-version-autoconfig_test"
-    - "//test:ubuntu-xenial-autoconfig_test"
-    - "//test:ubuntu-xenial-bazel-head-autoconfig_test"
-    - "//test:ubuntu-xenial-custom-bazel-version-autoconfig_test"
-    - "//test:ubuntu-xenial-custom-bazel-rc-version-autoconfig_test"
-    - "//test:debian8_clang_autoconfig_test"
+    - "//test/configs:debian-jessie-autoconfig_test"
+    - "//test/configs:debian-jessie-bazel-head-autoconfig_test"
+    - "//test/configs:debian-jessie-custom-bazel-version-autoconfig_test"
+    - "//test/configs:debian-jessie-custom-bazel-rc-version-autoconfig_test"
+    - "//test/configs:ubuntu-xenial-autoconfig_test"
+    - "//test/configs:ubuntu-xenial-bazel-head-autoconfig_test"
+    - "//test/configs:ubuntu-xenial-custom-bazel-version-autoconfig_test"
+    - "//test/configs:ubuntu-xenial-custom-bazel-rc-version-autoconfig_test"
+    - "//test/configs:debian8_clang_autoconfig_test"
     - "//rules:debian8-clang-0.2.0-bazel_0.9.0-autoconfig_test"
     - "//rules:debian8-clang-0.3.0-bazel_0.10.0-autoconfig_test"
     - "//container/debian8-clang-fully-loaded:fl-toolchain-test"
diff --git a/WORKSPACE b/WORKSPACE
index 56e00cb..01e3bde 100644
--- a/WORKSPACE
+++ b/WORKSPACE
@@ -22,9 +22,9 @@
 # https://docs.bazel.build/versions/master/be/workspace.html#git_repository
 http_archive(
     name = "io_bazel_rules_docker",
-    sha256 = "d2e6408b8f8b03ad1f2172acadf979204dedd0423c5194a82a1301cc6d1c224b",
-    strip_prefix = "rules_docker-17f2587dc58642cd494b9d56890e6210f406380d",
-    urls = ["https://github.com/bazelbuild/rules_docker/archive/17f2587dc58642cd494b9d56890e6210f406380d.tar.gz"],
+    sha256 = "3af41275396fba9fcbb88741d946ac37e51c48888cbdaba2a58a2e75a492da91",
+    strip_prefix = "rules_docker-4d8ec6570a5313fb0128e2354f2bc4323685282a",
+    urls = ["https://github.com/bazelbuild/rules_docker/archive/4d8ec6570a5313fb0128e2354f2bc4323685282a.tar.gz"],
 )
 
 load(
@@ -78,9 +78,9 @@
 
 http_archive(
     name = "debian_docker",
-    sha256 = "15068de8576474c1852f48f8bedc547247d121266eae537b033d9864669b1294",
-    strip_prefix = "base-images-docker-9a938f030b6eb1068ed9842bc43e2ceb601fb753",
-    urls = ["https://github.com/GoogleCloudPlatform/base-images-docker/archive/9a938f030b6eb1068ed9842bc43e2ceb601fb753.tar.gz"],
+    sha256 = "36a80b159ce0e1095dd0af662255318fcf1d47190cd132d16eb829cae7a211d5",
+    strip_prefix = "base-images-docker-33624db0aaa8bc98192a6ba126afa6296b9ce952",
+    urls = ["https://github.com/GoogleCloudPlatform/base-images-docker/archive/33624db0aaa8bc98192a6ba126afa6296b9ce952.tar.gz"],
 )
 
 http_file(
@@ -93,9 +93,9 @@
 # https://docs.bazel.build/versions/master/be/workspace.html#git_repository
 http_archive(
     name = "distroless",
-    sha256 = "5401f820fddcc65fae34b5dc025ed522731aa55d89507078e63da85f420a0d63",
-    strip_prefix = "distroless-886114394dfed219001ec3b068b139a3456e49d4",
-    urls = ["https://github.com/GoogleCloudPlatform/distroless/archive/886114394dfed219001ec3b068b139a3456e49d4.tar.gz"],
+    sha256 = "44c5d3370df6983ef53cfc2347447c6595fea2d1951a1645660baf67657b8e23",
+    strip_prefix = "distroless-94b5126dbe06c2cb4dc74f7c9bfe6394b8e6e44c",
+    urls = ["https://github.com/GoogleCloudPlatform/distroless/archive/94b5126dbe06c2cb4dc74f7c9bfe6394b8e6e44c.tar.gz"],
 )
 
 load(
diff --git a/container/debian8-clang-fully-loaded/BUILD b/container/debian8-clang-fully-loaded/BUILD
index b96c194..2bd3b57 100644
--- a/container/debian8-clang-fully-loaded/BUILD
+++ b/container/debian8-clang-fully-loaded/BUILD
@@ -16,27 +16,14 @@
 
 package(default_visibility = ["//visibility:public"])
 
-load("@distroless//cacerts:cacerts.bzl", "cacerts")
-load("@jessie_package_bundle//file:packages.bzl", "packages")
 load(
     "//container/rules:docker_toolchains.bzl",
     "language_tool_layer",
     "toolchain_container",
-)
-load(
-    "//skylib:packages.bzl",
-    "base_layer_packages",
-    "clang_layer_packages",
-    "java_layer_packages",
-    "python_layer_packages",
+    "cleanup_commands",
 )
 load("@io_bazel_rules_docker//contrib:test.bzl", "container_test")
 
-cacerts(
-    name = "cacerts",
-    deb = packages["ca-certificates"],
-)
-
 toolchain_container(
     name = "fl-toolchain",
     base = "@debian8//image",
@@ -52,29 +39,36 @@
         "java-ltl",
         "python-ltl",
     ],
-    tags = ["manual"],
 )
 
 language_tool_layer(
     name = "base-ltl",
     base = "@debian8//image",
-    debs = base_layer_packages(),
-    tags = ["manual"],
-    tars = [
-        ":cacerts.tar",
+    packages = [
+        "binutils",
+        "ca-certificates",
+        "curl",
+        "ed",
+        "file",
+        "git",
+        "openssh-client",
+        "wget",
+        "zip",
+        "unzip",
     ],
 )
 
 language_tool_layer(
     name = "clang-ltl",
     base = "@debian8//image",
-    debs = clang_layer_packages(),
     env = {
         "CC": "/usr/local/bin/clang",
         "ASAN_SYMBOLIZER_PATH": "/usr/local/bin/llvm-symbolizer",
     },
-    tags = ["manual"],
     tars = ["//third_party/clang:tar"],
+    packages = [
+        "libstdc++-4.9-dev",
+    ],
 )
 
 language_tool_layer(
@@ -84,27 +78,32 @@
         "GOPATH": "/go",
         "PATH": "$PATH:/usr/local/go/bin",
     },
-    tags = ["manual"],
     tars = ["//third_party/golang:tar"],
 )
 
 language_tool_layer(
     name = "java-ltl",
     base = "@debian8//image",
-    debs = java_layer_packages(),
     env = {
         "JAVA_HOME": "/usr/lib/jvm/java-8-openjdk-amd64",
     },
     symlinks = {
         "/usr/bin/java": "/usr/lib/jvm/java-8-openjdk-amd64/jre/bin/java",
     },
-    tags = ["manual"],
+    packages = [
+        "openjdk-8-jdk-headless",
+        "openjdk-8-jre-headless",
+        "ca-certificates-java=20161107'*'",
+    ],
+    additional_repos = [
+        "deb http://deb.debian.org/debian jessie-backports main",
+    ],
+    installation_cleanup_commands = cleanup_commands()["java"],
 )
 
 language_tool_layer(
     name = "python-ltl",
     base = "@debian8//image",
-    debs = python_layer_packages(),
     env = {
         "PATH": "$PATH:/opt/python3.6/bin",
     },
@@ -113,8 +112,16 @@
         "/opt/python3.6/bin/python3": "/opt/python3.6/bin/python3.6",
         "/opt/python3.6/bin/pip3": "/opt/python3.6/bin/pip3.6",
     },
-    tags = ["manual"],
     tars = ["//third_party/python:tar"],
+    packages = [
+        "python-dev",
+        "python-numpy",
+        "python-pip",
+        "python3-dev",
+        "python3-numpy",
+        "python3-pip",
+    ],
+    installation_cleanup_commands = cleanup_commands()["python"],
 )
 
 container_test(
diff --git a/container/debian8-clang-fully-loaded/build.sh b/container/debian8-clang-fully-loaded/build.sh
index d7e3e57..b8f2398 100755
--- a/container/debian8-clang-fully-loaded/build.sh
+++ b/container/debian8-clang-fully-loaded/build.sh
@@ -26,6 +26,7 @@
     -p|--project            GCP project ID
     -c|--container          docker container name
     -t|--tag                docker tag for the image
+    -b|--bucket             GCS bucket to store the tarball of debian packages
 
 Optional parameters (when build with Google Cloud Container Builder):
     -a|--async              asynchronous execute Cloud Container Builder
@@ -34,9 +35,11 @@
     -l|--local              build container locally
 
 To build with Google Cloud Container Builder:
-$ ./build.sh -p my-gcp-project -c debian8-clang-fully-loaded -t latest
+$ ./build.sh -p my-gcp-project -c debian8-clang-fully-loaded -t latest -b my_bucket
 will produce docker images in Google Container Registry:
     gcr.io/my-gcp-project/debian8-clang-fully-loaded:{latest, clang_revision}
+and the debian packages installed will be packed as a tarball and stored in
+gs://my_bucket for future reference.
 
 To build locally:
 $ ./build.sh -l
@@ -69,6 +72,11 @@
         TAG=$1
         shift
         ;;
+      -b|--bucket)
+        shift
+        BUCKET=$1
+        shift
+        ;;
       -a|--async)
         ASYNC=" --async "
         shift
@@ -85,7 +93,7 @@
     esac
   done
 
-  if [[ ("$PROJECT" == "" || "$CONTAINER" == "" || "$TAG" == "") && "$LOCAL" == "" ]]; then
+  if [[ ("$PROJECT" == "" || "$CONTAINER" == "" || "$TAG" == "" || "$BUCKET" == "" ) && "$LOCAL" == "" ]]; then
      echo "Please specify all required options for building in Google Cloud Container Builder"
      show_usage
      exit 1
@@ -125,7 +133,7 @@
     # Start Google Cloud Container Builder
     gcloud container builds submit . \
       --config=${PROJECT_ROOT}/container/debian8-clang-fully-loaded/cloudbuild.yaml \
-      --substitutions _PROJECT=${PROJECT},_CONTAINER=${CONTAINER},_TAG=${TAG},_DIR=${DIR} \
+      --substitutions _PROJECT=${PROJECT},_CONTAINER=${CONTAINER},_TAG=${TAG},_DIR=${DIR},_BUCKET=${BUCKET} \
       ${ASYNC}
   fi
 }
diff --git a/container/debian8-clang-fully-loaded/cloudbuild.yaml b/container/debian8-clang-fully-loaded/cloudbuild.yaml
index 7d93cb5..535162a 100644
--- a/container/debian8-clang-fully-loaded/cloudbuild.yaml
+++ b/container/debian8-clang-fully-loaded/cloudbuild.yaml
@@ -17,7 +17,9 @@
 steps:
 # Step: build the fully loaded container using rules_docker
   - name: 'gcr.io/cloud-builders/bazel'
-    args: ['run' , '--verbose_failures', '--spawn_strategy=standalone', '--genrule_strategy=standalone', '//${_DIR}:fl-toolchain']
+    # Set Bazel output_base to /workspace, which is a mounted directory on Google Cloud Builder.
+    # This is to make sure Bazel generated files can be accessed by multiple containers.
+    args: ['--output_base=/workspace', 'run' , '--verbose_failures', '--spawn_strategy=standalone', '--genrule_strategy=standalone', '//${_DIR}:fl-toolchain']
     id: 'fl-container'
     waitFor:
     - "-" # wait for nothing - start immediately
@@ -35,6 +37,13 @@
     '--image', 'gcr.io/${_PROJECT}/${_CONTAINER}:${_TAG}',
     '--config', '/workspace/container/test/rbe-debian8.yaml']
 
+# Step: store the tarball of debian packages in GCS
+  - name: gcr.io/cloud-builders/gsutil
+    args: ['cp',
+           '/workspace/bazel-out/k8-fastbuild/bin/container/debian8-clang-fully-loaded/fl-toolchain-packages.tar',
+           'gs://${_BUCKET}/fl-toolchain-packages-${_TAG}.tar'
+    ]
+
 # Build the release-container
 images:
   - 'gcr.io/${_PROJECT}/${_CONTAINER}:${_TAG}'
diff --git a/container/rules/docker_toolchains.bzl b/container/rules/docker_toolchains.bzl
index 468430b..c999240 100644
--- a/container/rules/docker_toolchains.bzl
+++ b/container/rules/docker_toolchains.bzl
@@ -14,50 +14,184 @@
 
 """Definitions of language_tool_layer and toolchain_container rules."""
 
+load("@io_bazel_rules_docker//container:container.bzl", _container = "container")
+load("@debian_docker//package_managers:download_pkgs.bzl", _download = "download")
+load("@debian_docker//package_managers:install_pkgs.bzl", _install = "install")
 
-load(
-    "@io_bazel_rules_docker//container:container.bzl",
-    _container = "container",
-)
+def cleanup_commands():
+    return {
+        "java":"rm -rf /etc/ssl/certs/java/cacerts /tmp/hsperfdata_root/* /usr/lib/jvm/java-8-openjdk-amd64/jre/lib/amd64/server/classes.jsa",
+        "python":"rm -rf /usr/lib/python3.4/__pycache__/ /usr/lib/python3.4/idlelib/__pycache__/ /usr/lib/python3.4/asyncio/__pycache__/",
+    }
 
-
-def _language_tool_layer_impl(ctx):
+def _language_tool_layer_impl(ctx, symlinks=None, env=None, tars=None,
+                              files=None, packages=None, additional_repos=None,
+                              installables_tars=None, installation_cleanup_commands=""):
   """Implementation for the language_tool_layer rule.
 
   Args:
-    ctx: ctx as the same for container_image rule.
-    https://github.com/bazelbuild/rules_docker#container_image
+    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
+    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.
   """
-  result = _container.image.implementation(ctx)
+
+  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
+  installables_tars = installables_tars or []
+  if installables_tars == [] and ctx.file.installables_tar:
+    installables_tars = [ctx.file.installables_tar]
+  installation_cleanup_commands = installation_cleanup_commands or ctx.attr.installation_cleanup_commands
+
+  # Download packages if packages list is not empty.
+  if packages != []:
+    # Declare intermediate output file generated by download_pkgs rule.
+    download_pkgs_output_executable = ctx.actions.declare_file(ctx.attr.name + "-download_pkgs_output_executable.sh")
+    download_pkgs_output_tar = ctx.actions.declare_file(ctx.attr.name + "-download_pkgs_output_tar.tar")
+    download_pkgs_output_script = ctx.actions.declare_file(ctx.attr.name + "-download_pkgs_output_script.sh")
+
+    # download_pkgs rule consumes 'packages' and 'additional_repos'.
+    _download.implementation(
+      ctx,
+      image_tar=ctx.files.base[0],
+      packages=packages,
+      additional_repos=additional_repos,
+      output_executable=download_pkgs_output_executable,
+      output_tar=download_pkgs_output_tar,
+      output_script=download_pkgs_output_script
+    )
+    installables_tars.append(download_pkgs_output_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 intermediate output file generated by install_pkgs rule.
+    combined_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=[combined_installables_tar],
+      command="tar cvf {output_tar} --files-from /dev/null && tar --concatenate -v --file={output_tar} {input_tars}".format(
+        output_tar=combined_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 'combined_installables_tar' and 'installation_cleanup_commands'.
+    _install.implementation(
+      ctx,
+      image_tar=ctx.files.base[0],
+      installables_tar= combined_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)
+
   return struct(runfiles = result.runfiles,
                 files = result.files,
                 container_parts = result.container_parts,
-                base = ctx.attr.base,
-                debs = ctx.files.debs,
-                tars = ctx.files.tars,
-                input_files = ctx.files.files,
-                env = ctx.attr.env,
-                symlinks = ctx.attr.symlinks)
+                tars = tars,
+                input_files = files,
+                env = env,
+                symlinks = symlinks,
+                packages = packages,
+                additional_repos = additional_repos,
+                installables_tar = ctx.file.installables_tar,
+                installation_cleanup_commands = installation_cleanup_commands
+                )
+
+language_tool_layer_attrs = _container.image.attrs + _install.attrs + _download.attrs + {
+    # Redeclare following attributes as non-mandatory.
+    "image_tar": attr.label(
+        allow_files = True,
+        single_file = True,
+    ),
+    "packages": attr.string_list(),
+    "installables_tar": attr.label(
+        allow_files = True,
+        single_file = True,
+    ),
+    "output_image_name": attr.string(),
+}
 
 language_tool_layer_ = rule(
-    attrs = _container.image.attrs,
+    attrs = language_tool_layer_attrs,
     executable = True,
     outputs = _container.image.outputs,
     implementation = _language_tool_layer_impl,
 )
 
 def language_tool_layer(**kwargs):
-  """A thin wrapper around attrs in container_image rule.
+  """A wrapper around attrs in container_image, download_pkgs and install_pkgs rules.
 
-  All attrs in language_tool_layer will be passed into
-  toolchain_container rule.
+  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.
+      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' should also only be specified when
+      'packages' or 'installables_tar' is specified.
 
   Experimental rule.
   """
-  language_tool_layer_(**kwargs)
 
+  # Input validations
+  if "debs" in kwargs:
+    fail("debs is not supported in language_tool_layer.")
+
+  has_packages = "packages" not in kwargs or kwargs["packages"] == []
+  if has_packages and "additional_repos" in kwargs:
+    fail("'additional_repos' can only be specified when 'packages' is not empty.")
+
+  language_tool_layer_(**kwargs)
 
 def _toolchain_container_impl(ctx):
   """Implementation for the toolchain_container rule.
@@ -69,29 +203,57 @@
     ctx: ctx as the same as for container_image + list of language_tool_layer(s)
     https://github.com/bazelbuild/rules_docker#container_image
   """
-  debs = []
+
   tars = []
   files = []
   env = {}
   symlinks = {}
+  packages = []
+  additional_repos = []
+  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:
-    debs.extend(layer.debs)
     tars.extend(layer.tars)
     files.extend(layer.input_files)
     env.update(layer.env)
     symlinks.update(layer.symlinks)
-  debs.extend(ctx.attr.debs)
+    packages.extend(layer.packages)
+    additional_repos.extend(layer.additional_repos)
+    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.attr.tars)
   env.update(ctx.attr.env)
   symlinks.update(ctx.attr.symlinks)
-  debs = depset(debs).to_list()
+  packages.extend(ctx.attr.packages)
+  additional_repos.extend(ctx.attr.additional_repos)
+  if ctx.file.installables_tar:
+    installables_tars.append(ctx.file.installables_tar)
+  if ctx.attr.installation_cleanup_commands:
+    installation_cleanup_commands += (" && " + ctx.attr.installation_cleanup_commands)
+
   files = depset(files).to_list()
-  return _container.image.implementation(ctx, symlinks=symlinks, env=env, debs=debs, tars=tars, files=files)
+  packages = depset(packages).to_list()
+  additional_repos = depset(additional_repos).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,
+                                   installables_tars=installables_tars,
+                                   installation_cleanup_commands=installation_cleanup_commands
+                                   )
 
 toolchain_container_ = rule(
-    attrs = _container.image.attrs + {
+    attrs = language_tool_layer_attrs + {
         "language_layers": attr.label_list(),
     },
     executable = True,
@@ -102,6 +264,29 @@
 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.
+      installation_cleanup_commands: cleanup commands to run after package
+        installation.
+
   Experimental rule.
   """
+
+  # Input validations
+  if "debs" in kwargs:
+    fail("debs is not supported in toolchain_container.")
+
+  has_packages = "packages" not in kwargs or kwargs["packages"] == []
+  if has_packages and "additional_repos" in kwargs:
+    fail("'additional_repos' can only be specified when 'packages' is not empty.")
+
   toolchain_container_(**kwargs)
diff --git a/rules/docker_config.bzl b/rules/docker_config.bzl
index f4e430e..499c43f 100644
--- a/rules/docker_config.bzl
+++ b/rules/docker_config.bzl
@@ -179,7 +179,7 @@
   download_pkgs(
       name = name + "_pkgs",
       additional_repos = additional_repos,
-      image_tar = ":" + name + "_with_keys",
+      image_tar = ":" + name + "_with_keys.tar",
       packages = packages,
   )
 
@@ -301,7 +301,7 @@
                        (project_repo_dir, package_name, repo_pkg_tar, project_repo_dir))
     remove_repo_cmd = ("rm -drf ./%s" % project_repo_dir)
 
-  result = _container.image.implementation(ctx, cmd=docker_cmd, output=ctx.outputs.load_image)
+  result = _container.image.implementation(ctx, cmd=docker_cmd, output_executable=ctx.outputs.load_image)
 
   # By default we copy the produced tar file to /tmp/
   output_location = "/tmp/" + ctx.attr.name + ".tar"
@@ -521,7 +521,7 @@
       name = kwargs["name"] + "_test",
       size = "medium",
       timeout = "long",
-      srcs = ["//test:autoconfig_test.sh"],
+      srcs = ["//test/configs:autoconfig_test.sh"],
       data = [":" + kwargs["name"]],
     )
 
diff --git a/test/BUILD b/test/configs/BUILD
similarity index 98%
rename from test/BUILD
rename to test/configs/BUILD
index c2b2ece..3a3f129 100644
--- a/test/BUILD
+++ b/test/configs/BUILD
@@ -66,7 +66,7 @@
 docker_toolchain_autoconfig(
     name = "debian-jessie-custom-bazel-version-autoconfig",
     base = ":jessie-with-pkgs.tar",
-    bazel_version = "0.9.0",
+    bazel_version = "0.11.0",
     env = gcc_env(),
     tags = ["manual"],
     test = True,
@@ -125,7 +125,7 @@
 docker_toolchain_autoconfig(
     name = "ubuntu-xenial-custom-bazel-version-autoconfig",
     base = ":xenial-with-pkgs.tar",
-    bazel_version = "0.9.0",
+    bazel_version = "0.11.0",
     env = gcc_env(),
     tags = ["manual"],
     test = True,
diff --git a/test/autoconfig_test.sh b/test/configs/autoconfig_test.sh
similarity index 100%
rename from test/autoconfig_test.sh
rename to test/configs/autoconfig_test.sh
diff --git a/test/debian8_clang_autoconfig_test.sh b/test/configs/debian8_clang_autoconfig_test.sh
similarity index 100%
rename from test/debian8_clang_autoconfig_test.sh
rename to test/configs/debian8_clang_autoconfig_test.sh
diff --git a/test/container/BUILD b/test/container/BUILD
new file mode 100644
index 0000000..9c1604a
--- /dev/null
+++ b/test/container/BUILD
@@ -0,0 +1,17 @@
+# 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.
+
+licenses(["notice"])  # Apache 2.0
+
+package(default_visibility = ["//visibility:public"])
diff --git a/test/container/container_determinism_test.bzl b/test/container/container_determinism_test.bzl
new file mode 100644
index 0000000..698396a
--- /dev/null
+++ b/test/container/container_determinism_test.bzl
@@ -0,0 +1,18 @@
+def container_determinism_test(name, container_name, container_sha):
+  """Macro to invoke a sh_test rule with right parameters.
+
+  Args:
+      name: name of this rule.
+      container_name:  name of the container
+      container_sha: value of the expected sha to check against
+  """
+
+  return native.sh_test(
+           name = name,
+           size = "medium",
+           timeout = "long",
+           srcs = ["container_determinism_test.sh"],
+           data = [container_name],
+           tags = ["manual"],
+           args = ["-c %s -s %s" % (container_name, container_sha)],
+         )
diff --git a/test/container/container_determinism_test.sh b/test/container/container_determinism_test.sh
new file mode 100755
index 0000000..8af78b1
--- /dev/null
+++ b/test/container/container_determinism_test.sh
@@ -0,0 +1,71 @@
+#!/bin/bash
+#
+# 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.
+
+# Test each language layer and the fully-loaded container can be fully
+# reproduced from our saved tar of debian packages.
+
+set -e
+
+function print_usage {
+    echo "Usage:"
+    echo "  -c name of the container"
+    echo "  -s value of the expected sha to check against"
+    if [[ -n $1 ]]; then
+      echo $1
+    fi
+    exit 1
+}
+
+# Define constants.
+WORKSPACE_ROOT=$(pwd)
+NAME=${TEST_BINARY##*/}
+DIR=${TEST_BINARY%${NAME}}
+
+container=""
+valid_sha=""
+
+OPTIND=1 # Reset for getopts, just in case.
+  while getopts "c:s:" opt; do
+    case "$opt" in
+      c)
+        [[ -z "$container" ]] || print_usage "ERROR: Flag specified twice"
+        container=$OPTARG
+        ;;
+      s)
+        [[ -z "$valid_sha" ]] || print_usage "ERROR: Flag specified twice"
+        valid_sha=$OPTARG
+        ;;
+      *)
+        print_usage "ERROR: unknown option"
+        ;;
+    esac
+  done
+
+[[ "$container" != "" ]] || print_usage "ERROR: must specify the container name"
+[[ "$valid_sha" != "" ]] || print_usage "ERROR: must specify the value of valid valid_sha"
+
+# Execute the script to build the container.
+${WORKSPACE_ROOT}/${DIR}${container}
+current_sha=$(docker inspect --format="{{.Id}}" bazel/${DIR%/}:${container})
+
+if [ "${current_sha}" != "${valid_sha}" ]; then
+  echo "Image valid_sha of bazel/${DIR%/}:${container} is changed."
+  exit -1
+fi
+
+echo "PASS"
+
+# TODO(xingao): clean up test images when test finishes.