1#!/bin/bash
2
3# Note: not intended to be invoked directly, see rebuild.sh.
4#
5# Rebuilds Crosvm and its dependencies from a clean state.
6
7: ${TOOLS_DIR:="$(pwd)/tools"}
8
9setup_env() {
10  : ${SOURCE_DIR:="$(pwd)/source"}
11  : ${WORKING_DIR:="$(pwd)/working"}
12  : ${CUSTOM_MANIFEST:=""}
13
14  ARCH="$(uname -m)"
15  : ${OUTPUT_DIR:="$(pwd)/${ARCH}-linux-gnu"}
16  OUTPUT_BIN_DIR="${OUTPUT_DIR}/bin"
17  OUTPUT_ETC_DIR="${OUTPUT_DIR}/etc"
18  OUTPUT_SECCOMP_DIR="${OUTPUT_ETC_DIR}/seccomp"
19  OUTPUT_LIB_DIR="${OUTPUT_DIR}/bin"
20
21  export PATH="${PATH}:${TOOLS_DIR}:${HOME}/.local/bin"
22}
23
24set -o errexit
25set -x
26
27fatal_echo() {
28  echo "$@"
29  exit 1
30}
31
32prepare_cargo() {
33  echo Setting up cargo...
34  cd
35  rm -rf .cargo
36  # Sometimes curl hangs. When it does, retry
37  retry curl -LO \
38    "https://static.rust-lang.org/rustup/archive/1.14.0/$(uname -m)-unknown-linux-gnu/rustup-init"
39  # echo "0077ff9c19f722e2be202698c037413099e1188c0c233c12a2297bf18e9ff6e7 *rustup-init" | sha256sum -c -
40  chmod +x rustup-init
41  ./rustup-init -y --no-modify-path
42  source $HOME/.cargo/env
43  if [[ -n "$1" ]]; then
44    rustup target add "$1"
45  fi
46  rustup component add rustfmt-preview
47  rm rustup-init
48
49  if [[ -n "$1" ]]; then
50  cat >>~/.cargo/config <<EOF
51[target.$1]
52linker = "${1/-unknown-/-}"
53EOF
54  fi
55}
56
57install_custom_scripts() {
58  # install our custom utility script used by $0 to ${TOOLS_DIR}
59  echo "Installing custom scripts..."
60  SCRIPTS_TO_INSTALL=("/static/policy-inliner.sh")
61  mkdir -p ${TOOLS_DIR} || /bin/true
62  for scr in ${SCRIPTS_TO_INSTALL[@]}; do
63    if ! [[ -f $scr ]]; then
64      >&2 echo "$scr must exist but does not"
65     exit 10
66    fi
67    chmod a+x $scr
68    cp -f $scr ${TOOLS_DIR}
69  done
70}
71
72install_packages() {
73  echo Installing packages...
74  sudo dpkg --add-architecture arm64
75  sudo apt-get update
76  sudo apt-get install -y \
77      autoconf \
78      automake \
79      build-essential \
80      "$@" \
81      cmake \
82      curl \
83      gcc \
84      g++ \
85      git \
86      libcap-dev \
87      libdrm-dev \
88      libfdt-dev \
89      libegl1-mesa-dev \
90      libgl1-mesa-dev \
91      libgles2-mesa-dev \
92      libssl-dev \
93      libtool \
94      libusb-1.0-0-dev \
95      libwayland-dev \
96      make \
97      nasm \
98      ninja-build \
99      pkg-config \
100      protobuf-compiler \
101      python \
102      python3 \
103      python3-pip \
104      xutils-dev # Needed to pacify autogen.sh for libepoxy
105  mkdir -p "${TOOLS_DIR}"
106  curl https://storage.googleapis.com/git-repo-downloads/repo > "${TOOLS_DIR}/repo"
107  chmod a+x "${TOOLS_DIR}/repo"
108
109  # Meson getting started guide mentions that the distro version is frequently
110  # outdated and recommends installing via pip.
111  pip3 install meson
112
113  # Tools for building gfxstream
114  pip3 install absl-py
115  pip3 install urlfetch
116
117  case "$(uname -m)" in
118    aarch64)
119      prepare_cargo
120      ;;
121    x86_64)
122      # Cross-compilation is x86_64 specific
123      sudo apt install -y crossbuild-essential-arm64
124      prepare_cargo aarch64-unknown-linux-gnu
125      ;;
126  esac
127}
128
129retry() {
130  for i in $(seq 5); do
131    "$@" && return 0
132    sleep 1
133  done
134  return 1
135}
136
137fetch_source() {
138  echo "Fetching source..."
139
140  mkdir -p "${SOURCE_DIR}"
141  cd "${SOURCE_DIR}"
142
143  if ! git config user.name; then
144    git config --global user.name "AOSP Crosvm Builder"
145    git config --global user.email "nobody@android.com"
146    git config --global color.ui false
147  fi
148
149  if [[ -z "${CUSTOM_MANIFEST}" ]]; then
150    # Building Crosvm currently depends using Chromium's directory scheme for subproject
151    # directories ('third_party' vs 'external').
152    fatal_echo "CUSTOM_MANIFEST must be provided. You most likely want to provide a full path to" \
153               "a copy of device/google/cuttlefish_vmm/${ARCH}-linux-gnu/manifest.xml."
154  fi
155
156  repo init -q -u https://android.googlesource.com/platform/manifest
157  cp "${CUSTOM_MANIFEST}" .repo/manifests
158  repo init -m "${CUSTOM_MANIFEST}"
159  repo sync
160}
161
162prepare_source() {
163  if [ "$(ls -A $SOURCE_DIR)" ]; then
164    echo "${SOURCE_DIR} is non empty. Run this from an empty directory if you wish to fetch the source." 1>&2
165    exit 2
166  fi
167  fetch_source
168}
169
170resync_source() {
171  echo "Deleting source directory..."
172  rm -rf "${SOURCE_DIR}/.*"
173  rm -rf "${SOURCE_DIR}/*"
174  fetch_source
175}
176
177compile_minijail() {
178  echo "Compiling Minijail..."
179
180  cd "${SOURCE_DIR}/external/minijail"
181
182  make -j OUT="${WORKING_DIR}"
183
184  cp "${WORKING_DIR}/libminijail.so" "${OUTPUT_LIB_DIR}"
185}
186
187compile_minigbm() {
188  echo "Compiling Minigbm..."
189
190  cd "${SOURCE_DIR}/third_party/minigbm"
191
192  # Minigbm's package config file has a default hard-coded path. Update here so
193  # that dependent packages can find the files.
194  sed -i "s|prefix=/usr\$|prefix=${WORKING_DIR}/usr|" gbm.pc
195
196  # The gbm used by upstream linux distros is not compatible with crosvm, which must use Chrome OS's
197  # minigbm.
198  local cpp_flags=()
199  local make_flags=()
200  local minigbm_drv=(${MINIGBM_DRV})
201  for drv in "${minigbm_drv[@]}"; do
202    cpp_flags+=(-D"DRV_${drv}")
203    make_flags+=("DRV_${drv}"=1)
204  done
205
206  make -j install \
207    "${make_flags[@]}" \
208    CPPFLAGS="${cpp_flags[*]}" \
209    DESTDIR="${WORKING_DIR}" \
210    OUT="${WORKING_DIR}" \
211    PKG_CONFIG=pkg-config
212
213  cp ${WORKING_DIR}/usr/lib/libgbm.so.1 "${OUTPUT_LIB_DIR}"
214}
215
216compile_epoxy() {
217  cd "${SOURCE_DIR}/third_party/libepoxy"
218
219  meson build \
220    --libdir="${WORKING_DIR}/usr/lib" \
221    --pkg-config-path="${WORKING_DIR}/usr/lib/pkgconfig" \
222    --prefix="${WORKING_DIR}/usr" \
223    -Dglx=no \
224    -Dx11=false \
225    -Degl=yes
226
227  cd build
228
229  ninja install
230
231  cp "${WORKING_DIR}"/usr/lib/libepoxy.so.0 "${OUTPUT_LIB_DIR}"
232}
233
234compile_virglrenderer() {
235  echo "Compiling VirglRenderer..."
236
237    # Note: depends on libepoxy
238  cd "${SOURCE_DIR}/third_party/virglrenderer"
239
240  # Meson doesn't like gbm's version code.
241  sed -i "s|_gbm_ver = '0.0.0'|_gbm_ver = '0'|" meson.build
242
243  # Meson needs to have dependency information for header lookup.
244  sed -i "s|cc.has_header('epoxy/egl.h')|cc.has_header('epoxy/egl.h', dependencies: epoxy_dep)|" meson.build
245
246  # Need to figure out the right way to pass this down...
247  grep "install_rpath" src/meson.build || \
248    sed -i "s|install : true|install : true, install_rpath : '\$ORIGIN',|" src/meson.build
249
250  meson build \
251    --libdir="${WORKING_DIR}/usr/lib" \
252    --pkg-config-path="${WORKING_DIR}/usr/lib/pkgconfig" \
253    --prefix="${WORKING_DIR}/usr" \
254    -Dplatforms=egl \
255    -Dgbm_allocation=false
256
257  cd build
258
259  ninja install
260
261  cp "${WORKING_DIR}/usr/lib/libvirglrenderer.so.1" "${OUTPUT_LIB_DIR}"
262
263  cd "${OUTPUT_LIB_DIR}"
264  ln -s -f "libvirglrenderer.so.1" "libvirglrenderer.so"
265}
266
267compile_gfxstream() {
268  echo "Compiling gfxstream..."
269
270    # Note: depends on libepoxy
271  cd "${SOURCE_DIR}/external/qemu"
272
273  # TODO: Fix or remove network unit tests that are failing in docker,
274  # so we can take out "notests"
275  python3 android/build/python/cmake.py --gfxstream_only --notests
276  local dist_dir="${SOURCE_DIR}/external/qemu/objs/distribution/emulator/lib64"
277
278  cp "${dist_dir}/libc++.so.1" "${OUTPUT_LIB_DIR}"
279  cp "${dist_dir}/libandroid-emu-shared.so" "${OUTPUT_LIB_DIR}"
280  cp "${dist_dir}/libemugl_common.so" "${OUTPUT_LIB_DIR}"
281  cp "${dist_dir}/libOpenglRender.so" "${OUTPUT_LIB_DIR}"
282  cp "${dist_dir}/libgfxstream_backend.so" "${OUTPUT_LIB_DIR}"
283}
284
285compile_crosvm() {
286  echo "Compiling Crosvm..."
287
288  source "${HOME}/.cargo/env"
289  cd "${SOURCE_DIR}/platform/crosvm"
290
291  local crosvm_features=gpu,composite-disk
292
293  if [[ $BUILD_GFXSTREAM -eq 1 ]]; then
294      crosvm_features+=,gfxstream
295  fi
296
297  RUSTFLAGS="-C link-arg=-Wl,-rpath,\$ORIGIN -C link-arg=-L${OUTPUT_LIB_DIR}" \
298    cargo build --features ${crosvm_features}
299
300  # Save the outputs
301  cp Cargo.lock "${OUTPUT_DIR}"
302  cp target/debug/crosvm "${OUTPUT_BIN_DIR}"
303
304  cargo --version --verbose > "${OUTPUT_DIR}/cargo_version.txt"
305  rustup show > "${OUTPUT_DIR}/rustup_show.txt"
306}
307
308compile_crosvm_seccomp() {
309  # note that this depends on compile_crosvm
310  #
311  # for aarch64, this function should do nothing
312  # as the aarch64 subdirectory does not exist yet
313  #
314  echo "Processing Crosvm Seccomp..."
315
316  cd "${SOURCE_DIR}/platform/crosvm"
317  case ${ARCH} in
318    x86_64) subdir="${ARCH}" ;;
319    amd64) subdir="x86_64" ;;
320    arm64) subdir="aarch64" ;;
321    aarch64) subdir="${ARCH}" ;;
322    *)
323      echo "${ARCH} is not supported"
324      exit 15
325  esac
326  policy-inliner.sh \
327    -p $(pwd)/seccomp/$subdir \
328    -o ${OUTPUT_SECCOMP_DIR} \
329    -c $(pwd)/seccomp/$subdir/common_device.policy
330}
331
332compile() {
333  echo "Compiling..."
334  mkdir -p \
335    "${WORKING_DIR}" \
336    "${OUTPUT_DIR}" \
337    "${OUTPUT_BIN_DIR}" \
338    "${OUTPUT_ETC_DIR}" \
339    "${OUTPUT_SECCOMP_DIR}" \
340    "${OUTPUT_LIB_DIR}"
341
342  compile_minijail
343
344  compile_minigbm
345
346  compile_epoxy
347
348  compile_virglrenderer
349
350  # TODO: Finish the aarch64 cross/native gfxstream build
351  if [[ $BUILD_GFXSTREAM -eq 1 ]]; then
352      compile_gfxstream
353  fi
354
355  compile_crosvm
356
357  compile_crosvm_seccomp
358
359  dpkg-query -W > "${OUTPUT_DIR}/builder-packages.txt"
360  repo manifest -r -o "${OUTPUT_DIR}/manifest.xml"
361  echo "Results in ${OUTPUT_DIR}"
362}
363
364aarch64_retry() {
365  MINIGBM_DRV="RADEON VC4" compile
366}
367
368aarch64_build() {
369  rm -rf "${WORKING_DIR}/*"
370  aarch64_retry
371}
372
373x86_64_retry() {
374  MINIGBM_DRV="I915 RADEON VC4" BUILD_GFXSTREAM=1 compile
375}
376
377x86_64_build() {
378  rm -rf "${WORKING_DIR}/*"
379  x86_64_retry
380}
381
382if [[ $# -lt 1 ]]; then
383  echo Choosing default config
384  set setup_env prepare_source x86_64_build
385fi
386
387echo Steps: "$@"
388
389for i in "$@"; do
390  echo $i
391  case "$i" in
392    ARCH=*) ARCH="${i/ARCH=/}" ;;
393    CUSTOM_MANIFEST=*) CUSTOM_MANIFEST="${i/CUSTOM_MANIFEST=/}" ;;
394    aarch64_build) $i ;;
395    aarch64_retry) $i ;;
396    setup_env) $i ;;
397    install_custom_scripts) $i ;;
398    install_packages) $i ;;
399    fetch_source) $i ;;
400    resync_source) $i ;;
401    prepare_source) $i ;;
402    x86_64_build) $i ;;
403    x86_64_retry) $i ;;
404    *) echo $i unknown 1>&2
405      echo usage: $0 'install_packages|prepare_source|resync_source|fetch_source|$(uname -m)_build|$(uname -m)_retry' 1>&2
406       exit 2
407       ;;
408  esac
409done
410