Add a container for COS kernel development

To make kernel development easier for external teams add
a cos-kernel-devenv container that can be set up for:
 - building custom kernels
 - building modules for specific release
 - supports cross-compilation

BUG=None
TEST=None
RELEASE_NOTE=None

Change-Id: I2dcc1f07459a07373d9b3ea8a3910960b1102b0b
Reviewed-on: https://cos-review.googlesource.com/c/cos/tools/+/18550
Cloud-Build: GCB Service account <228075978874@cloudbuild.gserviceaccount.com>
Reviewed-by: Meena Shanmugam <meenashanmugam@google.com>
Tested-by: Oleksandr Tymoshenko <ovt@google.com>
diff --git a/src/cmd/cos_kernel_devenv/Dockerfile b/src/cmd/cos_kernel_devenv/Dockerfile
new file mode 100644
index 0000000..b2ce351
--- /dev/null
+++ b/src/cmd/cos_kernel_devenv/Dockerfile
@@ -0,0 +1,10 @@
+# Start from google/cloud-sdk:slim base.
+FROM google/cloud-sdk:slim
+
+RUN echo 'deb http://deb.debian.org/debian buster-backports main' > /etc/apt/sources.list.d/buster-backports.list
+RUN apt-get update && apt-get install -y make gcc git libssl-dev bc \
+          libelf-dev/buster-backports bison flex cpio dwarves/buster-backports kmod
+
+COPY ./devenv.sh /devenv.sh
+
+ENTRYPOINT ["/devenv.sh"]
diff --git a/src/cmd/cos_kernel_devenv/README.md b/src/cmd/cos_kernel_devenv/README.md
new file mode 100644
index 0000000..46b17b0
--- /dev/null
+++ b/src/cmd/cos_kernel_devenv/README.md
@@ -0,0 +1,170 @@
+# COS Kernel Devenv container
+
+## Overview
+
+## Building COS Kernel Devenv Image
+
+### Locally (for testing the image)
+
+For testing, you can simply build and test this docker container locally on your
+workstation:
+
+```shell
+  $ docker build -t gcr.io/cloud-kernel-build/cos-kernel-devenv:dev .
+```
+
+### Production (push into GCR)
+
+
+```shell
+  $ VERSION=<version>  # e.g., 20171008
+  $ docker build -t gcr.io/cloud-kernel-build/cos-kernel-devenv:$VERSION .
+  $ docker build -t gcr.io/cloud-kernel-build/cos-kernel-devenv:latest .
+  $ gcloud docker -- push gcr.io/cloud-kernel-build/cos-kernel-devenv:$VERSION
+  $ gcloud docker -- push gcr.io/cloud-kernel-build/cos-kernel-devenv:latest
+```
+
+## Using COS Kernel Devenv Image
+
+### Download
+
+Get the latest version of the `cos-kernel-devenv` container by running the
+following command:
+
+```shell
+$ docker pull gcr.io/cloud-kernel-build/cos-kernel-devenv
+```
+
+### Overview
+
+`cos-kernel-devenv` provides the development environment for building Linux
+kernel or standalone kernel modules. The container supports three operation
+modes: automatic kernel build, automatic module build, and an interactive shell.
+
+The development environment includes a cross-compilation toolchain and an
+optional kernel headers. Kernel headers only provided if the environment
+replicates the environment of the specific COS build.
+
+Kernel sources or module sources are passed to the container as a volume
+and automatic modes assume that the working directory is the top-level
+build directory.
+
+In the interactive mode the contianer provides `kmake` shell function to
+simplify kernel builds. It's a wrapper around `make command with arguments
+specific for kernel build: toolchain and arch-specific. It also configures
+standard `make` specific shell variables like `CC` or `LD`.
+
+There are three ways to initialize dev environment:
+
+### Modes of Operation
+#### Generic Cross-compilation Environment
+
+Unless no arguments specifying COS build are passed to the container,
+env is initialized only with a cross-compilation toolchain
+
+```
+$ docker run --rm -ti -v $(pwd):/src -w /src gcr.io/cloud-kernel-build/cos-kernel-devenv
+... some output ...
+
+root@daf505e41e51:/src# echo $CC
+x86_64-cros-linux-gnu-clang
+
+root@daf505e41e51:/src# $CC -v
+Chromium OS 12.0_pre422132_p20210405-r9 clang version 13.0.0 (/var/tmp/portage/sys-devel/llvm-12.0_pre422132_p20210405-r9/work/llvm-12.0_pre422132_p20210405/clang cd442157cff4aad209ae532cbf031abbe10bc1df)
+Target: x86_64-cros-linux-gnu
+Thread model: posix
+InstalledDir: /build/x86_64-cros/usr/bin
+Found candidate GCC installation: /build/x86_64-cros/usr/bin/../lib/gcc/x86_64-cros-linux-gnu/10.2.0
+Selected GCC installation: /build/x86_64-cros/usr/bin/../lib/gcc/x86_64-cros-linux-gnu/10.2.0
+Candidate multilib: .;@m64
+Selected multilib: .;@m64
+
+root@daf505e41e51:/src# kmake -j48 defconfig && kmake -j48 bzImage
+... some output ...
+Kernel: arch/x86/boot/bzImage is ready  (#2)
+```
+
+By default container sets environment for an x86_64 build but can also target ARM64
+builds if passed `-A arm64` command-line argument:
+
+```
+$ docker run --rm -ti -v $(pwd):/src -w /src gcr.io/cloud-kernel-build/cos-kernel-devenv -A arm64
+...
+root@099747a46b80:/src# kmake -j48 defconfig && kmake -j48 Image
+...
+  OBJCOPY arch/arm64/boot/Image
+root@099747a46b80:/src#
+```
+
+#### Officially Released COS build
+
+When passed `-R <release>` command-line argument, the container reproduces
+the build environment of that specific releases. The generated devenv can
+be used both for building a modified kernel for troubleshooting or building
+a kernel module that can be used with the specified release. The `<release>`
+argument should be in the form of `MAJOR.MINOR.PATCH`, i.e.: `16442.0.0`.
+
+```
+$ docker run --rm -ti -v $(pwd):/src -w \
+    /src gcr.io/cloud-kernel-build/cos-kernel-devenv -R 16442.0.0
+...
+root@384c89409064:/src# kmake -C $KHEADERS M=$(pwd) modules
+make: Entering directory '/build/x86_64-16442.0.0/kernel-headers/usr/src/linux-headers-5.4.120+'
+  CC [M]  /src/samplemodule.o
+  Building modules, stage 2.
+  MODPOST 1 modules
+  CC [M]  /src/samplemodule.mod.o
+  LD [M]  /src/samplemodule.ko
+make: Leaving directory '/build/x86_64-16442.0.0/kernel-headers/usr/src/linux-headers-5.4.120+'
+root@384c89409064:/src#
+```
+#### COS CI build
+
+It's also possible to reproduce kernel dev environment for the particular CI
+build by specifying the voard and the build ID in the container's arguments:
+`-b <board> -B <buildid>`. The `<board>` argument is the board name (for
+example it's `lakitu` for COS on GCE`). The `<buildid>` is the combination of both
+milestone information and the build number, for instance: `R93-16623.0.0`
+
+For this use case it's also neccessary to pass gcloud config information to the
+container. It's required to get access to the build artifacts that are stored in
+the non-public GCS bucket.
+
+```
+$ docker run --rm -ti -v $(pwd):/src -w /src \
+    -v ~/.config/gcloud:/root/.config/gcloud \
+    gcr.io/cloud-kernel-build/cos-kernel-devenv -b lakitu -B R93-16623.0.0
+```
+### Automated Builds
+
+In addition to an interactive environment `cos-kernel-devenv` also provides
+a batch mode for building a kernel package and modules.
+
+#### Automated Kernel Build
+
+By passing `-k` command-line argument to the container developer can run an
+automated kernel build that consists of two steps: `kmake defconfig` and
+`kmake tarxz-pkg`. The kernel code is assumed to be in the working directory
+specified by `-w` docker argument.
+
+TODO(ovt): implement and document option to specify kernel configs
+
+#### Automated Kernel Build
+
+```
+$ docker run --rm -ti -v $(pwd):/src -w \
+    /src gcr.io/cloud-kernel-build/cos-kernel-devenv -R 16442.0.0 -k
+... skipped ...
+'./arch/x86/boot/bzImage' -> './tar-install/boot/vmlinuz-5.10.59'
+Tarball successfully created in ./linux-5.10.59-x86.tar.xz
+```
+
+#### Automated Modules Build
+
+```
+$ docker run --rm -ti -v $(pwd):/src -w \
+    /src gcr.io/cloud-kernel-build/cos-kernel-devenv -R 16442.0.0 -m
+... skipped ...
+  LD [M]  /src/samplemodule.ko
+make: Leaving directory '/build/x86_64-16442.0.0/kernel-headers/usr/src/linux-headers-5.4.120+'
+```
diff --git a/src/cmd/cos_kernel_devenv/devenv.sh b/src/cmd/cos_kernel_devenv/devenv.sh
new file mode 100755
index 0000000..f5583af
--- /dev/null
+++ b/src/cmd/cos_kernel_devenv/devenv.sh
@@ -0,0 +1,388 @@
+#!/bin/bash
+# Create kernel development environment for COS
+
+set -o errexit
+set -o pipefail
+
+ROOT_MOUNT_DIR="${ROOT_MOUNT_DIR:-/root}"
+RETRY_COUNT=${RETRY_COUNT:-5}
+
+readonly COS_DOWNLOAD_GCS="https://storage.googleapis.com/cos-tools"
+readonly COS_CI_DOWNLOAD_GCS="gs://cos-infra-prod-artifacts-official"
+readonly CHROMIUMOS_SDK_GCS="https://storage.googleapis.com/chromiumos-sdk"
+readonly TOOLCHAIN_URL_FILENAME="toolchain_path"
+readonly KERNEL_HEADERS="kernel-headers.tgz"
+readonly KERNEL_HEADERS_DIR="kernel-headers"
+readonly TOOLCHAIN_ARCHIVE="toolchain.tar.xz"
+readonly TOOLCHAIN_ENV_FILENAME="toolchain_env"
+ROOT_OS_RELEASE="${ROOT_MOUNT_DIR}/etc/os-release"
+readonly RETCODE_ERROR=1
+RELEASE_ID=""  # Loaded from host during execution
+BUILD_DIR="" # based on RELEASE_ID
+KERNEL_CONFIG="defconfig"
+
+BOARD=""
+BUILD_ID=""
+
+# official release, CI build, or cross-toolchain
+MODE=""
+
+CROS_TC_VERSION="2021.06.26.094653"
+CROS_TC_DATE="2021/06"
+CROS_TC_DOWNLOAD_GCS="https://storage.googleapis.com/chromiumos-sdk/"
+
+# Can be overridden by the command-line argument
+TOOLCHAIN_ARCH="x86_64"
+KERNEL_ARCH="x86"
+
+# CC and CXX will be set by set_compilation_env
+CC=""
+CXX=""
+
+_log() {
+  local -r prefix="$1"
+  shift
+  echo "[${prefix}$(date -u "+%Y-%m-%d %H:%M:%S %Z")] ""$*" >&2
+}
+
+info() {
+  _log "INFO    " "$*"
+}
+
+warn() {
+  _log "WARNING " "$*"
+}
+
+error() {
+  _log "ERROR   " "$*"
+}
+
+load_etc_os_release() {
+  if [[ ! -f "${ROOT_OS_RELEASE}" ]]; then
+    error "File ${ROOT_OS_RELEASE} not found, /etc/os-release from COS host must be mounted."
+    exit ${RETCODE_ERROR}
+  fi
+  . "${ROOT_OS_RELEASE}"
+  info "Running on COS build id ${RELEASE_ID}"
+}
+
+download_from_url() {
+  local -r url="$1"
+  local -r output="$2"
+  info "Downloading from URL: ${url}"
+  info "Local download location: ${output}"
+  local attempts=0
+  until curl --http1.1 -sfS "${url}" -o "${output}"; do
+    attempts=$(( attempts + 1))
+    if (( "${attempts}" >= "${RETRY_COUNT}" )); then
+      error "Could not download from ${url}"
+      return ${RETCODE_ERROR}
+    fi
+    warn "Error downloading from ${url}, retrying"
+    sleep 1
+  done
+  info "Download finished"
+}
+
+download_from_gcs() {
+  local -r url="$1"
+  local -r output="$2"
+  info "Downloading from Google Storage: ${url}"
+  info "Local download location: ${output}"
+  local attempts=0
+  until gsutil -q cp "${url}" "${output}"; do
+    attempts=$(( attempts + 1))
+    if (( "${attempts}" >= "${RETRY_COUNT}" )); then
+      error "Could not download from ${url}"
+      return ${RETCODE_ERROR}
+    fi
+    warn "Error downloading from ${url}, retrying"
+    sleep 1
+  done
+  info "Download finished"
+}
+
+# Get toolchain tarball from Chromium GCS bucket when
+# toolchain tarball is not found in COS GCS bucket
+get_cross_toolchain_pkg() {
+  # First, check if the toolchain path is available locally.
+  local -r tc_path_file="${ROOT_MOUNT_DIR}/etc/toolchain-path"
+  if [[ -f "${tc_path_file}" ]]; then
+    info "Found toolchain path file locally"
+    local -r tc_path="$(cat "${tc_path_file}")"
+    local -r tc_download_url="${CHROMIUMOS_SDK_GCS}/${tc_path}"
+  else
+    # Next, check if the toolchain path is available in GCS.
+    local -r tc_path_url="${COS_DOWNLOAD_GCS}/${RELEASE_ID}/${TOOLCHAIN_URL_FILENAME}"
+    info "Obtaining toolchain download URL from ${tc_path_url}"
+    local -r tc_download_url="$(curl --http1.1 -sfS "${tc_path_url}")"
+  fi
+  echo "${tc_download_url}"
+}
+
+install_cross_toolchain_pkg() {
+  local -r download_url=$1
+  info "Downloading prebuilt toolchain from ${download_url}"
+  local -r pkg_name="$(basename "${download_url}")"
+  download_from_url "${download_url}" "${BUILD_DIR}/${pkg_name}"
+  # Don't unpack Rust toolchain elements because they are not needed and they
+  # use a lot of disk space.
+  tar axf "${BUILD_DIR}/${pkg_name}" -C "${BUILD_DIR}" \
+    --exclude='./usr/lib64/rustlib*' \
+    --exclude='./usr/lib64/libstd-*.so' \
+    --exclude='./lib/libstd-*.so' \
+    --exclude='./lib/librustc*' \
+    --exclude='./usr/lib64/librustc*'
+  rm "${BUILD_DIR}/${pkg_name}"
+}
+
+# Set-up compilation environment using toolchain used for
+# kernel compilation
+install_release_cross_toolchain() {
+  info "Downloading and installing a toolchain"
+  # Get toolchain_env path from COS GCS bucket
+  local -r tc_env_file_path="${COS_DOWNLOAD_GCS}/${RELEASE_ID}/${TOOLCHAIN_ENV_FILENAME}"
+  info "Obtaining toolchain_env file from ${tc_env_file_path}"
+
+  # Download toolchain_env if present
+  if ! download_from_url "${tc_env_file_path}" "${BUILD_DIR}/${TOOLCHAIN_ENV_FILENAME}"; then
+    error "Failed to download toolchain file"
+    error "Make sure build id '$RELEASE_ID' is valid"
+    return ${RETCODE_ERROR}
+  fi
+
+  local -r tc_download_url="${COS_DOWNLOAD_GCS}/${RELEASE_ID}/${TOOLCHAIN_ARCHIVE}"
+
+  # Install toolchain pkg
+  install_cross_toolchain_pkg "${tc_download_url}"
+}
+
+install_release_kernel_headers() {
+  info "Downloading and installing a kernel headers"
+  local -r kernel_headers_file_path="${COS_DOWNLOAD_GCS}/${RELEASE_ID}/${KERNEL_HEADERS}"
+  info "Obtaining kernel headers file from ${kernel_headers_file_path}"
+
+  if ! download_from_url "${kernel_headers_file_path}" "${BUILD_DIR}/${KERNEL_HEADERS}"; then
+        return ${RETCODE_ERROR}
+  fi
+  mkdir -p "${BUILD_DIR}/${KERNEL_HEADERS_DIR}"
+  tar axf "${BUILD_DIR}/${KERNEL_HEADERS}" -C "${BUILD_DIR}/${KERNEL_HEADERS_DIR}"
+  rm -f "${BUILD_DIR}/${KERNEL_HEADERS}"
+}
+
+# Download and install toolchain from the CI or tryjob build directory
+install_build_cross_toolchain() {
+  local -r bucket="$1"
+
+  info "Downloading and installing a toolchain"
+  # Get toolchain_env path from COS GCS bucket
+  local -r tc_env_file_path="${bucket}/${TOOLCHAIN_ENV_FILENAME}"
+  local -r tc_url_file_path="${bucket}/${TOOLCHAIN_URL_FILENAME}"
+
+  info "Obtaining toolchain_env file from ${tc_env_file_path}"
+
+  # Download toolchain_env if present
+  if ! download_from_gcs "${tc_env_file_path}" "${BUILD_DIR}/${TOOLCHAIN_ENV_FILENAME}"; then
+        error "Failed to download toolchain file"
+        error "Make sure build id '$RELEASE_ID' is valid"
+        return ${RETCODE_ERROR}
+  fi
+
+  # Download toolchain_path if present
+  if ! download_from_gcs "${tc_url_file_path}" "${BUILD_DIR}/${TOOLCHAIN_URL_FILENAME}"; then
+        error "Failed to download toolchain file"
+        error "Make sure build id '$RELEASE_ID' is valid"
+        return ${RETCODE_ERROR}
+  fi
+
+  local -r tc_download_url="${CROS_TC_DOWNLOAD_GCS}$(cat ${BUILD_DIR}/${TOOLCHAIN_URL_FILENAME})"
+  if [[ -z "$tc_download_url" ]]; then
+    error "Failed to download toolchain URL file"
+    error "Make sure build id '$RELEASE_ID' is valid"
+    return ${RETCODE_ERROR}
+  fi
+
+  # Install toolchain pkg
+  install_cross_toolchain_pkg "${tc_download_url}"
+}
+
+install_build_kernel_headers() {
+  local -r bucket="$1"
+
+  info "Downloading and installing a kernel headers"
+  local -r kernel_headers_file_path="${bucket}/${KERNEL_HEADERS}"
+  info "Obtaining kernel headers file from ${kernel_headers_file_path}"
+
+  if ! download_from_gcs "${kernel_headers_file_path}" "${BUILD_DIR}/${KERNEL_HEADERS}"; then
+        return ${RETCODE_ERROR}
+  fi
+  mkdir -p "${BUILD_DIR}/${KERNEL_HEADERS_DIR}"
+  tar axf "${BUILD_DIR}/${KERNEL_HEADERS}" -C "${BUILD_DIR}/${KERNEL_HEADERS_DIR}"
+  rm -f "${BUILD_DIR}/${KERNEL_HEADERS}"
+}
+
+install_generic_cross_toolchain() {
+  info "Downloading and installing a toolchain"
+  # Download toolchain_env if present
+  local -r tc_download_url="${CROS_TC_DOWNLOAD_GCS}${CROS_TC_DATE}/${TOOLCHAIN_ARCH}-cros-linux-gnu-${CROS_TC_VERSION}.tar.xz"
+
+  # Install toolchain pkg
+  install_cross_toolchain_pkg "${tc_download_url}"
+}
+
+set_compilation_env() {
+  local -r tc_env_file_path="${COS_DOWNLOAD_GCS}/${RELEASE_ID}/${TOOLCHAIN_ENV_FILENAME}"
+  # toolchain_env file will set 'CC' and 'CXX' environment
+  # variable based on the toolchain used for kernel compilation
+  if [[ -f "${BUILD_DIR}/${TOOLCHAIN_ENV_FILENAME}" ]]; then
+    source "${BUILD_DIR}/${TOOLCHAIN_ENV_FILENAME}"
+    export CC
+    export CXX
+  else
+    export CC="${TOOLCHAIN_ARCH}-cros-linux-gnu-clang"
+    export CXX="${TOOLCHAIN_ARCH}-cros-linux-gnu-clang"
+  fi
+  info "Configuring environment variables for cross-compilation"
+  # CC and CXX are already set in toolchain_env
+  export PATH="${BUILD_DIR}/bin:${BUILD_DIR}/usr/bin:${PATH}"
+  export SYSROOT="${BUILD_DIR}/usr/${TOOLCHAIN_ARCH}-cros-linux-gnu"
+  export HOSTCC="x86_64-pc-linux-gnu-clang"
+  export HOSTCXX="x86_64-pc-linux-gnu-clang++"
+  export LD="${TOOLCHAIN_ARCH}-cros-linux-gnu-ld.lld"
+  export HOSTLD="x86_64-pc-linux-gnu-ld.lld"
+  # export BINUTILS="${BUILD_DIR}/usr/x86_64-pc-linux-gnu/x86_64-cros-linux-gnu/binutils-bin/2.27.0"
+  export OBJCOPY=llvm-objcopy
+  export STRIP=llvm-strip
+  export KERNEL_ARCH
+  export TOOLCHAIN_ARCH
+  export LLVM_IAS=1
+  if [[ "${MODE}" = "release" || "${MODE}" = "build" || "${MODE}" = "custom" ]]; then
+    local -r headers_dir=$(ls -d ${BUILD_DIR}/${KERNEL_HEADERS_DIR}/usr/src/linux-headers*)
+    export KHEADERS="${headers_dir}"
+  fi
+  # env ARCH=x86_64 make -j47 ARCH=x86_64 CC="${CC}" CXX="${CXX}" LD="${LD}" BINUTILS="${BINUTILS}" LD="${LD}" targz-pkg
+}
+
+kmake() {
+  env ARCH=${KERNEL_ARCH} make ARCH=${KERNEL_ARCH} \
+    CC="${CC}" CXX="${CXX}" LD="${LD}" \
+    STRIP="${STRIP}" OBJCOPY="${OBJCOPY}" \
+    HOSTCC="${HOSTCC}" HOSTCXX="${HOSTCXX}" HOSTLD="${HOSTLD}" \
+    "$@"
+}
+export -f kmake
+
+kernel_build() {
+  kmake mrproper
+  kmake "$@" "${KERNEL_CONFIG}"
+  kmake "$@" tarxz-pkg
+}
+
+module_build() {
+  kmake -C "${KHEADERS}" M="$(pwd)" "$@" clean
+  kmake -C "${KHEADERS}" M="$(pwd)" "$@" modules
+}
+
+usage() {
+  echo "Usage: $0 [-k | -m] [-A <x86|arm64>] [-B <build>] [-C <config>]" 1>&2
+  echo "    [-B <build> | -b <board> -R <release> | -G <GCS_bucket>]"
+  exit $RETCODE_ERROR
+}
+
+main() {
+  local build_target="shell"
+  local custom_bucket=""
+  while getopts "A:B:C:G:R:b:km" o; do
+    case "${o}" in
+      A) KERNEL_ARCH=${OPTARG} ;;
+      B) BUILD_ID=${OPTARG} ;;
+      C) KERNEL_CONFIG=${OPTARG} ;;
+      G) custom_bucket=${OPTARG} ;;
+      R) RELEASE_ID=${OPTARG} ;;
+      b) BOARD=${OPTARG} ;;
+      k) build_target="kernel" ;;
+      m) build_target="module" ;;
+      *) usage ;;
+    esac
+  done
+  shift $((OPTIND-1))
+
+  if [[ ! -z "${BOARD}" ]]; then
+    case "${BOARD}" in
+      lakitu-arm64) KERNEL_ARCH=arm64 ;;
+      *) KERNEL_ARCH=x86 ;;
+    esac
+  fi
+
+  case "${KERNEL_ARCH}" in
+    x86)
+      TOOLCHAIN_ARCH=x86_64
+      ;;
+    arm64)
+      TOOLCHAIN_ARCH=aarch64
+      ;;
+    *)
+      echo "Invalid -A value: $KERNEL_ARCH"
+      usage
+      ;;
+  esac
+
+  echo "** Kernel architecture: $KERNEL_ARCH"
+  echo "** Toolchain architecture: $TOOLCHAIN_ARCH"
+
+  if [[ -n "$RELEASE_ID" ]]; then
+    MODE="release"
+    BUILD_DIR="/build/${TOOLCHAIN_ARCH}-${RELEASE_ID}"
+    echo "** COS release: $RELEASE_ID"
+  fi
+
+  if [[ -z "$MODE" && -n "$BOARD" && -n "$BUILD_ID" ]]; then
+    MODE="build"
+    echo "** COS build: $BOARD-$BUILD_ID"
+    BUILD_DIR="/build/${BOARD}-${BUILD_ID}"
+  fi
+
+  if [[ -z "$MODE" && -n "$custom_bucket" ]]; then
+    MODE="custom"
+    BUILD_DIR="/build/$(basename "${custom_bucket}")"
+  fi
+
+  if [[ -z "$MODE" ]]; then
+    MODE="cross"
+    BUILD_DIR="/build/${TOOLCHAIN_ARCH}-cros"
+  fi
+  echo "Mode: $MODE"
+
+  if [[ ! -d ${BUILD_DIR} ]]; then
+    mkdir -p "${BUILD_DIR}"
+    case "$MODE" in
+      cross) install_generic_cross_toolchain ;;
+      release)
+        install_release_cross_toolchain
+        install_release_kernel_headers
+        ;;
+      build)
+        local -r bucket="${COS_CI_DOWNLOAD_GCS}/${BOARD}-release/${BUILD_ID}"
+        install_build_cross_toolchain "${bucket}"
+        install_build_kernel_headers "${bucket}"
+        ;;
+      custom)
+        install_build_cross_toolchain "${custom_bucket}"
+        install_build_kernel_headers "${custom_bucket}"
+        ;;
+    esac
+  fi
+
+  set_compilation_env
+
+  case "${build_target}" in
+    kernel) kernel_build -j"$(nproc)" ;;
+    module) module_build -j"$(nproc)" ;;
+    *)
+      echo "Starting interactive shell for the kernel devenv"
+      /bin/bash
+      ;;
+  esac
+}
+
+main "$@"