FreshSourcing. Čerstvé nápady. Kreatívne riešenia.

» Domov » Auto Tools » 11. časť

11. časť: Knižnice po druhé

Znovu sa pristavíme pri tvorbe knižníc, konkrétne si vysvetlíme, ako funguje číslovanie verzií v prostredí programu libtool.

Program libtool môžeme spustiť s viacerými parametrami (viď libtool --help, info libtool). Pravdepodobne však tento program nebudeme spúšťať sami, ale necháme to na make. Ako však potom môžeme spustiť libtool s rôznymi parametrami? Riešenie je celkom jednoduché. V súbore Makefile.am v podadresári so zdrojovými súbormi našej knižnice pribudne riadok podobný tomuto:

kniznica_la_LDFLAGS = --parameter1 --parameter2 ...

Takýto riadok, samozrejme, môžeme v prípade potreby pridať pre každú jednu knižnicu. V helpe si všimnite parameter --mode. Týmto parametrom prikazujete libtool-u, v ktorom móde má pracovať. V jednotlivých módoch program kompiluje, inštaluje, odinštaluje, linkuje a spúšťa programy (knižnice). Každý z týchto módov má svoje vlastné parametre, ktoré si môžete nechať zobraziť zadaním

libtool --help --mode=...

Nás teraz bude zaujímať linkovací mód, konkrétne parameter --version-info. Tento a ostatné parametre (niektoré z nich si takisto vysvetlíme) môžete uložiť do spomínanej premennej ..._la_LDFLAGS.

-release

Parametrom -release N určujeme poradové číslo (release) vašej knižnice. Tento parameter sa odrazí aj v názve výstupného súboru (samotnej knižnice). V našom príade by nám vznikol napríklad súbor libhello-N.so.0.0.0 (N je, samozrejme, kladné celé číslo). Nevýhoda takéhoto označovania verzií je dosť závažná - používanie tohto parametra spôsobí binárnu nekompatibilitu jednotlivých verzií knižníc. Napríklad: v našej knižnici sme našli vo funkcii hello() chybu, tým pádom sa chybne správa aj náš program hello, ktorý ju používa. Normálne by nám stačilo prekompilovať knižnicu a náš program by sa už správal (bez akejkoľvek zmeny v ňom samotnom) správne. Pri použití parametra -release to však nestačí. Pre porušenú kompatibilitu na binárnej úrovni musíme prekompilovať (resp. znovu zlinkovať s novou knižnicou) všetky programy, ktoré používali našu pôvodnú (neopravenú) knižnicu. Ako vidíte, tento spôsob nie je celkom ideálny - preto si ukážeme iný spôsob označovania verzií.

-version-info

Týmto parametrom nastavujeme verziu našej knižnice. Je veľmi dôležité uvedomiť si, že toto označenie nemá nič spoločné s verziou nášho balíčka! Je to informácia používaná libtoolom na označovanie kompatibility jednotlivých verzií knižníc. Používa sa takto:

-version-info current:revision:age

Význam parametrov current, revision a age si hneď vysvetlíme. Všetky tri sú kladné celé čísla a z nich sa vypočítajú všetky tri čísla v názve súboru knižnice (libhello.so.X.Y.Z). Teda ani tieto tri čísla (X, Y a Z) nemajú nič spoločné s oficiálnym číslom verzie (v našom prípade hello, world 2.0). Ak by sme sa trošku pohrali s jednotlivými číslami (teraz mám na mysli current, revision a age), prišli by sme na to, že s trochou šikovnosti by sme dokázali nájsť také tri čísla current, release, age, z ktorých by libtool vypočítal X, Y, Z rovnaké ako skutočné číslo nášho programu (2.0.0 - ak by sme používali tri číslice a nie dve, ako tomu bolo doteraz). To by však celá filozofia číslovania verzií, ktorú používa libtool stratila zmysel - preto sa o to nesnažme. Prinajlepšom porušíme binárnu kompatibilitu aj pri najmenších zmenách knižnice - a to by sme asi nechceli. Radšej si teda vysvetlíme význam spomínaných troch čísel current, release a age a ukážeme si, ako ich správne nastavovať a meniť. Predtým si však ešte povieme niečo o slovíčku interface.

Interface v "knižničnom" prostredí sa chápe ako súhrn globálnych premenných (názvy a ich typy), globálnych funkcií (počet a typ argumentov, návratové typy, názov funkcie), štandardný vstup, výstup, chybový výstup, formát súborov, sockety, rúry a pod. Statické funkcie sa nerátajú, pretože užívateľ ich nemôže priamo používať.

Teraz sa môžeme pustiť do vysvetlenia jednotlivých častí čísla označujúceho verziu knižnice.

current - poradové číslo interface. Ak sa zmení interface, toto číslo musíme zvýšiť. Začíname číslovať od nuly (toto platí aj pre ďalšie dve čísla).

revision - poradie implementácie najnovšieho interface. Napríklad revision 0 znamená, že ide o prvú implementáciu tohto interface. Ak sa v ďalšej verzii našej knižnice nezmení interface (stále ponúkamé rozvaké rozhranie, tie isté funkcie...), ale zmeníme jeho implementáciu (napríklad v nejakej funkcii nájdeme chybu a nám stačí opraviť iba jej "vnútornosti", a nie spôsob jej volania, jej návratovú hodnotu a pod.), inkrementujeme revision, ale current necháme nezmenené. Ak máme nainštalované viaceré verzie tej istej knižnice a current je rovnaké, pri natiahnutí knižnice do pamäte sa vyberie tá s najvyšším číslom revision.

age - Počet, udávajúci, koľko predchádzajúcich verzií interface je kompatibilných s tým najnovším. Toto číslo nesmie byť väčšie ako current. Ak by bol current napríklad 5 a age 2, potom by sme s touto knižnicou mohli spustiť aj programy, ktoré boli linkované s interface 3, 4 aj 5.

Tieto tri čísla musíme meniť v podstate pri každom release knižnice, a treba to robiť opatrne a dôsledne. Stačí, ak si dobre uvedomíme, čo znamenajú jednotlivé čísla a ak sa budeme riadiť nasledujúcimi pravidlami:

- Vždy, keď zmeníme zdrojové kódy knižnice, inkrementujeme revision. Teda ide o novú revíziu aktuálneho interface (došlo iba k interným zmenám).

- Ak zmeníme interface (napríklad pridáme argument do našej funkciu hello()), inkrementujeme current a revision nastavíme na nulu. Ide o prvú revíziu nového interface.

- Ak je nový interface rozšírením predchádzajúceho, ale všetky funkcie z predchádzajúceho interface ostali nezmenené (čo sa týka interface, nie nutne aj implementácie), inkrementujeme aj age. Nový interface je spätne kompatibilný s tým predchádzajúcim.

- Ak v novom interface odstránime funkciu, ktorá sa nachádzala v predchádzajúcom interface, porušíme tým spätnú kompatibilitu a age musíme vynulovať. Teda máme nový, avšak spätne nekompatibilný interface.

Prvý release vôbec má označenie 0:0:0. Tu sú zdrojové súbory našej knižnice:

hello.h:

#include <stdio.h>

void hello(char* name);
hello.c:

#include <hello.h>

void hello(char* name)
{
        printf("hello, %s", name);
}

Funkcia síce robí to, čo má, avšak v záujme estetickosti výpisu na koniec vypisovaného reťazca pridáme znak prechodu na nový riadok \n. Bohužiaľ, absenciu tohto znaku sme si všimli až po prvom release. Jeho pridanie je z nášho pohľadu dosť dôležitá udalosť na to, aby sme spravili nový release... Inkrementujeme teda revision a dostaneme označenie verzie: 0:1:0. Medzitým, samozrejme, zmeníme aj súbor configure.in, ktorý vyzerá takto:

dnl Process this file with autoconf to produce a configure script.

AC_INIT(src/Makefile.am)
AM_INIT_AUTOMAKE(hello, 2.0)

AC_PREFIX_DEFAULT(/usr/local)
AM_CONFIG_HEADER(config.h)

AC_PROG_INSTALL
AC_PROG_LN_S
AM_PROG_LIBTOOL

AC_OUTPUT(Makefile src/Makefile test/Makefile doc/Makefile)

Zmena sa týka druhého riadka, ktorý definuje názov balíka a jeho verziu. To, ako budeme meniť tento riadok, je iba na nás. Záleží to od mnohých vecí; okrem iných aj od toho, či sme sa rozhodli používať dve, tri alebo štyri čísla. Ako sme si už povedali, nemá to na označovanie verzií, ktoré používa libtool, žiaden vplyv. Dalo by sa povedať, že libtool verzia slúži systému, ale verzia na tom druhom riadku slúži užívateľom pre ľahšiu orientáciu.

Neskôr sme sa rozhodli znovu vylepšiť našu knižnicu a znovu to bola iba interná zmena - a tak sme sa dopracovali ku verzii 0:2:0. Potom sme pridali funkciu hi(), takže tu máme nové súbory:

hello.h:

#include <stdio.h>

void hello(char*);
void hi(char*);
hello.c:

#include <hello.h>

void hello(char* name)
{
        printf("Hello, %s!\n", name);
}

void hi(char* name)
{
        printf("Hi, %s!\n", name);
}

Pridanie novej funkcie však nijako neovplyvnilo funkčnosť funkcie hello() - nový interface je spätne kompatibilný s tým predchádzajúcim. Preto vynulujeme revision (máme nový interface) a inkrementujeme current a age (pre neporušenú kompatibilitu). Takže dostávame verziu 1:0:1. Neskôr sme sa však rozhodli pridať prvej funkcii parameter type, od ktorého závisí, ako bude vyzerať výsledný pozdrav:

hello.h:

#include <stdio.h>

void hello(char*, int);
void hi(char*);
hello.c:

#include <hello.h>

void hello(char* name, int type)
{
        if (type == 1)
                printf("hello, %s\n", name);
        else if (type == 2)
                printf("hello, %s!\n", name);
        else if (type == 3)
                printf("Hello, %s\n", name);
        else
                printf("Hello, %s!\n", name);
}

void hi(char* name)
{
        printf("Hi, %s!\n", name);
}

Sami vidíte, že funkciu hello() musíme volať trošku ináč, a preto sme dostali nekompatibilnú verziu. Vynulujeme teda age a revision a inkrementujeme current - dostali sme sa k verzii 2:0:0, ktorú, bohužiaľ, už nemôžeme použiť pre programy, ktoré používali predchádzajúce verzie našej knižnice. Ukážme si ešte výsledný Makefile.am:

## Process this file with automake to produce Makefile.in

lib_LTLIBRARIES = libhello.la

libhello_la_SOURCES = hello.c
libhello_la_LDFLAGS = -version-info 2:0:0

include_HEADERS = hello.h

DISTCLEANFILES = ./.deps/* ./.deps/.P

Pri zmene interface nehľadíme iba na to, či sme zmenili API (teda tých pár funkcií, ktoré môže nejaký program, využívajúci našu knižnicu, používať). Musíme si skontrolovať aj to, či sme nezmenili nejaký formát súboru, sieťový protokol, a všetko, s čím naša knižnica pracuje.

Na záver tejto témy si ešte uvedieme príklad súčasného použitia parametrov -version-info a -release. Predstavte si, že vaša knižnica sa utešene rozrastá a dostala sa do štádia, keď by ju bolo dobré rozdeliť na stabilnú a nestabilnú verziu. Vy však nechcete, aby si užívatelia museli svoje programy prelinkovať s vašou knižnicou vždy, keď vydáte nový release. V tomto prípade použijeme -release na označenie verzie priamo v samotnom názve suboru knižnice. Pre prvý release stabilnej vývojovej vetvy použijeme -release 1.0, pre nestabilnú vetvu to bude -release 1.1. Takto si zároveň zabezpečíte binárnu nekompatibilitu medzi týmito vetvami a užívatelia si nebudú môcť vyskúšať kombinovať napríklad program založený na stabilnej verzii knižnice s nestabilnou knižnicou a naopak a nespôsobia si napríklad možný pád programu.

CFLAGS

Na začiatku tejto časti sme si spomínali parametre programu libtool, ktoré sa použijú v móde linkovania. Takisto sme si spomenuli, že existuje viacero iných módov. Nás teraz bude zaujímať kompilovací mód. Všetko, čo priradíme premennej ..._la_LDFLAGS, sa použije ako parameter pri linkovaní. Predstavte si však, že by ste chceli predať nejaké parametre kompilátoru, a nie linkeru. Na to použijeme premennú CFLAGS, tentoraz to bude bez akéhokoľvek prefixu, pretože táto premenná je spoločné pre všetky súbory a programy, ktoré má daný súbor Makefile.am.

Všetky parametre, ktoré môžeme v kompilovacom móde programu libtool použiť, si zobrazíme spustením libtool --help --mode=compile. Nie je ich až tak veľa , zaujímavý je iba jeden z nich: -static. Tento parameter prinúti libtool, aby vytvoril iba statickú knižnicu - štandardne sa totiž vytvára statická aj dynamická knižnica. O rozdieloch medzi nimi sme si už čo-to povedali minule. Na záver si dáme dokopy to, čo sme sa dozvedeli v predchádzajúcom aj tomto odseku a vytvoríme nový súbor Makefile.am, ktorý nám vytvorí iba statickú knižnicu, ktorú navyše zoptimalizuje prepínačom prekladača -O3.

## Process this file with automake to produce Makefile.in

lib_LTLIBRARIES = libhello.la

libhello_la_SOURCES = hello.c
libhello_la_LDFLAGS = -version-info 2:0:0

CFLAGS = -O3 -static
include_HEADERS = hello.h

DISTCLEANFILES = ./.deps/* ./.deps/.P


Oto Komiňák


Článok bol uverejnený v magazíne PC Revue 12/2002.