Product SiteDocumentation Site

Глава 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]#