Skip to main content

How To Set Up Autotools for 42sh ?

This article will help you get started with Autotools for 42sh.

Project architecture

As an example, we will use a sample project with an architecture containing sub-folders, for each module of our interpreter.

For each sub-folder, we will create the corresponding static library and then generate the final binary.

Here is a sample architecture:

42sh
├── src
│ ├── ast
│ │ ├── ast.h
│ │ ├── ast.c
│ │ └── Makefile.am
│ ├── parser
│ │ ├── parser.h
│ │ ├── parser.c
│ │ └── Makefile.am
│ ├── Makefile.am
│ └── 42sh.c
├── configure.ac
└── Makefile.am

What is Autoconf?

Autoconf is a tool for configuring the build. For example, checking that our compiler has the right flags for our project, or checking that the libraries are installed correctly, or generating makefiles.

Creating configure.ac

Start by creating a file called configure.ac at your project’s root directory. This file is used by autoconf to create the configure POSIX shell script that users must run before building.

The file should include at a minimum the M4 macros AC_INIT and AC_OUTPUT. You are not required to have any knowledge of the M4 language to utilize these macros. Note that the relevant ones are documented.

To put it simply, a M4 macro is just a templated shell script.

The AC_INIT macro can include the package name, version, an email address for reporting bugs, the project URL, and, optionally, the name of the source TAR file.

AM_INIT_AUTOMAKE takes several (optional) parameters: the first one is used to specify that you will have makefiles in sub-folders, the second one is used to deactivate GNU architecture constraints, which include having different files at the root such as NEWS, Changelog, AUTHORS, README and others.

The AC_OUTPUT macro is much simpler and accepts no arguments.

The macros for generating a Makefile are as follows: AM_INIT_AUTOMAKE, which does not require any arguments, and AC_CONFIG_FILES, which takes the name you want to assign to your output files.

You need to use a macro that corresponds to the compiler required by your project. For instance, a C project would necessitate the use of AC_PROG_CC, as outlined in the Autoconf documentation.

AX_COMPILER_FLAGS is used to check if the compiler supports a given set of flags.

tip

Macros prefixed with AX_ come from the package autoconf-archive. It contains a set of macros to perform additional checks.

Checkout the list of macros defined in autoconf-archive.

The AM_PROG_AR macro verifies that ar, a tool to creates archives, is present on your system. It will then configure build options associated with archiving, if available. The AC_PROG_RANLIB macro checks for the existence of the ranlib tool on the system.

configure.ac
# Init the 42sh project
AC_INIT([42sh], [1.0], [42sh@assistants.epita.fr])

# Setup Automake
AM_INIT_AUTOMAKE([subdir-objects] [foreign])

# Pretty display of Makefile rules
AM_SILENT_RULES([yes])

# Enable ar for Makefile
AM_PROG_AR

# Check if ranlib is available
AC_PROG_RANLIB

# Check if a C compiler is available
AC_PROG_CC

# Check if a compiler has this list of flags
AX_COMPILER_FLAGS([], [], [], [-Wall -Wextra -Werror -Wvla -pedantic -std=c99])

# List Makefiles in subdirectories
AC_CONFIG_FILES([
Makefile
src/Makefile
src/ast/Makefile
src/parser/Makefile
])
AC_OUTPUT

Setting up the Build Environment

To generate the configure script from the configure.ac file, you can use autoreconf with the --install flag. This command processes the autoconf macros and generates the configure script.

42sh$ autoreconf --install

Now we can use the configure script to, as its name suggests, configure the build environment. This step involves detecting your system's capabilities, setting up paths, and ensuring all the necessary tools and libraries are available. To do so, simply run:

42sh$ ./configure

What is Automake?

Automake is the tool you will use to generate your Makefiles. It uses Makefile.am scripts as a blueprint for generating Makefile.in files. It is similar to how configure.ac simplifies the creation of complex files. The Makefile.in's will also serve as temporary blueprints to generate the final Makefiles.

Let's move on to the creation of the various static libraries.

Creating libast.a Library

In this section, the goal is to create the Makefile.am to produce src/ast/libast.a.

Automake is based on a template system to generate Makefiles. To do this, we need to define variables for our target.

lib_LIBRARIES is an automake variable to define the name of the library we want to produce. The lib_ prefix is a convention to indicate that this is a library target.

We are going to define a variable to store all of the source files that make up our library: libast_a_SOURCES.

# Name of the libaries
lib_LIBRARIES = libast.a

# List of file to compile in libast.a
libast_a_SOURCES = \
ast.c \
ast.h
tip

Automake has a variable system based on the following convention: name_of_target_AM_FEATURE.

For example:

  • name_of_target_CFLAGS: Compiler flags for the target.
  • name_of_target_LDFLAGS: Linker flags for the target.

Check out the GNU documentation.

In the target's name, . are replaced by _ for example libast.a becomes libast_a_AM_FEATURE.

The next part of the Makefile.am specifies compiler and preprocessor flags for building the library.

libast_a_CPPFLAGS = -I$(top_srcdir)/src

libast_a_CFLAGS = -std=c99 -Werror -Wall -Wextra -Wvla -pedantic

libast_a_CPPFLAGS sets the preprocessor flags. It specifies -I$(top_srcdir)/src, which is used to include $(top_srcdir)/src directory with -I option. This flag ensures that the compiler can find header files in the source and build directories.

libast_a_CFLAGS sets the compiler flags.

danger

You need to add the .h in the sources, they are important for creating the package.

The noinst_LIBRARIES variable is used to specify libraries (object code files or static libraries) that should not be installed when running make install.

src/ast/Makefile.am
lib_LIBRARIES = libast.a

libast_a_SOURCES = \
ast.c \
ast.h

libast_a_CPPFLAGS = -I$(top_srcdir)/src

libast_a_CFLAGS = -std=c99 -Werror -Wall -Wextra -Wvla -pedantic

noinst_LIBRARIES = libast.a

Here is the source code:

src/ast/ast.h
#ifndef AST_H
#define AST_H

void print_ast(void);

#endif /* ! AST_H */
src/ast/ast.c
#include "ast.h"

#include <stdio.h>

#include "parser/parser.h"

void print_ast(void)
{
puts("ast !!!");
print_parser();
}

Creating libparser.a Library

Now, we are going to repeat the same process for the parser.

src/parser/Makefile.am
lib_LIBRARIES = libparser.a

libparser_a_SOURCES = \
parser.c \
parser.h

libparser_a_CPPFLAGS = -I$(top_srcdir)/src

libparser_a_CFLAGS = -std=c99 -Werror -Wall -Wextra -Wvla -pedantic

noinst_LIBRARIES = libparser.a

Code source:

src/parser/parser.h
#ifndef PARSER_H
#define PARSER_H

void print_parser(void);

#endif /* ! PARSER_H */
src/parser/parser.c
#include "parser.h"

#include <stdio.h>

void print_parser(void)
{
puts("parser !!!");
}

Creating 42sh Binary

Now that we have created the libraries needed for the 42sh binary, we will edit the src/Makefile.am to indicate how to build the 42sh binary.

SUBDIRS = ast \
parser

Here, the SUBDIRS variable is used to specify two subdirectories that contain Makefile.am files: ast and parser.

bin_PROGRAMS = 42sh

The bin_PROGRAMS variable is set to the 42sh binary.

42sh_SOURCES = 42sh.c

42sh_CPPFLAGS = -I%D%

42sh_CFLAGS = -std=c99 -Werror -Wall -Wextra -Wvla -pedantic

The 42sh_SOURCES variable specifies the source files for the program . The 42sh_CPPFLAGS and 42sh_CFLAGS variables respectively define preprocessor and compiler flags.

42sh_LDADD =  \
ast/libast.a \
parser/libparser.a

The 42sh_LDADD variable lists the dependencies the main program relies on during the linking phase. In our case, it includes static libraries (libast.a and libparser.a).

danger

The order of dependencies for the linking is important.

src/42sh.c
#include "ast/ast.h"

int main(void)
{
print_ast();
return 0;
}
src/Makefile.am
# define the subdirectories
SUBDIRS = ast \
parser

bin_PROGRAMS = 42sh

42sh_SOURCES = 42sh.c

42sh_CPPFLAGS = -I%D%

42sh_CFLAGS = -std=c99 -Werror -Wall -Wextra -Wvla -pedantic

42sh_LDADD = \
ast/libast.a \
parser/libparser.a
tip

To check that your build system is valid, run the make distcheck command.

make distcheck is a command used to create and test a distribution package. It consists of configuring, building and checking the software in a separate directory to ensure that the distribution package is complete and functional. This allows potential problems to be detected before the software is distributed to other systems.

How To Make The Target

Now that everything is ready, we are going to launch the build. If you have not already done so, you need to configure the build.

42sh$ autoreconf --force --verbose --install

42sh$ ./configure

Let's launch the build.

42sh$ make
info

You can use make -j4 to parallelize the build on 4 cpu cores.

Hooking a Testsuite

The build system can also be used to run a testsuite for your project. To do this, you will need to add a Makefile.am in the tests folder. Then add the check-local rule.

tests/Makefile.am
check-local:
./testsuite.sh
danger

Don't forget to add the Makefile in the AC_CONFIG_FILES list in the configure.ac, and add the tests folder in the Makefile.am at the root.