Photo by Kelly Sikkema on Unsplash
“Programming is about managing complexity: the complexity of the problem, laid upon the complexity of the machine. Because of this complexity, most of our programming projects fail.” ~ Bruce Eckel
Tutti abbiamo quell'incubo: un vecchio progetto che torna dalla tomba per qualche motivo.
Qualche giorno fa ho ricevuto la chiamata di un cliente, che non aveva mai pagato per la manutenzione, che chiedeva "una semplice modifica". Ed era facile: dovevo solo modificare un'espressione regolare e lanciare una build.
E lì, in agguato dietro quella semplice modifica, aspettava la build di Android Studio. Appena ho premuto quel pulsante, sono iniziati tutti i miei problemi.
L'ultimo commit del progetto era di settembre '19, più di un anno prima. E secondo i repository npm, per quel progetto c'erano: 20 aggiornamenti major, 12 aggiornamenti minor e 6 patch.
Aggiornare tutto non era un'opzione: il cliente non voleva pagare per questo. Quindi dovevo trovare un modo per fare la build del progetto nel modo più rapido possibile.
Ricostruire l'ambiente di settembre '19
Questa era la sfida: ricostruire esattamente la stessa configurazione che avevo a settembre '19 senza rompere la mia configurazione attuale.
Docker: Package Software into Standardized Units for Development, Shipment and Deployment
Di recente, meno di 3 mesi fa, ho iniziato a usare docker. Volevo davvero capire come funzionano Docker e la containerizzazione, ma non avevo abbastanza tempo, così ho abbracciato la filosofia "usa prima, impara dopo".
Non avevo mai pensato di usarlo per lo sviluppo Android, ma... perché no?
Non sono un dockerista, quindi ho iniziato a cercare su Google e ho trovato un interessante repository Github di react-native-community.
Ho preso il Dockerfile dal repository e l'ho messo nel mio progetto.
Quello era un punto di partenza. Ora avevo bisogno di qualche ritocco:
- Non mi serviva Buck, quindi l'ho semplicemente rimosso.
- Non mi serviva l'NDK, buttato via anche quello.
- Le versioni delle dipendenze non erano quelle che mi servivano, quindi le ho cambiate.
Dopo questi ritocchi, non ho cambiato altro.
FROM ubuntu:20.04
# Original Dockefile: https://github.com/react-native-community/docker-android/blob/master/Dockerfile
ENV DEBIAN_FRONTEND=noninteractive
# https://github.com/react-native-community/docker-android
# set default build arguments
ARG SDK_VERSION=commandlinetools-linux-6609375_latest.zip
ARG ANDROID_BUILD_VERSION=28
ARG ANDROID_TOOLS_VERSION=28.0.3
ARG NODE_VERSION=10.x
# set default environment variables
ENV ADB_INSTALL_TIMEOUT=10
ENV ANDROID_HOME=/opt/android
ENV ANDROID_SDK_HOME=${ANDROID_HOME}
ENV JAVA_HOME=/usr/lib/jvm/java-8-openjdk-amd64
ENV PATH=${ANDROID_HOME}/cmdline-tools/tools/bin:${ANDROID_HOME}/emulator:${ANDROID_HOME}/platform-tools:${ANDROID_HOME}/tools:${ANDROID_HOME}/tools/bin:/opt/buck/bin/:${PATH}
# Install system dependencies
RUN apt update -qq && apt install -qq -y --no-install-recommends \
apt-transport-https \
curl \
file \
gcc \
git \
g++ \
gnupg2 \
libc++1-10 \
libgl1 \
libtcmalloc-minimal4 \
make \
openjdk-8-jdk-headless \
openssh-client \
python3 \
python3-distutils \
rsync \
ruby \
ruby-dev \
tzdata \
unzip \
sudo \
ninja-build \
zip \
&& gem install bundler \
&& rm -rf /var/lib/apt/lists/*;
# Refresh keys to prevent invalid signature
RUN apt-key adv --refresh-keys --keyserver keyserver.ubuntu.com
# install nodejs and yarn packages from nodesource and yarn apt sources
RUN curl -sL https://deb.nodesource.com/setup_${NODE_VERSION} | bash - \
&& curl -sS https://dl.yarnpkg.com/debian/pubkey.gpg | sudo apt-key add - \
&& echo "deb https://dl.yarnpkg.com/debian/ stable main" > /etc/apt/sources.list.d/yarn.list \
&& apt-get update -qq \
&& apt-get install -qq -y --no-install-recommends nodejs yarn \
&& rm -rf /var/lib/apt/lists/*
# Full reference at https://dl.google.com/android/repository/repository2-1.xml
# download and unpack android
RUN curl -sS https://dl.google.com/android/repository/${SDK_VERSION} -o /tmp/sdk.zip \
&& mkdir -p ${ANDROID_HOME}/cmdline-tools \
&& unzip -q -d ${ANDROID_HOME}/cmdline-tools /tmp/sdk.zip \
&& rm /tmp/sdk.zip \
&& yes | sdkmanager --licenses \
&& yes | sdkmanager "platform-tools" \
"emulator" \
"platforms;android-$ANDROID_BUILD_VERSION" \
"build-tools;$ANDROID_TOOLS_VERSION" \
"cmake;3.10.2.4988404" \
"system-images;android-21;google_apis;armeabi-v7a" \
&& rm -rf ${ANDROID_HOME}/.android
Ora costruisci il container ed eseguilo!
docker build -t project-android-build .
Nel mio ambiente, 4 core e 2048MB di RAM su WSL2, ci sono voluti 816.3s.
docker run --name=running_container -v F:\Projects\my-app:/app -it project-android-build
Avevo il mio ambiente di settembre '19!
È tempo di una build
Tutto era configurato e pronto. Dovevo installare le dipendenze con yarn install e lanciare la build per il mio progetto
cd android && ./gradlew clean && cd .. && npx jetify && cd android && ./gradlew bundleRelease
Se sei fortunato come lo sono stato io, alla fine avrai il tuo bundle pronto.
Una riflessione finale su Docker
Photo by Ugur Peker on Unsplash
Beh, se devo riassumere cosa penso in poche parole direi "È fantastico".
Docker fornisce un semplice ambiente di sviluppo usa e getta che puoi avviare, usare ogni volta che ti serve e infine distruggere e riavviare quando necessario. È fantastico perché anche se il progetto continua a raccogliere polvere per un po', la prossima volta che avrò bisogno di modificare un'altra regex, posso semplicemente riavviare il container e rifare la build. Posso persino eliminare l'immagine e ricostruirla se ogni mirror resta attivo. Questa, secondo me, è la vera potenza di docker: configura il tuo ambiente, cambia il tuo progetto!