<< Предыдущий раздел | /\ Содержание | >> Следующий раздел

Процесс компиляции и сборки программ в Unix

Создание исполняемого файла из одиночного файла .c выглядит довольно просто. Сначала .c-файл компилируется в объектный код (файл .o -- "object"), который затем при помощи сборщика (loader; его еще называют линкером -- linker) с добавлением системных библиотек превращается собственно в исполняемый файл.

Создание программы из одного исходного файла

Большинство же программ состоят из нескольких исходных файлов (крупные программы содержат по несколько сотен и даже тысяч файлов). Эти файлы также компилируются в объектный код, а потом сборщик делает из них исполняемый файл.

Создание крупной программы

Многие пакеты состоят из нескольких исполняемых файлов, в этом случае каждый из них собирается точно так же. При этом многие пакеты содержат собственные библиотеки, которые также компилируются из исходных текстов в объектные файлы (совершенно аналогично обычным исходным файлам -- на рисунке это показано упрощенно для экономии места) и затем при помощи программы-библиотекаря (archiver) собираются в т.н. архивы -- файлы .a.

Компиляторы языка Си в Unix обычно называются cc (C Compiler), а зачастую (особенно в Linux) используется т.н. GNU C Compiler -- gcc. Компиляторы Си++ аналогично называются c++ и g++. Сборщики именуются ld, хотя можно для этих целей пользоваться и gcc. Библиотекарь-архиватор -- программа ar.

Замечание
Практически все современные клоны Unix поддерживают также разделяемые библиотеки (именуемые .so (shared object) или .sa (shared archive)), которые подключаются к программе непосредственно перед исполнением (аналогично .dll в Windows). Собственно, большинство библиотек являются именно разделяемыми. Здесь мы разделяемые библиотеки не затрагиваем во-первых для краткости, а во-вторых потому, что обычно при компиляции никакого внешнего отличия между .a и .so нет.

Утилита make

Для чего нужен make


Приведенная в предыдущем разделе картина способна повергнуть в уныние -- неужели все эти действия надо выполнять "вручную"?

Естественно, нет -- для этого существует утилита make, которая все и делает ("make" -- дословно "делать").

Утилита make считывает из специального файла с именем Makefile или makefile в текущей директории инструкции о том, как (при помощи каких команд) компилировать и собирать программы, а также информацию, из каких файлов состоит программа, которую надо "сделать".

Одним из главных достоинств make (чрезвычайно полезным при создании больших программ) является то, что он сравнивает времена модификации файлов, и если, к примеру, файл file1.c новее, чем получаемый из него file1.o, то make поймет, что перекомпилировать надо только его, а остальные -- не нужно (если они не изменились).

Формат Makefile


Makefile содержат три основных компонента:

Ниже приведен пример простейшего Makefile (он и используемые файлы .c доступны здесь):

CC=             gcc
CFLAGS=         -c -W -Wall

.c.o:
                $(CC) $(CFLAGS) -o $@ $< 

all:            proggie

proggie:        prg_main.o prg_funcs.o
                $(CC) -o proggie prg_main.o prg_funcs.o

Определения переменных. Строки вида "ИМЯ=значение" -- это определения переменных. Для получения значения переменной используется запись "$(ИМЯ)" (знак доллара, а за ним имя переменной в скобках). Переменная может определяться через значения других переменных, например:

CFLAGS= -c $(WARNINGOPTIONS)

Правила. Запись ".c.o:" с последующей командой означает: "для любого файла .c, чтобы из него получить одноименный файл .o, надо выполнить такую-то команду"; в данном случае --

gcc -c -W -Wall -o $@ $<

В правилах всегда используются специальные переменные "$@" и "$<". Переменная "$@" обозначает "тот файл, который надо получить" (в данном случае .o), а "$<" -- "исходный файл" (в данном случае .c). Такие переменные называются автоматическими.

Команда располагается на следующей строке, причем эта строка обязательно должна начинаться с символа табуляции.

Это довольно странное правило является основным источником ошибок и запутанности Makefile'ов -- ведь визуально отличить символ табуляции от цепочки пробелов невозможно. Поэтому, к примеру, редактор Midnight Commander автоматически цветом выделяет строки, начинающиеся с символа табуляции.

Makefile в редакторе MC

Зависимости. Запись вида

proggie: prg_main.o prg_funcs.o
означает, что файл proggie зависит от файлов prg_main.o и prg_funcs.o. Файл proggie называется целью (target), а файлы .o -- зависимостями (dependencies).

Несмотря на то, что зависимости по внешнему виду похожи на правила, make их различает.

В принципе в данном Makefile не помешали бы и строки

prog_main.o:    prog_main.c
prog_funcs.o:   prog_funcs.c
но у make хватит интеллекта догадаться об этом самому (исходя из правила ".c.o").

Более длинные правила и зависимости.

В правилах можно указывать не только одну команду, но и несколько. Дополнительные команды должны располагаться на следующих строках, причем дополнительные строки также должны начинаться с символа табуляции.

Аналогично после зависимости на следующих строках можно указать команды, при помощи которых ее надо "делать". Например,

prog_main.o:    prog_main.c
                $(CC) $(CFLAGS) -D_GNU_SOURCE -o prog_main.o prog_main.c

При этом команды, определяемые в правиле ".c.o" использоваться не будут.

Замечание
В Linux используется GNU-версия программы make (GNU Make), имеющий более богатый синтаксис (он включает условные конструкции, директиву include для "вставки" других файлов, более гибкий формат определения правил (вида %.c: %.o)). Поэтому многие программы пользуются Makefile'ами именно под GNU Make. В других ОС для вызова GNU Make обычно служит команда "gmake".

Запуск make


При запуске без параметров make пытается сделать самую первую цель из перечисленных в Makefile. Обычно в качестве первой ставят дополнительную цель "all", зависящую от всех файлов, которые надо сделать для изготовления программы.

Если же указать в командной строке имя цели, то make выполнит команды, необходимые для этой цели (и, при надобности, команды для изготовления промежуточных целей).

Например, команда

make prog_main.o
выполнит только команду компиляции файла prog_main.c в prog_main.o, да и то только если файл .c новее чем .o.

Если запустить make с ключом "-n", то он лишь напечатает на экране команды, которые следует выполнить, не не станет реально их запускать. (Мнемоника для запоминания: "-n" -- "do Nothing" -- "Nичего не делай".)

Ключ "-f" позволяет заставить make читать инструкции из указанного файла, вместо Makefile или makefile, используемых по умолчанию. Пример:

make -f Makefile.irix

Если при компиляции или сборке возникает ошибка, то make прекращает процесс, не выполняя последующие команды.


Если возникает необходимость создать свой Makefile, то лучше всего взять за образец Makefile от какой-нибудь несложной программы.

Кроме того, очень подробная документация (включая введение для начинающих) есть в info-документации на GNU Make, вызываемой командой "info make", -- там содержится буквально "все, что вы хотели знать о Make, но боялись спросить".

Конфигурация, компиляция и установка программ из исходных текстов

При разработке программ под Unix авторы обычно стараются сделать так, чтобы их продукт можно было использовать с любым клоном Unix.

Но, поскольку разные клоны довольно сильно отличаются (особенно BSD-системы от SystemV), то написать код, который компилировался и работал бы без изменений на большом количестве систем, практически невозможно. Причем, чем больше программа, тем эта задача сложнее.

Поэтому перед компиляцией надо сначала произвести настройку.

Для этого обычно используется один из трех способов:

  1. Если к дистрибутиву прилагается скрипт configure, то надо его запустить (командой "./configure").
  2. Возможно, в Makefile имеется специальная цель "config" -- в таком случае для конфигурирования служит команда "make config".
  3. В Makefile могут быть разные цели для разных ОС, в таком случае для компиляции под Irix надо будет дать команду типа "make irix", а под Linux -- "make linux".

Первые два способа создают файлы настроек, специфичные для данной ОС (обычно это или include-файлы (.h), или же сразу генерируется нужный Makefile). В третьем случае сразу запускается компиляция с параметрами, специфичными для данной системы.

Какой из способов используется в конкретной программе -- надо смотреть в прилагаемой документации (т.е. в файлах типа README и INSTALL).

Непосредственно для инсталляции же практически всегда используется специальная цель "install" в Makefile. Т.е. для того, чтобы после компиляции и сборки установить программу, надо дать команду

make install

Для примера рассмотрим компиляцию двух программ -- Wget, использующей первый способ, и NetCat, использующей третий.

Замечание
В принципе, обе программы (вследствие своей популярности) существуют и в виде .rpm-пакетов, но они являют собой очень простые и "чистые" примеры обоих вариантов конфигурирования, поэтому мы ими и воспользуемся.

Компиляция и установка программы Wget


Wget -- это утилита командной строки, служащая для скачивания файлов с WWW- и FTP-серверов. Она имеет множество достоинств, в частности, рекуррентную перекачку и автоматическую докачку после обрыва соединения. (Более подробно Wget описан в разделе "Программа wget").

Возьмем локальную копию дистрибутива.

Развернув архив, мы увидим в нем файл INSTALL и скрипт configure, а вот Makefile там нет!

bobby:~/soft% tar xfz ~/wget-1.5.3.tar.gz
bobby:~/soft% cd wget-1.5.3/
bobby:~/soft/wget-1.5.3% ls
AUTHORS         MAILING-LIST    aclocal.m4      configure.in    src/
COPYING         Makefile.in     config.guess*   doc/            stamp-h.in
ChangeLog       NEWS            config.sub*     install-sh*     util/
INSTALL         README          configure*      mkinstalldirs*  windows/
MACHINES        TODO            configure.bat   po/
bobby:~/soft/wget-1.5.3% _

Как и следовало ожидать, в INSTALL рекомендуют запустить configure, а затем набрать "make".

В ответ на команду "./configure" компьютер сообщает, что конфигурирует программу GNU Wget 1.5.3, а затем около минуты печатает список свойств системы, которые он проверяет. В конце он уведомляет, что создает несколько Makefile (в разных поддиректориях).

Набрав теперь "make", запускаем процесс компиляции и сборки, который занимает некоторое время.

По его окончании можно набрать "make install". Вот и все!

Компиляция программы NetCat


NetCat -- это, вообще говоря, хакерская программа для "отладки" сети, позволяющая передавать данные в сеть по протоколам TCP и UDP и принимать их из сети. Как обычно, воспользуемся локальной копией дистрибутива версии 1.10.

Первым делом развернем архив:

bobby:~/soft% mkdir netcat
bobby:~/soft% cd netcat
bobby:~/soft/netcat% tar xfz ~/nc110.tgz
bobby:~/soft/netcat% ls
Changelog     README        generic.h     netcat.c      stupidh*
Makefile      data/         netcat.blurb  scripts/
bobby:~/soft/netcat% _

Из файла README узнаем, что для компиляции надо указать команде make тип ОС. То же увидим, и запустив make без параметров:

bobby:~/soft/netcat% make
Usage:  make  <systype>  [options]
bobby:~/soft/netcat% _

Беглый просмотр Makefile показывает, что в нем есть цель под названием "linux". Итак,

bobby:~/soft/netcat% make linux
make -e nc  XFLAGS='-DLINUX' STATIC=-static
make[1]: Entering directory `/export/bobby/ivanov/soft/netcat'
cc -O -s          -DLINUX -static -o nc netcat.c 
make[1]: Leaving directory `/export/bobby/ivanov/soft/netcat'
bobby:~/soft/netcat% ls
Changelog     README        generic.h     netcat.blurb  scripts/
Makefile      data/         nc*           netcat.c      stupidh*
bobby:~/soft/netcat% ls -l nc
-rwxr-xr-x   1 ivanov   lab5       142668 May 10 18:21 nc*
bobby:~/soft/netcat% _

Как мы видим, после компиляции появился исполняемый файл nc. Поскольку цель "install" в Makefile отсутствует, то для установки надо просто скопировать этот файл в общесистемную директорию:

bobby:~/soft/netcat% su
Password:
bobby:~/soft/netcat# cp nc /usr/local/bin/
bobby:~/soft/netcat# _

Особенности компиляции программ под X-Window

Imakefile и программа xmkmf


К моменту появления системы X-Window проблема различий при компиляции под разные клоны Unix стала уже широко известна, и был разработан способ, позволяющий унифицированно компилировать ПО для X под разными системами.

Идея заключалась в том, чтобы перед компиляцией Makefile автоматически генерировался специальной утилитой xmkmf, "знающей" про специфику конкретной системы, из другого файла, под названием Imakefile. В Imakefile же на некоем специальном языке записывается примерно та же информация, что в Makefile.

Замечание
Аббревиатура "xmkmf" расшифровывается очень просто -- "MaKe MakeFile" -- "сделай Makefile", а "x" -- префикс, обозначающий принадлежность программы к X-Window.

Хотя замысел был очень хороший, реализация оставляет желать лучшего. Во-первых, язык Imakefile'ов -- не менее "птичий", чем у Makefile, так что людей, умеющих их создавать, еще меньше. Во-вторых, xmkmf берет описание системы из нескольких файлов конфигурации (в глубине директории /usr/X11R6/), а они зачастую оказываются несовместимы с конкретным Imakefile, и xmkmf просто завершается с каким-нибудь маловразумительным сообщением об ошибке.

Тем не менее, большинство программ под X-Window поставляются именно с Imakefile.

Пример сборки и установки программы под X-Window


В качестве примера рассмотрим сборку и установку программы XRoach (той самой, что пускает бегать по экрану тараканов). Воспользуемся локальной копией дистрибутива.

Сначала развернем дистрибутив:

bobby:~/soft% tar xfz ~/xroach.tar.gz
bobby:~/soft% cd xroach/
viper:~/soft/xroach% ls
Imakefile     roach060.xbm  roach165.xbm  roach270.xbm  squish.xbm
README.linux  roach075.xbm  roach180.xbm  roach285.xbm  xroach.a
patchlevel.h  roach090.xbm  roach195.xbm  roach300.xbm  xroach.c
roach000.xbm  roach105.xbm  roach210.xbm  roach315.xbm  xroach.man
roach015.xbm  roach120.xbm  roach225.xbm  roach330.xbm
roach030.xbm  roach135.xbm  roach240.xbm  roach345.xbm
roach045.xbm  roach150.xbm  roach255.xbm  roachmap.h
bobby:~/soft/xroach% _

Прочтя файл README.linux, мы узнаем лишь, что при компиляции должно быть предупреждение (warning) в строке 373.

Запускаем xmkmf и затем make:

bobby:~/soft/xroach% xmkmf
imake -DUseInstalled -I/usr/X11R6/lib/X11/config
bobby:~/soft/xroach% make
gcc -O2 -fno-strength-reduce     -I/usr/X11R6/include   -Dlinux -D__i3
86__ -D_POSIX_C_SOURCE=199309L -D_POSIX_SOURCE -D_XOPEN_SOURCE=500L -D
_BSD_SOURCE -D_SVID_SOURCE   -DFUNCPROTO=15 -DNARROWPROTO     -c xroac
h.c -o xroach.o
xroach.c: In function `FindRootWindow':
xroach.c:373: warning: passing arg 9 of `XGetWindowProperty' from inco
mpatible pointer type
xroach.c:373: warning: passing arg 12 of `XGetWindowProperty' from in
compatible pointer type
rm -f xroach
gcc -o xroach -O2 -fno-strength-reduce      -L/usr/X11R6/lib xroach.o
 -lXext -lX11   -lm   
bobby:~/soft/xroach% _

Теперь, аналогично обычным программам, делаем "make install":

bobby:~/soft/xroach% su
Password:
bobby:~/soft/xroach# make install
install -c -s  xroach /usr/X11R6/bin/xroach
install in . done
bobby:~/soft/xroach# _

Единственно что, автор поленился сделать автоматическую установку man-страницы, хотя она и есть. Что ж, не беда -- скопируем ее в нужное место "руками":

bobby:~/soft/xroach# cp xroach.man /usr/X11R6/man/man1/
bobby:~/soft/xroach# _

(Поскольку "make install" установил программу в /usr/X11R6/bin/, то и man-страницу надо положить "рядом" -- внутри /usr/X11R6/. А поскольку xroach -- пользовательская программа, то ее man-страница должна лежать в разделе 1 (поддиректория man1/.)


<< Предыдущий раздел | /\ Содержание | >> Следующий раздел