Глава 4. Использование многофайловой сборки в пакете
При написании крупных программ многофайловость позволяет лучше ориентироваться в программе, разделять блоки кода и пространства имён. Для автоматической сборки программ, состоящих из множества файлов, используется утилита
GNU make.
При работе с крупными проектами часто используются сборочные библиотеки, при этом не только сторонние, но и собирающиеся из исходных файлов самого проекта. Например, если проект состоит из нескольких программ, общую их часть естественно вынести в отдельную библиотеку и собирать все программы с ней.
Построим пакет, в котором соберём библиотеку и два исполняемых файла: один будет компоноваться со статическим, а второй — с динамическим вариантом библиотеки.
Программа состоит из четырёх файлов: основной код, код библиотеки в двух файлах и заголовочный файл:
@builder:
RPM/SOURCES/Multilab-1.0/fun.c
#include <stdio.h>
#include "outlib.h"
void output(char *str) {
printf("%d: %s\012", Count++, str);
}
void usage(char *prog) {
fprintf(stderr, "%s v%.2f: Print all arguments\012\t"\
"Usage: %s arg1 [arg2 […]]\012", prog, VERSION, prog);
}
@builder:
RPM/SOURCES/Multilab-1.0/Multilab.c
#include <stdio.h>
#include "outlib.h"
int main(int argc, char *argv[]) {
int i;
if((Count = argc)>1) {
output("<INIT>");
for(i=1; i<argc; i++)
output(argv[i]);
output("<DONE>");
}
else
usage(argv[0]);
return 0;
}
@builder:
RPM/SOURCES/Multilab-1.0/Multilab.c
int Count=0;
@builder:
RPM/SOURCES/Multilab-1.0/outlib.h
void output(char *);
void usage(char *);
extern int Count;
#define VERSION 1.0
Опишем Makefile для проекта и рассмотрим подробнее его рецепты:
@builder:
RPM/SOURCES/Multilab-1.0/Makefile
GENS = Multilab-* README-* lib*
TRASH = *.o *~ o.*
CFLAGS = -Wall -fPIC
CC = cc
.PHONY: clean distclean
.SECONDARY: fun.o const.o
.INTERMEDIATE: libstatlib.a(fun.o const.o)
all: binfiles documentation
binfiles: Multilab-a Multilab-so
Multilab-a: Multilab.o libstatlib.a
$(CC) $(CFLAGS) $< -L. -lstatlib -o $@
Multilab-so: Multilab.o libdynlib.so
$(CC) $(CFLAGS) $< -o $@
libstatlib.a: libstatlib.a(fun.o const.o)
libdynlib.so: fun.o const.o
$(CC) $(CFLAGS) $^ -o $@ -shared
fun.o Multilab.o: outlib.h
documentation: README-a README-so
README-a: Multilab-a
./$< > $@ 2>&1
README-so: Multilab-so
LD_LIBRARY_PATH=`pwd` ./$< > $@ 2>&1
clean:
rm -f $(TRASH)
distclean: clean
rm -f $(GENS)
make поддерживает множество встроенных рецептов, для работы которых используются переменные окружения. Это позволяет задавать рецепты, лишь описывая правильный формат цели и исходников;
при повторных сборках make ориентируется на метки времени создания и модификации файлов для проведения выборочной перекомпиляции. В случае самостоятельной генерации статических библиотек необходимы дополнительные цели для настройки правил пересборки;
для каждого из целевых исполняемых файлов собирается своя библиотека на основе исходных файлов функции и глобальной переменной. Заметим, что для статических библиотек make поддерживает встроенный сценарий сборки (несколько неожиданный синтаксис, задающий такое правило, см. в примере), в то время как для динамической необходимо явно указывать сценарий;
оба варианта сборки программы с библиотекой — вида cc файлы.o libбиблиотека -o программа и вида cc файлы.o -:путь_к_библиотеке-l библиотека -o программа — годятся и для статической, и для динамической сборок. Второй вариант предпочтительнее;
ключ
-fPIC (сгенерировать
позиционно-независимый код) нужен только для динамической сборки, в статической он сравнительно бесполезен.
Для сборки пакета оформим tarball и перейдём к описанию spec-файла:
@builder
[builder@localhost Multilab-1.0]$ cd
[builder@localhost ~]$ tree RPM
RPM
├── BUILD
├── RPMS
│ └── noarch
├── SOURCES
│ └── Multilab-1.0
│ ├── Makefile
│ ├── Multilab.c
│ ├── const.c
│ ├── fun.c
│ └── outlib.h
├── SPECS
└── SRPMS
8 directories, 5 files
[builder@localhost ~]$ cd RPM/SOURCES/
[builder@localhost SOURCES]$ tar -cf Multilab-1.0.tar Multilab-1.0/*
[builder@localhost SOURCES]$ gzip Multilab-1.0.tar
[builder@localhost SOURCES]$ cd
[builder@localhost ~]$ tree -A RPM
RPM
├── BUILD
├── RPMS
│ └── noarch
├── SOURCES
│ ├── Multilab-1.0
│ │ ├── Makefile
│ │ ├── Multilab.c
│ │ ├── const.c
│ │ ├── fun.c
│ │ └── outlib.h
│ └── Multilab-1.0.tar.gz
├── SPECS
└── SRPMS
8 directories, 6 files
[builder@localhost ~]$
@builder:
RPM/SPECS/Multilab.spec
Name: Multilab
Version: 1.0
Release: alt1
Summary: Package with both types of libraries
License: GPL-3.0-or-later
Group: Development/Other
Source: %name-%version.tar.gz
%description
This is an example of make usage. Make is compiling project with static and dynamic libraries
%prep
%setup
%build
%make_build
%install
install -D %name-a %buildroot%_bindir/%name-a
install -D %name-so %buildroot%_bindir/%name-so
install -D -m644 libdynlib.so %buildroot%_libdir/libdynlib.so
%files
%_bindir/*
%_libdir/*
%changelog
* Thu Jul 04 2025 UsamG1t <usamg1t@altlinux.org> 1.1-alt1
- Makefile big package
Распределение файлов по каталогам системы при установке должно соответствовать общепринятой иерархии (она описана, например, в
man 7 hier).
Динамически собранная программа заработает, только когда библиотека окажется в системном каталоге (обычно — /usr/lib64), или в окружении будет явно задан каталог (LD_LIBRARY_PATH), содержащий эту библиотеку, или сама библиотека будет заранее подгружена с помощью LD_LIBRARY_PATH. Для проверки соберём оба исполняемых файла и сравним:
@builder
[builder@localhost ~]$ cd RPM/SOURCES/Multilab-1.0
[builder@localhost Multilab-1.0]$ make
cc -Wall -fPIC -c -o Multilab.o Multilab.c
cc -Wall -fPIC -c -o fun.o fun.c
ar -rv libstatlib.a fun.o
ar: creating libstatlib.a
a - fun.o
cc -Wall -fPIC -c -o const.o const.c
ar -rv libstatlib.a const.o
a - const.o
cc -Wall -fPIC Multilab.o -L. -lstatlib -o Multilab-a
cc -Wall -fPIC fun.o const.o -o libdynlib.so -shared
cc -Wall -fPIC Multilab.o -L. -ldynlib -o Multilab-so
./Multilab-a > README-a 2>&1
LD_LIBRARY_PATH=`pwd` ./Multilab-so > README-so 2>&1
Запустим утилиту ldd, возвращающую список динамических библиотек, требуемых файлу:
@builder
[builder@localhost Multilab-1.0]$ ldd Multilab-a
linux-vdso.so.1 (0x00007f6ca740e000)
libc.so.6 => /lib64/libc.so.6 (0x00007f6ca7219000)
/lib64/ld-linux-x86-64.so.2 (0x00007f6ca7410000)
[builder@localhost Multilab-1.0]$ ldd Multilab-so
linux-vdso.so.1 (0x00007fcb4fe1a000)
libdynlib.so => not found
libc.so.6 => /lib64/libc.so.6 (0x00007fcb4fc25000)
/lib64/ld-linux-x86-64.so.2 (0x00007fcb4fe1c000)
[builder@localhost Multilab-1.0]$ LD_LIBRARY_PATH=`pwd` ldd Multilab-so
linux-vdso.so.1 (0x00007f266dbe7000)
libdynlib.so > /usr/src/RPM/SOURCES/Multilab-1.0/libdynlib.so (0x00007f266dbd7000)
libc.so.6 => /lib64/libc.so.6 (0x00007f266d9ed000)
/lib64/ld-linux-x86-64.so.2 (0x00007f266dbe9000)
в Multilab-a добавлена статическая библиотека statlb.a, из динамических нужна только стандартная LibC;
Multilab-so собран с динамической библиотекой libdynlib.so, которой нет в стандартных местах;
после явного указания расположения библиотека нашлась.
@builder
[builder@localhost Multilab-1.0]$ ./Multilab-a
./Multilab-a v1.00: Print all arguments
Usage: ./Multilab-a arg1 [arg2 […]]
[builder@localhost Multilab-1.0]$ ./Multilab-so
./Multilab-so: error while loading shared libraries: libdynlib.so: cannot open shared object file: No s
uch file or directory
[builder@localhost Multilab-1.0]$ LD_LIBRARY_PATH=`pwd` ./Multilab-so
./Multilab-so v1.00: Print all arguments
Usage: ./Multilab-so arg1 [arg2 […]]
[builder@localhost Multilab-1.0]$ make distclean
rm -f *.o *~ o.*
rm -f Multilab-* README-* lib*
[builder@localhost Multilab-1.0]$ cd
[builder@localhost ~]$ mv RPM/SOURCES/Multilab-1.0
При сборке пакета явно выделяется директива %build:
@builder
[builder@localhost ~]$ rpmbuild -ba RPM/SPECS/Multilab.spec
<...>
Executing(%build): /bin/sh -e /usr/src/tmp/rpm-tmp.37137
+ umask 022
+ /bin/mkdir -p /usr/src/RPM/BUILD
+ cd /usr/src/RPM/BUILD
+ cd Multilab-1.0
+ make
make: Entering directory '/usr/src/RPM/BUILD/Multilab-1.0'
cc -Wall -fPIC -c -o Multilab.o Multilab.c
cc -Wall -fPIC -c -o fun.o fun.c
ar -rv libout.a fun.o
ar: creating libout.a
a - fun.o
cc -Wall -fPIC -c -o const.o const.c
ar -rv libout.a const.o
a - const.o
cc -Wall -fPIC Multilab.o -L. -lout -o Multilab-a
cc -Wall -fPIC fun.o const.o -o libout.so -shared
cc -Wall -fPIC Multilab.o libout.so -o Multilab-so
./Multilab-a > README-a 2>&1
LD_LIBRARY_PATH=`pwd` ./Multilab-so > README-so 2>&1
<...>
Wrote: /usr/src/RPM/SRPMS/Multilab-1.0-alt1.src.rpm (w2.lzdio)
Wrote: /usr/src/RPM/RPMS/x86_64/Multilab-1.0-alt1.x86_64.rpm (w2.lzdio)
Wrote: /usr/src/RPM/RPMS/x86_64/Multilab-debuginfo-1.0-alt1.x86_64.rpm (w2.lzdio)
Вот так выглядит дерево каталогов после сборки: распакованный архив и генераты в
~/RPM/BUILD/,
.rpm-пакеты в
~/RPM/RPMS/ (включая автоматически собранный debuginfo-пакет, в который попадает отладочная информация) и
.src.rpm-пакет в
~/RPM/SRPMS
[builder@localhost ~]$ tree RPM
RPM
├── BUILD
│ └── Multilab-1.0
│ ├── Makefile
│ ├── Multilab-a
│ ├── Multilab-so
│ ├── Multilab.c
│ ├── Multilab.o
│ ├── README-a
│ ├── README-so
│ ├── const.c
│ ├── const.o
│ ├── fun.c
│ ├── fun.o
│ ├── libout.a
│ ├── libout.so
│ └── outlib.h
├── RPMS
│ ├── noarch
│ └── x86_64
│ ├── Multilab-1.0-alt1.x86_64.rpm
│ └── Multilab-debuginfo-1.0-alt1.x86_64.rpm
├── SOURCES
│ └── Multilab-1.0.tar.gz
├── SPECS
│ ├── Multilab.spec
└── SRPMS
└── Multilab-1.0-alt1.src.rpm
9 directories, 20 files
[builder@localhost ~]$
После установки пакета динамическая библиотека попадает в стандартный каталог /usr/lib64, так что Multilab-so можно запускать просто по имени:
@rooter
[root@localhost .in]# rpm -i /usr/src/RPM/RPMS/x86_64/Multilab-1.0-alt1.x86_64.rpm
<13>Jul 4 18:05:27 rpm: Multilab-1.0-alt1 1751652277 installed
[root@localhost .in]#
[root@localhost .in]# rpm -ql Multilab
/usr/bin/Multilab-a
/usr/bin/Multilab-so
/usr/lib64/libdynlib.so
[root@localhost .in]# Multilab-a qwerty
2: <INIT>
3: qwerty
4: <DONE>
[root@localhost .in]# Multilab-so qwerty
2: <INIT>
3: qwerty
4: <DONE>
[root@localhost .in]#