Внесение изменений и локальное тестирование#

На этой странице Вы найдете информацию о том, как скомпилировать системное приложение Ubuntu Touch для своего устройства. Большая часть программного обеспечения, предустановленного на устройстве Ubuntu Touch, поставляется в в виде пакета Debian. Этот формат используется несколькими дистрибутивами Linux, такими как Debian, Ubuntu и Linux Mint. В сети доступно множество документов о стандарте deb <https://www.debian.org/doc/manuals/maint-guide/index.en.html> __, поэтому мы не будем здесь подробно об этом рассказывать. Кроме того, в большинстве случаев потребуется изменить существующее программное обеспечение, а не разрабатывать новые пакеты с нуля. По этой причине это руководство в основном посвящено компиляции уже существующего пакета для Ubuntu Touch.

There are essentially three ways of developing Ubuntu Touch system software locally:

Pros and cons of sbuild and crossbuilder#

sbuild uses a more minimal chroot-based build environment, while crossbuilder uses an LXD container with more pre-installed packages. When building with crossbuilder pre-installed build dependencies might be missed which would be caught by sbuild. crossbuilder can be quicker for subsequent builds since the LXD container persists. sbuild installs all build dependencies on each run. sbuild automatically creates a log file and runs lintian on the built packages in order to detect any problems. The use of LXD by crossbuilder also allows for easier inspection, debugging and manual modification of the build environment. crossbuilder can also automatically deploy build packages on a connected device via ADB.

We’ll examine the use of crossbuilder and builds on the device using address-book-app (the Contacts application) as an example.

Мы рекомендуем разрабатывать пакеты только с использованием устройства с Ubuntu Touch, установленным из канала разработки. Это гарантирует, что Вы тестируете программы в соответствии с самым последним состоянием кода Ubuntu Touch.

Preparing a package for a build#

sbuild or crossbuilder need to be invoked from a debianized package source tree (i.e. the package sources with a debian subdirectory), UBports packages mostly consists of git repositories containing native or non-native packages. Native packages can be built directly and the following script can be used in order to prepare a non-native package for a build:

#!/bin/sh

PATH=/bin:/usr/bin

die() {
    if [ $# -gt 0 ]; then
        printf "%s\n" "$1" >&2
    fi
    exit 1
}

if [ ! -d "./debian" ]; then
    die "not in a debianized package directory"
fi

if [ -f "./debian/ubports.source_location" ]; then

    {
        read -r src_url && \
        read -r src_filename
    } < "./debian/ubports.source_location" || \
        die "failed to parse ubports.source_location"
    case ${src_url} in
    http://*|https://*|ftp://*)
        ;;
    *)
        die "invalid url: \"${src_url}\""
        ;;
    esac
    src_filename="$(basename "${src_filename}")"

    wget -O "../${src_filename}" "${src_url}" || \
        die "failed to download source archive"
fi

Building packages in a chroot using sbuild#

sbuild is a tool for building Debian packages from source in an isolated environment using a chroot created by schroot. It closely resembles the package build process on the UBports CI system by using an isolated build environment with a minimal set of pre-installed packages. This detects any missing build dependencies and sbuild will also detect problems with the resulting packages by running lintian.

There are three types of building using sbuild:

  • Native build - this type of building is used when the host architecture matches the target architecture.

  • Cross compile build - this type of building is used when the host architecture does not match the target architecture. This method may fail due to foreign architecture dependencies that are not installable. In that case you need to use another method.

  • Qemu cross architecture build - this method allows you to perform a fake native build on a host architecture which does not match the target architecture, avoiding foreign architecture dependency issues. The drawback of this method is the slow speed, caused by the overhead of architecture binary translations. Check the link for more information about qemu.

Prerequisites#

A host system running either Debian 11 (Bullseye) or later or Ubuntu 20.04 (Focal Fossa) or later is required. An easy and performant way to set up Debian or Ubuntu on other distributions is to run it in a container using LXD.

A LXD container requires the following configuration setting in order to allow debootstrap to use the mknod system call for creating pseudo devices such as /dev/null inside a chroot:

lxc config set <container> security.syscalls.intercept.mknod true

It is assumed that the user who is building packages is allowed to execute commands with superuser privilege using sudo.

sbuild uses schroot in order to manage chroots which in turn uses debootstrap for creating the chroot. The required packages are installed with:

sudo apt install sbuild schroot devscripts debhelper dh-migrations ccache

A Debian or Ubuntu system can also be installed inside a virtual machine, however this comes with a performance overhead.

Setting up sbuild#

An unprivileged user needs to be added to the sbuild group in order to gain the necessary privilege to build packages:

sudo sbuild-adduser <username>

The build user can configure sbuild by creating a file ~/.sbuildrc as follows:

cat >~/.sbuildrc <<'EOF'
# directory containing the build logs
$log_dir = "$HOME/logs";
1;
EOF

For further customizations see the example file at /usr/share/doc/sbuild/examples/example.sbuildrc.

Create the directory ~/logs if it does not exist, yet:

mkdir ~/logs

UBports packages#

Building some of the Ubuntu Touch modules require packages from the UBports package repository to be installed on the host machine.

In that case UBports package repository needs to be added on the host machine. First initialize the chroot_repo and chroot_distro variables as show in the Native build section and then add the repository by using:

wget 'http://repo.ubports.com/keyring.gpg' -O - | sudo tee /usr/share/keyrings/ubports-keyring.gpg" >/dev/null
printf 'deb [signed-by=/usr/share/keyrings/ubports-keyring.gpg] %s %s main\n' "${chroot_repo}" "${chroot_distro}" | sudo tee "/etc/apt/sources.list.d/ubports.list" >/dev/null

Install the needed packages using:

sudo apt update
sudo apt install click-dev gobject-introspection

Native build#

In order to create a chroot based on Ubuntu 20.04 (Focal Fossa) with the amd64 architecture under the directory /srv/chroot/ubports-${chroot_distro}-amd64 (chroot_base can be changed if needed) the following variables can be defined for later use by the actual commands:

chroot_distro=focal
chroot_base=/srv/chroot/ubports-${chroot_distro}-amd64
chroot_repo=http://repo2.ubports.com/

For creating a chroot based on Ubuntu 16.04 (Xenial Xerus) with the amd64 architecture define the following variables instead:

chroot_distro=xenial
chroot_base=/srv/chroot/ubports-${chroot_distro}-amd64
chroot_repo=http://repo.ubports.com/

In both cases the chroot will be created by running the following command:

sudo sbuild-createchroot --components=main,restricted,universe --extra-repository="deb http://archive.ubuntu.com/ubuntu/ ${chroot_distro}-updates main restricted universe" --include=ccache "${chroot_distro}" "${chroot_base}" http://archive.ubuntu.com/ubuntu/

The UBports package repository needs to be added using:

wget 'http://repo.ubports.com/keyring.gpg' -O - | sudo tee "${chroot_base}/usr/share/keyrings/ubports-keyring.gpg" >/dev/null
printf 'deb [signed-by=/usr/share/keyrings/ubports-keyring.gpg] %s %s main\n' "${chroot_repo}" "${chroot_distro}" | sudo tee "${chroot_base}/etc/apt/sources.list.d/ubports.list" >/dev/null

Synchronizing package index files and subsequent package upgrades can be performed using:

sudo sbuild-update -u -d ${chroot_distro}

A build can be started from inside the debianized package source directory using:

sbuild -d ${chroot_distro}

Cross compile build#

The only difference with the native build comes in the command for starting the build which is the following:

sbuild --host=arm64 --build=amd64 -d "${chroot_distro}"

Qemu cross architecture build#

To simplify the chroot setup, we use mk-sbuild from the ubuntu-dev-tools package. The qemu-user-static package allows execution of non-native target executables just like native ones.

Install the packages with the following commands:

sudo apt install ubuntu-dev-tools qemu-user-static

In order to create a chroot based on Ubuntu 20.04 (Focal Fossa) with the arm64 architecture under the directory /var/lib/schroot/chroots/ubports-${chroot_distro}-arm64 (chroot_base can be changed if needed) the following variables can be defined for later use by the actual commands:

chroot_arch=arm64
chroot_distro=focal
chroot_base=/var/lib/schroot/chroots/ubports-${chroot_distro}-${chroot_arch}
chroot_repo=http://repo2.ubports.com/

For creating a chroot based on Ubuntu 16.04 (Xenial Xerus) with the arm64 architecture define the following variables instead:

chroot_arch=arm64
chroot_distro=focal
chroot_base=/var/lib/schroot/chroots/ubports-${chroot_distro}-${chroot_arch}
chroot_repo=http://repo.ubports.com/

In both cases the chroot will be created by running the following command:

mk-sbuild --arch=${chroot_arch} ${chroot_distro} --name ubports-${chroot_distro}

The UBports package repository needs to be added using:

wget 'http://repo.ubports.com/keyring.gpg' -O - | sudo tee "${chroot_base}/usr/share/keyrings/ubports-keyring.gpg" >/dev/null
printf 'deb [signed-by=/usr/share/keyrings/ubports-keyring.gpg] %s %s main\n' "${chroot_repo}" "${chroot_distro}" | sudo tee "${chroot_base}/etc/apt/sources.list.d/ubports.list" >/dev/null

Synchronizing package index files and subsequent package upgrades can be performed using:

sudo sbuild-update --arch=${chroot_arch} -u -d ubports-${chroot_distro}

A build can be started from inside the debianized package source directory using:

sbuild --build=${chroot_arch} --host=${chroot_arch} -d ubports-${chroot_distro}

Building completion#

If the build was successful, the binary packages will be placed in the parent directory. The build log will be placed inside ~/logs. In case the build failed, the chroot can be inspected using:

sudo sbuild-shell ${chroot_distro}

Optimizations#

Caching package downloads#

In order to save bandwidth and time it is highly advisable to cache downloaded packages by using apt-cacher-ng. It can be installed with:

apt install apt-cacher-ng

Chroots then need to be configured so that apt inside the chroot uses apt-cacher-ng on the host as a proxy server:

printf 'Acquire::http { Proxy "http://localhost:3142"; }\n' | sudo tee "${chroot_base}/etc/apt/apt.conf.d/proxy" >/dev/null

Caching compilation results#

ccache is a compiler cache which speeds up repeated compilation of the same source code by caching the resulting object files. The actual cache is stored on the host system and bind-mounted into sbuild chroots with a schroot hook:

ccache_dir=/var/cache/ccache-sbuild
sudo install --group=sbuild --mode=2775 -d "${ccache_dir}"
sudo env CCACHE_DIR="${ccache_dir}" ccache --max-size 4G
printf '%s %s none rw,bind 0 0\n' "${ccache_dir}" "${ccache_dir}" | sudo tee -a /etc/schroot/sbuild/fstab >/dev/null

In order to make use of ccache inside a sbuild chroot a wrapper script needs to be created:

cat >"${ccache_dir}/sbuild-ccache.sh" <<EOF
#!/bin/sh
export CCACHE_DIR=$ccache_dir
export CCACHE_UMASK=002
export CCACHE_COMPRESS=1
unset CCACHE_HARDLINK
export PATH=/usr/lib/ccache:\$PATH
exec "\$@"
EOF
chmod +x "${ccache_dir}/sbuild-ccache.sh"

In order to use this wrapper script the following line must be added to the configuration of a schroot chroot in /etc/schroot/chroot.d/:

command-prefix=/var/cache/ccache-sbuild/sbuild-ccache.sh

Further reading#

Technical details are available from the sbuild(1) and sbuild-createchroot(8) manual pages and the Debian wiki.

Building packages with crossbuilder#

Crossbuilder - это скрипт, который автоматизирует установку и работу среды кросс-компиляции для пакетов Debian. Он подходит для разработки на любом устройстве, поскольку компиляция кода происходит на настольном ПК, а не на целевом устройстве. поэтому Crossbuilder рекомендуется использовать как основной инструмент разработки для Ubuntu Touch.

Примечание

Для работы Crossbuilder требуется дистрибутива Linux с установленной программой``lxd`` и доступным непривилегированным набором команд. Другими словами, нужно иметь возможность запускать команду lxc. Если на настольном ПК установлен дистрибутив Ubuntu, Crossbuilder настроит lxd самостоятельно.

Сначала нужно установить Crossbuilder на десктоп:

cd ~
git clone https://github.com/ubports/crossbuilder.git

Crossbuilder - это скрипт для оболочки shell, он не требует сборки. Нужно будет только добавить его каталог в переменную окружения PATH, чтобы его можно было запустить из любого каталога:

echo 'export PATH="$HOME/crossbuilder:$PATH"' >> ~/.bashrc
# and add it to your current session:
source ~/.bashrc

Теперь установка Crossbuilder закончилась, и можно использовать его для настройки LXD:

crossbuilder setup-lxd

Если установка LXD происходит впервые, то после её окончания нужно будет перезагрузить компьютер.

After LXD has been set up, a build for UBports based on Ubuntu 20.04 (Focal Fossa) using the arm64 architecture can be started from inside the debianized package source directory using:

distro=20.04
arch=arm64
crossbuilder --lxd-image="ubuntu:${distro}" --architecture="${arch}" build

For building against a different UBports release or architecture change distro and arch as needed.

Crossbuilder will create the LXD container, download the development image, install all your package build dependencies, and perform the package build. The first two steps (creating the LXD image and getting the dependencies) can take a few minutes, but will be executed only the first time you launch crossbuilder for a new package.

In order to deploy the newly built package on a local device (see Shell access via ADB to learn more about connecting your device), you can use:

crossbuilder --lxd-image="ubuntu:${distro}" --architecture="${arch}" --password="password-or-0000-if-not-set" deploy

Now, whenever you change the source code in your git repository, the same changes will be available inside the container. The next time you type the above crossbuilder command, only the changed files will be rebuilt.

If the build dependencies have changed the following command can be used to update the container accordingly (distro and arch should be set as above):

crossbuilder --lxd-image="ubuntu:${distro}" --architecture="${arch}" dependencies

While crossbuilder does not create log files for the build process, the script utility may be used for that purpose:

script -c "crossbuilder --lxd-image=\"ubuntu:${distro}\" --architecture=\"${arch}\" build" build.log

When a build container is no longer needed it maybe removed using:

crossbuilder --lxd-image="ubuntu:${distro}" --architecture="${arch}" delete

Модульные тесты#

По умолчанию crossbuilder не запускает модульные тесты; это сделано и по соображениям скорости, и потому, что контейнер не предназначен для запуска собственных (целевых) исполняемых файлов: все инструменты разработки (qmake/cmake, make, gcc и т. д.) запускаются на ПК без эмуляции (опять же из соображений скорости работы). Однако эмуляция qemu доступна внутри контейнера, поэтому должна быть возможность запускать модульные тесты. Вы можете сделать это, запустив оболочку внутри контейнера:

crossbuilder --lxd-image="ubuntu:${distro}" --architecture="${arch}" shell

Затем найдите модульные тесты и выполните их. Имейте в виду, что эмуляция не идеальна. Скорее всего, тесты завершатся ошибкой, даже если они успешно завершились на настольном ПК. Поэтому, разумнее не беспокоиться о модульных тестах при работе с Crossbuilder и запускать их только тогда, когда кросс-компиляция не выполняется.

Компиляция приложения на мобильном устройстве#

Это самый быстрый и простой метод разработки небольших программ и их тестирования практически в реальном времени. Однако, в зависимости от ресурсов вашего устройства, этот путь может оказаться технически непростым: если на устройстве недостаточно свободного места в корневой файловой системе, Вы не сможете установить все файлы зависимостей для сборки пакета; на устройстве также может не хватить оперативной памяти во время компиляции.

Предупреждение

This method is limited. Many devices do not have enough free image space to install the packages required to build components of Ubuntu Touch. Installing packages has a risk of damaging the software on your device, rendering it unusable. If this happens, you can reinstall Ubuntu Touch.

В этом примере мы скомпилируем и установим приложение «Адресная книга» (address-book-app). Все приведенные здесь команды должны запускаться на устройстве с Ubuntu Touch через удаленную оболочку.

На устройство оболочку можно установить по инструкции тут:doc:/userguide/advanceduse/adb или тут:doc:/userguide/advanceduse/ssh. Сначала перемонтируйте корневую файловую систему для чтения и записи:

sudo mount / -o remount,rw

Затем установите все пакеты, необходимые для сборки программы, над которой Вы работаете (в данном случае, это приложение «Контакты»):

sudo apt update
sudo apt build-dep address-book-app
sudo apt install fakeroot

Скорее всего, Вам захочется установить git чтобы работать с исходным кодом приложения на устройстве, а затем отправить изменения обратно в репозиторий:

sudo apt install git

Когда все закончится, можно получить исходный код приложения (в нашем примере - адресная книга) и перейти в его каталог:

git clone https://gitlab.com/ubports/core/address-book-app.git
cd address-book-app

Теперь можно приступать к сборке пакета:

DEB_BUILD_OPTIONS="parallel=2 debug" dpkg-buildpackage -rfakeroot -b

Команда dpkg-buildpackage напечатает имена сгенерированных пакетов. Установите эти пакеты с помощью команды dpkg:

sudo dpkg -i ../<package>.deb [../<package2>.deb ...]

Однако обратите внимание, что установка всех пакетов может и не потребоваться: как правило, вы можно пропустить пакеты, имена которых заканчиваются на `` -doc`` или `` dev``, поскольку они не содержат кода, используемого устройством.

Дальнейшие шаги#

Теперь изменения успешно внесены и протестированы локально, можно загрузить их на GitHub. Перейдите на следующую страницу, чтобы узнать как формировать пакеты с помощью UBports CI!