Autoconf makra
V této a příští kapitole ukáži, jak použít autotools na trochu rozsáhlejším projektu. V této kapitole představím další užitečná autoconf makra, v příští kapitole ukáži jak za pomoci automake vygenerovat Makefile soubory.
Ukázkový projekt
V ukázkovém projektu použiji zdrojový kód z příkladu z kapitoly o ncurses. Zdrojový kód jsem vložil do src/ adresáře a rozdělil do několika souborů. Pokud jste kapitolu o ncurses nečetli, nevadí, zdrojovým kódům rozumět nemusíte.
Krom toho jsem ještě přidal zdrojové kódy dalšího miniprogramu, src/math.c a src/xstr.h. Potřeboval jsem přidat k projektu nějaké zajímavé kusy kódu a taky jsem chtěl ukázat, jak použít automake pro vygenerování více než jednoho programu (v příští kapitole).
Dále jsem k projektu přidal adresář data, ve kterém jsou nějaké soubory, které budu chtít instalovat spolu s programem. Jejich obsah není důležitý (jsou prázdné). Jejich smyslem je ukázat, jak správně soubory nainstalovat a jak se k jejich umístění na souborovém systému dostat.
Základní struktura zdrojových souborů tedy vypadá takto:
$ tree . ├─── data │ └── soubory │ ├── x1 │ ├── x2 │ ├── x3 │ ├── x4 │ └── x5 └── src ├── init.c ├── init.h ├── math.c ├── thnc │ ├── thread-ncurses.c │ └── thread-ncurses.h ├── threads.c ├── wposition.c ├── wposition.h └── xstr.h
Zdrojové kódy potřebné pro překlad threads programu nejsou nikterak zajímavé, tak je tu nebudu ukazovat. Zdrojový kód math.c vypadá takto:
- #include <math.h>
- #include <stdio.h>
- #include <stdlib.h>
- #include "config.h"
- #include "xstr.h"
- #ifndef HAVE_DOUBLE_T
- #endif
- }
- }
Makro PKGDATADIR
by mělo obsahovat cestu k adresáři,
kde je nainstalovaný adresář soubory/
se svým obsahem.
Bude se při překladu souboru math.c přidávat pomocí volby překladače
-D
nějak takto:
$ gcc -o math math.c -D"/usr/lib/xxxx"
Pokud bych ho v math.h h použil bez xstr()
,
po nahrazení bych dostal tento kus kódu:
A to samozřejmě není validní C program. Potřebuji dostat hodnotu
makra do uvozovek. K tomu slouží makro xstr()
, které
je definované v souboru xstr.h:
#define _XSTR_H
/* stringify the result of expansion of a macro argument */
/* First, argument is macro-expanded */
#define xstr(s) str(s)
#define str(s) #s
#endif
Jak to funguje? Argument makra, před kterým je #
se převede na string v uvozovkách. Pokud bych ale použil
rovnou str(PKGDATADIR)
,
výsledkem by bylo "PKGDATADIR"
. A CPP makra
v uvozovkách už neexpanduje.
Kdežto xstr(PKGDATADIR)
se převede
na str(/usr/local/xxx)
, a dál už je to jasné …
V main.c je potřeba se vypořádat s několika věcmi. První je
předání makra PKGDATADIR
. To bude práce pro automake
(viz příští kapitola). Dále budu chít zjistit, zda standardní knihovna
math.h definuje typ double_t
. Pokud ne, definiuji si
ho jako double
(viz řádek 8). Dále chci vypsat
volby, se kterými byl spuštěn skript configure (budou definované
v makru CONF_OPT
). A v neposlední řadě,
budu chít překládat program s volbou
-lm
, pokud je k překladu nutné přilinkovat libm knihovnu.
Autoscan
Prvním, čím začnu, bude spuštění autoscanu. Ten vygeneruje nějaký takový configure.scan:
# Process this file with autoconf to produce a configure script.
AC_PREREQ([2.69])
AC_INIT([FULL-PACKAGE-NAME], [VERSION], [BUG-REPORT-ADDRESS])
AC_CONFIG_SRCDIR([src/init.c])
AC_CONFIG_HEADERS([config.h])
# Checks for programs.
AC_PROG_CC
# Checks for libraries.
# Checks for header files.
AC_CHECK_HEADERS([locale.h stdlib.h unistd.h])
# Checks for typedefs, structures, and compiler characteristics.
AC_CHECK_HEADER_STDBOOL
AC_TYPE_SIZE_T
# Checks for library functions.
AC_CHECK_FUNCS([atexit setlocale])
AC_OUTPUT
Ve zbytku této kapitoly popíši makra z tohoto souboru, jak je upravit a další, která je vhodné do configure.ac přidat. Výsledný configure.ac najdete na konci kapitoly. (Ne že tam teď přeskočíte a budete si ho prohlížet.)
Autoconf makra
AC_PREQ
Toto makro vyžaduje minimální verzi autoconf. Vzhledem k tomu, že výsledný configure skript autoconf už nepotřebuje, nemusíte se bát, že by to nějak snižovalo přenositelnost vašeho výsledného balíčku. Nechte, co tam je.
AC_INIT
Toto makro už znáte z kapitoly GNU Build Systém. Defaultní hodnoty nahradím třeba takto:
AC_CONFIG_SRCDIR
Toto makro už také znáte z GNU Build Systém. Makro jen zkontroluje, že existuje soubor, jenž je jeho argumentem. Ověřuje tím, že je skript configure spuštěn oproti správnému adresáři. Pokud by autoscan přidal nějaký všední název (jako třeba src/main.c), je lepší ho změnit na něco vístřednějšího. Soubor src/init.c se mi nezdá dostatečně unikátní, tak ho změním na src/wposition.c:
AC_CONFIG_HEADERS
I toto makro už znáte z GNU Build Systém.
Já si chci vygenerovat config.h do adresáře src/:
Můžu jej tak includovat v .c souborech v src/ adrsáři jako
#include "config.h"
, namísto #include "../config.h"
.
Nicméně, při použití automake se při překladu přidájí gcc volby -I
,
které zahrnou do prohledávaných cest i root adresář projektu (tj aresář,
ve kterém je configure.ac, adresář src/ atd). Tudíž i v případě,
že by byl config.h v rootu projektu, nebylo by nutné používat tu druhou
variantu include …
AC_DEFINE a AC_DEFINE_UNQUOTED
Další, co přidám do configura.ac, bude toto:
CONF_OPT=$@
AC_DEFINE_UNQUOTED([CONF_OPT], ["$CONF_OPT"], [Define configure options])
První makro už znáte. Pokud je zavoláno (což v mém příkladu je, protože
není uvnitř žádné if
podmínky), postará se o nahrazení
#undef RELEASE_YEAR
z config.h.in hodnotou druhého argumentu.
Třetí argument využívá program autoheader, který jej použije jako dokumentační
text.
Makro AC_DEFINE_UNQUOTED
dělá to samé, jen druhý argument
interpretuje jako proměnnou shellu.
Do proměnné CONF_OPT
ukládám $@
,
což je v bashi speciální sekvence, jež se nahrazuje – hádejte za co
– za arugumenty, se kterými je skript spuštěn.
AC_CONFIG_MACRO_DIR
Program threads využívá knihovnu curses. K nalezení této
knihovny, jejích hlavičkových souborů
a případně voleb potřebných pro překlad (např. -lncurses
použiji makro ax_with_curses z autoconf archivu.
Toto makro ve svém těle používá makro ax_with_curses,
takže jsem soubor s definicí tohoto makra také stáhnul z autoconf archivu
a uložil do adresáře m4/.
Soubory s makry uložím do adresáře m4/ (namísto vložení do aclocal.m4)
a configure o tom informuji makrem AC_CONFIG_MACRO_DIR
.
Pak zavolám makro AX_WITH_CURSES
. Z jeho dokumentace
se dočtu, že v příladě nalezení knihovny ncuresw definuje proměnnou
$ax_cv_ncursesw
s hodnotou yes
atd.
Já se spokojím s tím, že je nalezena knihovna ncursesw nebo ncurses, a že knihovna podporuje barvy:
AX_WITH_CURSES
if test "x$ax_cv_ncursesw" != xyes && test "x$ax_cv_ncurses" != xyes; then
AC_MSG_ERROR([requires either NcursesW or Ncurses library])
fi
if test "x$ax_cv_curses_color" != xyes; then
AC_MSG_ERROR([requires an (N)Curses library with color])
fi
Další věcí, kterou makro AX_WITH_CURSES
dělá, je
to, že definuje různé cpp makra určující, jakou ncurses
knihovnu našel. Ty využiji v souboru src/config-local.h
takto:
#include <ncurses.h>
#elif defined HAVE_NCURSES_CURSES_H
#include <ncurses/curses.h>
#elif defined HAVE_CURSES_H
#include <curses.h>
#elif defined HAVE_NCURSESW_CURSES_H
#include <ncursesw/curses.h>
#elif defined HAVE_NCURSESW_H
#include <ncursesw.h>
#endif
Makra HAVE_NCURSES_H
a další budou definovány
v souboru src/config.h. Takže src/config-local.h by mělo
být includováno až za src/config.h (tam, kde je to potřeba).
AC_PROG_CC
Makro AC_PROG_CC
vyhledá překladač jazyka C. Pro C++
existuje AC_PROG_CXX
.
Tyto makra hledají překladač na základě hodnot proměnných prostředí, argumentů předaných skript configure a pak nastavý pár užitečných proměnných pro automake.
Autoconf definuje ještě další zajímavá AC_PROG_*
makra, na některá z nich přijde řada v příští kapitole.
AC_CHECK_PROGS
Makro AC_CHECK_PROGS
můžete použít k nalezení
libovolného programu. Prvním argumentem je název proměnné,
která bude obsahovat název nalezeného programu, druhým
argumentem je seznam programů, které se mají hledat (první
nalezený se uloží do proměnné), posledním argumentem je
defaultní hodnota (co bude proměnná obsahovat, když se nic
nenajde.
Například:
Do proměnné $hashprog
uloží první nalezený program z
sha512sum, sha256sum, sha224sum, sha1sum, shasum a md5sum. Pokud ani jeden
z těchto programů nenalezene, uloží do $hashprog
cat.
V našem příkladu toto makro k ničemu nepotřebuji. Uvádím jej zde jen pro pořádek.
AC_CHECK_HEADERS
Makro AC_CHECK_HEADERS
zjišťuje, jestli existují
zadané hlavičkové soubory. Zjišťuje to tak, že se snaží
přeložit zdrojový kód, ve kterém hlavičkové soubory includuje.
Pokud soubory najde, spustí svůj druhý argument, jinak třetí. Já využiji třetí argument k tomu, abych oznámil chybu:
Pokud bych nezavolal AC_MSG_ERROR
, configure by jen vypsal
že hlavičkový soubor nenalezl, upravil by podle toho výstup maker v config.h
a pokračoval vesele dále. Pokud vám stačí makra z config.h k tomu,
abyste podmínečně nepřekládali nějakou část kódu využívající
chybějící knihovnu, nemusíte běh configure ukončovat …
AC_CHECK_HEADER_STDBOOL
Toto makro zjišťuje, zda existuje standardní khihovna stdbool.h
(odpovídající standardu C99). Pokud ano, definuje HAVE_STDBOOL_H
na 1. A pokud je definován _Bool
, definuje na 1
makro HAVE__BOOL
.
Ve svých zdrojových souborech byste toho měli využít nějak takto:
# include <stdbool.h>
#else
# ifndef HAVE__BOOL
# ifdef __cplusplus
typedef bool _Bool;
# else
# define _Bool signed char
# endif
# endif
# define bool _Bool
# define false 0
# define true 1
# define __bool_true_false_are_defined 1
#endif
Pravděpodobnost, že dnes narazíte na prostředí bez stdbool.h je tak malá, že jsem si dovolil kus tohoto kódu do svého programu nepřidat.
AC_TYPE_SIZE_T a AC_CHECK_TYPE
Program autoscan si všiml, že ve svém zdrojovém souboru math.h
používám datový typ size_t
.
Proto přidal makro, které testuje existenci tohoto typu a které
v případě neexistence size_t
jej definuje na
něco vhodného (např. typ unsigned
).
Já ale v math.h využívám ještě jeden neobvyklý typ: double_t
.
Jeho existenci ověřím makrem AC_CHECK_TYPE
:
AS_IF([test x"$have_double_t" == xyes],[
AC_DEFINE([HAVE_DOUBLE_T],[1],[Defined if double_t is defined in math.h])
])
Prvním argumentem je testovaný typ, druhý argument se provede v případě
nalezení typu, třetí v případě nenalezení.
Čtvrtým argumentem je seznam knihoven, které se přidají k programu,
jenž configure skript překládá, aby zjistil, že typ existuje.
(Typ double_t
by měl být definován v knihovně <math.h>.)
Následná definice HAVE_DOUBLE_T
vysvětluje řádky 7 až 9 v souboru
math.c.
AC_CHECK_FUNCS
Poslední makro kontroluje existenci funkcí, o kterých si autoscan myslí, že by nemusely být dostupné na všech systémech. Test využiji stejně, jako jsem to udělal při testování hlavičkových souborů. Pokud nějaká funcke nebude nalezena, nemilosrdně configure skript ukončím.
A zase platí, že místo ukončení programu bych jen mohl definovat
nějaké makro a ve svém programu se na jeho základě pokusit
definovat funkce atexit()
či setlocale()
sám.
(Třeba definovat funkci atexit()
jako funkci,
která jen volá exit()
a funkci setlocale()
,
která nedělá nic.)
Jestli někdy narazíte na systém, kde tyto funkce ještě nejsou definovány, dejte mi vědět. (Jedna z věcí, která je autotools vyčítána je, že testuje problémy, které už v dnešní době nikde neexistují…)
AC_OUTPUT
Toto makro by mělo být posledním makrem v configure.ac. Postará se o vypsání vygenerovaného skriptu do souboru configure. Cokoliv je za ním, bude ignorováno.
Výsledný configure.ac
# Process this file with autoconf to produce a configure script.
AC_PREREQ([2.69])
AC_INIT([example1], [0.1], [sallyx@example.org])
AC_CONFIG_SRCDIR([src/wposition.c])
AC_CONFIG_HEADERS([src/config.h])
AC_DEFINE([RELEASE_YEAR],["2018"],[Define the year of release])
CONF_OPT=$@
AC_DEFINE_UNQUOTED([CONF_OPT], ["$CONF_OPT"], [Define configure options])
AC_CONFIG_MACRO_DIR([m4])
AX_WITH_CURSES
if test "x$ax_cv_ncursesw" != xyes && test "x$ax_cv_ncurses" != xyes; then
AC_MSG_ERROR([requires either NcursesW or Ncurses library])
fi
if test "x$ax_cv_curses_color" != xyes; then
AC_MSG_ERROR([requires an (N)Curses library with color])
fi
# Checks for programs.
AC_PROG_CC
# Checks for libraries.
# Checks for header files.
AC_CHECK_HEADERS([locale.h stdlib.h unistd.h], [],[AC_MSG_ERROR([Unable to find the header.])])
# Checks for typedefs, structures, and compiler characteristics.
AC_CHECK_HEADER_STDBOOL
AC_TYPE_SIZE_T
AC_CHECK_TYPE([double_t],[have_double_t=yes;],[],[[@%:@include <math.h>]])
AS_IF([test x"$have_double_t" == xyes],[
AC_DEFINE([HAVE_DOUBLE_T],[1],[Defined if double_t is defined in math.h])
])
# Checks for library functions.
AC_CHECK_FUNCS([atexit setlocale],[],[AC_MSG_ERROR([Unable to find function atexit or setlocale.])])
AC_OUTPUT
Teď už jen stačí zavolat autoreconf && ./configure -q
.
Pokud configure projde bezchybně, máte velkou šanci, že se i program přeloží
bez chyb. Podívejte se do vygenerovaného src/config.h, co v něm najdete.
Namátkou:
#define CONF_OPT "-q"
/* Define to 1 if you have the `atexit' function. */
#define HAVE_ATEXIT 1
/* Define to 1 if a SysV or X/Open compatible Curses library is present */
#define HAVE_CURSES 1
/* Define to the address where bug reports for this package should be sent. */
#define PACKAGE_BUGREPORT "sallyx@example.org"
/* Define to the full name of this package. */
#define PACKAGE_NAME "example1"
/* Define to the full name and version of this package. */
#define PACKAGE_STRING "example1 0.1"
/* Define to the version of this package. */
#define PACKAGE_VERSION "0.1"
/* Define the year of release */
#define RELEASE_YEAR 2018
/* Define to 1 if you have the ANSI C header files. */
#define STDC_HEADERS 1
/* Define to `unsigned int' if <sys/types.h> does not define. */
/* #undef size_t */
Závěr
Psát na tomto místě závěr je asi trochu předčasné, protože příklad pokračuje další kapitolou.