Preprocessor M4
Preprocessor m4 je mocný, ale také v určitých ohledech složitý jazyk. Naštěstí pro vás se s programováním v m4 téměř nesetkáte. Alespoň ne v počátcích práce s autotools. Nicméně, pro lepší pochopení formátu configure.ac je určitě dobré rozumět základům jazyka m4, které popíšu v této kapitole.
O M4
M4 je znám jako preprocessorový, nebo také makro jazyk. Prochází text a nahrazuje některé textové vzory jinými.
M4 vznikl v době, kdy si programátoři assembleru všimli, že dost často opakují kusy kódu. Jazyk M4 měl být jednoduchou pomůckou, která jim ušetří psaní.
Kromě programování v assembleru se M4 používá při konfiguraci sendmailu, nebo autoconf. Jinak je to poměrně neznámý jazyk.
Oproti CPP (C preprocessoru), je M4 o dost mocnější (rozuměj, složitější). Dokáže pracovat s argumenty maker, zvládá jednoduché aritmetické operace, je konfigurovatelný atp. Kritici autotools jako jeden z problémů uvádějí nutnost učit se tento jazyk. To je pravda jen z části. M4 se musíte učit, pokud chcete psát vlastní autotools makra. Pokud si vystačíte s těmi dodávanými autotools, nebo s těmi z autoconf archivu, stačí vám porozumět základům: co jsou klíčové prvky jazyka, jak správně zapsat argumenty maker, nebo jak používat uvozovky.
Základy m4
Program m4
funguje podobně jako mnoho jiných nástrojů pro
práci s textem v Linuxu: jako roura. Vstup buď může číst ze standardního
vstupu nebo ze souboru a výstup posílá do standardního výstupu.
Následující
příkazy jsou ekvivalentní:
$ cat vstup.m4 | m4 > vystup.txt $ m4 vstup.m4 > vystup.txt
Z CPP jste zvyklí, že se definuje makro pomocí #define NAZEV
hodnota
.
V M4 k tomu slouží makro define(NAZEV,hodnota)
Mezi makrem (např. define
) a závorkou uvozující argumenty makra
nesmí být mezera!
Další důležité makro m4 je dnl
. Cokoliv za tímto makrem
až do konce řádku se do výstupu nevypíše. A to ani konec řádku.
Používá se například k psaní komentářů, ale také k odstranění prázdných řádků,
které by zbyli po řádku s define()
makrem. Viz první příklad:
define(POZDRAV, Hello JMENO)dnl
define(JMENO, World)dnl
POZDRAV
Uhodnete co bude výstupem z tohoto vstupu?
Hello World
Jsou to dva prázdné řádky (bez dnl na řádkách s define() by byly 4) a text Hello World.
M4 pracuje tak, že nejdříve projde celý vstup, přičemž si zapamatuje všechny definice, na které narazí. Pokud ve vstupu něco nahradil, vezme výstup jako nový vstup a znovu začne hledat definice a nahrazovat. Výstup tedy prochází rekurzivně a ne sekvenčně, proto je výstupem Hello World a ne Hello JMENO.
Znak #
má pro m4 speciální význam: Vše od tohoto znaku dále
není nijak interpretováno (nedochází k expanzi maker).
Používá se pro komentář, který, na rozdíl od dnl, zůstene součástí výstupu.
Pokud chcete vypnout expanzi makra, můžete dát kus textu do uvozovek.
Defaultně je počáteční uvozovka `
(zpětná uvozovka) a
ukončovací je '
(jednoduchá uvozovka). Počáteční a koncová
uvozovka musí být jiná, protože m4 umožňuje uvozovky vnořovat (k čemu je to
dobré viz dále).
Pokud předchozí příklad rozšířím o následující:
`#` POZDRAV
`POZDRAV'
``POZDRAV''
xPOZDRAV
Celkový výstup bude takovýto:
Hello World # POZDRAV # Hello World POZDRAV `POZDRAV' xPOZDRAV
Všiměte si, jak se odstranila jedna „vrstva“ uvozovek.
Také si všiměte, že xPOZDRAV
není pro m4 stejný pattern jako
POZDRAV – m4 odděluje vzory pomocí mezer, nových řádků a dalších
speciálních znaků (které nemohou být součástí názvu makra).
Pokud nechcete, aby se vám nějaká část textu expandovala, můžete ji
uzavřít do uvozovek. Protože jsou ale názvy výchozích maker definovaných
jazykem m4 vcelku běžná (anglická) slova, má program m4
volbu
-P
, která přidá všem výchozím m4 makrům předponu m4_
.
Krom toho umožňuje m4 makrem changequote
změnit znaky, které bude
interpretovat jako počáteční a koncovou uvozovku. Jak už jste si mohli
všimnout, autotools používá jako uvozovky hranaté závorky. Důvodem je,
že autotools generuje bash skripty, které jsou plné zpětných a jednoduchých
uvozovek. Proto není dobé, aby měli nějaký speciální význam pro m4.
Druhý příklad:
m4_changequote([,])m4_dnl
m4_define(POZDRAV, [Hello JMENO])m4_dnl
m4_define(JMENO, World)m4_dnl
POZDRAV
# POZDRAV
[POZDRAV]
`POZDRAV'
[#] POZDRAV
Při použití volby -P
je výstup následující:
Hello World # POZDRAV POZDRAV `Hello World' # Hello World
Předpona m4_
se týká jen defaultních m4 maker, nikoliv
speciálních znaků, nebo vámi definovaných maker.
Podobně, jako CPP používá #include
, s m4 můžete použít
makro include()
. Výstup z následujícího příkladu je na chlup
stejný, jako ten z druhého příkladu (pokud použijete volbu -P
).
m4_include(example2.m4)dnl
A to je tak vše, co potřebujete jako uživatelé autotools o m4 vědět. Nebylo to tak hrozné, že? Máte pocit, že toho zvládnete více? Pak čtěte dále.
Definice maker
Nová makra se definují pomocí makra define()
se dvěma argumenty.
Prvním je název makra, druhým jeho tělo. Ovšem ani tyto argumenty nejsou imunní
vůči expanzi maker, proto je slušnost uzavírat je do uvozovek.
Viz následující příklad:
changequote([,])dnl
define(POZDRAV, Hello World)dnl
POZDRAV
define(POZDRAV, [Viszontlatasra])dnl
POZDRAV
define([POZDRAV], [Není [POZDRAV] jako [POZDRAV]])dnl
POZDRAV
Výsledek vypadá takto:
Hello World Hello World Není POZDRAV jako POZDRAV
Při druhém volání makra define()
se POZDRAV
už
nahradí textem Hello World
,
takže se reálně zavolá define(Hello World,[Viszontlatasra])dnl
.
(Text "Hello World" není platný název makra, ale define si nestěžuje).
V třetí definici je POZDRAV
v uvozovkách, takže už se makro s tímto
názvem redefinuje.
Nejdůležitějším v tomto příkladu jsou závorky v těle definice makra kolem
POZDRAV
.
Pokud vynecháte první závorky, tak se m4 pokusí nahradit POZDRAV
za Není POZDRAV jako [POZDRAV]
.
V nahrazeném textu se znovu pokusí nahradit makra. Druhý pozdrav je v
závorkách, takže jen odstraní závorky a nechá POZDRAV
. Nicméně,
první pozdrav se pokusí nahradit za – chvilka napětí – Není
POZDRAV jako [POZDRAV]
. Výsledek je tak Není Není POZDRAV jako
[POZDRAV] jako [POZDRAV]
. A tak to pokračuje dál, takže výsledkem je,
že se m4 zacyklí a donekonečna tiskne jen Není Není Není …
.
(Když vynecháte závorky kolem druhého POZDRAV
, bude m4 tisknout
Není POZDRAV jako Není POZDRAV jako Není POZDRAV jako …
.
Parametry
Makra mohou mít parametry. Argument dosazený za první parametr
je v těle makra dostupný pod $1
, druhý pod $2
atd.
$1, $2 atd. se nahradí hodnotou argumentu i v případě, že je uzavřen v
uvozovkách. Uvozovky kolem $1
zajistí to, že se hodnota argumentu
neexpanduje. Jinak řečeno, pokud bude první argument POZDRAV
, pak
výsledkem [$1]
bude POZDRAV
, který už se
neexpanduje.
Pokud byste chtěli v tělě vypsat $1, což při psaní maker pro bourne shell není
neobvyklé, musíte použít takovýto trik:$[]1
. Závorky (pro m4 tedy
prázdné uvozovky) se odstraní, takže výsledkem při expanzi těla definice bude
to, co je mezi nimi – prázdno.
Tělo makra můžete rozepsat na více řádek. A můžete v něm volat další makra.
Jen pamatujte, že pokud na konci řádku nedáte dnl
, pak se každý
nový řádek dostane i do výstupu (což při generování bash skriptů většinou
nevadí).
changequote([,])dnl
define(ECHO,[dnl
echo $1 $2;
echo [$1] [$2];
echo $[]1 $[2];
])dnl
ECHO(Hello, World)dnl
define(World,Lana del Rey)dnl
ECHO(Hello, World)dnl
ECHO([Hello], [World])dnl
ECHO([Hello], [[World]])dnl
Výstup:
echo Hello World; echo Hello World; echo $1 $2; echo Hello Lana del Rey; echo Hello Lana del Rey; echo $1 $2; echo Hello Lana del Rey; echo Hello World; echo $1 $2; echo Hello World; echo Hello [World]; echo $1 $2;
Při prvním volání ECHO
ještě nebylo definováno makro
World
, proto se všude vypsalo Hello World.
U druhého volání makra ECHO
se World
nahradilo už
před zavoláním makra, tj $2
už obsahovalo Lana del Rey.
Proto ani závorky uvnitř těla nepomohli.
Při třetím volání už závorka kolem [$2] zabránila, aby se World
expandovalo.
Poslední příklad znovu ukazuje, jak dochází k odlupování jedněch uvozovek za
druhými. Závorky navíc zabránily expanzi maka World
u prvního
echa, u druhého zůstaly závorky neexpandované.
M4sh
M4sh je dialekt m4 vytvořený autory autoconf. Je zaměřen na vytváření přenostielných Bourne shell skriptů.
Součástí tohoto dialektu jsou makra začínající na AS_
.
V minulé kapitole jste se seznámili s těmi nejdůležitějšími, jako je
AS_MKDIR_P
, AS_ECHO_N
, atp.
Volání některých AS_* maker vytvářejí proměnné shellu, jejichž jména
začínají na as_
.
Kromě AS_
a as_
prefixů si M4sh rezervuje
_AS_
prefix pro interní použití.
Součástí M4sh je také tzv. „M4sugar“. Ten má na svědomí
přejmenování maker m4 – přidává jim na začátek m4_
. Tj.
místo define
je m4_define
atp. Ale makro
dnl
zůstává dnl
(m4 se nevolá s volbou
-P
, jak by se mohlo na první pohled zdát).
K přejmenovávání maker se používá makro defn.
Některá makra se chovají malinko odlišně od těch původních z m4. Například
m4_include
varuje před několikanásobným inkludováním stejného
souboru.
Z předchozího plyne, že, pokud jde o autotools, měli byste se učit spíše m4 a M4sh z nápovědy k autoconf.
Quadrigraphs
Generování některých speciálních znaků, jako je třeba [
, je
obtížné. Autoconf proto přichází s tzv. Quadrigraphs.
Jedná se o speciální sekvence, které se nahrazují až po expanzi maker programem
m4.
Zde je přehled toho, co se za co nahrazuje:
- @<:@
- [
- @:>@
- ]
- @S|@
- $
- @%:@
- #
- @{:@
- (
- @:}@
- )
- @&t@
- „nic“
Program autom4te nejdříve spustí m4, pak vymaže všechny
prázdné řádky a nakonec nahradí quadrigraphs. Pokud z nějakého důvodu
chcete ve výstupu nechat prázdný řádek, můžete k tomu použít
@&t@
.
Definice maker v autotools: AC_DEFUN
Autotconf používá pro definici maker makro AC_DEFUN
. To
funguje skoro stejně jako makro m4_define
, jen přidává navíc
trochu kódu. Navíc makra definovaná pomocí AC_DEFUN
nalezne
program aclocal.
Vlastní definovaná makra byste měli mít v aclocal.m4, nebo v adresáři určeném makrem AC_CONFIG_MACRO_DIR (preferovaný způsob).
Před definicí makra by měla být nápověda (řádky začínající znakem #), která popisuje co makro dělá, jaké definuje proměnné shellu, nebo volání AC_SUBST maker. Prostě vše, co je pro uživatele užitečné. A taky licenci :-).
Mezi dokumentací a definicí makra ještě může být řádek s číslem verze makra, (serial). Pokud aclocal najde dvě definice stejně pojmenovaného makra s různým číslem verze, vezme v potaz jen tu novější.
Zkrácená ukázka definice makra ax_lib_postgresql.m4 z autotools archive:
# https://www.gnu.org/software/autoconf-archive/ax_lib_postgresql.html
# ===========================================================================
#
# SYNOPSIS
#
# AX_LIB_POSTGRESQL([MINIMUM-VERSION])
#
# DESCRIPTION
#
# This macro provides ...
...
#
# LICENCE
#
...
#serial 15
AC_DEFUN([AX_LIB_POSTGRESQL],
[
...
])
Pojmenování maker
Jméno maker se skládají ze tří částí. Pvní je namespace, druhé uvádí kategorii vlastnosti, která je testována a třetí je název testované vlastnosti (feature).
Namespace (jmenný prostor) se používá k zabránění kolizí jmen maker od různých dodavatelů. Autoconf používá namespace AC_ (pro běžná makra) a AN_ (interně), autoupdate AU_ (pro definování deprecated maker), autoheader AH_ a automake AM_. Autoconf archive používá AX_. Těmto namespace se tedy vyhněte. Vymyslete si nějaké vlastní. Nemusí být dvoupísmenné.
Kategorie může být „co vás napadne“, nejčastěji s používá následující:
- C
- Kontrola vlastností jazyka C
- DECL
- Deklarace proměnných v hlavičkových souborech jazyka C
- FUNC
- Existence funkcí v knihovnách.
- GROUP
- Skupinoví vlastníci souborů (dle posix normy).
- HEADER
- Testuje hlavičkové soubory.
- LIB
- Testuje C knihovny
- PROG
- Testuje programy
- MEMBER
- Testuje členy struktur, union nebo tříd.
- SYS
- Vlastnosti operačního systému.
- TYPE
- Existence a vlastnosti typů v C
- VAR
- Proměnné jazyka C v knihovnách.
Název feature už je zcela na vás.
Příklad vlastního makra
Pokud si ještě vzpomínáte, tak v kaptiole o GDBM jsem měl trochu problém se zjištěním, jaká verze gdbm je na počítači nainstalována. K řešení tohoto problému jsem napsal následující skript:
# create-xdbm.sh
if [ -f "/usr/include/gdbm-ndbm.h" ]
then
echo "#include <gdbm-ndbm.h>" > xdbm.h
elif [ -f "/usr/include/gdbm/ndbm.h" ]
then
echo "#include <gdbm/ndbm.h>" > xdbm.h
else
if
[ ! -f "/usr/include/ndbm.h" ] &&
[ ! -f "/src/include/ndbm.h" ]
then
echo "Error: Nebyl nalezen soubor ndbm.h !"
echo " Pravdepodobne nemate nainstalovany vyvojovy balik knihovny gdbm"
echo " Nainstalujte, prosim, balik libgdbm-dev"
else
echo "#include <ndbm.h>" > xdbm.h
fi
fi
Tento skript vytvoří soubor xdbm.h, který includuje správnou verzi (n)dbm.h knihovny. Nebo vypíše chybovou hlášku, když žádnou (mě) známou verzi nenajde.
Tento skript bych s drobnou úpravou mohl vložit přímo do configure.ac a on by odvedl svou práci.
Tou úpravou by bylo nahrazení podmínek zjišťující existenci souboru
za přenositelnější verzi (místo verze s hranatými závorkamy,
verze s test
):
if [ -f "/usr/include/gdbm-ndbm.h" ];then
→
if test -f "/usr/include/gdbm-ndbm.h"; then
.
(Nejen že je přenositelnější, ale ani nemusím řešit problémy
se zápisem hranatých závorek.)
Za výpis chybového hlášení o nenalezení ndbm.h bych ještě přidal
exit
, aby se configure skript ukončil. A taky by stálo za to
používat AS_ECHO
namísto echo
.
Nevýhoda je, že takový skript musím psát a upravovat v každém projektu,
kde DBM používám. Tím dostává znovupoužitelnost na zadek. Proto vytvořím soubor
sallyx_header_dbm.m4
, kde definuji makro
SALLYX_HEADER_DBM
, které zjistí vše potřebné.
Soubor configure.ac
Nejdříve si vytvořím soubor configure.ac, pomocí kterého své nové makro budu
testovat. Makro bude produkovat nějaké definice, které se budou zapistovat do
config.h souboru, jenž nechám vygenerovat autoheader
em.
AC_INIT([dbmtest], [2.0], [sallyx@example.org], [],
[http://www.sallyx.org/sally/c/autotools/m4])
AC_CONFIG_MACRO_DIR([m4])
SALLYX_HEADER_DBM
AC_CONFIG_HEADERS([config.h])
AC_OUTPUT
Makro SALLYX_HEADER_DBM
zatím nemám definované, takže tento text
autoconf zapíše do configure skriptu jak je …
$ mkdir m4 $ autoreconf $ ./configure configure: loading site script /usr/share/site/x86_64-unknown-linux-gnu ./configure: line 1671: SALLYX_HEADER_DBM: command not found configure: creating ./config.status config.status: creating config.h
Mnou vytvořený soubor sallyx_header_dbm.m4 s makrem
SALLYX_HEADER_DBM
přijde do adresáře m4.
Hledání knihoven pomocí shellu
Na začátku souboru bude dokumentace:
# http://www.sallyx.org/sally/c/autoools/m4
#=============================================================
#
# SYNOPSIS
#
# SALLYX_HEADER_DBM([action-if-found],[action-if-not-found])
#
# DESCRIPTION
#
# This macro checks for [N|G]DBM header files.
#
# The following preprocessor symbols may be defined by this macro if the
# appropriate conditions are met:
# HAVE_GDBM - if any dbm header was found
# HAVE_GDBMNDBM_H - if gdbm-ndbm.h was found
# HAVE_GDBM_NDBM_H - if gdbm/ndbm.h was found
# HAVE_NDBM_H - if ndbm.h was found
#
...
V dokumentaci se dočtete co makro definuje a v jakém případě. Preprocessor
symbols je to, co se dostane do config.h a co se definuje pomocí makra
AC_DEFINE
.
Dále je v dokumentaci popsáno, jak má uživatel správně makra využít, licence a sériové číslo:
# To use the HAVE_xxx_H preprocessor symbols, insert the following into
# your system.h (or equivalent) header file:
#
# #if defined HAVE_GDBMNDBM_H
# # include <gdbm-ndbm.h>
# #elif defined HAVE_GDBM_NDBM_H
# # include <gdbm/ndbm.h>
# #elif defined HAVE_NDBM_H
# # include <ndbm.h>
# #else
# # error "[g|n]dbm header file required"
# #endif
#
#
# LICENCE
#
# do What The Fuck you want to Public License
#
# Version 1.0, March 2000
# Copyright (C) 2000 Banlu Kemiyatorn (d).
# 136 Nives 7 Jangwattana 14 Laksi Bangkok
# Everyone is permitted to copy and distribute verbatim copies
# of this license document, but changing it is not allowed.
#
# Ok, the purpose of this license is simple
# and you just
#
# DO WHAT THE FUCK YOU WANT TO.
# serial 2
...
Váš projekt by tedy měl obsahovat nějaký hlavičkový soubor s tímto obsahem:
# include <gdbm-ndbm.h>
#elif defined HAVE_GDBM_NDBM_H
# include <gdbm/ndbm.h>
#elif defined HAVE_NDBM_H
# include <ndbm.h>
#else
#error "[g|n]dbm header file required"
#endif
Makro error
vypíše chybu a přeruší překlad.
No a konečně vlastní definice makra:
AC_MSG_CHECKING([for dbm])
AS_IF([test -f "/usr/include/gdbm-ndbm.h"],[
sallyx_header_dbm=1
AC_DEFINE([HAVE_GDBM], [1], [Define to 1 if any [n|g]dbm header found])
AC_DEFINE([HAVE_GDBMNDBM_H], [1], [Define to 1 if gdbm-ndbm.h found])
]);
AS_IF([test -f "/usr/include/gdbm/ndbm.h"],[
sallyx_header_dbm=1
AC_DEFINE([HAVE_GDBM], [1], [Define to 1 if any [n|g]dbm header found])
AC_DEFINE([HAVE_GDBM_NDBM_H], [1], [Define to 1 if gdbm/ndbm.h found])
]);
AS_IF([test -f "/usr/include/ndbm.h" || test -f "/src/include/ndbm.h"],[
sallyx_header_dbm=1
AC_DEFINE([HAVE_GDBM], [1], [Define to 1 if any [n|g]dbm header found])
AC_DEFINE([HAVE_NDBM_H], [1], [Define to 1 if ndbm.h found])
]);
AS_IF([test "x$sallyx_header_dbm" == "x"], [AC_MSG_RESULT([no])],[AC_MSG_RESULT([yes])])
AS_IF([test "x$sallyx_header_dbm" == "x"], [$2], [$1])
])
Na začátku těla makra používám makro AC_MSG_CHECKING
k vypsání
checking for dbm ... a na konci makro AC_MSG_RESULT
k
vypsání výsledku. Použití těchto maker je důležité pro zachování funkce
přepínače -q
skriptu configure.
Dále jsou v těle skritpu tři podmínky testující existenci souborů.
Pokud je podmínka splněna (soubor existuje), definuje se proměnná shellu
$sallyx_header_dbm a definují se příslušné symboly preprocessoru.
Všiměte si, že HAVE_GDBM
může být teoreticky definováno 3x.
To ale nevadí, poslední definice „vyhraje“
(krom toho definuji vždy stejnou hodnotu – 1).
Proměnnou $sallyx_header_dbm využívám v posledních dvou testech v
těle makra. Ale tato proměnná bude existovat i v skriptu configure (po
zavolání SALLYX_HEADER_DBM
). Protože jsem ji ale nezadokumentoval,
uživatelé mého makra by ji měli ignorovat. Měli by předpokládat, že v nějaké
budoucí verzi už nebude existovat, nebo bude obsahovat jiné hodnoty …
Poslední test pošle do výstupu $1 nebo $2, tedy jeden z (nepovinných) argumentů
makra SALLYX_HEADER_DBM
. Pokud budu chtít přerušit configure
skript s chybou v případě nenalezení ani jedné verze dbm, zavolám
SALLYX_HEADER_DBM
v configure.ac takto:
V případě nalezení nějaké dbm knihovny nechci dělat žádnou akci, tak
nechám první argument prázdný. V opačném případě zavolám makro
AC_MSG_ERROR
, které se postará o vypsání chyby a ukončení
skritpu:
$ autoreconf $ ./configure configure: loading site script /usr/share/site/x86_64-unknown-linux-gnu checking for dbm... no configure: error: Please install (g|n)dbm devel package.
Kromě AC_MSG_ERROR existuje i makro AC_MSG_NOTICE, které jen vypíše hlášku, ale configurační skript neukončí.
Takto se makra chovají běžně. Jen zjistí co mají a definují různé proměnné shellu, makra preprocessoru, nebo proměnné pro automake. Pokud chcete, aby se configure z nějakého důvodu ukončil, musíte o to požádat v configure.ac. To dává smysl. Můj program by mohl jít přeložit i bez dbm, jen by za pomoci maker preprocessoru byla zakomentována tu část kódu, která dbm potřebuje. (Místo ní bych například odkomentoval kód, který bude data ukládat do postgresu …)
Hledání knihoven pomocí překladače
Předchozí definice makra SALLYX_HEADER_DBM
má jednu podstatnou
chybu: hlavičkové subory hledá v konkrétních adresářích. V /usr/include/ a v
jednom případě v /src/include. Ač to nejspíš v 98% případů bude správné místo,
najdou se výjimky.
Nic jiného vám nedokáže přesněji odpovědět na otázku, zda to či ono
bude pro překladač dostupné nebo ne, než samotný překladač. Proto autotools
poskytuje makro AC_LINK_IFELSE
.
Autotools toto makro často využívá v makrech zjišťující podporu toho či onoho, a tak to udělám i já.
Pro vytvoření zdrojového kódu můžete využít makro AC_LANG_PROGRAM
.
Toto makro očekává jako první argument to, co se ve zdrojáku objeví před tělem
funkce main()
a jako druhý argument to, co bude v těle
main()
.
V příkladu si všiměte quadrigraphu @%:@ nahrazující speciální znak
#
:
@%:@include <ndbm.h>
], [
dbm_open("/tmp/test.db", 0, 0);
])
Tento program se nepodaří sestavit, pokud se nenalezne ve standardních
adresářích knihovna ndbm.h
, nebo pokud nedefinuje funkci
dbm_open()
. (Protože se program pouze linkuje, ale nespouští,
nemusíte se bát o existenci, čitelnost či formát /tmp/test.db
.)
S makrem AC_LINK_IFELSE
pak testování existence ndbm.h vypadá
takto:
@%:@include <ndbm.h>
]], [[
dbm_open("/tmp/test.db", 0, 0);
]])],
[sallyx_cv_header_ndbm_h=yes],
[sallyx_cv_header_ndbm_h=no]
)
Překlad programu zabere nějaký čas, proto je dobré výsledek takového testu
cachovat. K tomu slouží makro AC_CACHE_CHECK
. Prvním agumentem je
text, který se vypíše při spuštění configure skriptu, druhým nějaký univerzální
identifikátor testu – název proměnné, která se nastavuje v akci třetího
argumentu:
AC_LINK_IFELSE([AC_LANG_PROGRAM([[
@%:@include <ndbm.h>
]], [[
dbm_open("/tmp/test.db", 0, 0);
]])],
[sallyx_cv_header_ndbm_h=yes],
[sallyx_cv_header_ndbm_h=no])
])
Po tomto testu se už jen stačí podívat na výsledek a podle něj nastavit, co je potřeba:
sallyx_header_dbm=1
AC_DEFINE([HAVE_GDBM], [1], [Define to 1 if any [n|g]dbm header found])
AC_DEFINE([HAVE_NDBM_H], [1], [Define to 1 if ndbm.h found])
])
K úspěšnému překladu (g|n)dbm knihovny je poskytnout předat překladači
nějaké přepínče s názvy statických knihoven
(-lgdbm -lgdbm_compat
). Jak AC_LINK_IFELSE tyto knihovny zjistí?
Nezjistí. Musíte zavolat makro AC_CHECK_LIB
, které zjišťuje
přítomnost knihovny a případně přidá -llibrary
do
makra preprocesoru LIBS
.
Makro AC_CHECK_LIB
bych mohl zavolat uvnitř svého makra. Ale
volby v LIBS
se budou využívat i s automake při generování
Makefile, takže raději jen přidám do dokumentace makra tento komentář:
# DESCRIPTION
#
# This macro checks for [N|G]DBM header files.
# It must be run after:
# AC_CHECK_LIB([gdbm], [main])
# AC_CHECK_LIB([gdbm_compat], [main])
...
Druhým argumentem makra je název funkce, která se v knihovně hledá. Pokud vás
žádná konkrétní funkce nezajímá, můžete použít main
.
Do makra SALLYX_HEADER_DBM
dám předchozí kód pro všechny tři
testované knihovny. Poslední test spustí druhý nebo třetí argument makra
SALLYX_HEADER_DBM
podle výsledku testů.
# serial 3
AC_DEFUN([SALLYX_HEADER_DBM],[
AC_CACHE_CHECK([for working gdbm-ndbm.h], [sallyx_cv_header_gdbmndbm_h], [
AC_LINK_IFELSE([AC_LANG_PROGRAM([[
@%:@include <gdbm-ndbm.h>
]], [[
dbm_open("/tmp/test.db", 0, 0);
]])],
[sallyx_cv_header_gdbmndbm_h=yes],
[sallyx_cv_header_gdbmndbm_h=no])
])
AS_IF([test "x$sallyx_cv_header_gdbmndbm_h" = xyes], [
sallyx_header_dbm=1
AC_DEFINE([HAVE_GDBM], [1], [Define to 1 if any [n|g]dbm header found])
AC_DEFINE([HAVE_GDBMNDBM_H], [1], [Define to 1 if gdbm-ndbm.h found])
])
AC_CACHE_CHECK([for working gdbm/ndbm.h], [sallyx_cv_header_gdbm_ndbm_h], [
AC_LINK_IFELSE([AC_LANG_PROGRAM([[
@%:@include <gdbm/ndbm.h>
]], [[
dbm_open("/tmp/test.db", 0, 0);
]])],
[sallyx_cv_header_gdbm_ndbm_h=yes],
[sallyx_cv_header_gdbm_ndbm_h=no])
])
AS_IF([test "x$sallyx_cv_header_gdbm_ndbm_h" = xyes], [
sallyx_header_dbm=1
AC_DEFINE([HAVE_GDBM], [1], [Define to 1 if any [n|g]dbm header found])
AC_DEFINE([HAVE_GDBM_NDBM_H], [1], [Define to 1 if gdbm/ndbm.h found])
])
AC_CACHE_CHECK([for working ndbm.h], [sallyx_cv_header_ndbm_h], [
AC_LINK_IFELSE([AC_LANG_PROGRAM([[
@%:@include <ndbm.h>
]], [[
dbm_open("/tmp/test.db", 0, 0);
]])],
[sallyx_cv_header_ndbm_h=yes],
[sallyx_cv_header_ndbm_h=no])
])
AS_IF([test "x$sallyx_cv_header_ndbm_h" = xyes], [
sallyx_header_dbm=1
AC_DEFINE([HAVE_GDBM], [1], [Define to 1 if any [n|g]dbm header found])
AC_DEFINE([HAVE_NDBM_H], [1], [Define to 1 if ndbm.h found])
])
AS_IF([test "x$sallyx_header_dbm" == "x"], [$2], [$1])
])
Příslušný configure.ac soubor vypadá takto:
AC_INIT([dbmtest], [3.0], [sallyx@example.org], [], [http://www.sallyx.org/sally/c/autotools/m4])
AC_CONFIG_MACRO_DIR([m4])
AC_CHECK_LIB([gdbm], [main])
AC_CHECK_LIB([gdbm_compat], [dbm_open])
SALLYX_HEADER_DBM([], [AC_MSG_ERROR([Please install (g|n)dbm devel package.])])
AC_CONFIG_HEADERS([config.h])
AC_OUTPUT
Makro AC_CHECK_LIB
zase ke svému běhu potřebuje překladač,
takže volá makro AC_PROG_CC
atp. Díky tomu při spuštění
configure skriput uvidíte ve výstupu checking for gcc... gcc,
checking whether the C compiler works... yes atp.
Závěr
V této kapitole jste měli možnost proniknout do tajů jazyka m4, dozvěděli jste se něco o m4sh a quadrigrapheh a nakonec jste viděli, jak se tvoří autotools makra. Ne že byste to nějak často potřebovali, ale snad vám to pomohlo pochopit fungování autotools maker.