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:

  1. #include <math.h>
  2. #include <stdio.h>
  3. #include <stdlib.h>
  4. #include "config.h"
  5. #include "xstr.h"
  6.  
  7. #ifndef HAVE_DOUBLE_T
  8. typedef double_t double;
  9. #endif
  10.  
  11. int main(int argc, char *argv[]) {
  12.         if(argc != 3) {
  13.                 fprintf(stderr, "usage: %s n m \n", argv[0]);
  14.                 size_t size = sizeof(double_t);
  15.                 printf("datadir = %s\n",xstr(PKGDATADIR));
  16.                 printf("configure options = %s\n", CONF_OPT);
  17.                 printf("size of double_t = %lld\n", (long long) size);
  18.                 return 1;
  19.         }
  20.  
  21.         double_t number1 = (double_t) atof(argv[1]);
  22.         double_t number2 = (double_t) atof(argv[2]);
  23.         printf("%lf %s %lf\n", (double) number1, isgreater(number1, number2) ? ">" : "<=", (double) number2);
  24.         return 0;
  25. }

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:

printf("datadir = %s\n",/usr/lib/xxxx);

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:

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

#                                               -*- Autoconf -*-
# 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_INIT([example1], [0.1], [sallyx@example.org])

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_SRCDIR([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/:

AC_CONFIG_HEADERS([src/config.h])

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:

AC_DEFINE([RELEASE_YEAR],["2018"],[Define the year of release])

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:

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

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:

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

AC_CHECK_PROGS(hashprog, [sha512sum sha256sum sha224sum sha1sum shasum md5sum],[cat])

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:

AC_CHECK_HEADERS([locale.h stdlib.h unistd.h], [],[AC_MSG_ERROR([Unable to find the header.])])

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:

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

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

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.

AC_CHECK_FUNCS([atexit setlocale],[],[AC_MSG_ERROR([Unable to find function atexit or setlocale.])])

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

#                                               -*- Autoconf -*-
# 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 configure options */
#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.

Komentář Hlášení chyby
Created: 24.12.2018
Last updated: 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..