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
9# Stable is usually too old for crosvm, but make sure you bump this
10# up as far as you can each time this script is touched..
11RUST_TOOLCHAIN_VER=1.65.0
12
13setup_env() {
14  : ${SOURCE_DIR:="$(pwd)/source"}
15  : ${WORKING_DIR:="$(pwd)/working"}
16  : ${CUSTOM_MANIFEST:=""}
17
18  ARCH="$(uname -m)"
19  : ${OUTPUT_DIR:="$(pwd)/${ARCH}-linux-gnu"}
20  OUTPUT_BIN_DIR="${OUTPUT_DIR}/bin"
21  OUTPUT_ETC_DIR="${OUTPUT_DIR}/etc"
22  OUTPUT_SECCOMP_DIR="${OUTPUT_ETC_DIR}/seccomp"
23
24  export PATH="${PATH}:${TOOLS_DIR}:${HOME}/.local/bin"
25  export PKG_CONFIG_PATH="${WORKING_DIR}/usr/lib/pkgconfig"
26}
27
28set -e
29set -x
30
31fatal_echo() {
32  echo "$@"
33  exit 1
34}
35
36prepare_cargo() {
37  echo Setting up cargo...
38  cd
39  rm -rf .cargo
40  # Sometimes curl hangs. When it does, retry
41  retry curl -L \
42    --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs -o rustup-init
43  chmod +x rustup-init
44  ./rustup-init -y --no-modify-path --default-toolchain ${RUST_TOOLCHAIN_VER}
45  source $HOME/.cargo/env
46  if [[ -n "$1" ]]; then
47    rustup target add "$1"
48  fi
49  rm -f rustup-init
50
51  if [[ -n "$1" ]]; then
52  cat >>~/.cargo/config <<EOF
53[target.$1]
54linker = "${1/-unknown-/-}"
55EOF
56  fi
57}
58
59install_packages() {
60  echo Installing packages...
61
62  sudo dpkg --add-architecture arm64
63  sudo apt-get update
64  sudo apt-get install -y \
65      "$@" \
66      autoconf \
67      automake \
68      build-essential \
69      curl \
70      doxygen \
71      g++ \
72      gcc \
73      git \
74      graphviz \
75      libcap-dev \
76      libegl1-mesa-dev \
77      libfdt-dev \
78      libgl1-mesa-dev \
79      libgles2-mesa-dev \
80      libpciaccess-dev \
81      libssl-dev \
82      libtool \
83      libusb-1.0-0-dev \
84      libwayland-bin \
85      libwayland-dev \
86      libxml2-dev \
87      make \
88      nasm \
89      ninja-build \
90      pkg-config \
91      protobuf-compiler \
92      python3 \
93      python3-pip \
94      texinfo \
95      wayland-protocols \
96      xmlto \
97      xutils-dev # Needed to pacify autogen.sh for libepoxy
98  mkdir -p "${TOOLS_DIR}"
99
100  # Repo needs python3 but python-is-python3 package not available:
101  sudo ln -s -f /usr/bin/python3 /usr/bin/python
102
103  curl https://storage.googleapis.com/git-repo-downloads/repo > "${TOOLS_DIR}/repo"
104  chmod a+x "${TOOLS_DIR}/repo"
105
106  # Gfxstream needs a new-ish version of CMake
107  mkdir -p "${TOOLS_DIR}/cmake"
108  cd "${TOOLS_DIR}/cmake"
109  curl -O -L https://cmake.org/files/v3.22/cmake-3.22.1-linux-$(uname -m).sh
110  chmod +x cmake-3.22.1-linux-$(uname -m).sh
111  sudo ./cmake-3.22.1-linux-$(uname -m).sh --skip-license --exclude-subdir --prefix=/usr/local
112  cmake --version
113
114  # Meson getting started guide mentions that the distro version is frequently
115  # outdated and recommends installing via pip.
116  pip3 install --no-warn-script-location meson
117
118  # Tools for building gfxstream
119  pip3 install absl-py
120  pip3 install urlfetch
121
122  case "$(uname -m)" in
123    aarch64)
124      prepare_cargo
125      ;;
126    x86_64)
127      # Cross-compilation is x86_64 specific
128      sudo apt install -y crossbuild-essential-arm64
129      prepare_cargo aarch64-unknown-linux-gnu
130      ;;
131  esac
132}
133
134retry() {
135  for i in $(seq 5); do
136    "$@" && return 0
137    sleep 1
138  done
139  return 1
140}
141
142fetch_source() {
143  echo "Fetching source..."
144
145  mkdir -p "${SOURCE_DIR}"
146  cd "${SOURCE_DIR}"
147
148  if ! git config user.name; then
149    git config --global user.name "AOSP Crosvm Builder"
150    git config --global user.email "nobody@android.com"
151    git config --global color.ui false
152  fi
153
154  if [[ -z "${CUSTOM_MANIFEST}" ]]; then
155    # Building Crosvm currently depends using Chromium's directory scheme for subproject
156    # directories ('third_party' vs 'external').
157    fatal_echo "CUSTOM_MANIFEST must be provided. You most likely want to provide a full path to" \
158               "a copy of device/google/cuttlefish_vmm/manifest.xml."
159  fi
160
161  cp ${CUSTOM_MANIFEST} manifest.xml
162  repo init --depth=1 -q -u https://android.googlesource.com/platform/manifest -m $PWD/manifest.xml
163  repo sync
164}
165
166prepare_source() {
167  if [ "$(ls -A $SOURCE_DIR)" ]; then
168    echo "${SOURCE_DIR} is non empty. Run this from an empty directory if you wish to fetch the source." 1>&2
169    exit 2
170  fi
171  fetch_source
172}
173
174resync_source() {
175  echo "Deleting source directory..."
176  rm -rf "${SOURCE_DIR}/.*"
177  rm -rf "${SOURCE_DIR}/*"
178  fetch_source
179}
180
181# $1 = installed library filename
182debuglink() {
183  objcopy --only-keep-debug "${OUTPUT_BIN_DIR}/$1" "${OUTPUT_BIN_DIR}/$1.debug"
184  strip --strip-debug "${OUTPUT_BIN_DIR}/$1"
185  cd "${OUTPUT_BIN_DIR}"
186  objcopy --add-gnu-debuglink="$1.debug" "$1"
187  cd -
188}
189
190compile_libdrm() {
191  cd "${SOURCE_DIR}/external/libdrm"
192
193  # Ensure pkg-config file supplies rpath to dependent libraries
194  grep "install_rpath" meson.build || \
195    sed -i "s|install : true,|install : true, install_rpath : '\$ORIGIN',|" meson.build
196
197  meson build \
198    --libdir="${WORKING_DIR}/usr/lib" \
199    --prefix="${WORKING_DIR}/usr" \
200    -Damdgpu=false \
201    -Dfreedreno=false \
202    -Dintel=false \
203    -Dlibkms=false \
204    -Dnouveau=false \
205    -Dradeon=false \
206    -Dvc4=false \
207    -Dvmwgfx=false
208
209  cd build
210  ninja install
211
212  cp -a "${WORKING_DIR}"/usr/lib/libdrm.so* "${OUTPUT_BIN_DIR}"
213  debuglink libdrm.so.2.4.0
214}
215
216compile_minijail() {
217  echo "Compiling Minijail..."
218
219  cd "${SOURCE_DIR}/platform/minijail"
220
221  if ! grep '^# cuttlefish_vmm-rebuild-mark' Makefile; then
222    # Link minijail-sys rust crate dynamically to minijail
223    sed -i '/BUILD_STATIC_LIBS/d' rust/minijail-sys/build.rs
224    sed -i 's,static=minijail.pic,dylib=minijail,' rust/minijail-sys/build.rs
225
226    # Use Android prebuilt C files instead of generating them
227    sed -i 's,\(.*\.gen\.c: \),DISABLED_\1,' Makefile
228    cat >>Makefile <<EOF
229libconstants.gen.c: \$(SRC)/linux-x86/libconstants.gen.c
230	@cp \$< \$@
231libsyscalls.gen.c: \$(SRC)/linux-x86/libsyscalls.gen.c
232	@cp \$< \$@
233# cuttlefish_vmm-rebuild-mark
234EOF
235  fi
236
237  make -j OUT="${WORKING_DIR}"
238  cp "${WORKING_DIR}"/libminijail.so "${WORKING_DIR}"/usr/lib
239
240  cp -a "${WORKING_DIR}"/usr/lib/libminijail.so "${OUTPUT_BIN_DIR}"
241  debuglink libminijail.so
242}
243
244compile_minigbm() {
245  echo "Compiling Minigbm..."
246
247  cd "${SOURCE_DIR}/platform/minigbm"
248
249  # Minigbm's package config file has a default hard-coded path. Update here so
250  # that dependent packages can find the files.
251  sed -i "s|prefix=/usr\$|prefix=${WORKING_DIR}/usr|" gbm.pc
252
253  # The gbm used by upstream linux distros is not compatible with crosvm, which must use Chrome OS's
254  # minigbm.
255  local cpp_flags=(-I/working/usr/include -I/working/usr/include/libdrm)
256  local ld_flags=(-Wl,-soname,libgbm.so.1 -Wl,-rpath,\\\$\$ORIGIN -L/working/usr/lib)
257  local make_flags=()
258  local minigbm_drv=(${MINIGBM_DRV})
259  for drv in "${minigbm_drv[@]}"; do
260    cpp_flags+=(-D"DRV_${drv}")
261    make_flags+=("DRV_${drv}"=1)
262  done
263
264  make -j install \
265    "${make_flags[@]}" \
266    CPPFLAGS="${cpp_flags[*]}" \
267    DESTDIR="${WORKING_DIR}" \
268    LDFLAGS="${ld_flags[*]}" \
269    OUT="${WORKING_DIR}"
270
271  cp -a "${WORKING_DIR}"/usr/lib/libminigbm.so* "${OUTPUT_BIN_DIR}"
272  cp -a "${WORKING_DIR}"/usr/lib/libgbm.so* "${OUTPUT_BIN_DIR}"
273  debuglink libminigbm.so.1.0.0
274}
275
276compile_epoxy() {
277  cd "${SOURCE_DIR}/third_party/libepoxy"
278
279  meson build \
280    --libdir="${WORKING_DIR}/usr/lib" \
281    --prefix="${WORKING_DIR}/usr" \
282    -Dglx=no \
283    -Dx11=false \
284    -Degl=yes
285
286  cd build
287  ninja install
288
289  cp -a "${WORKING_DIR}"/usr/lib/libepoxy.so* "${OUTPUT_BIN_DIR}"
290  debuglink libepoxy.so.0.0.0
291}
292
293compile_virglrenderer() {
294  echo "Compiling VirglRenderer..."
295
296  # Note: depends on libepoxy
297  cd "${SOURCE_DIR}/third_party/virglrenderer"
298
299  # Meson needs to have dependency information for header lookup.
300  sed -i "s|cc.has_header('epoxy/egl.h')|cc.has_header('epoxy/egl.h', dependencies: epoxy_dep)|" meson.build
301
302  # Ensure pkg-config file supplies rpath to dependent libraries
303  grep "install_rpath" src/meson.build || \
304    sed -i "s|install : true|install : true, install_rpath : '\$ORIGIN'|" src/meson.build
305
306  meson build \
307    --libdir="${WORKING_DIR}/usr/lib" \
308    --prefix="${WORKING_DIR}/usr" \
309    -Dplatforms=egl \
310    -Dminigbm_allocation=false
311
312  cd build
313  ninja install
314
315  cp -a "${WORKING_DIR}"/usr/lib/libvirglrenderer.so* "${OUTPUT_BIN_DIR}"
316  debuglink libvirglrenderer.so.1.7.7
317}
318
319compile_libffi() {
320  cd "${SOURCE_DIR}/third_party/libffi"
321
322  ./autogen.sh
323  ./configure \
324    --prefix="${WORKING_DIR}/usr" \
325    --libdir="${WORKING_DIR}/usr/lib"
326  make && make check && make install
327
328  cp -a "${WORKING_DIR}"/usr/lib/libffi.so* "${OUTPUT_BIN_DIR}"
329  debuglink libffi.so.7.1.0
330}
331
332compile_wayland() {
333  cd "${SOURCE_DIR}/third_party/wayland"
334
335  # Need to figure out the right way to pass this down...
336  sed -i "s|install: true\$|install: true, install_rpath : '\$ORIGIN'|" src/meson.build
337
338  meson build \
339    --libdir="${WORKING_DIR}/usr/lib" \
340    --prefix="${WORKING_DIR}/usr"
341  ninja -C build/ install
342
343  cp -a "${WORKING_DIR}"/usr/lib/libwayland-client.so* "${OUTPUT_BIN_DIR}"
344  debuglink libwayland-client.so.0.3.0
345}
346
347compile_gfxstream() {
348  echo "Compiling gfxstream..."
349
350  local dist_dir="${SOURCE_DIR}/hardware/google/gfxstream/build"
351  [ -d "${dist_dir}" ] && rm -rf "${dist_dir}"
352  mkdir "${dist_dir}"
353  cd "${dist_dir}"
354
355  cmake .. \
356    -G Ninja \
357    -DBUILD_GRAPHICS_DETECTOR=ON \
358    -DDEPENDENCY_RESOLUTION=AOSP
359
360  ninja
361
362  chmod +x "${dist_dir}"/gfxstream_graphics_detector
363  cp -a "${dist_dir}"/gfxstream_graphics_detector "${OUTPUT_BIN_DIR}"
364  debuglink gfxstream_graphics_detector
365
366  chmod +x "${dist_dir}"/libgfxstream_backend.so
367  cp -a "${dist_dir}"/libgfxstream_backend.so "${WORKING_DIR}"/usr/lib
368  cp -a "${WORKING_DIR}"/usr/lib/libgfxstream_backend.so "${OUTPUT_BIN_DIR}"
369  debuglink libgfxstream_backend.so
370}
371
372compile_crosvm() {
373  echo "Compiling Crosvm..."
374
375  source "${HOME}/.cargo/env"
376
377  # Workaround for aosp/1412815
378  cd "${SOURCE_DIR}/platform/crosvm/protos/src"
379  if ! grep '^mod generated {$' lib.rs; then
380    cat >>lib.rs <<EOF
381mod generated {
382    include!(concat!(env!("OUT_DIR"), "/generated.rs"));
383}
384EOF
385  fi
386  sed -i "s/pub use cdisk_spec_proto::cdisk_spec/pub use generated::cdisk_spec/" lib.rs
387
388  cd "${SOURCE_DIR}/platform/crosvm"
389
390  # Workaround for minijail-sys prepending -L/usr/lib/$arch dir
391  # which breaks the preferred search path for libdrm.so
392  sed -i '0,/pkg_config::Config::new().probe("libdrm")?;/{/pkg_config::Config::new().probe("libdrm")?;/d;}' rutabaga_gfx/build.rs
393
394  # Workaround rutabaga build thinking it needs at later version of virglrenderer.
395  sed -i 's/atleast_version("1.0.0")/atleast_version("0.10.0")/g' rutabaga_gfx/build.rs
396
397  local crosvm_features=audio,gdb,gpu,composite-disk,usb,virgl_renderer
398  if [[ $BUILD_GFXSTREAM -eq 1 ]]; then
399      crosvm_features+=,gfxstream
400  fi
401
402  CROSVM_USE_SYSTEM_MINIGBM=1 \
403  CROSVM_USE_SYSTEM_VIRGLRENDERER=1 \
404  GFXSTREAM_PATH="${WORKING_DIR}/usr/lib" \
405  PKG_CONFIG_PATH="${WORKING_DIR}/usr/lib/pkgconfig" \
406  RUSTFLAGS="-C link-arg=-Wl,-rpath,\$ORIGIN -C link-arg=${WORKING_DIR}/usr/lib/libdrm.so" \
407    cargo build --features ${crosvm_features}
408
409  # Save the outputs
410  cp Cargo.lock "${OUTPUT_DIR}"
411  cp target/debug/crosvm "${OUTPUT_BIN_DIR}"
412  debuglink crosvm
413
414  cargo --version --verbose > "${OUTPUT_DIR}/cargo_version.txt"
415  rustup show > "${OUTPUT_DIR}/rustup_show.txt"
416}
417
418compile_crosvm_seccomp() {
419  echo "Processing Crosvm Seccomp..."
420
421  cd "${SOURCE_DIR}/platform/crosvm"
422  case ${ARCH} in
423    x86_64) subdir="${ARCH}" ;;
424    amd64) subdir="x86_64" ;;
425    arm64) subdir="aarch64" ;;
426    aarch64) subdir="${ARCH}" ;;
427    *)
428      echo "${ARCH} is not supported"
429      exit 15
430  esac
431
432  inlined_policy_list="\
433    jail/seccomp/$subdir/common_device.policy \
434    jail/seccomp/$subdir/gpu_common.policy \
435    jail/seccomp/$subdir/serial.policy \
436    jail/seccomp/$subdir/net.policy \
437    jail/seccomp/$subdir/block.policy \
438    jail/seccomp/$subdir/vvu.policy \
439    jail/seccomp/$subdir/vhost_user.policy \
440    jail/seccomp/$subdir/vhost_vsock.policy"
441  for policy_file in "jail/seccomp/$subdir/"*.policy; do
442    [[ "$inlined_policy_list" = *"$policy_file"* ]] && continue
443    jail/seccomp/policy-inliner.sh $inlined_policy_list <"$policy_file" | \
444      grep -ve "^@frequency" \
445      >"${OUTPUT_SECCOMP_DIR}"/$(basename "$policy_file")
446  done
447}
448
449compile() {
450  echo "Compiling..."
451  mkdir -p \
452    "${WORKING_DIR}" \
453    "${OUTPUT_DIR}" \
454    "${OUTPUT_BIN_DIR}" \
455    "${OUTPUT_ETC_DIR}" \
456    "${OUTPUT_SECCOMP_DIR}"
457
458  if [[ $BUILD_CROSVM -eq 1 ]]; then
459    compile_libdrm
460    compile_minijail
461    compile_minigbm
462    compile_epoxy
463    compile_virglrenderer
464    compile_libffi # wayland depends on it
465    compile_wayland
466  fi
467
468  if [[ $BUILD_GFXSTREAM -eq 1 ]]; then
469    compile_gfxstream
470  fi
471
472  compile_crosvm
473  compile_crosvm_seccomp
474
475  dpkg-query -W > "${OUTPUT_DIR}/builder-packages.txt"
476  echo "Results in ${OUTPUT_DIR}"
477}
478
479aarch64_retry() {
480  BUILD_CROSVM=1 BUILD_GFXSTREAM=1 compile
481}
482
483aarch64_build() {
484  rm -rf "${WORKING_DIR}/*"
485  aarch64_retry
486}
487
488x86_64_retry() {
489  MINIGBM_DRV="I915 RADEON VC4" BUILD_CROSVM=1 BUILD_GFXSTREAM=1 compile
490}
491
492x86_64_build() {
493  rm -rf "${WORKING_DIR}/*"
494  x86_64_retry
495}
496
497if [[ $# -lt 1 ]]; then
498  echo Choosing default config
499  set setup_env prepare_source x86_64_build
500fi
501
502echo Steps: "$@"
503
504for i in "$@"; do
505  echo $i
506  case "$i" in
507    ARCH=*) ARCH="${i/ARCH=/}" ;;
508    CUSTOM_MANIFEST=*) CUSTOM_MANIFEST="${i/CUSTOM_MANIFEST=/}" ;;
509    aarch64_build) $i ;;
510    aarch64_retry) $i ;;
511    setup_env) $i ;;
512    install_packages) $i ;;
513    fetch_source) $i ;;
514    resync_source) $i ;;
515    prepare_source) $i ;;
516    x86_64_build) $i ;;
517    x86_64_retry) $i ;;
518    *) echo $i unknown 1>&2
519      echo usage: $0 'install_packages|prepare_source|resync_source|fetch_source|$(uname -m)_build|$(uname -m)_retry' 1>&2
520       exit 2
521       ;;
522  esac
523done
524