• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1#!/usr/bin/env python3
2# Copyright 2021 The Chromium OS Authors. All rights reserved.
3# Use of this source code is governed by a BSD-style license that can be
4# found in the LICENSE file.
5#
6# Usage:
7#
8# To get an interactive shell for development:
9#   ./tools/dev_container
10#
11# To run a command in the container, e.g. to run presubmits:
12#   ./tools/dev_container ./tools/presubmit
13#
14# The state of the container (including build artifacts) are preserved between
15# calls. To stop the container call:
16#   ./tools/dev_container --stop
17#
18# The dev container can also be called with a fresh container for each call that
19# is cleaned up afterwards (e.g. when run by Kokoro):
20#
21#   ./tools/dev_container --hermetic CMD
22
23import argparse
24import getpass
25from argh import arg
26from impl.common import CROSVM_ROOT, run_main, cmd, chdir, quoted
27import sys
28import os
29
30CONTAINER_NAME = f"crosvm_dev_{getpass.getuser()}"
31IMAGE_VERSION = (CROSVM_ROOT / "tools/impl/dev_container/version").read_text().strip()
32
33try:
34    docker = cmd(os.environ.get("DOCKER", "docker"))
35except ValueError:
36    docker = cmd("podman")
37
38is_podman = docker.executable.name == "podman"
39
40# Enable interactive mode when running in an interactive terminal.
41TTY_ARGS = "--interactive --tty" if sys.stdin.isatty() else None
42
43
44DOCKER_ARGS = [
45    TTY_ARGS,
46    # Podman will not share devices when `--privileged` is specified
47    "--privileged" if not is_podman else None,
48    # Share crosvm source
49    f"--volume {quoted(CROSVM_ROOT)}:/workspace:rw",
50    # Share devices and syslog
51    "--device /dev/kvm",
52    "--volume /dev/log:/dev/log",
53    "--device /dev/net/tun",
54    "--device /dev/vhost-net",
55    "--device /dev/vhost-vsock",
56    # Use tmpfs in the container for faster performance.
57    "--mount type=tmpfs,destination=/tmp",
58    # For plugin process jail
59    "--mount type=tmpfs,destination=/var/empty",
60    f"gcr.io/crosvm-packages/crosvm_dev:{IMAGE_VERSION}",
61]
62
63
64def container_revision(container_id: str):
65    image = docker("container inspect -f {{.Config.Image}}", container_id).stdout()
66    parts = image.split(":")
67    assert len(parts) == 2, f"Invalid image name {image}"
68    return parts[1]
69
70
71@arg("command", nargs=argparse.REMAINDER)
72def main(command: tuple[str, ...], stop: bool = False, hermetic: bool = False):
73    chdir(CROSVM_ROOT)
74    container_id = docker(f"ps -q -f name={CONTAINER_NAME}").stdout()
75
76    # Start an interactive shell by default
77    if not command:
78        command = ("/bin/bash",)
79
80    command = list(map(quoted, command))
81
82    if stop:
83        if container_id:
84            print(f"Stopping dev-container {container_id}.")
85            docker("rm -f", container_id).fg(quiet=True)
86        else:
87            print(f"Dev-container is not running.")
88        return
89
90    if hermetic:
91        docker(f"run --rm", *DOCKER_ARGS, *command).fg()
92    else:
93        if container_id and container_revision(container_id) != IMAGE_VERSION:
94            print(f"New image is available. Stopping old container ({container_id}).")
95            docker("rm -f", container_id).fg(quiet=True)
96            container_id = None
97
98        if not container_id:
99            container_id = docker(f"run --detach --name {CONTAINER_NAME}", *DOCKER_ARGS).stdout()
100            print(f"Started dev-container ({container_id}).")
101        else:
102            print(f"Using existing dev-container instance ({container_id}).")
103
104        docker("exec", TTY_ARGS, container_id, *command).fg()
105
106
107run_main(main)
108