Add docker builds for x86 crosvm binaries

Cleaning up:
-- renamed --x86_instance, --x86_user --> --gce_instance, --gce_user;
-- renamed --arm_system to --arm_instance for consistency with
   option -- gce_instance;
-- added top-level --gce, --arm, --docker toggles, all false by default;
   these now need to be specified to trigger the respective type of
   build, e.g:

	./device/google/cuttlefish_vmm/rebuild.sh --gce
	./device/google/cuttlefish_vmm/rebuild.sh --arm \
		--arm_instance 192.168.0.6 --arm_user cf

-- fixed typo: rebuild-internal.sh, not rebuild_internal.sh;
-- put in checks to ensure --arm_system and --arm_user aren't empty when
   option --arm is specified;
-- invoke rebuild-internal.sh with install_packages separately from
   rebuild-internal.sh to actually build crosvm, since
   rebuild-internal.sh no longer directly invokes install_packages;
-- Rename option --custom_manifest to --manifest; make --manifest take
   the default value of ./devices/google/cuttlefish_vmm/$(uname
   -m)-linux-gnu/manifest.xml;
-- Always pass the manifest flag to the build script.  The implication
   is that you no longer have to specify the manifest.  Previously,
   failing to specify explicitly a manifest file meant that the entire
   Android tree would be synced, which was an enormous waste of time.

Added support for x86 docker images:
-- New option --docker that enables a docker build;
-- New options --docker_source, --docker_working, and --docker_output to
   specify external paths to contain respectively the sources, working
   directory, and output directory for the finished binary artifacts;
-- Option --docker_output defaults to
   ./device/google/cuttlefish_vmm/$(uname -m)-linux-gnu, meaning the
   docker build will put the built images in the right place;
-- If options --docker_source and --docker_working are left unspecified,
   the container will pull the sources internally and stash its
   intermediate object files internally as well;
-- Options --reuse and --reuse_resync work with the docker build;
-- The .dockerignore file is meant to prevent the entire directory from
   being copied to the docker deamon as context, except for the three
   files (rebuild-internal.sh, and *-linux-gnu/manifest.xml) that we
   care about.

Test: built crosvm for gce, arm, and docker
Bug: 148642775 Clean up the crosvm build

Change-Id: I861c5ee8f1d7c7e1b1048380af6b49ea7834ca88
Signed-off-by: Iliyan Malchev <malchev@google.com>
diff --git a/.dockerignore b/.dockerignore
new file mode 100644
index 0000000..f33aa59
--- /dev/null
+++ b/.dockerignore
@@ -0,0 +1,7 @@
+.*
+*
+*/*
+*/*/*
+!rebuild-internal.sh
+!x86_64-linux-gnu/manifest.xml
+!aarch64-linux-gnu/manifest.xml
diff --git a/Dockerfile b/Dockerfile
new file mode 100644
index 0000000..2d56f74
--- /dev/null
+++ b/Dockerfile
@@ -0,0 +1,45 @@
+FROM debian:buster-slim
+
+ENV container docker
+ENV LC_ALL C.UTF-8
+ENV DEBIAN_FRONTEND noninteractive
+
+# Set up the user to be the same as the user creating the container.  Not
+# strictly necessary, but this way all the permissions of the generated files
+# will match.
+
+ARG USER
+ARG UID
+
+ENV USER $USER
+ENV HOME /home/$USER
+ENV CUSTOM_MANIFEST ""
+
+RUN apt update \
+    && apt install -y sudo
+
+RUN useradd -m -s /bin/bash $USER -u $UID -d $HOME \
+    && passwd -d $USER \
+    && echo "$USER ALL=(ALL) NOPASSWD: ALL" >> /etc/sudoers
+
+RUN mkdir /source && chown -R $USER /source
+RUN mkdir /output && chown -R $USER /output
+RUN mkdir /working && chown -R $USER /working
+RUN mkdir /static && chown -R $USER /static
+
+SHELL [ "/bin/bash", "-c" ]
+
+USER $USER
+WORKDIR /working
+
+COPY --chown=$USER x86_64-linux-gnu/manifest.xml /static/x86_64-linux-gnu/manifest.xml
+COPY --chown=$USER aarch64-linux-gnu/manifest.xml /static/aarch64-linux-gnu/manifest.xml
+COPY --chown=$USER rebuild-internal.sh /static/rebuild-internal.sh
+
+RUN TOOLS_DIR=/static/tools /static/rebuild-internal.sh install_packages
+
+VOLUME /source
+VOLUME /working
+VOLUME /output
+
+ENTRYPOINT ["/static/rebuild-internal.sh"]
diff --git a/rebuild.sh b/rebuild.sh
index 13c693a..9678d2a 100755
--- a/rebuild.sh
+++ b/rebuild.sh
@@ -11,21 +11,39 @@
 source "${ANDROID_BUILD_TOP}/external/shflags/src/shflags"
 DIR="${ANDROID_BUILD_TOP}/device/google/cuttlefish_vmm"
 
-DEFINE_string arm_system \
-  "" "IP address or DNS name of an ARM system to do the secondary build"
-DEFINE_string arm_user \
-  "vsoc-01" "User to invoke on the ARM system"
-DEFINE_string custom_manifest "" "Custom manifest to use for the build"
-DEFINE_string project "$(gcloud config get-value project)" "Project to use" "p"
-DEFINE_string source_image_family debian-10 "Image familty to use as the base" \
-  "s"
-DEFINE_string source_image_project debian-cloud \
-  "Project holding the base image" "m"
-DEFINE_string x86_instance \
-  "${USER}-build" "Instance name to create for the build" "i"
-DEFINE_string x86_user cuttlefish_crosvm_builder \
-  "User name to use on GCE when doing the build"
-DEFINE_string zone "$(gcloud config get-value compute/zone)" "Zone to use" "z"
+# ARM-board options
+
+DEFINE_boolean arm false "Build on an ARM board"
+DEFINE_string arm_instance "" "IP address or DNS name of an ARM system to do the secondary build"
+DEFINE_string arm_user "vsoc-01" "User to invoke on the ARM system"
+
+# Docker options
+
+DEFINE_boolean docker false "Build inside docker"
+DEFINE_string docker_arch "$(uname -m)" "Target architectre"
+DEFINE_string docker_image "docker_vmm" "Name of docker image to build"
+DEFINE_string docker_container "docker_vmm" "Name of docker container to create"
+DEFINE_string docker_source "" "Path to sources checked out using manifest"
+DEFINE_string docker_working "" "Path to working directory"
+DEFINE_string docker_output "${ANDROID_BUILD_TOP}/device/google/cuttlefish_vmm/$(uname -m)-linux-gnu" "Output directory"
+DEFINE_string docker_user "${USER}" "Docker-container user"
+DEFINE_string docker_uid "${UID}" "Docker-container user ID"
+
+# GCE options
+
+DEFINE_boolean gce false "Build on a GCE instance"
+DEFINE_string gce_project "$(gcloud config get-value project)" "Project to use" "p"
+DEFINE_string gce_source_image_family debian-10 "Image familty to use as the base" "s"
+DEFINE_string gce_source_image_project debian-cloud "Project holding the base image" "m"
+DEFINE_string gce_instance "${USER}-build" "Instance name to create for the build" "i"
+DEFINE_string gce_user cuttlefish_crosvm_builder "User name to use on GCE when doing the build"
+DEFINE_string gce_zone "$(gcloud config get-value compute/zone)" "Zone to use" "z"
+
+# Common options
+
+DEFINE_string manifest \
+          "${ANDROID_BUILD_TOP}/device/google/cuttlefish_vmm/$(uname -m)-linux-gnu/manifest.xml" \
+          "manifest to use for the build"
 DEFINE_boolean reuse false "Set to true to reuse a previously-set-up instance."
 DEFINE_boolean reuse_resync false "Reuse a previously-set-up instance, but clean and re-sync the sources. Overrides --reuse if both are specified."
 
@@ -43,45 +61,52 @@
   set -o errexit
   set -x
   fail=0
-  source_files=("${DIR}"/rebuild_internal.sh)
+  source_files=("${DIR}"/rebuild-internal.sh)
+  # These must match the definitions in the Dockerfile
+  docker_flags=("-e SOURCE_DIR=/source" "-e WORKING_DIR=/working" "-e OUTPUT_DIR=/output" "-e TOOLS_DIR=/static/tools")
   gce_flags=()
   arm_flags=()
-  if [[ -n "${FLAGS_custom_manifest}" ]]; then
-    if [[ ! -f "${FLAGS_custom_manifest}" ]]; then
-      echo custom manifest not found: ${FLAGS_custom_manifest} 1>&1
+  if [[ -n "${FLAGS_manifest}" ]]; then
+    if [[ ! -f "${FLAGS_manifest}" ]]; then
+      echo custom manifest not found: ${FLAGS_manifest} 1>&1
       exit 2
     fi
-    source_files+=("${FLAGS_custom_manifest}")
-    gce_flags+=("CUSTOM_MANIFEST=/home/${FLAGS_x86_user}/$(basename "${FLAGS_custom_manifest}")")
-    arm_flags+=("CUSTOM_MANIFEST=/home/${FLAGS_arm_user}/$(basename "${FLAGS_custom_manifest}")")
+    source_files+=("${FLAGS_manifest}")
+    docker_flags+=("-e CUSTOM_MANIFEST=/static/${FLAGS_docker_arch}-linux-gnu/$(basename ${FLAGS_manifest})")
+    gce_flags+=("CUSTOM_MANIFEST=/home/${FLAGS_gce_user}/$(basename "${FLAGS_manifest}")")
+    arm_flags+=("CUSTOM_MANIFEST=/home/${FLAGS_arm_user}/$(basename "${FLAGS_manifest}")")
   fi
-  local _prepare_source=(install_packages prepare_source);
+  local _prepare_source=(setup_env fetch_source);
   local _reuse=0
-  if [ ${FLAGS_reuse} -eq ${FLAGS_TRUE} ]; then
+  if [[ ${FLAGS_reuse} -eq ${FLAGS_TRUE} ]]; then
     # neither install packages, nor sync sources; skip to building them
-    _prepare_source=()
+    _prepare_source=(setup_env)
     _reuse=1
   fi
-  if [ ${FLAGS_reuse_resync} -eq ${FLAGS_TRUE} ]; then
+  if [[ ${FLAGS_reuse_resync} -eq ${FLAGS_TRUE} ]]; then
     # do not install packages but clean and sync sources afresh
-    _prepare_source=(resync_source);
+    _prepare_source=(setup_env resync_source);
     _reuse=1
   fi
-  if [[ -n "${FLAGS_x86_instance}" ]]; then
-    if [[ -z "${FLAGS_project}" ]]; then
+  if [[ ${FLAGS_gce} -eq ${FLAGS_TRUE} ]]; then
+    if [[ -z "${FLAGS_gce_instance}" ]]; then
+      echo Must specify instance 1>&2
+      fail=1
+    fi
+    if [[ -z "${FLAGS_gce_project}" ]]; then
       echo Must specify project 1>&2
       fail=1
     fi
-    if [[ -z "${FLAGS_zone}" ]]; then
+    if [[ -z "${FLAGS_gce_zone}" ]]; then
       echo Must specify zone 1>&2
       fail=1
     fi
     if [[ "${fail}" -ne 0 ]]; then
       exit "${fail}"
     fi
-    project_zone_flags=(--project="${FLAGS_project}" --zone="${FLAGS_zone}")
+    project_zone_flags=(--project="${FLAGS_gce_project}" --zone="${FLAGS_gce_zone}")
     if [ ${_reuse} -eq 0 ]; then
-      delete_instances=("${FLAGS_x86_instance}")
+      delete_instances=("${FLAGS_gce_instance}")
       gcloud compute instances delete -q \
         "${project_zone_flags[@]}" \
         "${delete_instances[@]}" || \
@@ -90,50 +115,124 @@
         "${project_zone_flags[@]}" \
         --boot-disk-size=200GB \
         --machine-type=n1-standard-4 \
-        --image-family="${FLAGS_source_image_family}" \
-        --image-project="${FLAGS_source_image_project}" \
-        "${FLAGS_x86_instance}"
-      wait_for_instance "${FLAGS_x86_instance}"
+        --image-family="${FLAGS_gce_source_image_family}" \
+        --image-project="${FLAGS_gce_source_image_project}" \
+        "${FLAGS_gce_instance}"
+      wait_for_instance "${FLAGS_gce_instance}"
     fi
     local _status=$(gcloud compute instances list \
-                    --project="${FLAGS_project}" \
-                    --zones="${FLAGS_zone}" \
-                    --filter="name=('${FLAGS_x86_instance}')" \
+                    --project="${FLAGS_gce_project}" \
+                    --zones="${FLAGS_gce_zone}" \
+                    --filter="name=('${FLAGS_gce_instance}')" \
                     --format=flattened | awk '/status:/ {print $2}')
     if [ "${_status}" != "RUNNING" ] ; then
-      echo "Instance ${FLAGS_x86_instance} is not running."
+      echo "Instance ${FLAGS_gce_instance} is not running."
       exit 1;
     fi
     # beta for the --internal-ip flag that may be passed via SSH_FLAGS
     gcloud beta compute scp "${SSH_FLAGS[@]}" \
       "${project_zone_flags[@]}" \
       "${source_files[@]}" \
-      "${FLAGS_x86_user}@${FLAGS_x86_instance}:"
+      "${FLAGS_gce_user}@${FLAGS_gce_instance}:"
+    if [ ${_reuse} -eq 0 ]; then
+      gcloud compute ssh "${SSH_FLAGS[@]}" \
+        "${project_zone_flags[@]}" \
+        "${FLAGS_gce_user}@${FLAGS_gce_instance}" -- \
+        ./rebuild-internal.sh install_packages
+    fi
     gcloud compute ssh "${SSH_FLAGS[@]}" \
       "${project_zone_flags[@]}" \
-      "${FLAGS_x86_user}@${FLAGS_x86_instance}" -- \
-        ./rebuild_internal.sh "${gce_flags[@]}" ${_prepare_source[@]} x86_64_build
+      "${FLAGS_gce_user}@${FLAGS_gce_instance}" -- \
+      ./rebuild-internal.sh "${gce_flags[@]}" ${_prepare_source[@]} '$(uname -m)_build'
     gcloud beta compute scp --recurse "${SSH_FLAGS[@]}" \
       "${project_zone_flags[@]}" \
-      "${FLAGS_x86_user}@${FLAGS_x86_instance}":x86_64-linux-gnu \
+      "${FLAGS_gce_user}@${FLAGS_gce_instance}":x86_64-linux-gnu \
       "${ANDROID_BUILD_TOP}/device/google/cuttlefish_vmm"
     gcloud compute disks describe \
-      "${project_zone_flags[@]}" "${FLAGS_x86_instance}" | \
+      "${project_zone_flags[@]}" "${FLAGS_gce_instance}" | \
         grep ^sourceImage: > "${DIR}"/x86_64-linux-gnu/builder_image.txt
   fi
-  if [[ -n "${FLAGS_arm_system}" ]]; then
+  if [ ${FLAGS_arm} -eq ${FLAGS_TRUE} ]; then
+    if [[ -z "${FLAGS_arm_instance}" ]]; then
+      echo Must specify IP address of ARM board 1>&2
+      fail=1
+    fi
+    if [[ -z "${FLAGS_arm_instance}" ]]; then
+      echo Must specify a user account on ARM board 1>&2
+      fail=1
+    fi
+    if [[ "${fail}" -ne 0 ]]; then
+      exit "${fail}"
+    fi
     scp \
       "${source_files[@]}" \
-      "${FLAGS_arm_user}@${FLAGS_arm_system}:"
-    ssh -t "${FLAGS_arm_user}@${FLAGS_arm_system}" -- \
-        ./rebuild-internal.sh "${arm_flags[@]}" ${_prepare_source[@]} arm64_build
-    scp -r "${FLAGS_arm_user}@${FLAGS_arm_system}":aarch64-linux-gnu \
+      "${FLAGS_arm_user}@${FLAGS_arm_instance}:"
+    if [ ${_reuse} -eq 0 ]; then
+      ssh -t "${FLAGS_arm_user}@${FLAGS_arm_instance}" -- \
+        ./rebuild-internal.sh install_packages
+    fi
+    ssh -t "${FLAGS_arm_user}@${FLAGS_arm_user}" -- \
+      ./rebuild-internal.sh "${arm_flags[@]}" ${_prepare_source[@]} '$(uname -m)_build'
+    scp -r "${FLAGS_arm_user}@${FLAGS_arm_instance}":aarch64-linux-gnu \
       "${ANDROID_BUILD_TOP}/device/google/cuttlefish_vmm"
   fi
+  if [[ ${FLAGS_docker} -eq ${FLAGS_TRUE} ]]; then
+    if [[ -z "${FLAGS_docker_image}" ]]; then
+      echo Option --docker_image must not be empty 1>&1
+      fail=1
+    fi
+    if [[ -z "${FLAGS_docker_container}" ]]; then
+      echo Options --docker_container must not be empty 1>&2
+      fail=1
+    fi
+    case "${FLAGS_docker_arch}" in
+      aarch64) ;;
+      x86_64) ;;
+      *) echo Invalid value ${FLAGS_docker_arch} for --docker_arch 1>&2
+        fail=1
+        ;;
+    esac
+    if [[ -z "${FLAGS_docker_user}" ]]; then
+      echo Options --docker_user must not be empty 1>&2
+      fail=1
+    fi
+    if [[ -z "${FLAGS_docker_uid}" ]]; then
+      echo Options --docker_uid must not be empty 1>&2
+      fail=1
+    fi
+    if [[ "${fail}" -ne 0 ]]; then
+      exit "${fail}"
+    fi
+    if [[ ${_reuse} -eq 0 ]]; then
+      docker build \
+        -f ${DIR}/Dockerfile \
+        -t ${FLAGS_docker_image}:latest \
+        ${DIR} \
+        --build-arg USER=${FLAGS_docker_user} \
+        --build-arg UID=${FLAGS_docker_uid}
+    fi
+    _docker_source=()
+    if [ -n "${FLAGS_docker_source}" ]; then
+      _docker_source+=("-v ${FLAGS_docker_source}:/source")
+    fi
+    _docker_working=()
+    if [ -n "${FLAGS_docker_working}" ]; then
+      _docker_working+=("-v ${FLAGS_docker_working}:/working")
+    fi
+    docker run -it --rm \
+      --user ${FLAGS_docker_user} \
+      --name ${FLAGS_docker_container} \
+      ${_docker_source} \
+      ${_docker_working} \
+      -v "${FLAGS_docker_output}":/output \
+      ${docker_flags[@]} \
+      ${FLAGS_docker_image}:latest \
+      ${_prepare_source[@]} ${FLAGS_docker_arch}_build
+  fi
   exit 0
   gcloud compute instances delete -q \
     "${project_zone_flags[@]}" \
-    "${FLAGS_x86_instance}"
+    "${FLAGS_gce_instance}"
 }
 
 FLAGS "$@" || exit 1