SDPT Lab 3

De la WikiLabs
Versiunea din 16 martie 2026 17:07, autor: Rhobincu (discuție | contribuții)
(dif) ← Versiunea anterioară | Versiunea curentă (dif) | Versiunea următoare → (dif)
Jump to navigationJump to search

Lab 3: Build Automation - Makefiles and Modern CMake

Introduction

In professional embedded development, clicking a "Build" button in an IDE is a luxury you rarely have. When working with Embedded Linux, RTOS environments, or CI/CD pipelines, you must define exactly how your source code is translated into machine code.

This lab transitions you from manual terminal commands to scalable build automation.

Goals

  • Understand the C++ compilation pipeline (Preprocessing $\rightarrow$ Compilation $\rightarrow$ Linking).
  • Master GNU Make: pattern rules, automatic variables, and header dependency generation.
  • Master Modern CMake: out-of-source builds, targets, and static libraries.
  • Integrate remote third-party libraries using CMake FetchContent.

Part 1: Theoretical Reference

Use this section as a reference manual to complete the tasks in Part 2.

1.1 GNU Make Fundamentals

Make tracks file modification timestamps to avoid recompiling code that hasn't changed. A Makefile consists of rules:

target: dependencies
<TAB> command

(Note: Makefiles strictly require a real TAB character for indentation, not spaces!)

Automatic Variables: Make provides shortcuts to avoid typing duplicate filenames:

  • $@ : The target filename.
  • $< : The first dependency.
  • $^ : All dependencies (space-separated).

Pattern Rules: Instead of writing a rule for every file, use a pattern template:

%.o: %.cpp
	$(CXX) $(CXXFLAGS) -c $< -o $@

GCC Auto-Dependencies (The Header Problem): If you change a .h file, Make won't normally detect it because object files depend on .cpp files. We ask GCC to generate dependency (.d) files for us:

  • Add -MMD -MP to your CXXFLAGS.
  • Include the generated files at the bottom of your Makefile using: -include $(DEPS)

1.2 Modern CMake Fundamentals

CMake is a meta-build system. It generates Makefiles (or Ninja files, or Visual Studio solutions) based on a high-level CMakeLists.txt script. Modern CMake is "Target-Based"—you define targets (executables/libraries) and attach properties to them.

Core Commands:

cmake_minimum_required(VERSION 3.10)
project(MyProject CXX)

# Build an executable from source
add_executable(my_app main.cpp)

# Build a static library (.a archive)
add_library(my_driver STATIC driver.cpp)

# Define where a target should look for header files
target_include_directories(my_driver PUBLIC include/)

# Link a library to an executable
target_link_libraries(my_app PRIVATE my_driver)

Out-of-Source Builds: Never pollute your source directory with binaries. Always build in a separate folder:

mkdir build && cd build
cmake ..
make

Fetching External Libraries: CMake can download libraries directly from Git during the configuration phase:

include(FetchContent)
FetchContent_Declare(
  json_lib
  GIT_REPOSITORY https://github.com/nlohmann/json.git
  GIT_TAG        v3.11.2
)
FetchContent_MakeAvailable(json_lib)

1.3 Useful Resources

Part 2: The Challenges

Setup: The Raw Materials

Create a new directory for this lab (sdpt-lab3). Inside, create an include/ directory and a src/ directory. Populate them with the following three files to simulate an IoT sensor node.

include/sensor.h

#pragma once

struct SensorData {
    int temperature;
    int humidity;
};

void init_sensor();
SensorData read_sensor();

src/sensor.cpp

#include <iostream>
#include "../include/sensor.h"

void init_sensor() {
    std::cout << "[Hardware] I2C Sensor Initialized." << std::endl;
}

SensorData read_sensor() {
    return {24, 60};
}

src/main.cpp

#include <iostream>
#include "../include/sensor.h"

int main() {
    std::cout << "Starting IoT Node..." << std::endl;
    init_sensor();
    
    SensorData data = read_sensor();
    std::cout << "Temp: " << data.temperature << "C, Hum: " << data.humidity << "%" << std::endl;
    
    return 0;
}


Challenge 1: The Scalable Makefile (Estimated time: 25 mins)

Your Task: Write a Makefile in the root directory that compiles these files into an executable named iot_node.bin.

  • Do not hardcode the .cpp file names. Use the wildcard function to find them dynamically.
  • Use variables for the compiler (CXX) and flags (CXXFLAGS).
  • Use Pattern Rules and Automatic Variables to compile .cpp to .o.
  • Implement GCC header dependency tracking (-MMD -MP).

Verification:

  1. Run make. It should compile successfully.
  2. Run make again immediately. It should say "Nothing to be done".
  3. Open include/sensor.h and add a dummy integer to the SensorData struct. Run make again. It MUST recompile both .cpp files automatically. If it doesn't, your dependency tracking is broken.


Challenge 2: Migrating to CMake (Estimated time: 20 mins)

Makefiles are not cross-platform. We are moving to CMake. Your Task:

  1. Run make clean (or manually delete all generated .o, .d, and .bin files).
  2. Create a CMakeLists.txt file to build the exact same executable.
  3. Set the C++ standard to C++14.
  4. Perform an Out-of-Source build using a build/ directory.

Verification: Your root directory should contain only your source files and the CMakeLists.txt. Inside the build/ directory, running make should yield the executable.

Challenge 3: Modular Architecture (Estimated time: 25 mins)

Dumping all source files into a single executable is bad practice. Hardware drivers should be isolated into static libraries. Your Task: Modify your CMakeLists.txt.

  1. Remove src/sensor.cpp from your executable target.
  2. Create a new STATIC library target named sensor_driver from src/sensor.cpp.
  3. Attach the include/ directory to this library target using target_include_directories.
  4. Link the sensor_driver library to your main executable.

Verification: Run make inside the build folder. You must see it compile libsensor_driver.a first, and then link it to the main executable.

Challenge 4: Third-Party Git Dependencies (Estimated time: 30 mins)

Our IoT node needs to transmit its data as a JSON payload, but writing a JSON parser from scratch is inefficient. Your Task:

  1. Use CMake's FetchContent module to download the popular "Nlohmann JSON" library directly from GitHub.
  2. Link the imported target (nlohmann_json::nlohmann_json) to your main executable.
  3. Modify main.cpp to include <nlohmann/json.hpp>. Serialize your SensorData into a JSON object and print it to the console using .dump(4).

Verification: Running your executable should print a perfectly formatted, multi-line JSON string representing your sensor data. Check build/_deps/ to see the downloaded source code.

Lab Evaluation

To receive full credit, submit the following elements on Moodle:

  1. Final version of the Makefile.
  2. Final version of the CMakeLists.txt.
  3. Screenshot of the console output of your executable printing the JSON payload.