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:

dnl 04m4/example1.m4

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'

``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_dnl 04m4/example2.m4
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_dnl 04m4/example3.m4
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:

dnl 04m4/example4.m4
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í).

dnl 04m4/example5.m4
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:

#!/bin/bash
# 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" ];thenif 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 autoheaderem.

# 04m4/dbm2/configure.ac
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:

#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

Makro error vypíše chybu a přeruší překlad.

No a konečně vlastní definice makra:

AC_DEFUN([SALLYX_HEADER_DBM],[
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:

SALLYX_HEADER_DBM([], [AC_MSG_ERROR([Please install (g|n)dbm devel package.])])

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.

AC_LINK_IFELSE([zdrojovy kod], [akce pri uspechu], [akce pri chybe])

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 #:

AC_LANG_PROGRAM([
        @%:@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:

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]
)

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_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])
])

Po tomto testu se už jen stačí podívat na výsledek a podle něj nastavit, co je potřeba:

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])
])

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:

# 04m4/dbm3/configure.ac
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.

Komentář Hlášení chyby
Vytvořeno: 24.12.2018
Naposledy upraveno: 24.12.2018
Tato stánka používá ke svému běhu cookies, díky kterým je možné monitorovat, co tu provádíte (ne že bych to bez cookies nezvládl). Také vás tu bude špehovat google analytics. Jestli si myslíte, že je to problém, vypněte si cookies ve vašem prohlížeči, nebo odejděte a už se nevracejte :-). Prohlížením tohoto webu souhlasíte s používáním cookies. Dozvědět se více..