При работе с проектами частой задачей становится создание и подключение библиотек. В библиотеки обычно выносится логика, общая для нескольких создаваемых исполняемых файлов, или публичное API, позволяющее использовать функции проекта в других разработках. Использование библиотек делает процесс сборки многоступенчатым: сначала надо скомпилировать исходники, затем из некоторых объектных файлов собрать библиотеку, а только затем — готовую программу. Если библиотека разделяемая, для запуска программы, которая её использует, необходимо вручную указывать место её расположения через
LD_PRELOAD и
LD_LIBRARY_PATH. Для удобной работы с библиотеками и их тестирования необходимо использовать специальные инструменты по автосборке. Классическим инструментом сборки библиотек является
GNU Libtool.
12.1. Использование Libtool вручную
Рассмотрим простую программу, которая прибавляет константу к числу, разобьём её на основной код, файл с основной функцией, файл с глобальной переменной-константой и заголовочный файл:
@user:
inc-pkg/main.c
#include <stdio.h>
#include <stdlib.h>
#include "libinc.h"
int main(int argc, char *argv[]) {
int n;
if(argc < 2) {
fprintf(stderr, "Usage: %s NUMBER\n", argv[0]);
return 1;
}
n = atoi(argv[1]);
printf("%d\n", inc(n));
return 0;
}
@user:
inc-pkg/fun.c
#include "libinc.h"
int inc(int var) {
return var + inc_var;
}
@user:
inc-pkg/global.c
#include "libinc.h"
int inc_var = 1;
@user:
inc-pkg/libinc.h
int inc(int);
extern int inc_var;
Опишем Makefile с использованием libtool и рассмотрим подробнее используемые команды:
@user:
inc-pkg/Makefile
CFLAGS = -g -O
LTFLAGS = --tag=CC
all: inc
%.lo: %.c
libtool --mode=compile $(LTFLAGS) $(CC) -c $<
libinc.la: fun.lo global.lo
libtool --mode=link $(LTFLAGS) $(CC) -o $@ $^ -rpath /usr/lib64
inc: main.o libinc.la
libtool --mode=link $(LTFLAGS) $(CC) -o $@ $^
check: inc
test "`./$< 123`" = "124"
clean:
rm -rf *.so inc .libs *.l? *.o
Утилита libtool управляет и компиляцией, и компоновкой (определяется флагом --mode=), и ей необходимо передавать дополнительные параметры (например, информацию о компиляторе), такие параметры мы храним в переменной LTFLAGS. При этом базовые флаги компиляции и компоновки (например, -fPIC для разделяемых библиотек) добавляются автоматически.
В результате компиляции порождаются .lo-файлы (так называемые «libtool objects»). Libtool object — это shell-сценарий с мета-информацией о том, где лежат «настоящие» объектные файлы (причём как собранные с флагом -fPIC для динамической компоновки, так и без него для статической).
На основе .lo-файлов создаётся «libtool archive» (.la). Это тоже shell-сценарии с мета-информацией о собранных вариантах библиотек (статическом и динамическом).
Исполняемый файл, который порождается libtool — это, конечно, тоже shell-сценарий, в котором вся информация перерабатывается, выбирается тип сборки, при необходимости указывается место расположения «настоящей» динамической библиотеки, после чего вызывается соответствующая программа. Таким образом, результат работы libtool можно запускать сразу, минуя все эти стадии.
Простейшие примеры запуска libtool для получения .lo, .la и исполняемых файлов приведены выше.
Соберём проект и рассмотрим ближе некоторые файлы:
@user
[user@VM inc-pkg]$ make
cc -g -O -c -o main.o main.c
libtool --mode=compile --tag=CC cc -c fun.c
libtool-default: compile: cc -c fun.c -fPIC -DPIC -o .libs/fun.o
libtool-default: compile: cc -c fun.c -o fun.o >/dev/null 2>&1
libtool --mode=compile --tag=CC cc -c global.c
libtool-default: compile: cc -c global.c -fPIC -DPIC -o .libs/global.o
libtool-default: compile: cc -c global.c -o global.o >/dev/null 2>&1
libtool --mode=link --tag=CC cc -o libinc.la fun.lo global.lo -rpath /usr/lib64
libtool-default: link: x86_64-alt-linux-gcc -shared -fPIC -DPIC .libs/fun.o .libs/global.o -Wl,-soname -Wl,libinc.so.0 -o .libs/libinc.so.0.0.0
libtool-default: link: (cd ".libs" && rm -f "libinc.so.0" && ln -s "libinc.so.0.0.0" "libinc.so.0")
libtool-default: link: (cd ".libs" && rm -f "libinc.so" && ln -s "libinc.so.0.0.0" "libinc.so")
libtool-default: link: ar cr .libs/libinc.a fun.o global.o
libtool-default: link: ranlib .libs/libinc.a
libtool-default: link: ( cd ".libs" && rm -f "libinc.la" && ln -s "../libinc.la" "libinc.la" )
libtool --mode=link --tag=CC cc -o inc main.o libinc.la
libtool-default: link: cc -o .libs/inc main.o ./.libs/libinc.so
[user@VM inc-pkg]$
.
├── fun.c
├── fun.lo
├── fun.o
├── global.c
├── global.lo
├── global.o
├── inc
├── libinc.h
├── libinc.la
├── main.c
├── main.o
└── Makefile
.libs/
├── fun.o
├── global.o
├── inc
├── libinc.a
├── libinc.la -> ../libinc.la
├── libinc.lai
├── libinc.so -> libinc.so.0.0.0
├── libinc.so.0 -> libinc.so.0.0.0
└── libinc.so.0.0.0
Во-первых, среди получившихся файлов появились два файла inc. Находящийся в .libs/ — непосредственно исполняемый файл. Находящийся в текущем каталоге — на самом деле shell-сценарий, в котором автоматически прописываются пути к динамическим библиотекам, и программа вызывается с их неявным указыванием:
@user
[user@VM inc-pkg]$ diff inc .libs/inc
Двоичные файлы inc и .libs/inc различаются
[user@VM inc-pkg]$ head -n1 inc
#! /bin/sh
[user@VM inc-pkg]$ grep "LD_LIBRARY_PATH" inc
relink_command="(cd /home/user/inc-pkg; { test -z \"\${LIBRARY_PATH+set}\" || unset LIBRARY_PATH || { LIBRARY_PATH=; export LIBRARY_PATH; }; }; { test -z \"\${COMPILER_PATH+set}\" || unset COMPILER_PA
TH || { COMPILER_PATH=; export COMPILER_PATH; }; }; { test -z \"\${GCC_EXEC_PREFIX+set}\" || unset GCC_EXEC_PREFIX || { GCC_EXEC_PREFIX=; export GCC_EXEC_PREFIX; }; }; { test -z \"\${LD_RUN_PATH+set}\" || unset
LD_RUN_PATH || { LD_RUN_PATH=; export LD_RUN_PATH; }; }; { test -z \"\${LD_LIBRARY_PATH+set}\" || unset LD_LIBRARY_PATH || { LD_LIBRARY_PATH=; export LD_LIBRARY_PATH; }; }; PATH=/home/user/.gemie/bin
:/home/user/bin:/usr/bin:/bin:/usr/local/bin:/usr/games; export PATH; cc -o \$progdir/\$file main.o ./.libs/libinc.so -Wl,-rpath -Wl,/home/user/inc-pkg/.libs)"
[user@VM inc-pkg]$
[user@VM inc-pkg]$ ./inc 123
124
[user@VM inc-pkg]$ .libs/inc 123
.libs/inc: error while loading shared libraries: libinc.so.0: cannot open shared object file: No such file or directory
[user@VM inc-pkg]$ LD_LIBRARY_PATH=`pwd`/.libs .libs/inc 123
124
[user@VM inc-pkg]$
.la- и .lo-файлы также не являются объектными, а лишь описывают параметры итоговой библиотеки для сборки:
@user
[user@VM inc-pkg]$ cat libinc.la
# libinc.la - a libtool library file
# Generated by libtool (GNU libtool) 2.4.7
#
# Please DO NOT delete this file!
# It is necessary for linking the library.
# The name that we can dlopen(3).
dlname='libinc.so.0'
# Names of this library.
library_names='libinc.so.0.0.0 libinc.so.0 libinc.so'
# The name of the static archive.
old_library='libinc.a'
# Linker flags that cannot go in dependency_libs.
inherited_linker_flags=''
# Libraries that this one depends upon.
dependency_libs=''
# Names of additional weak libraries provided by this library
weak_library_names=''
# Version information for libinc.
current=0
age=0
revision=0
# Is this an already installed library?
installed=no
# Should we warn about portability when linking against -modules?
shouldnotlink=no
# Files to dlopen/dlpreopen
dlopen=''
dlpreopen=''
# Directory that this library needs to be installed in:
libdir='/usr/lib64'
[user@VM inc-pkg]$ cat *.lo
# fun.lo - a libtool object file
# Generated by libtool (GNU libtool) 2.4.7
#
# Please DO NOT delete this file!
# It is necessary for linking the library.
# Name of the PIC object.
pic_object='.libs/fun.o'
# Name of the non-PIC object
non_pic_object='fun.o'
# global.lo - a libtool object file
# Generated by libtool (GNU libtool) 2.4.7
#
# Please DO NOT delete this file!
# It is necessary for linking the library.
# Name of the PIC object.
pic_object='.libs/global.o'
# Name of the non-PIC object
non_pic_object='global.o'
[user@VM inc-pkg]$