SDPT Lab 5
Week 5 Lab Activity: Containerization & Cross-Compilation
Objective
This week, we eliminate the "It works on my machine" problem. You will learn how to build isolated Linux environments, cross-compile your OvenController C++ code for an ARM architecture, and solve the complex problem of linking third-party static and dynamic libraries compiled for foreign architectures.
We will accomplish this in three phases:
- Preparing CMake: Updating the build system to link an external database library.
- The Manual Container (The "Old Way"): Building the cross-compilation environment by hand to understand the underlying mechanics.
- Infrastructure as Code (The "Modern Way"): Automating the entire environment with a
Dockerfile.
Phase 1: Preparing CMake for Foreign Libraries
Imagine our OvenController needs to log telemetry data locally using an SQLite database. To do this, we need to link the sqlite3 library.
Open your CMakeLists.txt from Week 4 and add the standard linking commands. (Note: CMake is smart enough to look for the correct architecture version of this library based on the compiler we provide later).
# Add this near the bottom of your CMakeLists.txt
find_package(PkgConfig REQUIRED)
pkg_check_modules(SQLITE3 REQUIRED sqlite3)
# Add the includes and link the library to your executable
target_include_directories(unit_tests PRIVATE ${SQLITE3_INCLUDE_DIRS})
target_link_libraries(unit_tests PRIVATE ${SQLITE3_LIBRARIES})
Phase 2: The Manual Container (The "Old Way")
Before we automate, let's understand what Docker is actually doing under the hood.
1. Start a raw, interactive Ubuntu container:
Open your terminal and run a fresh Ubuntu 22.04 image, mounting your current code directory into /app:
docker run -it --name manual-build-env -v ${PWD}:/app ubuntu:22.04 /bin/bash
Notice your terminal prompt has changed. You are now the root user inside an isolated Linux environment.
2. The "Multiarch" Superpower:
If we just run apt-get install libsqlite3-dev, Ubuntu will download the x86_64 version. If we try to cross-compile our ARM code against it, the linker will violently crash. We must explicitly tell Ubuntu's package manager to accept packages for a foreign architecture (arm64).
dpkg --add-architecture arm64
apt-get update
3. Install the Toolchain and Foreign Libraries:
Now we install our cross-compiler, QEMU emulator, and the specifically compiled ARM version of our library (:arm64).
apt-get install -y build-essential cmake pkg-config g++-aarch64-linux-gnu qemu-user qemu-user-static
apt-get install -y libsqlite3-dev:arm64
4. Cross-Compile and Emulate: Navigate to your mounted code and build the project for ARM. We use a command-line flag to tell CMake to use the ARM compiler instead of native GCC.
cd /app
mkdir build_arm && cd build_arm
cmake -DCMAKE_CXX_COMPILER=aarch64-linux-gnu-g++ ..
make
If you try to run ./unit_tests natively, Linux will throw an Exec format error. Use QEMU to emulate the execution of your ARM binary. Because we linked a dynamic library, we must tell QEMU where to find the ARM shared objects (-L flag):
qemu-aarch64 -L /usr/aarch64-linux-gnu/ ./unit_tests
Verify that your Google Tests pass on the emulated architecture! Type exit to leave and stop the container.
Phase 3: Infrastructure as Code (The "Modern Way")
Manually typing apt-get commands, enabling architectures, and committing containers is tedious and error-prone. Modern DevOps engineers automate this using a Dockerfile.
Your Challenge:
Create a file named exactly Dockerfile (no extension) in your project root. Translate the manual steps you just took into an automated recipe that meets the following strict specifications:
- Base Image: Must use
ubuntu:22.04. - Environment: Set
DEBIAN_FRONTENDtononinteractiveto prevent the build from hanging on timezone prompts. - Architecture: Use a
RUNcommand to add thearm64architecture todpkg. - The Mega-Layer: Use a single
RUNcommand to updateapt-get, install all required tools (build-essential cmake pkg-config g++-aarch64-linux-gnu qemu-user qemu-user-static libsqlite3-dev:arm64), and immediately clean up the apt cache (rm -rf /var/lib/apt/lists/*) to keep the image size small. (Hint: Chain your commands with&&and use\for multi-line formatting). - Workspace: Set the working directory inside the container to
/app. - PID 1: Set the default command (
CMD) to launch/bin/bashso the container stays alive interactively.
Build and Test Your Image: Once you have written your Dockerfile, test your infrastructure code by building and running it. It should behave exactly like your manual container did:
docker build -t automated-arm-env:latest .
docker run -it --rm -v ${PWD}:/app automated-arm-env:latest
Assignment Submission
Upload ONLY your Dockerfile to the Moodle VPL assignment. The automated grading server will run static analysis on your infrastructure-as-code syntax to verify your architecture setup, toolchain requirements, and layer optimization.