blob: 0128508d151fb5dfecee3cb7d1b09ba11861dfcd [file] [log] [blame]
#!/usr/bin/env python3
# Copyright 2021 The Chromium OS Authors. All rights reserved.
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.
#
# Usage:
#
# To get an interactive shell for development:
# ./tools/dev_container
#
# To run a command in the container, e.g. to run presubmits:
# ./tools/dev_container ./tools/presubmit
#
# The state of the container (including build artifacts) are preserved between
# calls. To stop the container call:
# ./tools/dev_container --stop
#
# The dev container can also be called with a fresh container for each call that
# is cleaned up afterwards (e.g. when run by Kokoro):
#
# ./tools/dev_container --hermetic CMD
import argparse
import getpass
from argh import arg
from impl.common import CROSVM_ROOT, run_main, cmd, chdir, quoted
import sys
import os
CONTAINER_NAME = f"crosvm_dev_{getpass.getuser()}"
IMAGE_VERSION = (CROSVM_ROOT / "tools/impl/dev_container/version").read_text().strip()
try:
docker = cmd(os.environ.get("DOCKER", "docker"))
except ValueError:
docker = cmd("podman")
is_podman = docker.executable.name == "podman"
# Enable interactive mode when running in an interactive terminal.
TTY_ARGS = "--interactive --tty" if sys.stdin.isatty() else None
DOCKER_ARGS = [
TTY_ARGS,
# Podman will not share devices when `--privileged` is specified
"--privileged" if not is_podman else None,
# Share crosvm source
f"--volume {quoted(CROSVM_ROOT)}:/workspace:rw",
# Share devices and syslog
"--device /dev/kvm",
"--volume /dev/log:/dev/log",
"--device /dev/net/tun",
"--device /dev/vhost-net",
"--device /dev/vhost-vsock",
# Use tmpfs in the container for faster performance.
"--mount type=tmpfs,destination=/tmp",
f"gcr.io/crosvm-packages/crosvm_dev:{IMAGE_VERSION}",
]
def container_revision(container_id: str):
image = docker("container inspect -f {{.Config.Image}}", container_id).stdout()
parts = image.split(":")
assert len(parts) == 2, f"Invalid image name {image}"
return parts[1]
@arg("command", nargs=argparse.REMAINDER)
def main(command: tuple[str, ...], stop: bool = False, hermetic: bool = False):
chdir(CROSVM_ROOT)
container_id = docker(f"ps -q -f name={CONTAINER_NAME}").stdout()
# Start an interactive shell by default
if not command:
command = ("/bin/bash",)
command = list(map(quoted, command))
if stop:
if container_id:
print(f"Stopping dev-container {container_id}.")
docker("rm -f", container_id).fg(quiet=True)
else:
print(f"Dev-container is not running.")
return
if hermetic:
docker(f"run --rm", *DOCKER_ARGS, *command).fg()
else:
if container_id and container_revision(container_id) != IMAGE_VERSION:
print(f"New image is available. Stopping old container ({container_id}).")
docker("rm -f", container_id).fg(quiet=True)
container_id = None
if not container_id:
container_id = docker(f"run --detach --name {CONTAINER_NAME}", *DOCKER_ARGS).stdout()
print(f"Started dev-container ({container_id}).")
else:
print(f"Using existing dev-container instance ({container_id}).")
docker("exec", TTY_ARGS, container_id, *command).fg()
run_main(main)