1#!/bin/bash
2
3# Common code to build a host image on GCE
4
5# INTERNAL_extra_source may be set to a directory containing the source for
6# extra package to build.
7
8# INTERNAL_IP can be set to --internal-ip run on a GCE instance
9# The instance will need --scope compute-rw
10
11source "${ANDROID_BUILD_TOP}/external/shflags/shflags"
12DIR="${ANDROID_BUILD_TOP}/device/google/cuttlefish_vmm"
13
14# ARM-board options
15
16DEFINE_boolean arm false "Build on an ARM board"
17DEFINE_string arm_instance "" "IP address or DNS name of an ARM system to do the secondary build"
18DEFINE_string arm_user "vsoc-01" "User to invoke on the ARM system"
19
20# Docker options
21
22DEFINE_boolean docker false "Build inside docker"
23DEFINE_boolean docker_persistent true "Build inside a privileged, persistent container (faster for iterative development)"
24DEFINE_string docker_arch "$(uname -m)" "Target architectre"
25DEFINE_boolean docker_build_image true "When --noreuse is specified, this flag controls building the docker image (else we assume it was built and reuse it)"
26DEFINE_string docker_image "docker_vmm" "Name of docker image to build"
27DEFINE_string docker_container "docker_vmm" "Name of docker container to create"
28DEFINE_string docker_source "" "Path to sources checked out using manifest"
29DEFINE_string docker_working "" "Path to working directory"
30DEFINE_string docker_output "" "Output directory (when --docker is specified)"
31DEFINE_string docker_user "${USER}" "Docker-container user"
32DEFINE_string docker_uid "${UID}" "Docker-container user ID"
33
34# GCE options
35
36DEFINE_boolean gce false "Build on a GCE instance"
37DEFINE_string gce_project "$(gcloud config get-value project)" "Project to use" "p"
38DEFINE_string gce_source_image_family debian-10 "Image familty to use as the base" "s"
39DEFINE_string gce_source_image_project debian-cloud "Project holding the base image" "m"
40DEFINE_string gce_instance "${USER}-build" "Instance name to create for the build" "i"
41DEFINE_string gce_user cuttlefish_crosvm_builder "User name to use on GCE when doing the build"
42DEFINE_string gce_zone "$(gcloud config get-value compute/zone)" "Zone to use" "z"
43
44# Common options
45
46DEFINE_string manifest "" "Path to custom manifest to use for the build"
47DEFINE_boolean reuse false "Set to true to reuse a previously-set-up instance."
48DEFINE_boolean reuse_resync false "Reuse a previously-set-up instance, but clean and re-sync the sources. Overrides --reuse if both are specified."
49
50SSH_FLAGS=(${INTERNAL_IP})
51
52wait_for_instance() {
53  alive=""
54  while [[ -z "${alive}" ]]; do
55    sleep 5
56    alive="$(gcloud compute ssh "${SSH_FLAGS[@]}" "$@" -- uptime || true)"
57  done
58}
59
60check_common_docker_options() {
61  if [[ -z "${FLAGS_docker_image}" ]]; then
62    echo Option --docker_image must not be empty 1>&1
63    fail=1
64  fi
65  if [[ -z "${FLAGS_docker_container}" ]]; then
66    echo Options --docker_container must not be empty 1>&2
67    fail=1
68  fi
69  if [[ -z "${FLAGS_docker_user}" ]]; then
70    echo Options --docker_user must not be empty 1>&2
71    fail=1
72  fi
73  if [[ -z "${FLAGS_docker_uid}" ]]; then
74    echo Options --docker_uid must not be empty 1>&2
75    fail=1
76  fi
77  # Volume mapping are specified only when a container is created.  With
78  # --reuse, an already-created persistent container is reused, which implies
79  # that we cannot change the volume maps.  For non-persistent containers, we
80  # use docker run, which creates and runs the continer in one step; in that
81  # case, we must pass the same values for --docker_source and --docker_output
82  # that we passed when we ran the non-persistent continer the first time.
83  if [[ ${_reuse} -eq 1 && ${FLAGS_docker_persistent} -eq ${FLAGS_TRUE} ]]; then
84    if [ -n "${FLAGS_docker_source}" ]; then
85      echo Option --docker_source may not be specified with --reuse and --docker_persistent 1>&2
86      fail=1
87    fi
88    if [ -n "${FLAGS_docker_working}" ]; then
89      echo Option --docker_working may not be specified with --reuse and --docker_persistent 1>&2
90      fail=1
91    fi
92    if [ -n "${FLAGS_docker_output}" ]; then
93      echo Option --docker_output may not be specified with --reuse and --docker_persistent 1>&2
94      fail=1
95    fi
96  fi
97  if [[ "${fail}" -ne 0 ]]; then
98    exit "${fail}"
99  fi
100}
101
102build_locally_using_docker() {
103  check_common_docker_options
104  case "${FLAGS_docker_arch}" in
105    aarch64) ;;
106    x86_64) ;;
107    *) echo Invalid value ${FLAGS_docker_arch} for --docker_arch 1>&2
108      fail=1
109      ;;
110  esac
111  if [[ "${fail}" -ne 0 ]]; then
112    exit "${fail}"
113  fi
114  local -i _persistent=0
115  if [[ ${FLAGS_docker_persistent} -eq ${FLAGS_TRUE} ]]; then
116    _persistent=1
117  fi
118
119  local -i _build_image=0
120  if [[ ${FLAGS_docker_build_image} -eq ${FLAGS_TRUE} ]]; then
121    _build_image=1
122  fi
123
124  local _docker_output=""
125  if [ -z "${FLAGS_docker_output}" ]; then
126    _docker_output="${ANDROID_BUILD_TOP}/device/google/cuttlefish_vmm/${FLAGS_docker_arch}-linux-gnu"
127  else
128    _docker_output="${FLAGS_docker_output}"
129  fi
130
131  local _temp="$(mktemp -d)"
132  rsync -avR "${relative_source_files[@]/#/${DIR}/./}" "${_temp}"
133  if [ -n "${custom_manifest}" ]; then
134    cp "${custom_manifest}" "${_temp}"/custom.xml
135  else
136    touch "${_temp}"/custom.xml
137  fi
138
139  ${DIR}/rebuild-docker.sh "${FLAGS_docker_image}" \
140                     "${FLAGS_docker_container}" \
141                     "${FLAGS_docker_arch}" \
142                     "${FLAGS_docker_user}" \
143                     "${FLAGS_docker_uid}" \
144                     "${_persistent}" \
145                     "x${FLAGS_docker_source}" \
146                     "x${FLAGS_docker_working}" \
147                     "x${_docker_output}" \
148                     "${_reuse}" \
149                     "${_build_image}" \
150                     "${_temp}/Dockerfile" \
151                     "${_temp}" \
152                     "${#docker_flags[@]}" "${docker_flags[@]}" \
153                     "${#_prepare_source[@]}" "${_prepare_source[@]}"
154
155  rm -rf "${_temp}"
156}
157
158function build_on_gce() {
159  check_common_docker_options
160  if [[ -z "${FLAGS_gce_instance}" ]]; then
161    echo Must specify instance 1>&2
162    fail=1
163  fi
164  if [[ -z "${FLAGS_gce_project}" ]]; then
165    echo Must specify project 1>&2
166    fail=1
167  fi
168  if [[ -z "${FLAGS_gce_zone}" ]]; then
169    echo Must specify zone 1>&2
170    fail=1
171  fi
172  if [[ "${fail}" -ne 0 ]]; then
173    exit "${fail}"
174  fi
175  project_zone_flags=(--project="${FLAGS_gce_project}" --zone="${FLAGS_gce_zone}")
176  if [ ${_reuse} -eq 0 ]; then
177    delete_instances=("${FLAGS_gce_instance}")
178    gcloud compute instances delete -q \
179      "${delete_instances[@]}" \
180      "${project_zone_flags[@]}" || \
181        echo Instance does not exist
182    gcloud compute images delete -q \
183      "${delete_instances[@]/%/-image}" \
184      --project "${FLAGS_gce_project}" || \
185        echo Image does not exist
186    gcloud compute disks delete -q \
187      "${delete_instances[@]/%/-disk}" \
188      "${project_zone_flags[@]}" || \
189        echo Disk does not exist
190
191    gcloud compute disks create \
192      "${delete_instances[@]/%/-disk}" \
193      "${project_zone_flags[@]}" \
194      --image-project="${FLAGS_gce_source_image_project}" \
195      --image-family="${FLAGS_gce_source_image_family}"
196    gcloud compute images create \
197      "${delete_instances[@]/%/-image}" \
198      --source-disk "${delete_instances[@]/%/-disk}" \
199      --project "${FLAGS_gce_project}" --source-disk-zone "${FLAGS_gce_zone}" \
200      --licenses "https://www.googleapis.com/compute/v1/projects/vm-options/global/licenses/enable-vmx"
201    gcloud compute instances create \
202      "${delete_instances[@]}" \
203      "${project_zone_flags[@]}" \
204      --image "${delete_instances[@]/%/-image}" \
205      --boot-disk-size=200GB \
206      --machine-type=n1-standard-8 \
207      --min-cpu-platform "Intel Skylake"
208
209    wait_for_instance "${FLAGS_gce_instance}" "${project_zone_flags[@]}"
210
211    # install docker
212    gcloud beta compute ssh "${SSH_FLAGS[@]}" \
213        "${project_zone_flags[@]}" \
214        "${FLAGS_gce_user}@${FLAGS_gce_instance}" -- \
215        'curl -fsSL https://get.docker.com | /bin/bash'
216    gcloud beta compute ssh "${SSH_FLAGS[@]}" \
217        "${project_zone_flags[@]}" \
218        "${FLAGS_gce_user}@${FLAGS_gce_instance}" -- \
219      sudo usermod -aG docker "${FLAGS_gce_user}"
220
221    # beta for the --internal-ip flag that may be passed via SSH_FLAGS
222
223    gcloud beta compute ssh "${SSH_FLAGS[@]}" \
224        "${project_zone_flags[@]}" \
225        "${FLAGS_gce_user}@${FLAGS_gce_instance}" -- \
226        mkdir -p '$PWD/docker $PWD/docker/source $PWD/docker/working $PWD/docker/output'
227
228    tar czv -C "${DIR}" -f - "${relative_source_files[@]}" | \
229      gcloud beta compute ssh "${SSH_FLAGS[@]}" \
230          "${project_zone_flags[@]}" \
231          "${FLAGS_gce_user}@${FLAGS_gce_instance}" -- \
232          'tar xzv -C ~/docker -f -'
233    if [ -n "${custom_manifest}" ]; then
234      gcloud beta compute scp "${SSH_FLAGS[@]}" \
235        "${project_zone_flags[@]}" \
236        "${custom_manifest}" \
237        "${FLAGS_gce_user}@${FLAGS_gce_instance}:~/docker/custom.xml"
238    else
239      gcloud beta compute ssh "${SSH_FLAGS[@]}" \
240        "${project_zone_flags[@]}" \
241        "${FLAGS_gce_user}@${FLAGS_gce_instance}" -- \
242        "touch ~/docker/custom.xml"
243    fi
244  fi
245
246  local _status=$(gcloud compute instances list \
247                  --project="${FLAGS_gce_project}" \
248                  --zones="${FLAGS_gce_zone}" \
249                  --filter="name=('${FLAGS_gce_instance}')" \
250                  --format=flattened | awk '/status:/ {print $2}')
251  if [ "${_status}" != "RUNNING" ] ; then
252    echo "Instance ${FLAGS_gce_instance} is not running."
253    exit 1;
254  fi
255
256  local -i _persistent=0
257  if [[ ${FLAGS_docker_persistent} -eq ${FLAGS_TRUE} ]]; then
258    _persistent=1
259  fi
260  local -i _build_image=0
261  if [[ ${FLAGS_docker_build_image} -eq ${FLAGS_TRUE} ]]; then
262    _build_image=1
263  fi
264  gcloud beta compute ssh "${SSH_FLAGS[@]}" \
265      "${project_zone_flags[@]}" \
266      "${FLAGS_gce_user}@${FLAGS_gce_instance}" -- \
267      ./docker/rebuild-docker.sh "${FLAGS_docker_image}" \
268                       "${FLAGS_docker_container}" \
269                       "${FLAGS_docker_arch}" \
270                       '${USER}' \
271                       '${UID}' \
272                       "${_persistent}" \
273                       'x$PWD/docker/source' \
274                       'x$PWD/docker/working' \
275                       'x$PWD/docker/output' \
276                       "${_reuse}" \
277                       "${_build_image}" \
278                       '~/docker/Dockerfile' \
279                       '~/docker/' \
280                       "${#docker_flags[@]}" "${docker_flags[@]}" \
281                       "${#_prepare_source[@]}" "${_prepare_source[@]}"
282
283  gcloud beta compute ssh "${SSH_FLAGS[@]}" \
284      "${project_zone_flags[@]}" \
285      "${FLAGS_gce_user}@${FLAGS_gce_instance}" --command \
286      'tar czv -C $PWD/docker/output -f - $(find $PWD/docker/output -printf "%P\n")' | \
287    tar xzv -C ${DIR}/${FLAGS_docker_arch}-linux-gnu -f -
288
289  gcloud compute disks describe \
290    "${project_zone_flags[@]}" "${FLAGS_gce_instance}" | \
291      grep ^sourceImage: > "${DIR}"/x86_64-linux-gnu/builder_image.txt
292}
293
294function build_on_arm_board() {
295  check_common_docker_options
296  if [[ "${FLAGS_docker_arch}" != "aarch64" ]]; then
297    echo ARM board supports building only aarch64 1>&2
298    fail=1
299  fi
300  if [[ -z "${FLAGS_arm_instance}" ]]; then
301    echo Must specify IP address of ARM board 1>&2
302    fail=1
303  fi
304  if [[ -z "${FLAGS_arm_user}" ]]; then
305    echo Must specify a user account on ARM board 1>&2
306    fail=1
307  fi
308  if [[ "${fail}" -ne 0 ]]; then
309    exit "${fail}"
310  fi
311  if [[ "${_reuse}" -eq 0 ]]; then
312    ssh -t "${FLAGS_arm_user}@${FLAGS_arm_instance}" -- \
313      rm -rf '$PWD/docker'
314  fi
315  rsync -avR -e ssh \
316    "${relative_source_files[@]/#/${DIR}/./}" \
317    "${FLAGS_arm_user}@${FLAGS_arm_instance}:~/docker/"
318
319  if [ -n "${custom_manifest}" ]; then
320    scp "${custom_manifest}" "${FLAGS_arm_user}@${FLAGS_arm_instance}":~/docker/custom.xml
321  else
322    ssh -t "${FLAGS_arm_user}@${FLAGS_arm_instance}" -- \
323      "touch ~/docker/custom.xml"
324  fi
325
326  local -i _persistent=0
327  if [[ ${FLAGS_docker_persistent} -eq ${FLAGS_TRUE} ]]; then
328    _persistent=1
329  fi
330  local -i _build_image=0
331  if [[ ${FLAGS_docker_build_image} -eq ${FLAGS_TRUE} ]]; then
332    _build_image=1
333  fi
334  ssh -t "${FLAGS_arm_user}@${FLAGS_arm_instance}" -- \
335    mkdir -p '$PWD/docker/source' '$PWD/docker/working' '$PWD/docker/output'
336  ssh -t "${FLAGS_arm_user}@${FLAGS_arm_instance}" -- \
337    ./docker/rebuild-docker.sh "${FLAGS_docker_image}" \
338                     "${FLAGS_docker_container}" \
339                     "${FLAGS_docker_arch}" \
340                     '${USER}' \
341                     '${UID}' \
342                     "${_persistent}" \
343                     'x$PWD/docker/source' \
344                     'x$PWD/docker/working' \
345                     'x$PWD/docker/output' \
346                     "${_reuse}" \
347                     "${_build_image}" \
348                     '~/docker/Dockerfile' \
349                     '~/docker/' \
350                     "${#docker_flags[@]}" "${docker_flags[@]}" \
351                     "${#_prepare_source[@]}" "${_prepare_source[@]}"
352
353  rsync -avR -e ssh "${FLAGS_arm_user}@${FLAGS_arm_instance}":docker/output/./ \
354    "${ANDROID_BUILD_TOP}/device/google/cuttlefish_vmm/${FLAGS_docker_arch}-linux-gnu"
355}
356
357main() {
358  set -o errexit
359  set -x
360  fail=0
361  relative_source_files=("rebuild-docker.sh"
362     "rebuild-internal.sh"
363     "Dockerfile"
364     "x86_64-linux-gnu/manifest.xml"
365     "aarch64-linux-gnu/manifest.xml"
366     "policy-inliner.sh"
367     ".dockerignore")
368  # These must match the definitions in the Dockerfile
369  docker_flags=("-eSOURCE_DIR=/source" "-eWORKING_DIR=/working" "-eOUTPUT_DIR=/output" "-eTOOLS_DIR=/static/tools")
370
371  if [[ $(( $((${FLAGS_gce}==${FLAGS_TRUE})) + $((${FLAGS_arm}==${FLAGS_TRUE})) + $((${FLAGS_docker}==${FLAGS_TRUE})) )) > 1 ]]; then
372    echo You may specify only one of --gce, --docker, or --arm 1>&2
373    exit 2
374  fi
375
376  if [[ -n "${FLAGS_manifest}" ]]; then
377    if [[ ! -f "${FLAGS_manifest}" ]]; then
378      echo custom manifest not found: ${FLAGS_manifest} 1>&1
379      exit 2
380    fi
381    custom_manifest="${FLAGS_manifest}"
382    docker_flags+=("-eCUSTOM_MANIFEST=/static/custom.xml")
383  else
384    custom_manifest="${DIR}/${FLAGS_docker_arch}-linux-gnu/manifest.xml"
385    docker_flags+=("-eCUSTOM_MANIFEST=/static/${FLAGS_docker_arch}-linux-gnu/manifest.xml")
386  fi
387  local -a _prepare_source=(setup_env fetch_source);
388  local -i _reuse=0
389  if [[ ${FLAGS_reuse} -eq ${FLAGS_TRUE} ]]; then
390    # neither install packages, nor sync sources; skip to building them
391    _prepare_source=(setup_env)
392    # unless you're setting up a non-persistent container and --docker_source is
393    # the empty string; in this case, --reuse implies --reuse_resync
394    if [[ "${FLAGS_docker_persistent}" -eq ${FLAGS_FALSE} && \
395          -z "${FLAGS_docker_source}" ]]; then
396      _prepare_source+=(resync_source)
397    fi
398    _reuse=1
399  fi
400  if [[ ${FLAGS_reuse_resync} -eq ${FLAGS_TRUE} ]]; then
401    # do not install packages but clean and sync sources afresh
402    _prepare_source=(setup_env resync_source);
403    _reuse=1
404  fi
405  if [[ ${FLAGS_gce} -eq ${FLAGS_TRUE} ]]; then
406    build_on_gce
407    exit 0
408    gcloud compute instances delete -q \
409      "${project_zone_flags[@]}" \
410      "${FLAGS_gce_instance}"
411  fi
412  if [[ ${FLAGS_arm} -eq ${FLAGS_TRUE} ]]; then
413    build_on_arm_board
414    exit 0
415  fi
416  if [[ ${FLAGS_docker} -eq ${FLAGS_TRUE} ]]; then
417    build_locally_using_docker
418    exit 0
419  fi
420}
421
422FLAGS "$@" || exit 1
423main "${FLAGS_ARGV[@]}"
424