Packaging - at home

Пример 2.2: Пример за бързо и dirty получаване на binary packages

Нека да получим deb-файлове по-възможно най-краткия път за малка програма, която се получава от един единствен сорс-файл, изискващ само наличието на основната C-библиотека, която всички имат, и освен това не конфликтира с нищо, така че нашите maintainer scripts ще са възможно най-прости. По-лесен случай май не може да се измисли ;-). Нека имаме сорса на една такава програмка

/*
 * In terms of GNU GPL
 * by <zimage@delbg.com>
 * 1995-2000 Davidov Electric Ltd.
 */

#include <netinet/in.h>
#include <arpa/inet.h>
#include <netdb.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <errno.h>
#include <string.h>
#include <stdio.h>

#define DATASIZE 1024

int main (int argc, char *argv[]){
  int sockfd, bytesRecv, bytesSent;
  char *sendHeader, rData[DATASIZE];
  char *addr;
  struct hostent *wsHost;
  struct sockaddr_in wsAddr;
  struct in_addr wsIP;

  sendHeader = "HEAD / HTTP/1.0\n\r\n\r\n\r";

  if (argc < 2)
  {
    printf ("wsver v1.1 by zImage <zimage@delbg.com>\n\n\t");
    printf ("Usage: %s <ipaddress | hostname>\n\n", argv[0]);
    exit (0);
  }


  addr = argv[1];
  
  if((wsIP.s_addr = inet_addr(addr)) == -1)
  {
        printf("Lookin' up %s...\t", addr);
        fflush(stdout);
        if((wsHost = gethostbyname(addr)) == NULL)
        {
          printf("Failed.\n");
          herror(NULL);
          exit(1);
        }
        wsIP = *(struct in_addr *)wsHost->h_addr_list[0];
        printf("%s\n", inet_ntoa(wsIP) );
  }

  if ((sockfd = socket (PF_INET, SOCK_STREAM, 0)) == -1)
  {
    printf ("Error: %i", errno);
    perror(NULL);
    exit (1);
  } else {
    printf ("Connecting to %s...\t", inet_ntoa(wsIP));
    fflush(stdout);
  }

  wsAddr.sin_family = PF_INET;
  wsAddr.sin_port = htons (80);
  wsAddr.sin_addr.s_addr = wsIP.s_addr;
  bzero (&(wsAddr.sin_zero), 8);

  if ((connect(sockfd, (struct sockaddr *) &wsAddr, sizeof (struct sockaddr))) == -1)
  {
      printf ("Failed.\n");
      perror (NULL);
      exit (1);
  } else {
      printf ("OK.\n");
  }

  printf ("Sending header...\t");
  fflush(stdout);
  if ((bytesSent = send (sockfd, sendHeader, strlen (sendHeader), 0)) == -1)
  {
      printf ("Failed.\n");
      perror (NULL);
      exit (1);
  } else {
      printf ("OK - %i bytes sent.\n", bytesSent);
  }

  printf ("Reading header...\t");
  fflush(stdout);
  if ((bytesRecv = recv (sockfd, rData, DATASIZE, 0)) == -1)
  {
    printf ("Failed.\n");
    perror (NULL);
    exit (1);
  } else {
    printf ("OK - %i bytes read\n", bytesRecv);
  }

  rData[bytesRecv] = '\0';

  printf (" \n- - - Header received - - -\n%s- - - end of header - - -\n", rData);

  return 0;
}

която се компилира с:

gcc -o wsver wsver.c

и сме получили динамично свързан изпълним файл wsver, за който проверяваме и се убеждаваме, че наистина не е претенциозен:

# ldd ./wsver
libc.so.6 => /lib/libc.so.6 (0x40026000)
/lib/ld-linux.so.2 => /lib/ld-linux.so.2 (0x40000000)

# dpkg -S /lib/libc.so.6 /lib/ld-linux.so.2
libc6: /lib/libc.so.6
libc6: /lib/ld-linux.so.2

# apt-cache show libc6 | grep Section
Section: base

Идеално...Всички имат libc6, защото е в base, така че спокойно можем да прескочим описването на каквито и да са зависимости и конфликти и минаваме напряко, прескачайки целия Debian New Maintainers' Guide.

# mkdir -p wsver/usr/bin wsver/DEBIAN
# cp wsver wsver/usr/bin
# echo "Package: wsver" > wsver/DEBIAN/control
# echo "Version: 1" >> wsver/DEBIAN/control
# echo "Architecture: i386" >> wsver/DEBIAN/control
# echo "Maintainer: You <you@some.net>" >> wsver/DEBIAN/control
# echo "Description: Check web server version" >> wsver/DEBIAN/control
# dpkg-deb -b wsver
dpkg-deb: building package `wsver' in `wsver.deb'.
# dpkg -i wsver.deb
# whereis wsver 
wsver: /usr/bin/wsver
# apt-cache show wsver
Package: wsver
Status: install ok installed
Maintainer: You <you@some.net> 
Version: 1
Description: Check web server version
# wsver localhost

Подобно пакетиране не се прави от debian maintainers и е само като пример, който едва ли ще срещнете някъде. Това е fast & dirty home made packaging на специално подбран, възможно най-непретенциозен сорс-код. Понякога дори и fast & dirty може да се окаже, че е по-приемливо за самия потребител. Ако го компилирате за i386, можете спокойно да споделите този deb-файл с други x86 потребители, както можете да го компилирате за произволна друга хардуерна архитектура. В случая не сме оформили debian source package, от който да се получават един или няколко binary packages за различните хардуерни архитектури, също така не сме направили проверка с програми като lintian, linda и т.н.

Пример 3: Mplayer -- директорията debian/ идва с upstream sources

Нека бъде от current CVS и отделните му Releases, които ако сте мързеливи, може да получите барабар с шрифтове, кожи и кодеци с помощта на този красив Makefile, който да кажем сте съхранили в /usr/local/src/MPlayer/ и сте разгледали какво точно прави, разбира се. Макар и все още невключен в официалния Debian-архив, тези сорсове дефакто са оформени като debian source packages. В main/ директорията разгледайте съдържанието на директорията debian/, за да добиете идея за maintainer scripts, които в момента са такива и които могат да бъдат промени впоследствие. Изпълнявайки от main/:

# fakeroot debian/rules binary

или:

# DEB_BUILD_OPTIONS="--compile-options-here" debian/rules binary

Имайте предвид, че в глава 10.1 на debian-policy е указано, че променливата DEB_BUILD_OPTIONS е запазена за други цели и официално приема noopt и nostrip стойности. Така, че ако MPlayer ще влиза в официалния Debian архив, то в неговия debian/rules ще се ползва друга променлива за целта за която сега се ползва DEB_BUILD_OPTIONS.

Ще получите съответния binary package, можете да генерирате list files за apt repository ( dpkg-scanpackages(8), dpkg-scansources(8) и т.н.) Нека за момент предположим, че нямате инсталирана библиотеката SDL и съответно сте получите изпълним файл на Mplayer, който е без такава поддръжка, което не е фатално, но пък може да се получи така, че да имате липса на нещо (най-често някоя библиотека), поради което компилацията на Mplayer няма да завърши успешно. Освен това нямате никакви list files, които да подскажат Build-Depends информация (т.е. какво е нужно като файлове и в кои пакети са те, за да се изкомпилират изходните кодове успешно) за Mplayer на командата apt-get build-dep, т.е. разполагаме само с upstream сорсовете на Mplayer, в които има и debian/ директория, съдържаща maintainer scripts. Тогава ако нямаме auto-apt, го инсталираме и конфигурираме:

# apt-get install auto-apt
# auto-apt update updatedb update-local
# fakeroot auto-apt run debian/rules binary

Ще бъдете запитани за всичко, което бъде потърсено и ненамерено в момента във вашата система, и вие ще решите кое да бъде изтеглено и инсталирано и кое да бъде отказано... debian/rules е най-обикновен файл за програмата make(1), разгледайте го.

Нещо повече: ако предположим, че имаме upstream sources на Mplayer без maintainer scripts, намиращи се в debian/ директорията, то auto-apt пак ще свърши работа:

# auto-apt run ./configure --prefix=/usr/local/somewhere --more-options-here 
# auto-apt run make

Отново, ако има липси, необходими за компилационния и свързващия процес, ще бъдете запитани дали искате да ги инсталирате. Това важи, разбира се, и за произволни сорсове, за които auto-apt ще може да намери необходимото в рамките на източниците от пакети, до които има достъп, за да търси съответните липсващи файлове. Как да инсталираме необходимото при поискване, е описано в APT-HOWTO.

Може да продължите с по-сложни експерименти, следвайки горните официални документи, за сорсове, изискващи по-задълбочено конфигуриране. Добър пример е даден в Debian New Maintainers' Guide. Ще видите, че нещата невинаги са толкова лесни и прости. Ето например следващото може да е доста полезно за тези "`advanced"' или "`power"' root потребители, които обичат да компилират и инсталират system-wide, без да разбират и осъзнават какво всъщност правят и докарват своите GNU/Linux дистрибуции до състояние на омазан до безпомощност виндовс: библиотеки, поделени библиотеки, Debian Library Packaging guide

Точно обратното, може да се наложи тотален контрол върху инсталирания софтуер в системата, така че да се предотвратява наличието на broken stuff. Налага се да се контролират зависимостите и конфликтите при многобройните парчета софтуер, които имате в системата, особено при споделените библиотеки. Например, защо се налага и как се пресмятат shared library dependencies: dpkg-shlibdeps(1), dh_shlibdeps(1) или опитайте на http://www.fifi.org/doc/HTML/. Като че ли няма аналог извън Debian ;-) За това се грижи дистрибуцията или package maintainers, но вие ако знаете повече или искате някакво по-custom решение, разбира се, че ще се намесите. Ето няколко случайни примера:

Пример 4: Промяна на Depends and Conflicts

Ако чувствате, че знаете какво правите, можете спокойно да дръпнете интересуващите ви debian source packages. От всеки такъв пакет се получават по един или няколко debian binary packages (deb-файлове), всеки от които доставящ по една или няколко изпълними програми и/или библиотеки и др., като разни shared data files.... Можете да промените зависимостите и конфликтите между тях като редактирате файловете debian/control, debian/rules и други в директорията debian/). Може да се намесите и в самите upstream sources и след това да ги инсталирате. Трябва да имате предвид, че променянето на дадена опция при компилацията или свързването може да даде, или не, отражение върху работата на други програми. Променянето на upstream sources също може да доведе до такова поведение. Така че мислете глобално, когато се намесвате. Изключително голяма радост за очите е да се наблюдава как на разни системи "`advanced"' потребители компилират и инсталират каквото им дойде на ума и както се сетят, без наличието на какъвто и да е мисловен процес. Важното е, че компилират нещо там (т.е. наблюдават как например gcc компилира) и се изживяват като "`advanced"', но че после може да има breaks въобще на осъзнават, я усетили, я не. Debian учи как да не се правят подобни lame изпълнения и то на "`сляпо"', а с механизмите за контрол, които предоставя, препоръчва нещата да се правят така, че да може да се наложи от потребителя прецизен и тотален контрол върху софтуера, бил той като debian source или binary packages. Разбирайки това, вече разбирате и колко много weakness и мъка може да има по този свят. Познаването на Debian New Maintainers' Guide и Debian Developer's Reference би било от изключителна полза, както и познанията за upstream souces, в които намесите са задължителни. След като направите промените в сорса и получите вашите custom binary packages и ги инсталирате, можете да се погрижите да ги заковете чрез pinning feature на apt, така че да не upgrade-нете вашата custom версия, без да усетите или забележите. Ако при бъдещи промени в системата, като upgrades или downgrades, все пак се наложи да unpin ("`отковете"') вашия custom pin-нат пакет, за да може да бъде upgrade-нат например, то тогава ще бъдете уведомен от apt и ще помислите кое от двете по ви изнася да изберете, като пак може можете да внесете вашите промени (мислейки!) в новата версия на вече бившия закован и upgrade-нат package и да го заковете отново.

Пример 5: Намеса в upstream sources, препакетиране и инсталиране

Нека ви се налага по някаква причина (и вие наистина знаете какво правите) да промените нещо в сорса на glibc. Да речем, съдържанието на някой заглавен файл: искате да увеличите стойността на някоя дефиниция в /usr/include/директория/файл.h да прекомпилирате glibc и да го инсталирате. Прекомпилация може да се наложи, за да се отрази тази промяна и в библиотечните файлове в /usr/lib/ и вероятно на още няколко места, такава може да прецените, че не се налага, и просто да се задоволите само с промяна на заглавния файл без прекомпилация и инсталация. Да предположим, че все пак сте преценили, че се налага прекомпилация на сорса на glibc с последваща инсталация. Няма да преоткриваме колелото и да обясняваме, че изтеглянето на чистите upstream sources, разпакетирането им, компилирането им и инсталирането им system-wide с ./configure; make; make install, без да се проверява и осъзнава кой файл точно къде се инсталира, е пълна глупост и може да нанесе големи проблеми на системата най-малко поради факта, че трябва да има гаранция, че старите файлове ще бъдат напълно заменени от новите, както и че няма да останат стари stalled files, които да пречат по някакъв начин. Ако имате желание, може да се занимаете с въпроса, проверявайки кое къде отива при новата компилация и как да отстраните старата ;-) Ето как ще го направим бързо и безопасно като оставим горната рутинна и тежка работа по "`бройкането"' на стари/нови файлове на dpkg. Той знае как да премахне старите и да инсталира новите файлове и няма смисъл ние да си бодем очите с подобна досадна и незаслужаваща времето ни задачка. Няма да вадим корен крадратен от голямо число с лист и молив, я. Това, че знаем алгоритъма, не е основание да го правим с беден инструментариум. Първо, проверяваме в кои binary package е интересуващият ни файл /usr/include/директория/файл.h:

# dpkg -S /usr/include/somedir/somefile.h

да кажем, че dpkg отговори, че този файл идва с пакета libc6-dev.

Изтегляме съответния му source package, от който този binary package е получен, защото от един source package могат да се получат един или няколко binary packages, но това не е задължително, разбира се, винаги да е точно така, като преди това проверяваме дали имате необходимото, за да може да бъде компилиран успешно този source package:

# apt-get build-dep libc6-dev
# apt-get source libc6-dev

В текущата директория, забележете, получаваме: glibc-2.3.1/, glibc_2.3.1-5.diff.gz, glibc_2.3.1-5.dsc и glibc_2.3.1.orig.tar.gz. Няма да обясняваме кое какво е, в документацията си пише, например в Debian New Maintainers' Guide.

Променяме сорса. В директорията debian/ са конфигуриращите скриптове на maintainer-a, всичко останало са чистите upstream sources. Забележете, че наименованието на софтуера upstream може да не съвпада в имената на пакетите в GNU/Linux дистрибуцията, а също че upstream може да бъде разбит на няколко пакета. Трябва да знаем къде да търсим и променим това, което ни трябва, в upstream sources на glibc, променяме и версията на пакета в debian/changelog. Забележете, че в debian/patches/ са предоставени и кръпките които са приложени към upstream sources на glibc, така че внимавайте вашите промени да се "`понасят"' с тях. Не всички upstream sources, идващи с debian source packages, се закърпват. Такива кръпки, специфични за дистрибуцията, се прилагат от maintainers много внимателно и само когато това наистина се налага, така че да не се "`вадят очи, вместо да се изписват вежди"'. Понякога това може да се наложи поради изискванията, които се поставят от дистрибуцията. Например, за по-добра съвместимост с File Hierachy Standard или Linux Standard Base, по-добра съвместимост с различни хардуерни архитектури като IA-64, ARM, HPPA, Sparc64, S390x, ..., и ядра като Hurd-on-GnuMach и т.н. и т.н....Тези кръпки най-вероятно след това влизат и в следващия официален upstream release на glibc или който и да е софтуер в дадения случай. По подобен начин дефакто Debian служи и се "`експлоатира"' като платформа за пренасянето на XFree86 (това "`86"', напомнящо x86 или PC в името, е дразнещо определено ;-) за GNU/Linux на доста хардуерни архитектури, вкл. и hurd-i386.

Компилираме и получаваме binary package(s), кой(и)то инсталираме:

        
# debian/rules binary
# dpkg -i ../*.deb

Допълнение: тук дори можете ако имате желание да си направите local apt repository и да го добавите в /etc/apt/sources.list, така ще ползвате apt да смята зависимостите и конфликтите и да подава пакетите в определения ред на dpkg, вместо просто само dpkg. Един бърз пример как става това е How to do apt-get install for local debs

Забележете, че от този source package се получават няколко binary packages (deb-файлове), които ние ще инсталираме. Не е задължително от всеки source package да се получават по няколко binary такива. Частен случай е, когато от един source package се получава един binary package. Това се контролира от debian/control, в зависимост от решенията на maintainer-а, които го е създал или от вас разбира се.

Така всичко ще е под пълен и бърз контрол, като рутинната и досадна работа по издирването на файловете при инсталацията сме оставили на dpkg. Това е случая, когато инсталирате вашия си custom glibc build system-wide, т.е. по-опасния случай, а иначе в /usr/local/ в отделни директории може да имате компилирано и инсталирано glibc колкото пъти се сетите и както се сетите и във всеки отделен терминал да export различен LD_LIBRARY_PATH, за да ползвате различен build на glibc...;-). Тази библиотека е основна градивна единица и няма да се спираме на това колко динамично свързани програми зависят от нея, така че внимателно с custom-изацията. Тук по-наблюдателните ще отправят основателен въпрос: как dpkg, който е динамично свързан и зависи от библиотечни файлове на glibc, ги премахва и докато инсталира новите, и все пак продължава да работи добре. Отговорът е, че докато прави тази операция, dpkg заедно със старите споделени библиотеки, с които е свързан динамично, е зареден в паметта. След като завърши тази операция, на следващото стартиране dpkg ще се свързва с новите такива споделени библиотеки. Точно поради това, че е зареден в паметта (е то няма иначе къде другаде;-), dpkg може да upgrade-ва или downgrade-ва в момента инсталирания dpkg на диска (образно казано), така че apt-get install dpkg apt или dpkg -i dpkg_*.deb apt_*.deb са операции абсолютно в реда на нещата и няма място за опасения. Разбира се, може да имате и статично свързан dpkg, както и други важни програми. Ако изтеглите някой source package на dpkg от ftp/http mirrors или пък някой сорс (даден таг) на dpkg от cvs.debian.org и разгледате debian/control, ще видите, че може да се получи и е предвиден и binary package: dpkg-static. FreeBSD държи някои такива статично свързани програми в отделна директория /stand, но те са си за /bin, /sbin, /usr/bin, /usr/sbin и т.н. но именувани, например, с подходящ суфикс -static или подобен, за да е ясно за какво става дума, не че с ldd(1) не може да се провери кое е статично и кое е динамично свързано -- въпрос на вкус.

Повечето потребители, наблюдавайки работата на dpkg и apt при себе си, остават с впечатлението, че заслугата е изцяло и единствено на тези прогами. Някои дори не забелязват, че apt, след като си свърши своята част от работата, извиква dpkg, за да прави реално инсталацията. Разбира се, че това са корави програми, изпитани и разширявани постепенно с времето. Също така не е случаен и фактът, че функционалността е разпределена между тях (и други разбира се), а не набутана само в една от тях -- това е по стара Unix традиция, за да не се достига до bloatware при претрупване на едно приложение с прекалено голям брой features, които е трудно да се поддържат реализирани в едно единствено приложение. Всичко това се вижда от потребителите на пръв поглед, но като се погледне малко по-обстойно на нещата, се разбира, че dpkg и apt се "`захранват"' и "`разполагат"' със страшно много и прецизна информация, без значение какво имате инсталирано в системата. Това са list files в /var/lib/apt/lists/, които вие обновявате при всеки apt-get update в зависимост от посочените от вас източници в /etc/apt/sources.list. Така те разполагат с пълна информация за пакетите, достъпни от споменатите от вас източници, освен информацията, която пакетите носят сами със себе си. Дръпнете някой debian source package с apt-get source пакет, разгледайте го, и по-специално директорията debian/, получете от него binary package(s), след това разархивирайте един binary package deb-файл, изпълнявайки ar -x пакет_версия.deb, и след това разгледайте какво има в control.tar.gz, в data.tar.gz е самото приложение. След това разгледайте тези *Packages, *Sources, *Release файлове в /var/lib/apt/lists/, защо така са именувани при вас и какво съдържат. Grep-вайки небрежно, потърсете вашия пакет. Не редактирайте тези файлове, ако не разбирате какво правите, но ако ги омажете, можете да ги изтриете и да ги обновите пак. Тази мета-информация идва от файловете Packages, Sources, Release от Debian архива(ите), съответно избрани от вас и посочени в /etc/apt/sources.list. Тя пък е получена от контролните файлове в debian source packages, от които са получени съответните debian binary packages. Няма как да не се сетите, че debian source packages, и по-точно контролната информация, която носят те, се създава и поддържа от debian maintainers и потребителите, ако решат, могат да се намесват. Дотук говорихме за една и съща мета-информация, разпространявана независимо по няколко "`канала"' и поради наличието на която може да се оценява коректността на работа на произволно избрана селекция от пакети, налични или неналични в потребителската система. Т.е. всичко в крайна сметка зависи от мета-информацията, която тръгва от debian source packages (тя трябва да е пълна и прецизна), отива в debian binary packages и list files в архива и впоследствие и в /var/lib/apt/lists/ на потребителя. Разбира се, че последната не е задължителна и може да имате само debian binary packages или debian source package, от които да получите debian binary package(s), и мета-информацията, идваща с тях. Това пак ще ви свърши някаква работа, но е доста скромен случай, разбира се. Съвсем отделен е въпросът, че dpkg си поддържа своя база данни за състоянието/статуса на пакетите, която вие запитвате с dpkg -l, dpkg -get-selections и т.н.

Съществуват и т.н. Съдържание files (намиращи се на огледало/debian/dists/издание/Съдържание-архитектура.gz), които съдържат пълната информация за това, кой файл в кой пакет се намира. Всичко това е за дадената архитуктура, разбира се, понеже между тях може да има разлики. Тази информация е полезна, когато знаете файла (или пътя до него) и ви интересува в кой пакет се намира той. Например, скриптовете apt-file (apt-file update) и auto-apt (auto-apt update updatedb update-local) изтеглят тези Съдържание files.

Споделят се и аналогични впечатления, като за dpkg и apt пакети, или пакети предоставящи набор от програми, и от работата на скрипта auto-apt, който прекъсва процеса на компилация или свързване за произволни дървета от сорс код, изнамира в кой пакет е липсващия header/lib, стига този пакет да е в обсега му (sources.list) разбира се, предлага го за инсталация, които вие може да откажете, и след това resume-ва процеса на компилацията и/или свързването, от там докъдето е бил временно прекъснат. Няма магии тука, има добре обмислен дизайн, който служи като добра основа за създаване на едни или други инструменти с една или друга функционалност. Тези инструменти също трябва да са на ниво, разбира се.


Nikola Antonov 2004-10-31