software development

Creating Robust Makefiles: Using Conditional Directives (Ifelse, Ifdef, Ifndef)

Makefiles employ conditional directives to create robust makefiles by limiting the code lines that are being performed based on the contents of the variables or the outcomes of other tests. This may be helpful for several things such as constructing various codes based on the target platform, running various tests following the build mode, and adding or omitting various files from the compilation.

Each of these directives requires one or two parameters according to the kind of test that is being run. Although various conditional directives are available in a makefile, we will discuss the use and examples of “ifelse”, “ifdef”, and “ifndef” in this guide.

Ifdef Directive

As the name suggests, the makefile’s “ifdef” directive checks whether a variable is defined in a makefile or not, i.e. has been assigned with a value. The lines of the makefile that follow the “ifdef” directive are performed if the value of the variable is defined. If not, those lines are omitted.

To elaborate on the use of the “ifdef” directive in a makefile, we use the following makefile code. We define the “S” variable that contains a string and the NEW variable that uses a “subst” function to get an updated version of the “S” variable, i.e. by replacing “stones” with “waves”.

The default “all” target is defined along with PHONY targets to clean the build. The “print” target uses the @echo command to display the value of both variables that are defined in the previous lines.

It’s time to make use of the “ifdef” directive to apply a condition on both variables. The “S” or “NEW” label of the variable that is being tested is the only input that is accepted by the “ifdef” directive. Parentheses can be used to enclose the variable name, but they are not necessary. If the variable is already defined in the file, the first “echo” command is executed to display “Yes, ‘S’/’new’ is defined”. Otherwise, the “else” part of the directive is executed to display the contrary message which is “No, ‘variable-label’ is not defined”.

You can just skip the “else” part if you want to. In this case, we utilize a lowercase label for the second variable, “new”, which is not defined. Therefore, the “else” part should be executed after the make build. The “ifdef” directive ends with the “endif” keyword as depicted in the following:

S := Crossing the river by feeling the stones
NEW := $(subst stones, waves, $(S))
.PHONY: all print
all: print
print:
    @echo S = $(S)
    @echo New = $(NEW)
ifdef S
    @echo Yes, 'S' is defined
else
    @echo  No, 'S' is not defined
endif
ifdef new
    @echo Yes, 'new' is defined
else
    @echo No, 'new' is not defined
endif

Let’s run the makefile via the “make” instruction in the CMD. First, it displays the values of both variables using the “echo” command. After that, it displays that the “S” variable is defined and “new” is not defined.

make

Ifndef Directive

The “ifndef” directive of makefile works exactly the opposite of the “ifdef” directive which confirms that a variable is not defined in a makefile as its name suggests. The “n” part of the “ifndef” directive represents “not”.

If required, you can also utilize the “else” part of the directive. To elaborate on the use of the “ifndef” directive, we employed the same makefile that we used for the “ifdef” directive with a little update. Nothing has been changed except that the “ifdef” keyword is replaced with the “ifndef” keyword for both the conditions at lines 9 and 15. The “ifndef” directive should display “defined” in the case of the “S” variable and “not defined” in the case of the “new” variable. But, we can expect the opposite because the execution lines of the code are the same as we used in the case of the “ifdef” directive.

S := Crossing the river by feeling the stones
NEW := $(subst stones, waves, $(S))
.PHONY: all print
all: print
print:
    @echo S = $(S)
    @echo New = $(NEW)
ifndef S
    @echo Yes, 'S' is defined
else
    @echo  No, 'S' is not defined
endif
ifndef new
    @echo Yes, 'new' is defined
else
    @echo No, 'new' is not defined
endif

Running this makefile displays the variable values but also displays the wrong output in case of each variable, i.e. “S” is defined and “new” is not defined in the file.

make

It’s time to correct the makefile to output the logically correct statements on the execution of the “ifndef” directive. Therefore, we switch the placement of echo statements in both the conditions at lines 9 and 15. The @echo command at line 10 is switched with the @echo command at line 12 in case of the “S” variable, while the @echo command at line 16 is switched with the @echo command of line 18 in case of a “new” variable.

S := Crossing the river by feeling the stones
NEW := $(subst stones, waves, $(S))
.PHONY: all print
all: print
print:
    @echo S = $(S)
    @echo New = $(NEW)
ifndef S
    @echo  No, 'S' is not defined
else
    @echo 'S' is defined
endif
ifndef new
    @echo  No, 'new' is not defined
else
    @echo 'new' is defined
endif

Now, running the “make” instruction displays the logically correct output on our command screen: the “S” variable is defined, and “new” is not defined in the makefile.

make

Ifelse Directive

In a makefile, the “ifelse” statement is a conditional directive that accepts two inputs. The condition that is put to the test is the first input. The code that runs if the condition is met is the second parameter. The code that runs if the criteria is false is the last parameter.

To elaborate on the “ifelse” directive of a makefile, we create a new makefile illustration that is shown in the following code. After declaring the C++ compiler and its flags through variables, we define the SRC variable which contains the “name.cpp” filename as its value. The TARGET variable utilizes the “ifelse” directive to get its value. The “ifelse” uses the SRC variable as its first argument to be checked if it exists or not. If it exists, the target file name should be “name”. Otherwise, it will be “test” as per the second and third arguments of the “ifelse” directive.

The default “all” target depends on the “TARGET” variable that is used to define the makefile rule. The makefile rule at line 6 defines the “TARGET” target which depends on the SRC variable and uses the C++ compiler and its flags to create an executable file. The last line defines the “clean” target that can be utilized to remove the build files from the directory.

CXX = g++
CXXFLAGS = -std=c++11 -Wall
SRC = name.cpp
TARGET := $(if $(SRC), name, test)
all: $(TARGET)
$(TARGET): $(SRC)
    $(CXX) $(CXXFLAGS) -o $(TARGET) $(SRC)
clean:
    rm -f new

When the condition in the “ifelse” directive is true, the executable named “name” is generated in the same working directory.

make

Let’s replace the SRC variable with “S” in the “ifelse” directive; no variable is defined with the name “S”.

CXX = g++
CXXFLAGS = -std=c++11 -Wall
SRC = name.cpp
TARGET := $(if $(S), name, test)
all: $(TARGET)
$(TARGET): $(SRC)
    $(CXX) $(CXXFLAGS) -o $(TARGET) $(SRC)
clean:
    rm -f new

The target executable “test” is generated since the “S” variable is missing in the makefile.

Conclusion

This guide is all about the use of conditional directives in makefiles to create a robust structure. We elaborated the easy-to-learn examples of “ifelse”, “ifdef”, and “ifndef” conditional directives with the help of variables.

About the author

Saeed Raza

Hello geeks! I am here to guide you about your tech-related issues. My expertise revolves around Linux, Databases & Programming. Additionally, I am practicing law in Pakistan. Cheers to all of you.