Home | History | Annotate | Download | only in build
      1 #!/bin/bash -e
      2 
      3 # Copyright (c) 2010 The Chromium Authors. All rights reserved.
      4 # Use of this source code is governed by a BSD-style license that can be
      5 # found in the LICENSE file.
      6 
      7 # This script installs Debian-derived distributions in a chroot environment.
      8 # It can for example be used to have an accurate 32bit build and test
      9 # environment when otherwise working on a 64bit machine.
     10 # N. B. it is unlikely that this script will ever work on anything other than a
     11 # Debian-derived system.
     12 
     13 usage() {
     14   echo "usage: ${0##*/} [-m mirror] [-g group,...] [-s] [-c]"
     15   echo "-g group,... groups that can use the chroot unauthenticated"
     16   echo "             Default: 'admin' and current user's group ('$(id -gn)')"
     17   echo "-m mirror    an alternate repository mirror for package downloads"
     18   echo "-s           configure default deb-srcs"
     19   echo "-c           always copy 64bit helper binaries to 32bit chroot"
     20   echo "-h           this help message"
     21 }
     22 
     23 process_opts() {
     24   local OPTNAME OPTIND OPTERR OPTARG
     25   while getopts ":g:m:sch" OPTNAME; do
     26     case "$OPTNAME" in
     27       g)
     28         [ -n "${OPTARG}" ] &&
     29           chroot_groups="${chroot_groups}${chroot_groups:+,}${OPTARG}"
     30         ;;
     31       m)
     32         if [ -n "${mirror}" ]; then
     33           echo "You can only specify exactly one mirror location"
     34           usage
     35           exit 1
     36         fi
     37         mirror="$OPTARG"
     38         ;;
     39       s)
     40         add_srcs="y"
     41         ;;
     42       c)
     43         copy_64="y"
     44         ;;
     45       h)
     46         usage
     47         exit 0
     48         ;;
     49       \:)
     50         echo "'-$OPTARG' needs an argument."
     51         usage
     52         exit 1
     53         ;;
     54       *)
     55         echo "invalid command-line option: $OPTARG"
     56         usage
     57         exit 1
     58         ;;
     59     esac
     60   done
     61 
     62   if [ $# -ge ${OPTIND} ]; then
     63     eval echo "Unexpected command line argument: \${${OPTIND}}"
     64     usage
     65     exit 1
     66   fi
     67 }
     68 
     69 
     70 # Check that we are running as a regular user
     71 [ "$(id -nu)" = root ] && {
     72   echo "Run this script as a regular user and provide your \"sudo\""           \
     73        "password if requested" >&2
     74   exit 1
     75 }
     76 mkdir -p "$HOME/chroot/"
     77 
     78 process_opts "$@"
     79 
     80 # Error handler
     81 trap 'exit 1' INT TERM QUIT
     82 trap 'sudo apt-get clean; tput bel; echo; echo Failed' EXIT
     83 
     84 # Install any missing applications that this script relies on. If these packages
     85 # are already installed, don't force another "apt-get install". That would
     86 # prevent them from being auto-removed, if they ever become eligible for that.
     87 # And as this script only needs the packages once, there is no good reason to
     88 # introduce a hard dependency on things such as dchroot and debootstrap.
     89 dep=
     90 for i in dchroot debootstrap; do
     91   [ -d /usr/share/doc/"$i" ] || dep="$dep $i"
     92 done
     93 [ -n "$dep" ] && sudo apt-get -y install $dep
     94 sudo apt-get -y install schroot
     95 
     96 # Create directory for chroot
     97 sudo mkdir -p /var/lib/chroot
     98 
     99 # Find chroot environments that can be installed with debootstrap
    100 targets="$(cd /usr/share/debootstrap/scripts
    101            ls | grep '^[a-z]*$')"
    102 
    103 # Ask user to pick one of the available targets
    104 echo "The following targets are available to be installed in a chroot:"
    105 j=1; for i in $targets; do
    106   printf '%4d: %s\n' "$j" "$i"
    107   j=$(($j+1))
    108 done
    109 while :; do
    110   printf "Which target would you like to install: "
    111   read n
    112   [ "$n" -gt 0 -a "$n" -lt "$j" ] >&/dev/null && break
    113 done
    114 j=1; for i in $targets; do
    115   [ "$j" -eq "$n" ] && { distname="$i"; break; }
    116   j=$(($j+1))
    117 done
    118 
    119 # On x86-64, ask whether the user wants to install x86-32 or x86-64
    120 archflag=
    121 arch=
    122 if [ "$(uname -m)" = x86_64 ]; then
    123   while :; do
    124     echo "You are running a 64bit kernel. This allows you to install either a"
    125     printf "32bit or a 64bit chroot environment. %s"                           \
    126            "Which one do you want (32, 64) "
    127     read arch
    128     [ "${arch}" == 32 -o "${arch}" == 64 ] && break
    129   done
    130   [ "${arch}" == 32 ] && archflag="--arch i386" || archflag="--arch amd64"
    131   arch="${arch}bit"
    132 fi
    133 target="${distname}${arch}"
    134 
    135 # Don't overwrite an existing installation
    136 [ -d /var/lib/chroot/"${target}" ] && {
    137   echo "This chroot already exists on your machine." >&2
    138   echo "Delete /var/lib/chroot/${target} if you want to start over." >&2
    139   exit 1
    140 }
    141 sudo mkdir -p /var/lib/chroot/"${target}"
    142 
    143 # Offer to include additional standard repositories for Ubuntu-based chroots.
    144 alt_repos=
    145 grep ubuntu.com /usr/share/debootstrap/scripts/"${distname}" >&/dev/null && {
    146   while :; do
    147     echo "Would you like to add ${distname}-updates and ${distname}-security "
    148     echo -n "to the chroot's sources.list (y/n)? "
    149     read alt_repos
    150     case "${alt_repos}" in
    151       y|Y)
    152         alt_repos="y"
    153         break
    154       ;;
    155       n|N)
    156         break
    157       ;;
    158     esac
    159   done
    160 }
    161 
    162 # Remove stale entry from /etc/schroot/schroot.conf. Entries start
    163 # with the target name in square brackets, followed by an arbitrary
    164 # number of lines. The entry stops when either the end of file has
    165 # been reached, or when the beginning of a new target is encountered.
    166 # This means, we cannot easily match for a range of lines in
    167 # "sed". Instead, we actually have to iterate over each line and check
    168 # whether it is the beginning of a new entry.
    169 sudo sed -ni '/^[[]'"${target%bit}"']$/,${:1;n;/^[[]/b2;b1;:2;p;n;b2};p'       \
    170          /etc/schroot/schroot.conf
    171 
    172 # Download base system. This takes some time
    173 if [ -z "${mirror}" ]; then
    174  grep ubuntu.com /usr/share/debootstrap/scripts/"${distname}" >&/dev/null &&
    175    mirror="http://archive.ubuntu.com/ubuntu" ||
    176    mirror="http://ftp.us.debian.org/debian"
    177 fi
    178  sudo debootstrap ${archflag} "${distname}" /var/lib/chroot/"${target}"        \
    179                   "$mirror"
    180 
    181 # Add new entry to /etc/schroot/schroot.conf
    182 grep ubuntu.com /usr/share/debootstrap/scripts/"${distname}" >&/dev/null &&
    183   brand="Ubuntu" || brand="Debian"
    184 if [ -z "${chroot_groups}" ]; then
    185   chroot_groups="admin,$(id -gn)"
    186 fi
    187 sudo sh -c 'cat >>/etc/schroot/schroot.conf' <<EOF
    188 [${target%bit}]
    189 description=${brand} ${distname} ${arch}
    190 type=directory
    191 directory=/var/lib/chroot/${target}
    192 priority=3
    193 users=root
    194 groups=${chroot_groups}
    195 root-groups=${chroot_groups}
    196 personality=linux$([ "${arch}" != 64bit ] && echo 32)
    197 script-config=script-${target}
    198 
    199 EOF
    200 
    201 # Set up a special directory that changes contents depending on the target
    202 # that is executing.
    203 sed '/^FSTAB=/s,/mount-defaults",/mount-'"${target}"'",'                       \
    204          /etc/schroot/script-defaults |
    205   sudo sh -c 'cat >/etc/schroot/script-'"${target}"
    206 sudo cp /etc/schroot/mount-defaults /etc/schroot/mount-"${target}"
    207 echo "$HOME/chroot/.${target} $HOME/chroot none rw,bind 0 0" |
    208   sudo sh -c 'cat >>/etc/schroot/mount-'"${target}"
    209 mkdir -p "$HOME/chroot/.${target}"
    210 
    211 # Install a helper script to launch commands in the chroot
    212 sudo sh -c 'cat >/usr/local/bin/'"${target%bit}" <<EOF
    213 #!/bin/bash
    214 if [ \$# -eq 0 ]; then
    215   exec schroot -c ${target%bit} -p
    216 else
    217   p="\$1"; shift
    218   exec schroot -c ${target%bit} -p "\$p" -- "\$@"
    219 fi
    220 exit 1
    221 EOF
    222 sudo chown root:root /usr/local/bin/"${target%bit}"
    223 sudo chmod 755 /usr/local/bin/"${target%bit}"
    224 
    225 # Add the standard Ubuntu update repositories if requested.
    226 [ "${alt_repos}" = "y" -a \
    227   -r "/var/lib/chroot/${target}/etc/apt/sources.list" ] &&
    228 sudo sed -i '/^deb .* [^ -]\+ main$/p
    229              s/^\(deb .* [^ -]\+\) main/\1-security main/
    230              p
    231              t1
    232              d
    233              :1;s/-security main/-updates main/
    234              t
    235              d' "/var/lib/chroot/${target}/etc/apt/sources.list"
    236 
    237 # Add a few more repositories to the chroot
    238 [ "${add_srcs}" = "y" -a \
    239   -r "/var/lib/chroot/${target}/etc/apt/sources.list" ] &&
    240 sudo sed -i 's/ main$/ main restricted universe multiverse/
    241              p
    242              t1
    243              d
    244           :1;s/^deb/deb-src/
    245              t
    246              d' "/var/lib/chroot/${target}/etc/apt/sources.list"
    247 
    248 # Update packages
    249 sudo schroot -c "${target%bit}" -p -- /bin/sh -c '
    250   apt-get update; apt-get -y dist-upgrade' || :
    251 
    252 # Install a couple of missing packages
    253 for i in debian-keyring ubuntu-keyring locales sudo; do
    254   [ -d "/var/lib/chroot/${target}/usr/share/doc/$i" ] ||
    255     sudo schroot -c "${target%bit}" -p -- apt-get -y install "$i" || :
    256 done
    257 
    258 # Configure locales
    259 sudo schroot -c "${target%bit}" -p -- /bin/sh -c '
    260   l='"${LANG:-en_US}"'; l="${l%%.*}"
    261   [ -r /etc/locale.gen ] &&
    262     sed -i "s/^# \($l\)/\1/" /etc/locale.gen
    263   locale-gen $LANG en_US en_US.UTF-8' || :
    264 
    265 # Configure "sudo" package
    266 sudo schroot -c "${target%bit}" -p -- /bin/sh -c '
    267   egrep '"'^$(id -nu) '"' /etc/sudoers >/dev/null 2>&1 ||
    268   echo '"'$(id -nu) ALL=(ALL) ALL'"' >>/etc/sudoers'
    269 
    270 # Install a few more commonly used packages
    271 sudo schroot -c "${target%bit}" -p -- apt-get -y install                       \
    272   autoconf automake1.9 dpkg-dev g++-multilib gcc-multilib gdb less libtool     \
    273   strace
    274 
    275 # If running a 32bit environment on a 64bit machine, install a few binaries
    276 # as 64bit. This is only done automatically if the chroot distro is the same as
    277 # the host, otherwise there might be incompatibilities in build settings or
    278 # runtime dependencies. The user can force it with the '-c' flag.
    279 host_distro=$(grep DISTRIB_CODENAME /etc/lsb-release 2>/dev/null | \
    280   cut -d "=" -f 2)
    281 if [ "${copy_64}" = "y" -o \
    282     "${host_distro}" = "${distname}" -a "${arch}" = 32bit ] && \
    283     file /bin/bash 2>/dev/null | grep -q x86-64; then
    284   readlinepkg=$(sudo schroot -c "${target%bit}" -p -- sh -c \
    285     'apt-cache search "lib64readline.\$" | sort | tail -n 1 | cut -d " " -f 1')
    286   sudo schroot -c "${target%bit}" -p -- apt-get -y install                     \
    287     lib64expat1 lib64ncurses5 ${readlinepkg} lib64z1
    288   dep=
    289   for i in binutils gdb strace; do
    290     [ -d /usr/share/doc/"$i" ] || dep="$dep $i"
    291   done
    292   [ -n "$dep" ] && sudo apt-get -y install $dep
    293   sudo cp /usr/bin/gdb "/var/lib/chroot/${target}/usr/local/bin/"
    294   sudo cp /usr/bin/ld "/var/lib/chroot/${target}/usr/local/bin/"
    295   for i in libbfd libpython; do
    296     lib="$({ ldd /usr/bin/ld; ldd /usr/bin/gdb; } |
    297            grep "$i" | awk '{ print $3 }')"
    298     if [ -n "$lib" -a -r "$lib" ]; then
    299       sudo cp "$lib" "/var/lib/chroot/${target}/usr/lib64/"
    300     fi
    301   done
    302   for lib in libssl libcrypt; do
    303     sudo cp /usr/lib/$lib* "/var/lib/chroot/${target}/usr/lib64/" || :
    304   done
    305 fi
    306 
    307 # Clean up package files
    308 sudo schroot -c "${target%bit}" -p -- apt-get clean
    309 sudo apt-get clean
    310 
    311 # Let the user know what we did
    312 trap '' INT TERM QUIT
    313 trap '' EXIT
    314 cat <<EOF
    315 
    316 
    317 Successfully installed ${distname} ${arch}
    318 
    319 You can run programs inside of the chroot by invoking the "${target%bit}"
    320 command.
    321 
    322 Your home directory is shared between the host and the chroot. But I configured
    323 $HOME/chroot to be private to the chroot environment. You can use it
    324 for files that need to differ between environments.
    325 EOF
    326