Skip to article frontmatterSkip to article content

TP4 : Build system et documentation

IUT d'Orsay, Université Paris-Saclay

Le but de ce TP est de comprendre les points suivants :

Exercice 1 : Makefile

Voici un exemple minimal d’un projet en C++ :

minimal-project/
    hello.h
    hello.cpp
    main.cpp
    makefile
hello.h
hello.cpp
main.cpp
makefile
#ifndef HELLO_H
#define HELLO_H

void sayHello();

#endif
  1. Créez le répertoire et les fichiers ci-dessus dans TP4/ avec les codes correspondant.
  1. Exécutez les commandes make, make run et make clean et observez leur effet dans le terminal et sur les fichiers.
  1. Apportez les modifications suivantes au makefile :
makefile
all: executable

executable: main.o hello.o
	g++ -o executable main.o hello.o

main.o: main.cpp hello.h
	g++ -c main.cpp

hello.o: hello.cpp hello.h
	g++ -c hello.cpp

run:
	./executable

clean:
	rm -f executable main.o hello.o

.PHONY: all run clean
  1. Exécutez à nouveau les différentes commandes make, make run et make clean. Le comportement de ces commandes ne doit pas changer.

  2. Apportez les modifications suivantes au makefile :

makefile
all: executable

executable: main.o hello.o
	g++ -o executable main.o hello.o

main.o: main.cpp
	g++ -c main.cpp -MMD 

hello.o: hello.cpp
	g++ -c hello.cpp -MMD 

run:
	./executable

clean:
	rm -f executable main.o hello.o main.d hello.d

.PHONY: all run clean

-include *.d
  1. Exécutez la commande make et examinez le contenu des fichiers .d générés.
  1. Apportez les modifications suivantes au makefile :
makefile
all: build build/binaries/executable

build:
	mkdir -p build/dependencies build/objects build/binaries 

build/binaries/executable: build/objects/main.o build/objects/hello.o
	g++ -o build/binaries/executable build/objects/main.o build/objects/hello.o

build/objects/main.o: main.cpp
	g++ -c main.cpp -o build/objects/main.o -MMD -MF build/dependencies/main.d  

build/objects/hello.o: hello.cpp
	g++ -c hello.cpp -o build/objects/hello.o -MMD -MF build/dependencies/hello.d  

run:
	./build/binaries/executable

clean:
	rm -rf build

.PHONY: all run clean build

-include build/dependencies/*.d
  1. Exécutez les différentes commandes make, make run et make clean et observez.

  2. Commentez le makefile (avec #) pour clarifier les points que nous avons abordés (en français si vous le souhaitez).

Bonus

Le code actuel du makefile contient beaucoup de redondances. Nous pouvons refactoriser ce code en utilisant des variables de la manière suivante :

makefile
BUILD_DIRECTORY = build
DEPENDENCY_DIRECTORY = $(BUILD_DIRECTORY)/dependencies
OBJECT_DIRECTORY = $(BUILD_DIRECTORY)/objects
BINARY_DIRECTORY = $(BUILD_DIRECTORY)/binaries

EXECUTABLE = $(BINARY_DIRECTORY)/executable

SOURCE_FILES = $(wildcard *.cpp)

OBJECT_FILES = $(SOURCE_FILES:.cpp=.o)
OBJECT_FILES := $(OBJECT_FILES:%=$(OBJECT_DIRECTORY)/%)

DEPENDENCY_FILES = $(SOURCE_FILES:.cpp=.d)
DEPENDENCY_FILES := $(DEPENDENCY_FILES:%=$(DEPENDENCY_DIRECTORY)/%)

all: $(BUILD_DIRECTORY) $(EXECUTABLE)

$(BUILD_DIRECTORY):
	mkdir -p $(DEPENDENCY_DIRECTORY) $(OBJECT_DIRECTORY) $(BINARY_DIRECTORY)

$(EXECUTABLE): $(OBJECT_FILES)
	g++ -o $@ $^

$(OBJECT_DIRECTORY)/%.o: %.cpp
	g++ -c $< -o $@ -MMD -MF $(DEPENDENCY_DIRECTORY)/$*.d

run:
	./$(EXECUTABLE)

clean:
	rm -rf $(BUILD_DIRECTORY)

.PHONY: all run clean $(BUILD_DIRECTORY)

-include $(DEPENDENCY_FILES)
  1. Exécutez les commandes make, make run et make clean et observez.
  1. Réorganisez votre projet minimal de la façon suivante :
minimal-project/
    source/
        first-module/
            hello.cpp
            hello.h
            makefile
        second-module/
            hi.cpp
            hi.h
            makefile
        main.cpp
    makefile
  1. Dans hi.h, recopiez le code de hello.h et modifiez l’include guard et le nom de la fonction à sayHi.

  2. Dans hi.cpp, recopiez le code de hello.cpp et modifiez l’include pour inclure hi.h, le nom de la fonction à sayHi et la sortie console à Hi! par exemple au lieu de Hello World!.

  3. Dans main.cpp, modifiez les includes pour inclure first-module/hello.h et second-module/hi.h et ajouter sayHi(); après sayHello();.

Pour cet exemple minimal, nous allons utiliser le même makefile dans les deux modules, mais nous pouvons imaginer que ces makefiles soient différents et même écrits par des personnes différentes.

  1. Modifiez les makefiles des modules de la façon suivante :
makefile
BUILD_DIRECTORY = ../../build
DEPENDENCY_DIRECTORY = $(BUILD_DIRECTORY)/dependencies
OBJECT_DIRECTORY = $(BUILD_DIRECTORY)/objects

SOURCE_FILES = $(wildcard *.cpp)

OBJECT_FILES = $(SOURCE_FILES:.cpp=.o)
OBJECT_FILES := $(OBJECT_FILES:%=$(OBJECT_DIRECTORY)/%)

DEPENDENCY_FILES = $(SOURCE_FILES:.cpp=.d)
DEPENDENCY_FILES := $(DEPENDENCY_FILES:%=$(DEPENDENCY_DIRECTORY)/%)

all: $(BUILD_DIRECTORY) $(OBJECT_FILES)

$(BUILD_DIRECTORY):
	mkdir -p $(DEPENDENCY_DIRECTORY) $(OBJECT_DIRECTORY) $(BINARY_DIRECTORY)

$(OBJECT_DIRECTORY)/%.o: %.cpp
	g++ -c $< -o $@ -MMD -MF $(DEPENDENCY_DIRECTORY)/$*.d

clean:
	rm -rf $(OBJECT_FILES) $(DEPENDENCY_FILES)

.PHONY: all clean $(BUILD_DIRECTORY)

-include $(DEPENDENCY_FILES)
  1. Modifiez le makefile à la racine du projet de la façon suivante :
makefile
BUILD_DIRECTORY = build
DEPENDENCY_DIRECTORY = $(BUILD_DIRECTORY)/dependencies
OBJECT_DIRECTORY = $(BUILD_DIRECTORY)/objects
BINARY_DIRECTORY = $(BUILD_DIRECTORY)/binaries

SOURCE_DIRECTORY = source

EXECUTABLE = $(BINARY_DIRECTORY)/executable

MODULES = $(wildcard $(SOURCE_DIRECTORY)/*/)
SOURCE_FILES = $(wildcard $(SOURCE_DIRECTORY)/*.cpp)

OBJECT_FILES = $(SOURCE_FILES:$(SOURCE_DIRECTORY)/%.cpp=%.o)
OBJECT_FILES := $(OBJECT_FILES:%=$(OBJECT_DIRECTORY)/%)

DEPENDENCY_FILES = $(SOURCE_FILES:$(SOURCE_DIRECTORY)/%.cpp=%.d)
DEPENDENCY_FILES := $(DEPENDENCY_FILES:%=$(DEPENDENCY_DIRECTORY)/%)

all: $(BUILD_DIRECTORY) $(EXECUTABLE)

$(BUILD_DIRECTORY):
	mkdir -p $(DEPENDENCY_DIRECTORY) $(OBJECT_DIRECTORY) $(BINARY_DIRECTORY)

$(EXECUTABLE): $(MODULES) $(OBJECT_FILES) 
	g++ -o $@ $(wildcard $(OBJECT_DIRECTORY)/*.o)

$(MODULES):
	$(MAKE) -C $@

$(OBJECT_DIRECTORY)/%.o: $(SOURCE_DIRECTORY)/%.cpp
	g++ -c $< -o $@ -MMD -MF $(DEPENDENCY_DIRECTORY)/$*.d

run:
	./$(EXECUTABLE)

clean:
	rm -rf $(BUILD_DIRECTORY)

.PHONY: all run clean $(MODULES) $(BUILD_DIRECTORY)

-include $(DEPENDENCY_FILES)
  1. Commentez les makefiles grâce à # (en français si vous le souhaitez) pour clarifier les différents blocs de code et ajoutez des exemples pour les commandes pour clarifier la syntaxe.

Par exemple :

# Compile source codes to object files
$(TARGET_VARIABLE): $(PREREQUISITE_VARIABLE)
    # For example: g++ -c prerequisite.cpp -o target.o
    g++ -c $< -o $@

Exercice 2 : Documentation

  1. Réécrivez votre README (en français si vous le souhaitez) et vérifiez le rendu du README sur GitLab.
  1. Créez un répertoire documentation/ dans TP4/.

En cours, nous avons vu un exemple de documentation utilisant Doxygen (très similaire à la syntaxe Javadoc). Étant donné que Doxygen n’est pas (encore) installé sur les machines de l’IUT, nous allons revoir le même exemple en Javadoc.

  1. Créez le fichier suivant.
Temperature.java
/**
 * Represents a temperature in Celsius and Fahrenheit.
 *
 * This class allows you to set a temperature in Celsius, convert it to Fahrenheit,
 * and vice versa. It also provides the ability to get the current temperature in either unit.
 *
 * <p> Example usage: </p> 
 * <pre>
 * <code>
 * Temperature currentTemperature = new Temperature(25.0);  // 25°C
 * currentTemperature.displayTemperature();  // Output: Temperature: 25°C / 77°F
 * currentTemperature.setFahrenheit(100.0);
 * currentTemperature.displayTemperature();  // Output: Temperature: 37.7778°C / 100°F
 * </code>
 * </pre>
 */
public class Temperature {
    private double celsius;

    /**
     * Constructs a new Temperature object.
     *
     * Initializes the temperature in Celsius.
     *
     * @param celsius The initial temperature in Celsius.
     */
    public Temperature(double celsius) {
        this.celsius = celsius;
    }

    /**
     * Gets the current temperature in Celsius.
     *
     * @return The temperature in Celsius.
     */
    public double getCelsius() {
        return celsius;
    }

    /**
     * Sets the temperature in Celsius.
     *
     * This method sets the temperature value directly in Celsius.
     *
     * @param celsius The new temperature in Celsius.
     */
    public void setCelsius(double celsius) {
        this.celsius = celsius;
    }

    /**
     * Gets the current temperature in Fahrenheit.
     *
     * @return The temperature in Fahrenheit.
     */
    public double getFahrenheit() {
        return celsius * 9 / 5 + 32; // Formula to convert Farenheit to Celsius
    }

    /**
     * Sets the temperature using a value in Fahrenheit.
     *
     * This method converts the given Fahrenheit value to Celsius and sets it.
     *
     * @param fahrenheit The temperature in Fahrenheit.
     */
    public void setFahrenheit(double fahrenheit) {
        this.celsius = (fahrenheit - 32) * 5 / 9; // Formula to convert Celsius to Farenheit
    }

    /**
     * Converts and displays the temperature in both Celsius and Fahrenheit.
     *
     * This method prints the current temperature in both Celsius and Fahrenheit.
     */
    public void displayTemperature() {
        System.out.println("Temperature: " + celsius + "°C / " + getFahrenheit() + "°F");
    }
}
  1. Générer la documentation pour Temperature.java avec la commande javadoc -d docs Temperature.java, ce qui créera un répertoire docs/.

  2. Consultez la documentation générée en ouvrant docs/index.html dans un navigateur web.

  3. Reprenez la syntaxe Doxygen vue en cours pour rédiger la documentation de la classe Product de l’exercice short-functions du TP3.

Revenez aux objectifs et cochez les points que vous avez maîtrisés. Revenez sur les points que vous n’avez pas encore bien compris. Appelez votre encadrant si besoin.