Skip to content

Flutter Integration Test Server in Debian 11 Nspawn Container

date: 2021-09-24

Introduction

Performance

Your Debian Server is way more powerful than your laptop or desktop and flutter integration_tests suck.

Ergonomics

You have an Android Emulator (or a real device) connected to the machine that you are sitting in front of for reference, and now you can run integration_tests on a different device without having to juggle adb connections on the same machine.

Nspawn Tho?

Because containers unlike virtual machines access the full power of the host, but nspawn containers are peristent like virtual machines, sparing you the cognitive overhead of dealing with the ephemerality of docker containers and/or of herding cats.

And you already have nspawn, it's build into systemd. Even including the (virtual) network interfaces.

Documentation

Let's face it: setting up an Android Development Environment is a nightmare.

So don't just follow this guide; follow this guide a repetition of three times, building your own step-by-step guide for yourself as you go. Your brain will thank you.

Host Preparation (Debian 11)

  1. install systemd-container and debootstrap
  2. enable unprivileged user namespaces
    • echo 'kernel.unprivileged_userns_clone=1' >/etc/sysctl.d/nspawn.conf
    • systemctl restart systemd-sysctl.service
  3. you might as well allow debootstrap to user your apt-cacher-ng proxy
    • export http_proxy=http://<ip address>:3142

br0 bridge

describe br0 bridge in /etc/systemd/nspawn/ftest.nspawn (optional).

# /etc/systemd/nspawn/ftest.nspawn
[Network]
VirtualEthernet=yes
Bridge=br0

ZFS mountpoint

This is optional, obviously; you might not even use zfs.

  • zfs create vm_pool/nspawn/ftest
  • zfs set mountpoint=/var/lib/machines/ftest vm_pool/nspawn/ftest
  • sanity check zfs list -r vm_pool/nspawn

bootstrap container

# for apt-cacher-ng proxy
export http_proxy=http://<ip address>:3142

debootstrap --include=systemd-container stable /var/list/machines/ftest

preboot config

  1. delete container's package cache
  2. copy /etc/apt/apt.conf to container
  3. copy /root/.bashrc to container
  4. copy /root/.inputrc to container
  5. edit /etc/hostname in container
  6. write nspawn file on host
  7. copy /etc/locale.gen to /etc/locale.gen.bak on container

first interactive boot

  1. systemd-nspawn -D /var/lib/machines/ftest -U --machine ftest
  2. set passwd: passwd
  3. stop container: logout

run as service

  1. systemctl start systemd-nspawn@ftest
  2. login: machinectl login ftest
  3. start/enable network systemctl enable --now systemd-networkd
  4. add regular user useradd <username>

install applications

locale

  • install locales
  • edit /etc/locale.gen to taste and then run the command locale-gen

essential apps

apt-get install openssh-server git unzip wget sudo curl file rsync

add regular user to sudo group

usermod -a -G sudo <user>

other apps

apt-get install mosh htop haveged byobu needrestart tree bash-completion

install openjdk-8 from stretch repo

  1. add following to /etc/apt/sources.list
    deb http://security.debian.org/debian-security stretch/updates main
    
  2. apt-get update && apt-get install openjdk-8-jdk-headless

user environment

You can now ssh into your container.

scp your favorite environment files over to the container

  • ~/.byobu/
  • ~/.bashrc
  • ~/.bash_aliases
  • ~/.inputrc

install flutter

Pick a location to taste; I prefer ~/.local/

cd ; cd .local
git clone https://github.com/flutter/flutter.git

downgrade flutter

if needed:

cd ~/.local/flutter
git checkout 2.2.3

install command-line-tools

The schuck and jive here is absurd, but here goes.

Now is the time to decide where ANDROID_HOME and ANDROID_SDK_ROOT are going to be; I prefer ~/.local/share/Android/Sdk/

mkdir -p ~/.local/share/Android/Sdk

temporary installation of cmdline-tools

Command line tools only Scroll half way down

cd ~/.local/share/Android/Sdk
wget https://dl.google.com/android/repository/commandlinetools-linux-7583922_latest.zip
unzip commandlinetools-linux-7583922_latest.zip
mkdir 5.0
mv cmdline-tools/* 5.0/
mv 5.0 cmdline-tools/

flutter and sdk environment

add the following to ~/.bashrc

function addToPATH {
  case ":$PATH:" in
    *":$1:"*) :;; # already there
    *) PATH="$PATH:$1";; # or PATH="$PATH:$1"
  esac
}

addToPATH ~/.local/flutter/bin
addToPATH ~/.local/share/Android/Sdk/cmdline-tools/latest/bin
addToPATH ~/.local/share/Android/Sdk/platform-tools

# temporary path to temporary version of cmdline-tools
addToPATH ~/.local/share/Android/Sdk/cmdline-tools/5.0/bin

add the following to ~/.bash_aliases

alias sdkmanager='sdkmanager --sdk_root=~/.local/share/Android/Sdk'

Confirm by logging out and then back in and:

which flutter ; which sdkmanager ; alias

now install cmdline-tools for real

sdkmanager --install "cmdline-tools;latest"

and then logout and log back in

cleanup

At this point I think you can remove or comment the temporary PATH statement from ~/.bashrc for the temporary location of cmdline-tools

install Android SDK

review your options

sdkmanager --list
and then install them (platform-tools: adb and fastboot will be pulled in automatically)
sdkmanager --install "platforms;android-30" \
  "build-tools;31.0.0" "build-tools;30.0.3"

confirm flutter installation

flutter doctor

run tests

At this point you shoud be able to rsync a flutter app over to the container, connect to a device using network adb, and run something like:

flutter drive --driver integration_test/driver.dart \
  --target integration_test/app_test.dart --profile