Havoc Pennington
hp@pobox.com 2002
Philip Withnall
philip.withnall@collabora.co.uk 2015
Jak psát knihovny, aby v budoucnu šly instalovat souběžně různé verze

# Souběžná instalace

#

        Shrnutí

        Pokud mohou být dva balíčky nainstalovány souběžně, nesmí mít společné názvy souborů a lidé provádějící vývoj vůči balíčku vždy kompilují vůči očekávané verzi. Pro démony, pomocné programy a konfigurační soubory to platí úplně stejně, jako pro hlavičkové soubory a binární knihovny.

          Zajistěte, aby všechny verze knihoven šly nainstalovat vedle sebe. ()
          Všem souborům instalovaným knihovnou přidělte číslo verze. ()
          Udržujte číslo verze balíčku oddělené od názvu souboru s knihovnou nebo čísla verze libtool. Je pak jasnější, která část čísla verze balíčku se mění s API. ()
          Nainstalujte hlavičkové soubory C do $(includedir)/liblibrary-version/library/. ()
          Nainstalujte spustitelné soubory knihovny do $(libdir)/liblibrary-version.so.soname. ()
          Nainstalujte soubory pkg-config do $(libdir)/pkgconfig/library-version.pc. ()
          U souborů s nastavením zajistěte zpětnou a dopřednou kompatibilitu nebo je instalujte do $(sysconfdir)/knihovna-verze/. ()
          Nastavte GETTEXT_PACKAGE na library-version. ()
          Do všech názvů rozhraní D-Bus, názvů služeb a cest k objektům zahrňte číslo verze. Například: org.domain.KnihovnaVerze.Rozhraní, org.domain.KnihovnaVerze a /org/domain/KnihovnaVerze/. ()
          Nainstalujte spustitelné soubory démonů do $(libexecdir)/library-daemon-version. ()
          Spustitelné soubory pomůcek instalujte do $(bindir)/knihovna-utility-verze a symbolické odkazy do $(bindir)/knihovna-utility. ()

# Odůvodnění

Všechny veřejné knihovny by měly být navržené tak, aby je šlo instalovat souběžně ve více verzích, kvůli jednoduššímu pozdějšímu porušení API po dobu existence knihovny. Pokud je knihovna používána více projekty a chcete porušit API, buď musí projekty naráz přejít na nové API, nebo některé z nich již nebude nadále možné nainstalovat souběžně s ostatními, protože závisí na konfliktní verzi této knihovny.

To komplikuje správu a žádat pro všech projektech, aby naráz přesly na novou verzi API je organizačně náročné a demoralizující, protože většina porušení API nepřinese mnoho nových funkcí, které by motivovaly k přechodu.

Řešením je zajistit, aby šly všechny verze knihoven nainstalovat naráz, takže staré i nové verze API budou nainstalované a aplikace zkompilované vůči nim souběžně, bez konfliktů. Vestavění podpory pro tento druh souběžné instalace je mnohem jednodušší udělat na začátku projektu, než to dělat zpětně.

Tím se odstraní problém „slepice nebo vejce“ při přechodu celé sady aplikací z jedné verze knihovny na novější a pro správce knihovny si tím velmi zjednoduší porušení API, takže mohou při vývoji mnohem rychleji reagovat na přání nových funkcí.

Jiným a rovněž platným řešením pro knihovny je, nikdy nenarušovat API — tímto se řídí `libc`.

# Řešení

Řešením problému v podstatě je přejmenovat knihovnu a ve většině případů je nejhezčím způsobem, jak to udělat, vložit do cesty ke každému instalovanému souboru číslo verze. Díky tomu může být naráz nainstalováno více verzí knihovny.

Kupříkladu řekněme, že knihovna `Foo` tradičně instaluje tyto soubory:

/usr/include/foo.h

/usr/include/foo-utils.h

/usr/lib/libfoo.so

/usr/lib/pkgconfig/foo.pc

/usr/share/doc/foo/foo-manual.txt

/usr/bin/foo-utility

Můžete změnit verzi 4 knihovny `Foo`, aby místo toho instalovala tyto soubory:

/usr/include/foo-4/foo/foo.h

/usr/include/foo-4/foo/utils.h

/usr/lib/libfoo-4.so

/usr/lib/pkgconfig/foo-4.pc

/usr/share/doc/foo-4/foo-manual.txt

/usr/bin/foo-utility-4

Pak by mohly být nainstalovány souběžně s verzí 5:

/usr/include/foo-5/foo/foo.h

/usr/include/foo-5/foo/utils.h

/usr/lib/libfoo-5.so

/usr/lib/pkgconfig/foo-5.pc

/usr/share/doc/foo-5/foo-manual.txt

/usr/bin/foo-utility-5

To je jednoduše podporováno pomocí pkg-config: foo-4.pc by přidal do cesty /usr/include/foo-4 a do seznamu knihoven odkaz libfoo-4.so; foo-5.pc by přidal /usr/include/foo-5 a libfoo-5.so.

# Čísla verzí

Číslo verze které se zahrne do názvu souboru je veze ABI/API. Nemělo by jít o celé číslo verze balíčku, jen o část, která významěmně porušuje API. Pokud používáte pro číslování verzí projektu standartní schéma `hlavní.vedlejší.setinkové`, je verzí API typicky hlavní číslo verze.

Vedlejší vydání (typicky, když je přidáno API, ale není změněno nebo odebráno) a setinková vydání (typicky opravy chyb) neovlivňují zpětnou kompatibilitu API, takže nevyžadují přesun všech souborů.

Příklady v následujícím oddíle předpokládají, že verze API a název souboru s knihovnou jsou exportovány z configure.ac pomocí následujícího kódu:

## Číslování verzí API v Autoconf

Kód pro export verze API a názvu souboru s knihovnou z configure.ac
# Před vydáním by se měl upravit řetězec KNIHOVNA_LT_VERSION, který # je ve formě a:r:v (aktuální:revize:věk). Postupujte podle následujících bodů: # # 1. Když od poslední aktualizace došlo ke změně zdrojového kódu knihovny # jako celku, zvyšte revizi („a:r:v“ se změní na „a:r+1:v“). # 2. Když bylo od poslední aktualizace přidáno, odebráno nebo změněno # rozhraní, zvyšte aktuální a revizi nastavte na 0. # 3. Když bylo od posledního veřejného vydání přidáno rozhraní, zvyšte věk. # 4. Když bylo od posledního veřejného vydání odebráno nebo změněno # rozhraní, nastavte věk na 0. AC_SUBST([KNIHOVNA_LT_VERSION],[1:0:0])

AC_SUBST([KNIHOVNA_API_VERSION],[4])

# Hlavičkové soubory C

Hlavičkové soubory by vždy měly být nainstalovány v podsložce s číslem verze, kterou požaduje přepínač -I pro kompilátor jazyka C. Například, když je hlavičkový soubor foo.h a aplikace udělá následující:

#include <foo/foo.h>

pak by měly být nainstalovány tyto soubory:

/usr/include/foo-4/foo/foo.h

/usr/include/foo-5/foo/foo.h

Aplikace by měla kompilátoru jazyka C předat přepínač -I/usr/include/foo-4 nebo -I/usr/include/foo-5. Jak již bylo několikrát řečeno, to vše vám může usnadnit pkg-config.

Všimněte si zvláštní podsložky foo/. Funguje jako jmenný prostor pro `#include`, aby se předešlo kolizi názvů s jinými knihovnami. Když například dvě různé knihovny nainstalují každá hlavičkový soubor s názvem utils.h, který se ve skutečnosti vloží, když použijete kód `#include <utils.h>`?

Existuje pokušení ponechat jeden z hlavičkových souborů mimo kteroukoliv podsložku:

/usr/include/foo.h

/usr/include/foo-5/foo.h

Problém je v tom, že uživatel určitě vždycky omylem použije nesprávnou hlavičku, protože -I/usr/include si při kompilaci vždy najde nějakou cestičku do příkazového řádku. Pokud to musíte udělat, minimálně do knihovny přidejte kontrolu, která při inicializaci knihovny objeví aplikaci používající nesprávný hlavičkový soubor.

Hlavičkové soubory s číslem verze mohou být instalovány z automake pomocí následujícího kódu:

## Hlavičkové soubory v Automake

Kód pro instalaci hlavičkových souborů s číslem verze z Makefile.am
library
includedir = $(includedir)/liblibrary-@LIBRARY_API_VERSION@/library library_headers = \ liblibrary/example1.h \ liblibrary/example2.h \ $(NULL)

# The following headers are private, and shouldn't be installed: private_headers = \ liblibrary/example-private.h \ $(NULL) # The main header simply #includes all other public headers: main_header = liblibrary/library.h public_headers = \ $(main_header) \ $(library_headers) \ $(NULL)

library
include_HEADERS = $(public_headers)

Kromě správného číslování verzí by měla mít všechna API v instalovaných hlavičkových souborech správný jmenný prostor.

# Knihovny

Objektové soubory knihovny by měly mít názvy s číslem verze. Například:

/usr/lib/libfoo-4.so

/usr/lib/libfoo-5.so

To umožňuje aplikacím získat při kompilaci právě tu, kterou chtějí a zajistí to, že verze 4 a 5 nemají žádné soubory společné.

Knihovny s číslem verze mohou být sestaveny a nainstalovány z automake pomocí následujícího kódu:

## Knihovny v Automake

Kód pro sestavení a instalaci knihoven s číslem verze z Makefile.am
lib_LTLIBRARIES = libknihovna/libknihovna-@KNIHOVNA_API_VERSION@.la

libknihovna_libknihovna_@KNIHOVNA_API_VERSION@_la_SOURCES = \ $(private_headers) \ $(knihovna_sources) \ $(NULL) libknihovna_libknihovna_@KNIHOVNA_API_VERSION@_la_CPPFLAGS = … libknihovna_libknihovna_@KNIHOVNA_API_VERSION@_la_CFLAGS = … libknihovna_libknihovna_@KNIHOVNA_API_VERSION@_la_LIBADD = … libknihovna_libknihovna_@KNIHOVNA_API_VERSION@_la_LDFLAGS = \ -version-info $(KNIHOVNA_LT_VERSION) \ $(AM_LDFLAGS) \ $(NULL)

## Názvy souborů s knihovnami

Názvy souborů s knihovnami (známé též jako soname nebo číslování verzí libtool) se zaměřují jen na problém linkování dříve přeložených aplikací za běhu. Neřeší záležitosti okolo kompilace aplikací, které požadují dřívější verzi, a ani nic jiného okolo knihoven.

Z toho důvodu byste ještě navíc měli používat pro knihovny názvy s číslováním verzi. Každá z těchto věcí cílí na jiný problém.

# Soubory pkg-config

Soubory pkg-config by měly mít názvy s číslem verze. Například:

/usr/lib/pkgconfig/foo-4.pc

/usr/lib/pkgconfig/foo-5.pc

Protože každý soubor pkg-config obsahuje informace o verzi názvu knihovny včetně cesty, měl by být kterýkoliv projekt, který závisí na knihovně, schopen se přepnout z jedné verze na jinou prostou změnou kontroly ve svém pkg-config z neco-4 na neco-5 (a provedením příslušného přechodu v API).

Soubory pkg-config s rozlišením verze mohou být instalovány z autoconf a automake pomocí následujícího kódu:

## Soubory pkg-config v Autoconf a Automake

Kód pro instalaci souborů pkg-config s rozlišením verze z configure.ac a Makefile.am
AC_CONFIG_FILES([ libknihovna/knihovna-$KNIHOVNA_API_VERSION.pc:libknihovna/knihovna.pc.in ],[], [KNIHOVNA_API_VERSION='$KNIHOVNA_API_VERSION'])
# Všimněte si, že soubor s šablonou se nazývá library.pc.in, ale generuje se # soubor .pc s číslem verze pomocí stejné magie, jako v AC_CONFIG_FILES. pkgconfigdir = $(libdir)/pkgconfig pkgconfig_DATA = libknihovna/knihovna-$(KNIHOVNA_API_VERSION).pc

DISTCLEANFILES += $(pkgconfig_DATA) EXTRA_DIST += libknihovna/knihovna.pc.in

# Soubory s nastavením

Z hlediska uživatele je nejlepším přístupem k souborům s nastavením udržovat formát dopředně i zpětně kompatibilní (obě verze knihovny rozumí právě té stejné syntaxi a sémantice souboru s nastavením). Pak lze použít ten samý soubor s nastavením pro všechny verze knihovny a není potřeba žádné číslování verzí u souborů s nastavením.

Pokud to ale dodržet nejde, měly by být soubory s nastavením jednoduše přejmenovány a uživatel musí nastavit každou verzi knihovny zvlášť.

# Překlady pomocí Gettext

Jestli používáte pro překlady gettext v kombinaci s autoconf a automake, je normální postup nainstalovat překlady do /usr/share/locale/jazyk/LC_MESSAGES/balíček. Údaj baliček musíte změnit na správný název. Podle zvyklostí používaných v GNOME se to celé vloží do configure.ac:

GETTEXT_PACKAGE=foo-4 AC_SUBST([GETTEXT_PACKAGE]) AC_DEFINE_UNQUOTED([GETTEXT_PACKAGE],["$GETTEXT_PACKAGE"])

Pak použijte `GETTEXT_PACKAGE` jako název balíčku předaný do `bindtextdomain()`, `textdomain()` a `dgettext()`.

# Rozhraní D-Bus

Rozhraní D-Bus je další formou API a podobá se API v C s tím rozdílem, že rozlišení verze se neprovádí při kompilaci, ale za běhu. Číslování verzí rozhraní D-Bus se jinak od API v C nijak neliší: čísla verzí musí být zahrnuta v názvech rozhraní, názvech služeb a v cestách k objektům.

Například pro službu `org.example.Foo` vystavující rozhraní `A` a `B` na objektech `Controller` a `Client`, by verze 4 a 5 API pro D-Bus vypadala nějak takto:

## Názvy služeb

`org.example.Foo4`

`org.example.Foo5`

## Názvy rozhraní

`org.example.Foo4.InterfaceA`

`org.example.Foo4.InterfaceB`

`org.example.Foo5.InterfaceA`

`org.example.Foo5.InterfaceB`

## Cesty k objektům

`/org/example/Foo4/Controller`

`/org/example/Foo4/Client`

`/org/example/Foo5/Controller`

`/org/example/Foo5/Client`

# Programy, démoni a pomůcky

Grafické aplikace obecně nepotřebují být nainstalované ve více verzích, protože na nich nezávisí další moduly. Démoni a pomocné programy ale spolupracují s dalšími částmi systému, a tak číslování verzí v názvech potřebují.

Mějme démona a pomocný program:

/usr/libexec/foo-daemon

/usr/bin/foo-lookup-utility

Číslo verze pak mohou mít přidělené jako:

/usr/libexec/foo-daemon-4

/usr/bin/foo-lookup-utility-4

Můžete nainstalovat symbolický odkaz z /usr/bin/foo-lookup-utility na doporučovanou verzi kopie pomocného programu, aby se pohodlněji používal uživatelům.