[Dev Container] - Adding VSCode Dev Container Capability (#12921)

This PR aims to add support for VS Code development container -
https://github.com/openxla/iree/issues/12907

Full Documentation: .devcontainer/README.md

---------

Co-authored-by: Scott Todd <scotttodd@google.com>
Co-authored-by: Geoffrey Martin-Noble <gcmn@google.com>
diff --git a/.devcontainer/README.md b/.devcontainer/README.md
new file mode 100644
index 0000000..12f4f1d
--- /dev/null
+++ b/.devcontainer/README.md
@@ -0,0 +1,125 @@
+# Getting Started with IREE Development Containers
+
+VS Code Development Container is a feature of Visual Studio Code that allows
+developers to create a consistent and isolated development environment for their
+projects. It leverages Docker containers to provide an environment that is
+identical to the production environment, which means developers can develop and
+test their code in an environment that closely mimics the production
+environment.
+
+With VS Code Development Container, developers can avoid the hassle of setting
+up a local development environment, installing dependencies, and managing
+different versions of software packages. Instead, they can use a pre-configured
+container that contains all the required dependencies and tools to develop their
+project. This makes it easier to share the development environment with other
+team members and ensures that everyone is using the same environment.
+
+In addition, VS Code Development Container provides a seamless development
+experience by allowing developers to work directly within the container using
+Visual Studio Code. They can edit code, run tests, and debug their applications
+as if they were working on their local machine.
+
+Overall, VS Code Development Container is a powerful tool for developers who
+want to streamline their development process and ensure consistency across their
+team's development environment.
+
+## A. Prerequisites
+
+Please follow the following steps to prepare your working environment:
+
+1. **Install Docker:** Before you can use VS Code Development Containers, you
+   need to have Docker installed on your machine. You can download and install
+   Docker for your operating system from the official Docker website.
+
+2. **[Optional] Install the NVIDIA-Docker runtime:** If you have an NVIDIA GPU
+   and want to use it for accelerated computing in your container, you can
+   install the NVIDIA-Docker runtime. Follow the instructions on the
+   [NVIDIA-Docker GitHub page](https://github.com/NVIDIA/nvidia-docker) or on
+   [NVIDIA Developer Documentation](https://developer.nvidia.com/nvidia-container-runtime)
+   to install the runtime for your operating system.
+
+3. **Install VS Code:** Naturally you will need VS Code to be installed on your
+   machine: https://code.visualstudio.com/download
+
+4. **Install Dev Container VS Code Extension:** Once VS Code installed, you will
+   need *Microsoft's Dev Containers extension:*
+   [ms-vscode-remote.remote-containers](https://marketplace.visualstudio.com/items?itemName=ms-vscode-remote.remote-containers).
+
+5. **[Optional] Install Remote Development VS Code Extension Pack:** If you wish
+   to setup the `Dev Container` on a remote machine over SSH, we recommend
+   installing *Microsoft's Remote Development extension pack:* :
+   [ms-vscode-remote.vscode-remote-extensionpack](https://marketplace.visualstudio.com/items?itemName=ms-vscode-remote.vscode-remote-extensionpack).
+
+
+## B. Configuring your Development Container.
+
+IREE Project provides multiple possible development containers which corresponds
+to different usecases (e.g CPU, NVIDIA GPUs, etc.).
+
+More containers will be integrated in the future, contributions are welcome.
+
+In order to setup the environment, please execute this script:
+
+```sh
+python3 .devcontainer/configure.py
+
+# This question is optional and will only appear if your environment was
+# previously configured. It aims to avoid overriding an existing configuration
+# inadvertently.
+>>> A `docker-compose.yml` already exists. Are you certain you want to overwrite it [y/N]?
+
+# Google provides prebuilt container images hosted on `gcr.io`. We invite you
+# and recommend using them. However, for expert users you can use locally built
+# containers by answering `N`.
+>>> Do you wish to use the official prebuild development containers [Y/n]?
+
+# We offer the option to mount a host directory to use as persistant local
+# CCache cache. The directory needs to exist in order to be mounted.
+>>> [OPTIONAL] Enter the the directory path to mount as CCache Cache.
+    It will be mounted in the container under: `/home/iree/.cache/ccache`. [Default: None]:
+
+# We offer the option to mount a host directory to use as persistant local
+# build directory. The directory needs to exist in order to be mounted.
+>>> [OPTIONAL] Enter the the directory path to mount as Build Directory.
+    It will mounted in the container under: `/home/iree/build` [Default: None]:
+
+# If your environment has any NVIDIA GPU with properly installed drivers and
+# NVIDIA Docker Runtime, you will be asked if you wish to use an NVIDIA GPU
+# ready container.
+>>> [OPTIONAL] Do you wish to use NVIDIA GPU [y/N]?
+
+# Finally a success message will be printed.
+>>> ================================================================================
+>>>
+>>> Success! Wrote Docker Compose file to `/path/to/iree/.devcontainer/docker-compose.yml`.
+```
+
+## C. (Building and) Launching the Development
+
+We can now build or download, if necessary, and launch the development
+container. In order to do so, we need to open the VS Code "Command Palette" with
+the shortcut: `Ctrl + Shift + P`.
+
+We need to select and click `Dev Containers: Rebuild and Reopen in Container`.
+
+![command palette](docs_data/command_palette.png)
+
+A message will appear signifying that the environment is being setup (this step
+might take some time if you need to download or build the container, you can
+click on `show log` to see the details and watch the progress):
+
+![dev container setup](docs_data/dev_container_setup.png)
+
+The window will refresh once the environment is ready, and the container will be
+started (You can click on `show log` to watch the progress, this might take a
+few minutes).
+
+![dev container start](docs_data/dev_container_start.png)
+
+Once the container is started, you will be greeted by the container terminal
+(notice the username `iree`):
+
+![dev container terminal](docs_data/dev_container_terminal.png)
+
+**Once done, you are ready to start developing, building and contributing to
+IREE 🎉🎉🎉.**
\ No newline at end of file
diff --git a/.devcontainer/configure.py b/.devcontainer/configure.py
new file mode 100644
index 0000000..cf44952
--- /dev/null
+++ b/.devcontainer/configure.py
@@ -0,0 +1,219 @@
+#!/usr/bin/env python3
+
+# Copyright 2023 The IREE Authors
+#
+# Licensed under the Apache License v2.0 with LLVM Exceptions.
+# See https://llvm.org/LICENSE.txt for license information.
+# SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+"""Configure script to setup VSCode DevContainer Environment."""
+
+import json
+import os
+import pathlib
+import shlex
+import subprocess
+import sys
+
+from importlib.machinery import SourceFileLoader
+from shutil import which
+
+CURRENT_DIR = pathlib.Path(__file__).resolve().parent
+
+DOCKER_IMAGE_SHORTNAME_DICT = {
+    "base": "base-bleeding-edge",
+    "nvidia": "nvidia-bleeding-edge",
+}
+
+
+def run_shell(cmd):
+  cmd = shlex.split(cmd)
+  return subprocess.run(cmd, check=True, text=True,
+                        stdout=subprocess.PIPE).stdout
+
+
+def is_nvidia_gpu_available():
+  """This function verifies NVIDIA Docker runtime is installed and
+  available. It also checks NVIDIA GPU are available and the driver
+  is installed."""
+
+  command_str = f"'{which('docker')}' info --format '{{{{json .}}}}'"
+  data = json.loads(run_shell(command_str))
+
+  if "nvidia" not in data["Runtimes"].keys():
+    return (
+        False, "NVIDIA Docker Runtime is not available. Please see: "
+        "https://developer.nvidia.com/nvidia-container-runtime for additional "
+        "information.")
+
+  nvidia_smi_executable = which('nvidia-smi')
+  if nvidia_smi_executable is None:
+    return False, "NVIDIA Driver is not installed or not in the user path."
+
+  command_str = f"{nvidia_smi_executable} --query-gpu=gpu_name --format=csv"
+
+  if run_shell(command_str).splitlines()[1:]:  # Skip header
+    return True, None
+  else:
+    return False, "No NVIDIA GPU was found on this system."
+
+
+def _get_input(question, default_answer):
+  try:
+    answer = input(f"\n- {question} ")
+  except EOFError:
+    answer = default_answer
+
+  return (answer or default_answer).strip().lower()
+
+
+def get_input(question, default_answer='', accepted_answers=None):
+  if accepted_answers is None:
+    raise RuntimeError("Argument `accepted_answers` is None.")
+
+  accepted_answers = [x.strip().lower() for x in accepted_answers]
+
+  while True:
+    answer = _get_input(question, default_answer)
+    if answer not in accepted_answers:
+      print(f"\tERROR: Unsupported answer received: {answer}."
+            f"Expected: {accepted_answers}")
+      continue
+    break
+
+  return answer
+
+
+def get_input_path(question):
+
+  while True:
+    answer = _get_input(question, default_answer="")
+    if answer != "" and not os.path.isdir(answer):
+      print(f"\tERROR: Received path does not exist: `{answer}`")
+      continue
+    break
+
+  return answer
+
+
+if __name__ == "__main__":
+
+  # ------------------------------------------------------------------------- #
+  #                         Environment Verifications                         #
+  # ------------------------------------------------------------------------- #
+
+  docker_executable = which('docker')
+  if docker_executable is None:
+    raise RuntimeError(
+        "Docker is not installed or in the user path. Please refer to: "
+        "https://docs.docker.com/desktop/")
+
+  try:
+    run_shell(f"'{docker_executable}' compose version")
+  except subprocess.CalledProcessError as e:
+    raise RuntimeError(
+        "Docker Compose must be installed in order to use IREE VS Code "
+        "Development Container. Please refer to: "
+        "https://docs.docker.com/compose/") from e
+
+  docker_image_key = "base"
+
+  # ------------------------------------------------------------------------- #
+  #                               Mandatory Steps                             #
+  # ------------------------------------------------------------------------- #
+
+  # STEP 1: Verify the user doesn't have a pre-existing `docker-compose.yml`.
+  #         If yes, confirm they want to overwrite it.
+  if os.path.isfile(CURRENT_DIR / "docker-compose.yml"):
+    if get_input(
+        "A `docker-compose.yml` already exists. Are you certain you want to overwrite it [y/N]?",
+        default_answer="n",
+        accepted_answers=["y", "n"]) == "n":
+      sys.exit(1)
+
+  # STEP 2: Read the template configuration file
+  with open(CURRENT_DIR / "docker-compose.base.yml") as f:
+    docker_config = json.load(f)
+
+  # STEP 3: Prebuilt vs Locally Built Containers
+  use_official_img = get_input(
+      "Do you wish to use the official prebuild development containers [Y/n]?",
+      default_answer="y",
+      accepted_answers=["y", "n"]) == "y"
+
+  # ------------------------------------------------------------------------- #
+  #                 Optional Mounting Points - Build & Cache                  #
+  # ------------------------------------------------------------------------- #
+
+  # STEP 4 [OPTIONAL]: Does the user want to mount a directory for CCACHE ?
+  ccache_mount_cache = get_input_path(
+      "[OPTIONAL] Enter the the directory path to mount as CCache Cache.\n"
+      "  It will be mounted in the container under: `/home/iree/.cache/ccache` [Default: None]:"
+  )
+  if ccache_mount_cache:
+    docker_config["services"]["iree-dev"]["volumes"].append(
+        f"{ccache_mount_cache}:/home/iree/.cache/ccache:cached")
+
+  # STEP 5 [OPTIONAL]: Does the user want to mount a directory for BUILD ?
+  build_mount_cache = get_input_path(
+      "[OPTIONAL] Enter the the directory path to mount as Build Directory.\n"
+      "  It will mounted in the container under: `/home/iree/build` [Default: None]:"
+  )
+  if build_mount_cache:
+    docker_config["services"]["iree-dev"]["volumes"].append(
+        f"{build_mount_cache}:/home/iree/build:cached")
+
+  # ------------------------------------------------------------------------- #
+  #                Optional Deep Learning Accelerator Support                 #
+  # ------------------------------------------------------------------------- #
+
+  # STEP 6 [OPTIONAL]: Does the user want to use NVIDIA GPUs ?
+  nvidia_gpu_available, err_msg = is_nvidia_gpu_available()
+
+  if nvidia_gpu_available:
+    if get_input("[OPTIONAL] Do you wish to use NVIDIA GPU [y/N]?",
+                 default_answer="n",
+                 accepted_answers=["y", "n"]) == "y":
+      docker_image_key = "nvidia"
+
+  else:
+    print(f"[INFO] NVIDIA GPUs are not available for use: {err_msg}")
+
+  if docker_image_key != "nvidia":
+    del docker_config["services"]["iree-dev"]["deploy"]
+
+  # ------------------------------------------------------------------------- #
+  #            Setting the right docker image / container to build            #
+  # ------------------------------------------------------------------------- #
+
+  docker_img_shortname = DOCKER_IMAGE_SHORTNAME_DICT[docker_image_key]
+
+  if use_official_img:
+    del docker_config["services"]["iree-dev"]["build"]
+
+    docker_iree_registry = SourceFileLoader(
+        "docker_iree_registry",
+        str(CURRENT_DIR /
+            "../build_tools/docker/get_image_name.py")).load_module()
+
+    image_name = docker_iree_registry.find_image_by_name(docker_img_shortname)
+    docker_config["services"]["iree-dev"]["image"] = image_name
+
+  else:
+    del docker_config["services"]["iree-dev"]["image"]
+
+    dockerfile = f"build_tools/docker/dockerfiles/{docker_img_shortname}.Dockerfile"
+    docker_config["services"]["iree-dev"]["build"]["dockerfile"] = dockerfile
+
+    if (not os.path.isfile(CURRENT_DIR / ".." / dockerfile)):
+      raise FileNotFoundError(f"The file `{dockerfile}` does not exist.")
+
+  docker_compose_filepath = os.path.join(CURRENT_DIR, "docker-compose.yml")
+  with open(docker_compose_filepath, "w") as f:
+    json.dump(docker_config, f, indent=2)
+
+  # ------------------------------------------------------------------------- #
+  #                                  SUCCESS                                  #
+  # ------------------------------------------------------------------------- #
+
+  print("\n" + "=" * 80)
+  print(f"\nSuccess! Wrote Docker Compose file to `{docker_compose_filepath}`.")
diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json
new file mode 100644
index 0000000..e9c762e
--- /dev/null
+++ b/.devcontainer/devcontainer.json
@@ -0,0 +1,45 @@
+// For format details, see https://aka.ms/devcontainer.json. For config options, see the README at:
+// https://github.com/microsoft/vscode-dev-containers/tree/v0.245.2/containers/docker-existing-dockerfile
+{
+    "name": "IREE Development Container",
+    "dockerComposeFile": [
+        "docker-compose.yml"
+    ],
+    "service": "iree-dev",
+    "remoteUser": "iree",
+    "workspaceFolder": "/workspaces/iree",
+    "customizations": {
+        "vscode": {
+            "extensions": [             
+                // LLVM & MLIR
+                "llvm-vs-code-extensions.vscode-clangd",
+                "llvm-vs-code-extensions.vscode-mlir",
+                "jakob-erzar.llvm-tablegen",
+                // CPP
+                "ms-vscode.cpptools-extension-pack",
+                // Python
+                "ms-python.python",
+                // Build Tools
+                "ms-azuretools.vscode-docker",
+                "ms-vscode.makefile-tools",
+                "ms-vscode.cmake-tools",
+                // Git & Github
+                "GitHub.vscode-pull-request-github"
+            ]
+        }
+    },
+    "features": {
+        "ghcr.io/devcontainers/features/common-utils:2": {
+            "username": "iree",
+            "uid": "automatic",
+            "gid": "automatic",
+            "installZsh": true,
+            "installOhMyZsh": true,
+            "configureZshAsDefaultShell": false,
+            "upgradePackages": true
+        },
+        "ghcr.io/devcontainers/features/git:1": {
+            "version": "latest"
+        }
+    }
+}
diff --git a/.devcontainer/docker-compose.base.yml b/.devcontainer/docker-compose.base.yml
new file mode 100644
index 0000000..ba6b5da2
--- /dev/null
+++ b/.devcontainer/docker-compose.base.yml
@@ -0,0 +1,42 @@
+{
+  "version": "3.9",
+  "services": {
+    "iree-dev": {
+      "image": null,
+      "build": {
+        "context": "../",
+        "network": "host",
+        "dockerfile": null
+      },
+      "network_mode": "host",
+      "ipc": "host",
+      "cap_add": [
+        "SYS_PTRACE",
+        "SYS_ADMIN"
+      ],
+      "security_opt": [
+        "seccomp:unconfined"
+      ],
+      "deploy": {
+        "resources": {
+          "reservations": {
+            "devices": [
+              {
+                "driver": "nvidia",
+                "count": 1,
+                "capabilities": [
+                  "gpu"
+                ]
+              }
+            ]
+          }
+        }
+      },
+      "volumes": [
+        "..:/workspaces/iree:cached",
+        "~/.gitconfig:/etc/gitconfig:cached"
+      ],
+      "command": "/bin/sh -c \"while sleep 1000; do :; done\""
+    }
+  }
+}
diff --git a/.devcontainer/docs_data/command_palette.png b/.devcontainer/docs_data/command_palette.png
new file mode 100644
index 0000000..b787ec9
--- /dev/null
+++ b/.devcontainer/docs_data/command_palette.png
Binary files differ
diff --git a/.devcontainer/docs_data/dev_container_setup.png b/.devcontainer/docs_data/dev_container_setup.png
new file mode 100644
index 0000000..e4f3302
--- /dev/null
+++ b/.devcontainer/docs_data/dev_container_setup.png
Binary files differ
diff --git a/.devcontainer/docs_data/dev_container_start.png b/.devcontainer/docs_data/dev_container_start.png
new file mode 100644
index 0000000..c0c260e
--- /dev/null
+++ b/.devcontainer/docs_data/dev_container_start.png
Binary files differ
diff --git a/.devcontainer/docs_data/dev_container_terminal.png b/.devcontainer/docs_data/dev_container_terminal.png
new file mode 100644
index 0000000..f7969d8
--- /dev/null
+++ b/.devcontainer/docs_data/dev_container_terminal.png
Binary files differ
diff --git a/.gitignore b/.gitignore
index d2056f0..569d140 100644
--- a/.gitignore
+++ b/.gitignore
@@ -72,5 +72,8 @@
 *.tar
 *.tar.*
 
+# VS Code DevContainer
+.devcontainer/docker-compose.yml
+
 # Local cache files
 llvm-external-projects/iree-dialects/.cache
diff --git a/build_tools/docker/get_image_name.py b/build_tools/docker/get_image_name.py
index bf99339..ae8e180 100755
--- a/build_tools/docker/get_image_name.py
+++ b/build_tools/docker/get_image_name.py
@@ -20,19 +20,24 @@
 from pathlib import Path
 import sys
 
+
+def find_image_by_name(img_name):
+  this_dir = Path(__file__).resolve().parent
+
+  with open(this_dir / "prod_digests.txt", "rt") as f:
+    for line in f.readlines():
+      line = line.strip()
+      if line.startswith(f"gcr.io/iree-oss/{img_name}@"):
+        return line
+    else:
+      raise ValueError(
+          f"ERROR: Image name {img_name} not found in prod_digests.txt")
+
+
 if __name__ == "__main__":
   if len(sys.argv) != 2:
     print("ERROR: Expected image short name", file=sys.stderr)
     sys.exit(1)
   short_name = sys.argv[1]
-  this_dir = Path(__file__).resolve().parent
-  with open(this_dir / "prod_digests.txt", "rt") as f:
-    for line in f.readlines():
-      line = line.strip()
-      if line.startswith(f"gcr.io/iree-oss/{short_name}@"):
-        print(line)
-        break
-    else:
-      print(f"ERROR: Image name {short_name} not found in prod_digests.txt",
-            file=sys.stderr)
-      sys.exit(1)
+  image_name = find_image_by_name(short_name)
+  print(image_name)