Pelconf is a simple tool that is similar to GNU's autoconf. It is used to test the compilation system and figure out which features are available and adapt the software accordingly. The difference with autoconf is that it does not depend on the POSIX shell and tools. Instead it uses the C language to define the tests. Unlike other autoconf replacements like cmake, pelconf does not need any previous installation: your C compiler is enough. You don't have to learn a new configuration language or syntax. Everything is done in C. The program that states which tests have to be run is written in C. You do not need to use any other tool beyond the C compiler.
There are many ways of porting software and plenty of books have been written about the subject. This section describes only enough background to understand what pelconf does.
A typical way of adapting software to work on multiple platforms is to use conditional compilation. The preprocessor is used to check for a condition and then according to the test result, different sections of code are selected for compilation. For instance:
#ifdef __GNUC__
... use GNU features
#endif
This fragment relies on the GCC compiler's predefined macro __GNUC__
. If we
are using GCC it is expected that __GNUC__
will be defined.
There are several problems inherent to this approach. First, you need to know which is the symbol that is defined by each of the supported compilers and environments. If a new symbol or a new version must be added then we must revisit each test. A more difficult problem is that it is not clear what should be activated by each define. For instance, in the item above we are just checking if we are using GCC. However GCC is available on many platforms and in many versions. Each of them offers features that are specific to the particular combination of platform and version. Maintaining such a battery of tests quickly becomes unmanageable. Just consider the different versions of GCC under Unix and Windows.
A better approach is to test for features, not for systems. This is what
autoconf does. You just check for the presence of a particular feature, and
if it exists use it. This way we do not need to adapt each test to each
particular version of the compiler and system. A single check is valid for
all existing and future systems. For instance if we want to use opendir()
we would have something like:
#ifdef HAVE_OPENDIR
... use opendir()
#endif
Of course you will be asking: who defines HAVE_OPENDIR? In the GNU configuration system this task is performed by the configure script. The configure script is a bash script that runs the compiler with different test cases and generates configuration files. For the case shown above it would try to compile something like:
#include <sys/types.h>
#include <dirent.h>
int main()
{
DIR * (*f)(const char *) = opendir;
return f != 0;
}
If this program compiles and links without errors then it means that the
opendir()
function is available. The configure script created by autoconf
is very convenient for the user, but to generate the script itself the
programmer must use a combination of Bash, M4 macro language and C. This
feels awkward.
Pelconf removes the need to prepare the script with shell, M4 and C and
instead allows the whole configuration test to be written in C. The user
still runs the configure script as usual: ./configure
The approach described above is what is used by autoconf, pelconf and many
other configuration systems. There are some limitations to the approach. The
configure script can check for the presence of features in the compiler,
library or environment. The program can then adapt (using conditional
compilation) by defining workarounds or limiting its functionality. However
the configure script can't adapt the program if features that were considered
to be always available are missing. For instance a program that uses
fopen()
will not compile on a system which lacks the fopen()
function. If
the author of a program did not foresee this possibility (which is likely
only on embedded systems) then there is no way for any configuration system
to solve the problem.
Although all hosted implementations are expected to have fopen()
, other
functions or features may be assumed to exist, and actually exist on all
systems tested by the author. If the program is later ported to a system
where this assumption fails then no configuration system can solve the
problem. An example is the opendir()
function. It is not part of the C
standard. It is defined by POSIX and most compilers for Windows also
implement it. Thus it is likely that the programmer will assume that the
opendir()
function is always available. If the program is later ported to a
compiler that does not have the opendir()
function the program will not
compile, even with the configuration system.
The previous paragraph makes it clear that when porting a program that uses autoconf/pelconf to a platform on which it has never been tested, new portability problems may become apparent. Thus it is better to consider pelconf as a way to adapt a program to many known optional features with a minimum effort.
The pelconf system assumes that you have a compiler that supports C90. This is the case for any current C or C++ implementation. Under pelconf you write a small C program called pelconf.c. You also distribute with it the pelconflib.c and configure files. The configure file attempts to compile pelconf.c and then run the resulting executable file. The executable file then conducts compilation and linking tests as directed by the pelconf.c file. Here is an example pelconf.c file
0 #include "pelconflib.c"
int main (int argc, char **argv)
{
1 ac_init (".cpp", argc, argv, 1);
2 ac_has_headers ("unistd.h", NULL);
3 ac_has_func_lib ("sys/time.h", NULL, "gettimeofday", NULL);
/* Clock_gettime could be in the standard library or in the extra rt library. */
4 ac_has_func_lib ("time.h", NULL, "clock_gettime", NULL) ||
5 ac_has_func_lib ("time.h", NULL, "clock_gettime", "rt");
6 ac_config_out ("config.h", "PELTK");
7 ac_edit_makefile ("makefile.in", "makefile");
8 ac_create_pc_file ("peltk", "General C++ utilies library");
9 ac_finish ();
return 0;
}
Line 0 just includes the pelconflib.c file and makes its functions available for the pelconf.c program. Including it in pelconf.c allows us to simplify the generation of the executable file.
Line 1 tells pelconf that we are going to use a C++ compiler and that we would like to use the latest C++ standard that is supported by the compiler.
Line 2 checks for the presence of the <unistd.h>
header.
Line 3 checks for the presence of the gettimeofday()
function
in the <sys/time.h>
header and the default system library.
Lines 4 and 5 check for the clock_gettime()
function in the header
<time.h>
and first in the default system library and if not found there in
the rt library.
Line 6 writes a file called config.h which contains the preprocessor macros that reflect the features that are available.
Line 7 processes the file makefile.in and puts the results in makefile. The file makefile contains suitable expansions of macros used in the input makefile.in.
Line 8 creates a peltk.pc file suitable for package-config.
Line 9 cleans up temporary files.
If we run configure with this pelconf.c file a config.h file will be
created. On a Mingw system pelconf found that gettimeofday()
was
available and defined the HAVE_GETTIMEOFDAY
macro in config.h. On the
other hand it could not find clock_gettime()
and did not define any macro.
Thus the program can adapt as follows:
#include "config.h"
#ifdef HAVE_GETTIMEOFDAY
timeval tv;
gettimeofday(&tv, NULL);
// use tv.
#else
// just use time().
#endif
...
...
#ifdef HAVE_CLOCK_GETTIME
timespec ts;
// use clock_gettime()
#else
// just use time()
#endif
The config.h file also contains many other pieces of information concerning
the compiler and the library. For instance it checks if <stdint.h>
is
available and if it isn't it creates the suitable typedefs.
Pelconf also processes the existing makefile.in and puts the result in
makefile. makefile contains the contents of makefile.in prepended with
information about the compiler, its options and required libraries. If for
instance the clock_gettime()
was found to be in the rt library the
makefile would contain a line like this: EXTRALIBS=-lrt
(or
EXTRALIBS=rt.lib
for a DOS/Windows compiler). In some cases you want to
have some particular makefile variables set to your own values. The
configuration program searches the compilation environment for a file called
pelconf.var. If this file is found then a directive will be inserted in the
makefile to include it in the file. The pelconf.var file usually contains
predefined settings of makefile variables. The include statement is inserted
after all the variable definitions created by the program: this implies that
whatever you write in pelconf.var overrides the settings created by the
configuration program. The configuration program will search for
pelconf.var in several places:
-
the file name and location may be given in the command line using the
--makevars=name
command line option. Using this option you can specify the exact path and name of the makefile variables file. -
the current directory will be searched for the
pelconf.var
file. -
the program will attempt to use the file
/etc/pelconf.var
, where is the installation prefix (by default/usr/local
but can be set using the--prefix=name
option).
Using option 2 enables having project specific options. Using option 3 enables having machine specific options that apply to all projects.
The pelconf program produces the configuration file, usually named config.h, the makefile and the pkg-config configuration file. The configuration file, config.h, contains all the code (defines, typedefs, inlines) that the source code will include. The makefile is generated by prepending to an existing input makefile (usually makefile.in) the set of makefile variables that the configuration program defines. The resulting makefile can then use these variables. Finally the generated pkg-config file can be directly stored in the directory containing the pkg-config database.
The different tests that are run by the pelconf program are written to the configure.log file. If a test fails and you want to figure out which is the problem have a look at configure.log.
The configure file just attempts to compile the pelconf.c file and run
it. If the compilation command is not given explicitely with --cc=<cmd>
option then it will try using the make program to compile pelconf using the
system's default compiler. If this fails it will try gcc or g++. If for
whatever reason configure fails to compile the pelconf.c program you can
try to compile it manually. For instance if your compiler is called dmc
use
./configure --cc=dmc
If this fails then try to compile manually and run the resulting program.
dmc pelconf.c -o pelconf
./pelconf --cc=dmc
All tests in pelconflib.c do not require to run the generated code and can be used to detect properties when using a cross compiler. In this case you need to have another compiler that generates programs that will run in the compilation environment:
host-cc -o pelconf pelconf.c
./pelconf --cc=cross-cc
Here host-cc is the compiler that generates binary files that can be executed in the computer hosting the compilation environment. cross-cc is the compiler that will be checked.
The pelconflib.c file provides the functionality to check the system. It
contains many functions, some of them intended for internal use. Those
functions that are intended for internal use have the aci_
prefix. The ones
that are part of the public API use the prefix ac_
. The rest of this
document describes the functions that are provided by public API of
pelconflib.c.
You write the pelconf.c file. Every pelconf.c includes pelconflib.c
first. The pelconf.c file has the main()
function. main()
always
starts with the ac_init()
function. This function performs argument
parsing and runs a series of tests that are always performed. After
ac_init()
follow the tests that are specific to this particular project:
checking for headers, functions, etc. Then main()
ends calling some
functions to create the output. ac_config_out()
writes the configuration*
file which contains the results of the tests. ac_edit_makefile()
creates
a customized makefile. ac_create_pc_file()
is optionally called to create
.pc
file that is ready for pkg-config. The last function called is
ac_finish()
which performs clean up.
An skeleton file would be:
ac_init(...);
...
project_specific_tests(...);
...
ac_config_out(...);
ac_edit_makefile(...);
ac_create_pc_file(...);
ac_finish();
General tests are run by ac_init()
. Project specific tests are written by
you using the functions that are available in pelconflib.c.
The general tests run by ac_init()
determine information about the compiler
command line, support for standards. They are listed below.
The configuration program probes the compiler to identify it and assert which compiler flags are accepted. The compilation command is taken from the command line or the environment variable CC (or CXX for C++).
Next the program checks for some known predefined macros used by compilers:
__clang__
, __GNUC__
, __BORLANDC__
or __TINYC__
. If any of these are
found then the compiler will be check to verify that it accepts some common
compiler flags. For each of the found valid flags a corresponding makefile
variable will be set.
For GCC the following options are checked: -march=native
, -fpic
, -fpie
,
-Wl,--dynamicbase,--nxcompat
, -fextended-identifiers
,
-fvisibility=hidden
, -Wl,--enable-new-dtags
, -Wl,--rpath='$$ORIGIN'
,
-Wl,--as-needed
, -mthreads
, -O2
, -fomit-frame-pointer
,
-ftree-vectorize
, -ffast-math
, -g
, -fstack-protector
,
-fstack-protector-all
, -gsplit-dwarf
, -Wa,--compress-debug-sections
,
-Wl,--gdb-index
, -ftrapv
, -fnon-call-exception
, -Wabi-tag
, -shared -Wl,--soname=foo
, -shared -Wl,--out-implib=foo.a
, -static
, -std
.
If an option is valid then a makefile variable will be defined with the
value being the option found. For instance if the -fpic
option is accepted
then the variable GCC_FPIC will be set in the makefile to take the value
-fpic
.
You can set the compilation flags in terms of makefile variables. If a particular flag is not available then the corresponding makefile variable will not be defined. For instance you may want to use the following flags in debug mode if they are available:
CFLAGS_DBG = $(GCC_G) $(GCC_STACK_PROTECTOR_ALL) \
$(GCC_COMPRESS_DEBUG_SECTIONS) $(GCC_SPLIT_DWARF) \
$(GCC_TRAPV) $(GCC_NON_CALL_EXCEPTION)
You can just use this definition of CFLAGS_DBG. If it turns out that this particular implementation of GCC does not support splitting of debugging information then GCC_SPLIT_DWARF will be undefined and it will expand to the empty string. Within the selected set of flags only those which are really available will be used.
The configuration program also checks if the compiler uses UNIX or DOS conventions. UNIX uses object files of the form .o and library files of the form lib.a and lib*.so whereas DOS uses *.obj and *.lib.
The configuration program will try to figure out how to select the name of the output file. It will also attempt to enable all warnings.
If the compiler is GCC then the following flags are checked and their corresponding makefile variables are defined if they are allowed:
-
-march=native
, variableTARGET_ARCH
-
-fpic
, variableGCC_FPIC
-
-fpie
, variableGCC_FPIE
-
if compiling for Windows check for
-Wl,--dynamicbase,--nxcompat
and if accepted setGCC_PIE
to these options. -
if not compiling for Windows check for
-pie
, variableGCC_PIE
. -
-fextended-identifiers
, variableGCC_EXTIDENT
-
-fvisibility=hidden
, variableGCC_VISHIDDEN
-
-Wl,--enable-new-dtags
, variableGCC_NEWDTAGS
-
-Wl,--rpath='$$ORIGIN'
,GCC_RPATH_LIB
. If this variable is set then also set the variablesGCC_RPATH_BIN
to-Wl,--rpath='$$ORIGIN/../lib'
andGCC_RPATH_PREFIX
to-Wl,--rpath=$(PREFIX)lib
-
-Wl,--as-needed
, variableGCC_ASNEEDED
-
-mthreads
, variableGCC_MTHREADS
-
-O2
, variableGCC_O2
-
-fomit-frame-pointer
, variableGCC_OMITFRAMEPOINTER
-
-ftree-vectorize
, variableGCC_TREEVECTORIZE
-
-ffast-math
, variableGCC_FASTMATH
-
-g
, variableGCC_G
-
-fstack-protector
, variableGCC_STACK_PROTECTOR
-
-fstack-protector-all
, variableGCC_STACK_PROTECTOR_ALL
-
if not compiling for Windows check for
-gsplit-dwarf
, variableGCC_SPLIT_DWARF
and-Wa,--compress-debug-sections
, variableGCC_COMPRESS_DEBUG_SECTIONS
-
-Wl,--gdb-index
, variableGCC_GDB_INDEX
-
-ftrapv
, variableGCC_TRAPV
-
-fnon-call-exception
, variableGCC_NON_CALL_EXCEPTION
-
-Wabi-tag
, variableGCC_WABI_TAG
-
if SONAME is supported then set
GCC_SONAME
to-Wl,--soname=$(notdir $@)
-
if
-out-implib
is supported then setGCC_OUTIMPLIB
to-Wl,--out-implib=$@$(A)
-
if the user requested a modern version of C or C++ then select the highest available version and define the variable
GCC_STD
to one of-std=gnu++17
,-std=gnu++1z
,-std=gnu++14
,-std=gnu++1y
,-std=gnu++11
,-std=gnu++0x
for C++ or-std=gnu11
,-std=gnu99
for C. -
-static
, variableGCC_STATIC
The program will check the support for the following features:
-
Support for <stdint.h>. If it is present it will be included by config.h. If it is not present then the macros and typedefs specified by <stdint.h> will be created by the configuration program.
-
Whether we are compiling for a little or big endian computer. It will define the macros WORDS_BIGENDIAN or WORDS_LITTLEENDIAN as appropriate.
-
Support for the
__attribute__((x))
syntax. TheGCC_ATTRIBUTE
macro will be defined if supported. -
Support for some kind of thread local storage. It checks for either
thread_local
,__thread
or__declspec(thread)
storage classes. Ifthread_local
is not supported but the other options are available then thethread_local
will be defined as a macro with the correct value. The macroHAVE_THREAD_LOCAL_STORAGE_CLASS
will be defined if thread local storage is supported. -
Support for alignment attributes. The macro ALIGN(X) will be defined.
-
Support for
bool
in C mode. -
Support for the
restrict
keyword or its variant__restrict
. It will definerestrict
if required. -
Ensure that
va_copy()
is available, either through <stdarg.h> or by defining it in config.h. -
Support for variadic macros, defining
HAVE_VARIADIC_MACROS
. -
Support for the flexible array member syntax. If available it will define the macro
FLEXIBLE_ARRAY_MEMBER
to empty. If not available it will be defined to 1. With this you can declare an structure like this:struct foo { int a, b; char c[FLEXIBLE_ARRAY_MEMBER]; };
If the flexible array member is supported then the last field will be declared as
char c[];
. If it is not supported it will be declared aschar c[1];
. -
Define the
PRIiMAX
family of macros if <inttypes.h> does not support them. -
Define the macro
HAVE_C99_MIXED_VAR_DECLS
if we can mix declarations and code in C mode. -
Ensure that
ssize_t
is available by typedefing it if required. -
Ensure that
char32_t
is available by typedefing it if required. -
Check if
__builtin_add_overflow()
is available and defineHAVE_GCC_OVERFLOW
if available. -
Check how to export symbols from libraries and define the
EXPORTFN
macro. -
Check if the attributes
deprecated
,warn_unused_result
,unused
are supported. -
Check the GCC format attribute family.
-
Ensure that the
__func__
syntax is valid. -
Ensure that the
inline
keyword is valid (even in C90 mode). -
Check if inline assembly is available.
-
Check for a suitable definition for _ReturnAddress().
-
Check if the
__COUNTER__
macro is supported. -
Define the makefile variables COPY, COPYREC, REMOVE, LN, LN_S, ME, INSTALL, INSTALL711, INSTALL_DATA, INSTALL_DIR. COPY will act like
cp
. COPYREC will act likecp -r
. REMOVE will act likerm
. LN will act likeln
if hard links are supported, otherwise it will act likecp
. LN_S will act likeln -s
if symbolic links are supported, otherwise it will act likecp
. ME is the prefix that is required to run a command from the current directory: empty for DOS/Windows and./
for UNIX. The INSTALL* variables will be defined toinstall *
if supported or otherwise as invocations of COPY.
If the compilation mode is C++ then the following checks for bugs are also performed:
-
Check if SFINAE works (some old compilers used to be buggy)
-
Check if overloading templates with const or volatile is buggy.
-
Ensure that numeric limits has been specialized for int64_t/uint64_t.
-
Support for intmax_t as template parameter.
If the compilation mode is C++ then the following checks for features are also performed.
-
Support for inline namespaces or the strong attribute.
-
Support for decltype, auto, constexpr, override and final.
-
Support for explicit extern instantiation of templates.
-
Support for rvalue references.
-
Support for variadic templates.
-
Support for the headers type_traits, chrono, tuple, system_error, ratio, atomic, thread.
-
Support for the abi_tag attribute.
Note that all the tests shown below are performed by running the compiler. No test requires that the compiled program is run. The compiler could be a cross compiler and our system may be unable to run the compiled programs. All the information is gathered from the compilation environment, not from the runtime environment. This applies even to such things like determining the size of a given type or determining the endianness. This is done without running the compiled program.
Some of the following functions accept a string that contains header files
that must be included. Some other functions accept a string that contains
library names that the test program must be linked with. When specifying such
strings the items are separated by spaces and commas. For instance the string
"sys/types.h stdio.h" specifies the two included files <sys/types.h>
and
<stdio.h>
. The string "rt peltk-formats" specifies the libraries given in
the command line with -lrt
and -lpeltk-formats
.
If no header file name or library name is to be passed to a function that
accepts such arguments you can either pass the empty string ""
or just pass
NULL.
In some cases we need to know whether a header file is present. Note that
the presence of a header file is a necessary condition but it may not be
enough to ensure that the program will compile and link properly. You check
for the presence of a set of headers as a single unit by using the function
ac_has_headers()
or ac_has_headers_tag()
. For instance
ac_has_headers_tag ("sys/types.h, sys/stat.h", NULL, "STAT_HEADER");
will check that after including first <sys/types.h> and then <sys/stat.h> we can compile an empty program. If both headers exist the HAVE_STAT_HEADER macro will be defined. Note that according to the POSIX specification we must include <sys/types.h> before including <sys/stat.h>.
The ac_check_each_header_sequence()
will run each individual header through
ac_has_headers(). This is different from above. Consider the call
ac_check_each_header_sequence ("sys/types.h, sys/stat.h", NULL);
This will first check if we can include <sys/types.h>. If the file exists it will define HAVE_SYS_TYPES_H. Then it will check if we can include <sys/stat.h>. If <sys/stat.h> needs that the definitions of <sys/types.h> are available this attempt will fail because we didn't include <sys/types.h> before including <sys/stat.h>.
Some times we need to check not only that a header file is available, but
also that it declares a given function prototype. This may be enough to
compile a source file that uses the declaration, but it may need further
flags to specify the libraries containing the function. We check if a
function declaration is available with the ac_has_proto_tag()
,
ac_has_proto()
and ac_has_signature()
functions. The
ac_has_proto()
and ac_has_proto_tag()
functions check if a function
has been declared with the requested name. For instance:
ac_has_proto ("process.h", "-mthreads", "_beginthread");
will check if the _beginthread()
function has been declared after including
<process.h> and compiled with the flag -mthreads. If it has been declared
it will define the macro HAVE__BEGINTHREAD
Note that the ac_has_proto*()
functions do not care about how the
function has been declared, only that it has been declared. This may not be
enough if we want to check for different possible declarations of the
function. One example is the strerror_r function. It is defined by both
POSIX and GNU in incompatible ways. If we use the proto functions above we
may discover that strerror_r has been declared, but we do not know if it has
been declared in the POSIX or GNU flavor. The ac_has_signature()
can
distinguish both:
ac_has_signature ("string.h", NULL, "strerror_r",
"int (*f)(int,char*,size_t)", "POSIX_STRERROR_R");
ac_has_signature ("string.h", NULL, "strerror_r",
"char* (*f)(int,char*,size_t)", "GNU_STRERROR_R");
Here we define HAVE_POSIX_STRERROR_R
if strerror_r has been declared as
int strerror_r(int,char*,size_t)
and we define HAVE_GNU_STRERROR_R
if
strerror_r has been declared as char* strerror_r(int,char*,size_t)
. Note
that the syntax of the signature is a function pointer with the requested
signature.
Note that this check works only in C++ mode. C allows casting between incompatible types of function pointers. Even in C11 this is allowed, with only the call through an incompatible pointer type being undefined behaviour.
Often we need to verify that a type is available after including some
headers. This is done with the ac_has_type()
and ac_has_type_tag()
functions. For instance:
ac_has_type ("sys/time.h", NULL, "struct timeval");
This will check if the struct timeval is available after including
<sys/time.h>. If it is available HAVE_STRUCT_TIMEVAL
will be defined.
In some cases it is not enough to know that a type or struct is declared. We
may need to check if it contains a given member. For instance, the struct lconv
contains the field int_p_cs_precedes
in C99 but not in C90. We can
check for this field with
ac_has_member_tag ("locale.h", NULL, "struct lconv","int_p_cs_precedes",
"C99_LCONV");
If the field is present HAVE_C99_LCONV
will be defined.
The ac_has_member()
function can test whether a field has been declared in
a C struct. When we need to know if a C++ class has a given member function
we use the ac_has_member_lib()
and ac_has_member_lib_tag()
functions.
These functions use the compiler and the linker to verify that the member
function has been declared and also that its implementation has been provided
in some library.
In some cases we need to know the size of a given type. We could use
sizeof() at run time but in some cases we need to know the size at compile
time. ac_get_sizeof()
can figure out the size of a type without running
any program. Thus it is usable even when cross-compiling. The following can
be used to figure out the size of a wchar_t
:
size_t n = ac_get_sizeof ("stddef.h", NULL, "wchar_t");
The test above will return the sizeof(wchar_t)
and will define the macro
SIZEOF_WCHAR_T
to the size of wchar_t
.
Any program may check for the presence of macros by using #ifdef. The test
ac_has_define()
allows the configuration program to verify if a given
macro is defined. It may then modify the makefile accordingly or run
additional tests.
We may need in some cases to check if a given expression is valid in the
preprocessor. Use the ac_valid_cpp_expression()
function for this purpose.
The tests above can be seen as specialized versions of a general check to
verify that some source code can compile. We can check if an arbitrary
source code compiles or fails to compile with the ac_does_compile()
and
ac_does_compile_fail()
functions. For instance, we can quickly check if a
given set of flags is defined by using the following check:
ac_does_compile ("has fcntl.h flags",
"#include <fcntl.h>\n"
"int x = O_CREAT | O_EXCL | O_TRUNC | O_APPEND | O_RDONLY | O_WRONLY;\n",
NULL, "FCNTL_FLAGS");
This will check if the above statement can be compiled. This will succeed if
after including <fcntl.h> the O_CREAT
, O_EXCL
, O_TRUNC
, O_APPEND
,
O_RDONLY
and O_WRONLY
symbols are available. If they are the macro
HAVE_FCNTL_FLAGS will be defined.
In some cases we are checking for bugs in the compilation environment. In
this case we use ac_does_compile_fail()
. If the given source code fails to
compile it will define the macro. This may be used to work around bugs.
As stated above it is not enough to verify that symbols are declared after including header files and specifying the correct compiler flags. In some cases we need to provide additional flags to the linker for it to locate and use the required libraries. The functions in this section check that both the compilation and linking steps succeed.
We need to check that functions are not only declared but that they are also
present in the libraries. The functions ac_has_func_lib()
and
ac_has_func_lib_tag()
perform the compilation and linking steps to verify
that a function can be used. For instance the function clock_gettime()
is
usually declared in <time.h> but it may be available in the default library
or we may need to link it explicitely with the rt library. The following
tests check that the clock_gettime
is declared in <time.h> and then
first if it is available in the default library and if it is not available
in the default library it checks if it is available in the rt library.
ac_has_func_lib ("time.h", NULL, "clock_gettime", NULL) ||
ac_has_func_lib ("time.h", NULL, "clock_gettime", "rt")
If it turns out that the rt library is required, the name of the required library will be added to the makefile variable EXTRALIBS.
There is a special case when headers and libraries are written in C and the
headers are not prepared to be used from C++. Many libraries work both with C
and C++ programs by wrapping their declarations with extern "C" {}
. However
some pure C libraries do not and the C++ program using them must provide the
wrapper. The functions ac_has_func_lib_cxx()
and ac_has_func_lib_tag_cxx()
perform the same tests as above but they wrap the headers within extern "C"
brackets. An example where this is needed is
ac_has_func_lib_cxx ("libavformat/avformat.h", "-D__STDC_CONSTANT_MACROS",
"av_register_all", "avformat avcodec")
Similar to ac_does_compile()
you can check if a given source code compiles
and links using some given flags. For an example of the need for this
function consider GCC's atomic builtin functions. GCC supports the function
__sync_fetch_and_add()
as an intrinsic function. However if the
architecture does not support the atomic fetch-and-add GCC will generate a
library call.
ac_does_compile_and_link ("has __sync_fetch_and_add builtin",
"int main() { int x = 5; return __sync_fetch_and_add(&x, 2); }\n",
"", NULL, "SYNC_FETCH_AND_ADD");
If we compile this with gcc -march=i386 the program will not link because
__sync_fetch_and_add
is missing from the library. If we compile it with
gcc -march=i686 the program will compile.
pkg-config
is a tool that is available under many systems that manages a
catalog of compilation and linking flags that are needed in order to use a
library. The command pkg-config --cflags <libname>
will output the flags
that need to be passed to the compiler in order to be able to compile a
program that uses the library . The command pkg-config --libs <libname>
will output the flags that are passed when linking a program that
uses the library. pelconfig can use the pkg-config database.
You can check if pkg-config
has been installed by calling
ac_has_pkg_config()
.
Pelconf can create a .pc
file to be used with pkg-config that describes the
flags that our library will use. You can generate such a file by calling
ac_create_pc_file ("foo.pc", "The foo library")
. The first argument is the
name of the .pc
file to be created. The second argument is the short
description of the library that pkg-config will show when listing the library.
The functions ac_has_func_pkg_config()
and ac_has_func_pkg_config_tag()
are the same as ac_has_func_lib()
variants but they first check the
availability of the function using pkg-config queries. If there is no
pkg-config or if it fails then these functions will call the _lib()
variants.
ac_has_member_pkg_config()
and ac_has_member_pkg_config_tag()
are the
same as the ac_has_member_lib()
functions but they first check if the member
is available using the pkg-config database. If the check fails then they will
attempt the compilation using the _lib()
functions.
You can directly query the pkg-config database using the
ac_pkg_config_flags()
function.
ac_has_file()
can be used to verify if a given file is present in the
compilation environment. Note that the file may not be present in the
execution environent.
void ac_add_code (const char *src_code, int unique);
Adds the given src_code verbatim to the configuration file. If unique is set you may call this function several times with the same src_code, but it will be output only once. A typical use is to perform a check and then output code if the feature is not available. The code would then emulate the feature.
void ac_add_flag (const char *name, const char *comment, int passed)
The public interface to add a preprocessor define to the configuration file
config.h
. Name is the name of the flag. The comment will be displayed
before the flag. The flag will be #defined if passed is true.
void ac_add_option_info (const char *opt, const char *desc)
Add the description of one command line option. This information will be
shown to the user when the ac_show_help()
function is called or when the
user gives the "--help" option. Call this function for each of the options
that the configuration program processes. opt
is the command line option
that is being described and desc is the text explaining it.
void ac_add_var_append (const char *name, const char *value);
Append text to a variable in the makefile. Appends *value to the current value of the makefile variable *name. If this function is called several times, the last value appended will be found at the end of *name's value.
void ac_add_var_prepend (const char *name, const char *value);
Prepend text to a variable in the makefile. Prepends *value to the current value of the makefile variable *name. If this function is called several times, the last value prepended will be found at the beginning.
void ac_check_each_func (const char *funcs, const char *cflags)
Check for the presence of each function in funcs. Use it after
ac_check_each_header_sequence()
.
void ac_check_each_header_sequence (const char *includes,
const char *cflags);
This will check for each header file in includes, behaving as if
ac_has_headers()
was called with each of the header files individually. In
addition the headers that are found will be remembered and included before
checking the following headers. For instance:
ac_check_each_header_sequence ("foo1.h foo2.h foo3.h foo4.h", NULL);
will check first if <foo1.h>
can be used. If <foo1.h>
exists then it will
check if <foo2.h>
can be included after including <foo1.h>
and so on.
Assume that <foo1.h>
, <foo2.h>
and <foo4.h>
exist but <foo3.h>
does
not exist. The checks will be as follows:
- First test:
#include <foo1.h>
(succeeds) - Second test:
#include <foo1.h> #include <foo2.h>
(succeeds) - Third test:
#include <foo1.h> #include <foo2.h> #include <foo3.h>
(fails) - Fourth test:
#include <foo1.h> #include <foo2.h> #include <foo4.h>
(succeeds)
The set of found headers will be remembered and used when the
ac_check_each_func()
function is called. Before running the tests for the
functions if will include all the headers found by
ac_check_each_header_sequence()
. The use of these two functions allows a
quick checking for the presence of a set of headers and functions.
void ac_check_same_cxx_types (const char *includes, const char *cflags,
const char *t1, const char *t2, const char *tag)
Include the files listed in includes and compile with the compilation flags cflags. Are the typedefed types t1 and t2 the same types in C++? If yes then define tag in the configuration file.
void ac_config_out (const char *config_name, const char *feature_pfx)
Write out the configuration file to config_name. Prefix the configuration macros with feature_pfx.
void ac_create_pc_file (const char *libname, const char *desc)
Create a .pc file for pkg-config. libname is the name of the library. You provide the one line description of the library in desc.
int ac_does_compile (const char *comment, const char *src,
const char *cflags, const char *tag);
Check if we can compile the source code in src, using the compilation flags in cflags. The comment comment will be put in the configuration file before the definition of the tag tag. If the code compiles it returns true and defines the tag in the configuration file. If the code does not compile the definition is left as a comment and the function returns zero.
int ac_does_compile_and_link (const char *comment, const char *src,
const char *flags, const char *libs, const char *tag);
Check if we can compile and link the source code in src, using the compilation flags in cflags and linking with the libraries in libs. The comment comment will be put in the configuration file before the definition of the tag tag. If the compilationg and linking is successful it will return non zero. If something fails zero will be returned.
int ac_does_compile_and_link_fail (const char *comment, const char *src,
const char *flags, const char *libs, const char *tag);
Perform the same check as ac_does_compile_and_link()
but set the flags and
return value only if the compilation fails.
int ac_does_compile_fail (const char *comment, const char *src,
const char *cflags, const char *tag);
Similar to ac_does_compile()
, but define the tag only if the compilation
fails. It returns zero if the source code compiles and non zero if the
compilation fails.
void ac_edit_makefile (const char *make_in, const char *make_out)
Write the completed makefile reading it from make_in and putting it in make_out.
void ac_finish (void);
This is the last function that pelconf.c should call. It performs the required clean up tasks.
int ac_get_sizeof (const char *includes, const char *cflags,
const char *tname);
Detects the size of a type without actually running the compiler program
(may be useful for cross compilers). It will include the files listed in
includes and it will use the compilation flags cflags. A macro of the
form SIZEOF_##tname will be defined in the configuration file. The
function returns -1 on failure or the sizeof(tname)
on success.
int ac_has_compiler_flag (const char *flag, const char *makevar)
See if the compiler supports the compilation flag flag. If it does set the makefile variable makevar to flag. It returns true if the flag is accepted by the compiler.
int ac_has_define (const char *includes, const char *cflags,
const char *defname);
Return true if defname has been defined as a macro after including the files listed in includes and compiling with the compilation flags cflags.
int ac_has_feature (const char *name, char *dest, size_t dest_size)
Check if the option name was given in the command line and put its value in dest.
int ac_has_file (const char *name);
Check if the file name is available in the compilation environment.
int ac_has_func_attribute (const char *attribute, const char *external,
int usedef, int literal);
Check if the attribute attribute is a valid attribute for functions. If usedef is not zero a default definition will be added to the function. This is sometimes required when an attribute is allowed only with function definitions but not with function declarations. If literal is zero then check if the attribute is available in the form attribute before checking the name of the attribute without underscores. If defines a macro with the name external to the correct syntax for the attribute. It returns non zero if the attribute is available.
int ac_has_func_lib (const char *includes, const char *cflags,
const char *func, const char *libs, int verbatim);
Same as ac_has_func_lib_tag()
but the tag name is deduced from the name of
the function. For instance we may want to check how to compile under Win32
when using threads:
ac_has_func_lib ("process.h", NULL, "_beginthread", NULL) ||
ac_has_func_lib ("process.h", "-tWM", "_beginthread", NULL) ||
ac_has_func_lib ("process.h", "-mthreads", "_beginthread", NULL);
This will define HAVE__BEGINTHREAD
in the configuration file if one of
the above tests compiles and links. In addition EXTRA_CFLAGS
will get the
compiler option that is required for multithreaded compilation under
windows.
int ac_has_func_lib_cxx (const char *includes, const char *cflags,
const char *func, const char *libs)
Same as ac_has_func_lib_cxx_tag()
but the tag name is deduced from the name
of the function.
int ac_has_func_lib_tag (const char *includes, const char *cflags,
const char *func, const char *libs,
int verbatim, const char *tag);
Checks if the function func is declared in the headers listed in includes and defined in the libraries listed in libs when compiled with the compiler options cflags. If the function is defined then it adds HAVE_<tag> to the configuration and returns non-zero.
If verbatim is true the libs contents is passed to the command line as is.
Otherwise the libs contents are processed to add the required prefixes and
suffixes as required to convert foo bar
into -lfoo -lbar
or foo.lib bar.lib
as needed.
If the function is found the contents of cflags will be added to the
variable EXTRA_CFLAGS
in the makefile and the additional libraries are
added to the EXTRALIBS
variable in the makefile. For instance:
ac_has_func_lib_tag ("pthread.h", NULL, "clock_gettime", "pthread",
"CLOCK_GETTIME_IN_PTHREAD");
If found, this will define HAVE_CLOCK_GETTIME_IN_PTHREAD
in the
configuration file and it will add -lpthread
to the EXTRALIBS
variable
in the makefile.
int ac_has_func_lib_tag_cxx (const char *includes, const char *cflags,
const char *func, const char *libs, const char *tag);
Similar to ac_has_func_lib_tag()
. The difference is that this function
assumes that the compiler is a C++ compiler and that the included files must
be wrapped with extern "C" {}
within our program.
int ac_has_func_pkg_config (const char *includes, const char *cflags,
const char *func, const char *package)
Same as ac_has_func_pkg_config_tag()
but the tag is deduced from func.
int ac_has_func_pkg_config_tag (const char *includes,
const char *cflags, const char *func, const char *package,
const char *tag)
Check for a function. If package config is available its information will be used to deduce the required flags. Otherwise check if the function is in the library package. It returns true if the function was found.
int ac_has_headers (const char *includes, const char *cflags);
Same as ac_has_headers_tag()
, but the tag name is deduced from the name
of the headers.
int ac_has_headers_tag (const char *includes, const char *cflags,
const char *tag);
Checks for the presence of the headers listed in includes, when compiled with the compilation flags given in cflags. If all the header files could be included then it will define the preprocessor macro HAVE_<tag> in the configuration file. You may write several headers in includes by separating them with commas. For instance you may want to check if the headers <sys/types.h> and <sys/stat.h> are available:
ac_has_headers_tag ("sys/types.h, sys/stat.h", "", "SYS_TYPES_STAT");
This will define HAVE_SYS_TYPES_STAT
in the configuration file if the
headers are available.
The function returns nonzero if the headers are available. If the headers
are available and cflags is not NULL or empty it will add its contents to
the EXTRA_CFLAGS
makefile variable.
int ac_has_member (const char *includes, const char *cflags,
const char *sname, const char *fname);
Same as ac_has_member_tag()
but the tag is deduced from the name of the
field.
int ac_has_member_lib (const char *includes, const char *cflags,
const char *func, const char *libs,
int verbatim)
Same as ac_has_member_lib_tag()
but the tag name is deduced from the name
of the function. For instance:
ac_has_member_lib ("peltk/formats/exif.hpp", NULL,
"peltk::formats::Exif_reader::get_metering", "peltk-formats");
This checks if the member function
peltk::formats::Exif_reader::get_metering()
is declared after including
<peltk/formats/exif.hpp> and it can be found by the linker after linking
with the library peltk-formats. If the get_metering()
function is found
then it #defines HAVE_PELTK__FORMATS__EXIF_READER__GET_METERING
.
int ac_has_member_lib_tag (const char *includes, const char *cflags,
const char *func, const char *libs,
int verbatim, const char *tag)
This function checks the the member function specified in func is present after including the headers listed in includes, compiling with cflags and linking with libs. It assumes that the compiler is a C++ compiler.
int ac_has_member_pkg_config (const char *includes, const char *cflags,
const char *func, const char *package)
Same as ac_has_member_pkg_config_tag()
but the tag is deduced from func.
int ac_has_member_pkg_config_tag (const char *includes, const char *cflags,
const char *func, const char *package,
const char *tag)
Similar to ac_has_member_lib_tag()
but it first uses pkg-config if
available and if this fails it uses ac_has_member_lib_tag()
.
int ac_has_member_tag (const char *includes, const char *cflags,
const char *sname, const char *fname, const char *tag);
After adding the includes files anc compiling with cflags check if the structure sname contains a member with the name fname. If it does the define tag and return 1.
int ac_has_pkg_config (void)
See if pkg-config is installed and working.
int ac_has_proto (const char *includes, const char *cflags,
const char *func);
Same as calling ac_has_proto_tag()
but with the tag deduced from the
function name. For instance:
ac_has_proto ("stdio.h", "-ansi", "fopen);
will define HAVE_FOPEN
if fopen is defined when including stdio.h and
compiling with the compiler option -ansi.
int ac_has_proto_tag (const char *includes, const char *cflags,
const char *func, const char *tag);
Checks if the prototype for the function func is defined in the headers
listed in includes when compiled with the compilation flags cflags. If it is
available it will add HAVE_<tag> to the configuration file and return a
non-zero value. If the prototype is found and cflags is neither NULL nor
empty it will add its contents to the EXTRA_CFLAGS
variable in the
makefile.
For instance:
ac_has_proto_tag ("stdio.h", "-ansi", "fopen",
"FOPEN_IN_STDIO_WITH_ANSI");
This will check if fopen() is defined in stdio.h when compiled with the
-ansi flag. If it is it will define HAVE_FOPEN_IN_STDIO_WITH_ANSI
.
int ac_has_signature (const char *includes, const char *cflags,
const char *func, const char *signature,
const char *tag);
Checks if the header files listed in includes, when compiled with cflags define the function func with the signature given in signature. If it does it adds HAVE_<tag> to the configuration file and returns non-zero. If the signature is found and cflags is neither NULL nor empty it will add its contents to the EXTRA_CFLAGS variable in the makefile. For instance:
ac_has_signature ("string.h", NULL, "strerror_r", "int (*f)(int,char*,size_t)",
"POSIX_STRERROR_R");
ac_has_signature ("string.h", NULL, "strerror_r", "char* (*f)(int,char*,size_t)",
"GNU_STRERROR_R");
Both GNU and POSIX define strerror_r but differ in the signature. The above checks will detect if we have the POSIX or GNU version. The signature must be written as a declaration of a function pointer with the desired signature.
It returns non zero if the signature is present, zero if it is missing.
int ac_has_type (const char *includes, const char *cflags,
const char *tname);
Same as ac_has_type_tag()
but the tag is deduced from the name of the type.
int ac_has_type_tag (const char *includes, const char *cflags,
const char *tname, const char *tag);
Check if tname is available as the name of a type after including the files listed in includes and compiling with the compilation flags cflags. If tname is available then define tag in the configuration file and return 1.
int ac_has_var_attribute (const char *attribute, const char *external);
Check if the attribute attribute is a valid attribute for variables. The macro external will be defined to the syntax required for the attribute.
int ac_has_windows (void)
Are we compiling for Windows?
void ac_init (const char *extension, int argc, char **argv,
int latest_c_version);
This is the first function that pelconf.c should call. Extension is the extension used for the file that will contain the source code to be tested. Use ".c" for C files. Any other extensions will be treated as C++. The recomended extension for C++ files which is understood by most compilers is ".cpp". argc and argv are just the arguments passed by the user. Set latest_c_version if you want to check for features of the latest C or C++ standards. If latest_c_version is 0 it will not check for the latest standards. On GCC if latest_c_version is true then it will also select the appropriate flag like -std=gnu++14
void ac_libobj (const char *func_name)
Add func_name.o to the list of object files that must be compiled. This
list is available as the value of the makefile variable LIBOBJS
. It will
usually be the list of files which provide replacements for missing
functions.
int ac_msg_error (const char *msg);
It will output an error message and stop the configuration. It always returns
zero. It is intended to be used as the last term in a sequence of tests:
ac_check...() || ac_check...() || ac_msg_erro("Give up");
int ac_pkg_config_flags (const char *s, char *buf, size_t n, pkgconf_flags what)
Get the flags as given by pkg-config. what can be pkgconf_cflags
or
pkgconf_libs
. Store the flags in the buffer buf of size n.
void ac_replace_funcs (const char *includes, const char *cflags, const char *funcs)
After including the files listed in includes and compiling with the compilation flags cflags check for the presence of each of the functions listed in funcs. If the function is not available the add the object file function_name.o to the makefile variable LIBOBJS.
void ac_set_var (const char *name, const char *value);
Sets the makefile variable whose name is *name to the value stored in *value.
void ac_show_help (void)
Show the available options for the configuration program.
void ac_use_macro_prefix (const char *pfx)
Prepend the prefix pfx to the macros created by the program.
int ac_valid_cpp_expression (const char *includes, const char *cflags,
const char *expr)
Check if the expression expr is a valid preprocessor expression after including the files listed in includes and compiling with the compilation flags cflags. Return true if the expresion compiles.