Як створювати, збирати, встановлювати і використовувати пакети з програмами і бібліотеками для UNIX-подібних систем

Мова піде про програми та бібліотеки для UNIX-подібних систем, які розповсюджуються у вигляді вихідного коду (у тому числі у вигляді тарболлов), написаних зазвичай на C та C++ (хоча цей же порядок роботи може застосовуватися до софту на будь-якій мові). Багато речей у цій статті написані стосовно конкретно до GNU/Linux, хоча багато з статті може бути узагальнена і на інші UNIX-подібні ОС.

Під словом «пакет» я розумію в цій статті пакет з вихідними текстами, причому не пакет конкретного дистрибутиву GNU/Linux, а просто пакет, що виходить від оригінальних авторів софта.

У цій статті я розберу наступні питання:

  • От скачав програму або бібліотеку. Як її зібрати і встановити? Як скористатися бібліотекою?
  • Що таке префікс (prefix) установки? У чому різниця між складанням та встановленням? Куди зазвичай встановлюють програми?


Я розберу тільки зовсім базові речі. Ті, які типові учасники спільноти вільного ПЗ, що програмують на C та C++ під UNIX-подібні системи, зазвичай вже знають. Як створювати тарболлы (на прикладі «голого» make) і як встановлювати чужі тарболлы. Advanced поради щодо створення «хороших» пакетів я не дам. «Продвинуті» речі читайте в документації систем складання, у чудовій статті «Upstream guide» від Debian (в її кінці є ще купа посилань про створення «хороших» пакетів). Багато чого у цій статті можна було зробити по-іншому, моя мета: дати хоча б один спосіб, не намагатися осягнути неосяжне.

Попереджу: початок буде зовсім простим, але ближче до кінця буде все цікавіше.

І ще одне попередження. Я написав цю статтю нашвидку для однієї людини. А після написання подумав, мовляв, раз вже написав, викладу вже на Хабр, щоб не пропала. Тому в статті є недоліки типу нечіткого структуирования статті, фраз типу "не здійснює сама инклудивание" і пр. Я це виправляти не буду. Може коли-небудь в наступному житті, коли руки дійдуть. Вибір був між тим, щоб так публікувати чи не публікувати зовсім.

Отже, почнемо ми з того, що створимо пакет з програмою Hello, world. Для початку визначимося з системою складання.

Власне, сама збірка пакету зазвичай здійснюється з допомогою програми make (хоча це не єдиний шлях). Конфіг для make зазвичай лежить в файлі під назвою Makefile.

Є кілька варіантів: просто використовувати make або використовувати спільно з make якусь високорівневу систему збирання (зазвичай autotools або cmake), яка, власне, генерувати конфіги для make.

Ми в нашому прикладі буде використовувати тільки make для простоти. Отже, створюємо hello.c:

#include < stdio.h>

int
main (void)
{
printf ("Hello, world!\n");
return 0;
}


Тепер Makefile:

Увага! Працює на GNU/Linux. Робота під macOS не гарантується! Цей Makefile не є портируемым взагалі на всі UNIX-подібні системи! Детальніше буде далі.

<------> тут означає символ табуляції.

PREFIX=/usr/local
CC=cc
CFLAGS=

all: hello

hello: hello.c
<------>$(CC) $(CFLAGS) -o hello hello.c

install: all
<------>mkdir -p $(PREFIX)/bin
<------>install hello $(PREFIX)/bin/


Це далеко не єдиний спосіб написати цей Makefile. Взагалі, мета моєї статті — дати якусь базу. Що ще можна в цьому Makefile змінити, ви можете потім дізнатися з інших джерел.

Також у цій статті я припускаю, що ви знаєте вже зовсім базові речі про make. Я маю на увазі, що ви знаєте, що Makefile — це опис дерева залежностей, і що він потрібен, щоб зібрати рівно ті файли, які потребують складання. У цій статті я розповім про використання make при створенні пакетів, про «віртуальні» цілі make такі як install all. Ще я припускаю, що ви вже знаєте зовсім базові речі про те, як запускати компілятор командного рядка, тобто ви знаєте, що таке
cc -o hello hello.c
.

Отже, тепер давайте розбирати наш Makefile.

Для початку скажу наступне. Припустимо, користувач завантажив пакет. Йому потрібно спершу зібрати його, тобто отримати бінарники в каталозі з самим пакетом (або в разі out of tree build отримати їх в якомусь іншому каталозі, що не змінює суті), а потім встановити, тобто скопіювати отримані бінарники в місце їх остаточного зберігання в системі.

Тобто дивіться. Ви залогінитись під юзером user. Ваш домашній каталог /home/user. Ви завантажили пакет, розпакували його, скажімо в /home/user/Desktop/foo. Далі ви його зібрали. Зібрали в цій же папці, /home/user/Desktop/foo. Або, якщо мова йде про out of tree build, ви створили ще одну папку /home/user/Desktop/foo-build і зібрали в ній. Тепер, після складання ви вирішуєте встановити її у /usr/local. Ось тут вже вам потрібні права root. Ви за допомогою sudo встановлюєте цю програму в /usr/local.

Пара слів про розміщення каталогів в системі. Розберу лише деякі каталоги. Більш детальну інформацію можна знайти в Filesystem Hierarchy Standard (підтриманий багатьма дистрибутивами Linux) і по команді «man 7 hier» в GNU/Linux і, можливо, інших ОС. У всякому разі я розповім, як вони були влаштовані, скажімо, років п'ять тому (тобто де-то в 2012-му році), всякі нові віяння типу недавнього нововведення у Fedora «давайте-но ми все перемістимо в /usr» я розглядати не буду.

/bin — це бінарники (тобто испоняемые файли), важливі для роботи системи на ранніх стадіях завантаження.

/sbin — це те ж саме, з тією відмінністю, що ці бінарники зазвичай може запускати тільки root.

/lib — двійкові файли бібліотек, потрібні для програм з /bin і /sbin

/usr — «друга ієрархія файлів», тобто це як би «ще один /»

/usr/bin — те ж, що /bin, але на цей раз це бінарники, некритичні для завантаження

/usr/sbin — те ж, що /sbin, але знову-таки, некритичні для завантаження

/usr/lib — двійкові файли бібліотек, які не потрібні для програм з /bin і /sbin

/usr/include — хедеры бібліотек

/usr/local — «третя ієрархія файлів», вона знову містить /usr/local/bin, /usr/local/sbin, /usr/local/lib /usr/local/include, на цей раз ці каталоги призначені для локального адміністратора». Що це означає? Це означає, що (в разі дистрибутивів GNU/Linux з пакетним менеджером) каталоги, про яких мова йшла до цього, перебувають під контролем пакетного менеджера. А ось каталоги всередині /usr/local знаходяться в розпорядженні «локального адміністратора», тобто вас. Якщо ви хочете поставити щось з сорца, в обхід менеджера пакетів, то можна поставити туди.

Тепер з приводу prefix. Коли ви встановлюєте який-небудь пакет, ви повинні вказати йому так званий prefix, тобто каталог, який все буде встановлено. У цьому каталозі буде створений підкаталог bin, в нього буде встановлено бінарники, буде створено підкаталог lib, туди будуть встановлені двійкові файли бібліотек, і т. д.

Тобто, наприклад, ви встановлюєте пакет, вказуючи йому prefix /usr/local. Тоді бінарники підуть в /usr/local/bin, бінарні файли бібліотек — в /usr/local/lib і так далі.

Тепер повернуся до розбору Makefile. PREFIX — це і є той prefix, про який я зараз говорив. В якості дефолтного префікса (користувач зможе його змінити) у нас вказаний /usr/local — хороший дефолтний вибір. Зазвичай його завжди вказують в якості дефолтного префікса при створенні пакетів (на жаль, деякі пакети цього все ж не роблять, далі буде детальніше). Далі йде CC. Це стандартна назва змінної make, яку кладуть компілятор, в даному випадку cc. cc — це в свою чергу зазвичай використовується команда для запуску компілятора за замовчуванням в даній системі. Це може бути gcc або clang. На деяких системах команда cc може бути відсутнім. CFLAGS — стандартна назва змінної з прапорами компіляції.

Якщо просто набрати make, то буде виконана та мета, яка йде в Makefile першої. Звичайна практика в тому, щоб називати її all. І така мета зазвичай збирає весь проект, але нічого не встановлює. Ця мета зазвичай «віртуальна», тобто у нас немає файлу під назвою all. Оскільки наше завдання зібрати лише один бінарники hello, то ми просто робимо all залежних від hello. Далі йде опис мети складання hello. Його можна було в принципі розбити на два етапи: складання hello.o з hello.c і складання hello з hello.o. Я так робити не став для простоти.

Далі йде install, установка. Це теж віртуальна мета. Вона залежить від all. Це зроблено на випадок, якщо користувач відразу набере «make install», без «make». В цієї мети ми спочатку створюємо папку, куди будемо встановлювати, т. е. як і слід було очікувати, $(PREFIX)/bin, а потім встановлюємо у неї утилітою install.

Що робить install? Це майже те ж, що і cp. Точні відмінності я і сам не знаю. Для установки програм потрібно використовувати install, а не cp.

Бінарники встановлюється $(PREFIX)/bin, тому що саме так і потрібно робити. Бінарники йдуть в папку bin в prefix, бінарні файли бібліотек — lib у PREFIX і т. д.

Тут я припускаю, що у вас на системі встановлений так званий BSD install. У GNU/Linux'е він є. В деяких системах його може не бути. Може бути який-небудь інший install, який в цій ситуації не спрацює. Саме це я мав на увазі, коли сказав, що робота в різних ОС не гарантується.

Тут я не розглядав DESTDIR, який, між іншим, вкрай рекомендується використовувати в Makefile. Я навіть не розглядав мета clean.

Окей, давайте тепер створимо остаточний тарболл, так називають файл з розширенням .tar.gz, .tar.xz і так далі. Помістіть файли hello.c і Makefile в папку hello-1.0 (прийнято вказувати номер версії при створенні тарболла). Потім встановіть в якості поточного каталогу той, який містить hello-1.0 і наберіть, наприклад:

tar --xz -cf hello-1.0.tar.xz hello-1.0


Це створить архів, всередині якого є каталог hello-1.0, який містить hello.c і Makefile. Такий спосіб поширення пакетів.

C++-варіант пакету. Ісходник буде тим же, його потрібно буде назвати hello.cpp. Makefile буде таким:

PREFIX=/usr/local
CXX=c++
CXXFLAGS=

all: hello

hello: hello.cpp
<------>$(CXX) $(CXXFLAGS) -o hello hello.cpp

install: all
<------>mkdir -p $(PREFIX)/bin
<------>install hello $(PREFIX)/bin/


Зверніть увагу, що стандартне назва змінної для прапорів до компілятор C++ — це CXXFLAGS, а не CPPFLAGS. CPPFLAGS ж — це назва змінної для прапорів до препроцессору C.

Тепер розберемо, як встановлювати пакети з сорца (будь-які, програми та бібліотеки). Незалежно від системи складання. Цей алгоритм буде придатний у тому числі для тарболла, який ми створили тільки що. Припустимо, що пакет, який потрібно зібрати, теж називається hello.

Перший крок: завантажити безкоштовно і зайти в папку з сорцами. Тут два варіанти: викачуємо з системи контролю версій (я розберу для прикладу git) або завантажуємо тарболл.

Перший варіант. git. Робимо clone:

git clone <репозиторій>


Це створить папку hello. Без номера версії, т. до. ми завантажили з системи контролю версій. Робимо cd в створену папку, тобто
cd hello
.

Другий варіант. Тарболл. Викачуємо тарболл. Потім набираємо команду, скажімо,
tar -xf hello-1.0.tar.xz
. Це створить папку, наприклад,
hello-1.0
. Зазвичай, якщо творець пакету зробив все правильно, ім'я папки буде містити номер версії. Потім робимо cd в отриману папку, наприклад,
cd hello-1.0
.

Тепер потрібно зібрати. Будемо вважати для простоти, що ми не будемо робити out of tree build (якщо автор пакету вимагає out of tree build, там зазвичай будуть написані інструкції, як це зробити). А значить, ми будемо збирати у цій же папці, де сорцы. Тобто в цій папці, в яку ми зараз зробили cd.

Подальші дії залежать від системи збирання, обраної у проекті. Але незалежно від системи збирання ми в процесі складання повинні будемо вказати prefix. Причому зазвичай його потрібно буде вказати саме на етапі складання, а не на етапі установки, т. к. часто prefix захардкоживается всередину бінарника. А це означає, що після встановлення програми в певне місце її не можна просто так взяти і пересунути.

Я дам тут примірні інструкції, які працюють в більшості випадків. Точні інструкції ви побачите у автора проекту, там можуть бути різні нюанси.

Обов'язково вказуйте prefix при складанні (що б там не писав в інструкції автор). Якщо ви не вкажіть, то буде вибрано дефолтний. Зазвичай це /usr/local, і це досить хороший вибір. А якщо ні? Якщо автор пакету вказав якийсь інший дефолтний префікс? Ви встановіть незрозуміло куди. Зокрема libqglviewer використовує в якості дефолтного префікса /usr, що абсолютно неправильно (я відправив автору баг репорт). Отже, абсолютно завжди вказуйте префікс. Читайте інструкції, які автор вказує на своєму сайті і прикидывайте, куди впихнути prefix.

Отже, які можуть бути системи складання. По-перше, може бути просто make. Такий варіант з голим make зустрічається рідко. Один з небагатьох пакетів з «голим» make — bzip ( www.bzip.org ). У випадку з нашим hello-1.0.tar.xz, який ми створили, у нас саме такий варіант.

Отже, збирати у разі голого make потрібно так:

make PREFIX=/префікс-що-ви-хочете


(Конкретно у випадку з bzip на етапі складання вказувати PREFIX не треба. Але теоретично можна уявити собі пакет, який захардкоживает PREFIX всередину бінарника. Тому в загальному випадку PREFIX потрібен.)

Наступний варіант — це autotools. У цьому випадку збираємо так:

./configure --prefix=/префікс-що-ви-хочете
make


Наступний варіант — cmake. Збираємо так (зверніть увагу на крапку в кінці команди cmake):

cmake -DCMAKE_INSTALL_PREFIX=/префікс-що-ви-хочете .
make


Звідки крапка в кінці? Справа в тому, що cmake'потрібно передавати шлях до сорцам. А оскільки у нас не out of tree build, ми збираємо тут же. Сорцы знаходяться там же, де ми знаходимося. Тому точка, тобто поточний каталог.

У випадку з autotools і cmake команда, що генерує Makefile (т. е. ./configure або cmake) записує prefix в конфіги до make, тому в команді make (і в команді make install, про яку мова піде далі) вказувати prefix вже не потрібно.

Отже, зібрали одним із цих способів. Що далі? Тепер потрібно встановити.

У разі голого make це робиться так:

make PREFIX=/префікс-що-ви-хочете install


Потрібно буде вказати той же prefix, який ви вказували при збірці.

У разі autotools і cmake так:

make install


У разі, якщо для запису в prefix вам потрібен sudo, то ось цю команду для установки потрібно буде набирати з sudo. Взагалі, збірка завжди здійснюється за звичайними правами, а ось установка здійснюється з тими правами, які потрібні, щоб записати в prefix.

Окей, тепер подивимося, що ми накоїли. Припустимо, що ми ставили не що-небудь, а той hello-1.0.tar.xz, який створювали до цього. Припустимо також, що prefix, який ми вказали, був /foo. Тоді в нашій системі з'являться папки /foo /foo/bin (якщо їх не було до цього) і файл /foo/bin/hello. Що сталося? Зазначена в командному рядку при складанні мінлива PREFIX=/foo переопределила задану в Makefile PREFIX=/usr/local. У результаті зазначені в Makefile команди mkdir -p і install стали такими:

mkdir -p /foo/bin
install hello /foo/bin/


В результаті бінарники поклався в /foo/bin.

Тепер хочу ще трохи поговорити про префікси. Які префікси взагалі є?

Префікс /. Навряд чи коли-небудь вам доведеться вибирати. Він використовується для програм, критичних для ранніх стадій завантаження ОС (тобто критичні елементи для завантаження знаходяться в /bin, /lib і т. д.) (втім, навіть якщо вам потрібно встановити програму в /, її спершу встановлюють в /usr, тобто збирають і встановлюють з префіксом /usr, а потім переміщують необхідне в / [тобто переміщують з /usr/bin /bin, скажімо], в усякому разі саме так роблять автори Linux From Scratch 7.10 з пакетом, скажімо, bash).

Префікс /usr. Стандартний префікс, який використовується зазвичай для програм, встановлених через менеджер пакетів. Тобто якщо ви встановили програму через менеджер пакетів, вона веде себе так, немов вона зібрана і встановлена на вашій системі з префіксом /usr. Самому встановлювати пакети з префіксом /usr не можна.

Префікс /usr/local. Відмінний префікс для установки туди програм самостійно. Хороший тим, що /usr/local/bin є в дефолтному PATH (у всякому разі в дебиане). Тобто відразу після установки програми ви зможете просто запускати програму за назвою. Тому що бінарники лежить в /usr/local/bin, а /usr/local/bin є в PATH. Поганий тим, що там всі програми лежать змішано. Ось, припустимо, ви встановили бібліотеки foo, а потім бібліотеку bar. Обидві в цей prefix. Тоді дерево може виглядати так (в зовсім спрощеному вигляді):

/usr/local/include/foo.h
/usr/local/include/bar.h
/usr/local/lib/foo.so
/usr/local/lib/bar.so


Бачите? Все змішано. Немає єдиної папки, яка б містила «все, пов'язане з foo» та іншої папки, яка б містила «все, пов'язане з bar». (Хоча це ваша справа, вважати, що це дійсно погано чи ні). Ясна річ, що така ж проблема присутня при будь-якій установці різних пакетів в один префікс. Тобто префікс /usr страждає від того ж: пакети «розмазані» по системі (тут вже мова йде про пакети, поставлених через менеджер пакетів, тобто тих, які, власне, складають систему). Власне, це і є одна з кидаються відмінностей більшості UNIX-подібних систем від Windows. У Windows кожна програма знаходиться у своїй папці Program Files. У більшості UNIX-подібних систем вона «розмазана» по системі. Існують дистибутивы GNU/Linux, «вирішують цю проблему, наприклад, GoboLinux. Там кожен пакет у своєму каталозі, як у Windows.

Префікси виду /opt/XXX. Каталог /opt передбачається використовувати наступним чином: в ній потрібно створювати підкаталоги, називати їх назвами пакетів і використовувати ці підкаталоги як префікси. При такому підході зазначена вище проблема /usr/local (якщо вважати її проблемою) зникає. Кожен пакет буде встановлено в свій каталог. Наведений вище приклад з foo bar і буде виглядати так (я б порадив у назві підкаталогів в /opt вказувати ще й номер версії):

/opt/foo-1.0/include/foo.h
/opt/foo-1.0/lib/foo.so
/opt/bar-2.0/include/bar.h
/opt/bar-2.0/lib/bar.so


Недолік у такого рішення теж є. Вам доведеться самому додавати всі ці незліченні каталоги /opt/foo-1.0/bin (для кожного пакета) у PATH.

Префікси, відповідні домашніх каталогів. Тобто, скажімо, /home/user. Раджу у разі, коли хочеться поставити «тільки для себе», тобто тільки для одного юзера. Або коли немає прав root. Можливо, ваші конфіги, що поставляються з ОС вже налаштовані таким чином, щоб поміщати ~/bin в PATH за умови, що такий каталог є. Так що PATH буде налаштований як треба.

Кожен префікс може містити в собі свої bin, sbin, lib, include і т. д.

Отже, що з цього вибрати? Якщо потрібно поставити на всій системі, то я б порадив /opt/XXX. Я сам так зазвичай ставлю.

Тепер про складання, встановлення бібліотеки та її використання. Збирається і ставиться бібліотека так само, як і будь-який інший пакет, я це вже розповідав вище. Так що перейдемо відразу до використання. Ось ми встановили бібліотеку в якийсь префікс, припустимо, /foo. Тепер в /foo/include з'явилися хедеры цієї бібліотеки, а в /foo/lib — двійкові файли для бібліотеки.so — загальна бібліотека, або .a — статична, або і те, і те).

Припустимо, потрібно зібрати якийсь файл a.c з цією бібліотекою. Як це зробити?

По-перше, вгорі файлу потрібно написати #include, відповідний подключаему хедеру. Ну а збирати треба так:

cc -c -I/foo/include a.c
cc -L/foo/lib -o a a.o -lfoo


Давайте розбиратися. Для початку скажу, що I (велика англійська) і l (маленька англійська ель) — це дві різні літери. Не переплутайте їх у наведених командах.

Перша команда виконує компіляцію, тобто створює a.o на основі a.c. Друга — лінковку, тобто остаточний бінарники на підставі a.o.

У першій команді ми вказали -I/foo/include. Це вказівка тієї папки, де потрібно шукати хедеры. Шлях з цієї опції з'єднається з файлом, зазначеним у #include. Тобто якщо в командному рядку зазначено -I/foo/include, а у файлі написано #include <foo.h>, то вийде /foo/include/foo.h, він-то і буде заинклуден.

Тут опція -I/foo/include не здійснює сама инклудивание. Вона лише вказує на папку, де потрібно шукати, тому потрібен ще й #include. Тобто потрібен і -I/foo/include, і #include, одного з них недостатньо.

Лінкування. -L/foo/lib — це вказівка папки, де потрібно шукати двійкові файли бібліотеки, тобто файли .so і .a. -lfoo — це вказівка на те, що потрібно, власне, прилинковать цю бібліотеку до результирующему бинарнику. Ім'я бібліотеки, зазначене в опції -lfoo, з'єднається з папкою, зазначеної в -L/foo/lib і вийде /foo/lib/foo, потім сюди додасться .so (або .a) і опціонально номер версії і вийде /foo/lib/foo.so або, скажімо, /foo/lib/foo.so.1. Це і буде тим ім'ям .so-файлу, який буде шукатися.

Так само, як і при компіляції (a.o з a.c), потрібні обидві опції -L/foo/lib і -lfoo. -L/foo/lib вказує, де шукати. А -lfoo дає остаточну команду на прилинковку.

Замість -lfoo можна прямо написати цілком шлях до файлу бібліотеки, який потрібно слинковать, наприклад, /foo/lib/foo.so.1. Тоді опція -L/foo/lib не потрібна. Вийде так:

cc -o a a.o /foo/lib/foo.so.1


Бібліотеку (будь то повний шлях до бібліотеки або опція виду -lfoo) потрібно вказувати після «своїх» об'єктних файлів, у даному випадку a.o. (Може, і не потрібно, але на всяк випадок краще так робити.)

Можна з'єднати дві наші команди в одну, тоді буде щось таке:

cc -I/foo/include -L/foo/lib -o a a.c -lfoo


У разі, якщо бібліотека встановлена з префіксом /usr (тобто просто встановлена через менеджер пакетів), то опції -I -L не потрібні, вважайте, що -I/usr/include і -L/usr/lib у вас вже є. То ж, можливо, відноситься до /usr/local.

Якщо існує якась бібліотека під назвою foo, то вона зазвичай запаковується для дебиан в пакети з назвами libfoo (або libfoo1, libfoo2) і libfoo-dev. libfoo містить файли .so, а libfoo-dev — хедеры. Тобто хитрі розробники дебиан вміють з одного пакета зробити кілька. На складальних машинах дебиана пакет збирається і розфасовується в кілька пакетів.

Якщо ви встановіть у себе на машині libfoo і libfoo-dev, то результат буде як якщо б ви самі зібрали пакет foo з вихідних кодів з префіксом /usr. У вас на системі будуть, скажімо, файли:

/usr/include/foo.h (із пакета libfoo-dev)
/usr/lib/foo.so.1 (з пакету libfoo)


Нагадаю, що список файлів у цьому пакеті можна подивитися по команді, скажімо,
dpkg -L libfoo
. Ну а знайти пакет по файлу з допомогою
dpkg -S /usr/include/foo.h
.
Джерело: Хабрахабр

0 коментарів

Тільки зареєстровані та авторизовані користувачі можуть залишати коментарі.