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.
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.
# 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 Makefile
s.
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 Makefile
s.
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
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.
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
.
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:
#ifndef AST_H
#define AST_H
void print_ast(void);
#endif /* ! AST_H */
#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.
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:
#ifndef PARSER_H
#define PARSER_H
void print_parser(void);
#endif /* ! PARSER_H */
#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
).
The order of dependencies for the linking is important.
#include "ast/ast.h"
int main(void)
{
print_ast();
return 0;
}
# 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
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
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.
check-local:
./testsuite.sh
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.