Текст книги "Основы программирования в Linux"
Автор книги: Нейл Мэтью
Соавторы: Ричард Стоунс
Жанры:
Программирование
,сообщить о нарушении
Текущая страница: 34 (всего у книги 67 страниц)
Часто бывает полезно создать вместо одного выходного файла несколько или собрать несколько групп команд в одном файле. Вы можете сделать это, расширив свой make-файл. В упражнении 9.3 вы добавите задание clean
на удаление ненужных объектных файлов, и задание install
, перемещающее окончательное приложение в другой каталог.
Далее приведена следующая версия make-файла с именем Makefile3:
all: myapp
# Какой компилятор
CC = gcc
# Куда установить
# INSTDIR=/usr/local/bin
# Где хранятся файлы include
INCLUDE = .
# Опции для разработки
CFLAGS = -g -Wall -ansi
# Опции для рабочей версии
# CFLAGS = -О -Wall -ansi
myapp: main.o 2.o 3.o
$(CC) -о myapp main.о 2.о 3.o
main.о: main.c a.h
$(CC) -I$(INCLUDE) $(CFLAGS) -c main.c
2.о: 2.c a.h b.h
$(CC) -I$(INCLUDE) $(CFLAGS) -c 2.c
3.o: 3.c b.h c.h
$(CC) -I$(INCLUDE) $(CFLAGS) -c 3.c
clean:
-rm main.o 2.o 3.o
install: myapp
@if [ -d $(INSTDIR) ];
then
cp myapp $(INSTDIR);
chmod a+x $(INSTDIR)/myapp;
chmod og-w $(INSTDIR)/myapp;
echo «Installed in $(INSTDIR)»;
else
echo «Sorry, $(INSTDIR) does not exist»;
fi
В этом make-файле есть несколько вещей, на которые следует обратить внимание. Во-первых, специальная цель all
, которая задает только один выходной файл myapp. Следовательно, если вы выполняете make
без указания задания, поведение по умолчанию – сборка файла myapp.
Следующая важная особенность относится к двум дополнительным заданиям: clean
и install
. В задании clean
для удаления объектных файлов применяется команда rm
. Команда начинается со знака -
, тем самым сообщая команде make
о необходимости игнорировать результат команды, поэтому make
выполнится успешно, даже если объектных файлов нет и команда rm
вернет ошибку. Правила для задания clean
ни от чего не зависят, остаток строки после clean:
пуст. Таким образом, задание всегда считается измененным со времени последнего выполнения, и его правило всегда выполняется, если clean
указывается в качестве задания.
Задание install
зависит от myapp, поэтому команда make
знает, что должна создать myapp перед выполнением других команд задания install
. Правила выполнения install
состоят из нескольких команд сценария командной оболочки. Поскольку команда make
запускает командную оболочку для выполнения правил и применяет новую командную оболочку для каждого правила, следует добавлять обратные слэши, чтобы все команды сценария были в одной логической строке и передавались для выполнения все вместе одному сеансу командной оболочки. Эта команда начинается со знака отменяющего вывод команды в стандартный файл вывода перед выполнением правила.
Задание install
выполняет несколько команд одну за другой для установки приложения в указанное расположение. Оно не проверяет успешность выполнения предыдущей команды перед выполнением следующей. Если очень важно, чтобы последующие команды выполнялись только в случае успешного завершения предыдущей, можно написать команды, объединенные с помощью операции &&,
как показано далее:
@if [ -d $(INSTDIR) ];
then
cp myapp $(INSTDIR) &&
chmod a+x $(INSTDIR)/myapp &&
chmod og-w $(INSTDIR/myapp &&
echo «Installed in $(INSTDIR)» ;
else
echo «Sorry, $(INSTDIR) does not exist»; false ;
fi
Как вы, вероятно, помните из главы 2, у командной оболочки есть команда and
, благодаря которой выполнение последующей команды возможно лишь при успешном завершении предыдущей. В данном примере вас не слишком заботит успешное завершение предыдущих команд, поэтому можно придерживаться более простой формы.
Если вы – обычный пользователь, то у вас может не быть прав на установку новых команд в каталог /usr/local/bin. Можно изменить в make-файле каталог установки, изменить права доступа к этому каталогу или заменить пользователя (с помощью команды su
) на root перед запуском make install
.
$ rm *.о myapp
$ make -f Makefile3
gcc -I. -g -Wall -ansi -c main.c
gcc -I. -g -Wall -ansi -c 2.c
gcc -I. -g -Wall -ansi -с 3.c
gcc -o myapp main.о 2.o 3.o
$ make -f Makefile3
make: Nothing to be done for 'all'.
$ rm myapp
$ make -f Makefile3 install
gcc -o myapp main.o 2.o 3.o
Installed in /usr/local/bin
$ make -f Makefile3 clean
rm main.о 2.о 3.о
$
Как это работает
Сначала вы удаляете файл myapp и все объектные файлы. Команда make
самостоятельно выбирает задание all
, которое приводит к сборке myapp. Затем вы снова запускаете команду make
, но т.к. у файла myapp свежая версия, make
не делает ничего. Далее вы удаляете файл myapp и выполняете make install
. Эта команда создает заново двоичный файл и копирует его в каталог установки. В заключение выполняется команда make clean
, удаляющая объектные файлы.
До сих пор вы описывали в make-файле подробно, как выполнять каждый шаг. В действительности у команды make
есть много встроенных правил, которые существенно упрощают make-файлы, особенно если у вас много исходных файлов. Для того чтобы проверить это, создайте традиционную программу, приветствующую мир:
#include
#include
int main() {
printf(«Hello Worldn»);
exit(EXIT_SUCCESS);
}
He задавая make-файла, попробуйте откомпилировать ее с помощью команды make
:
$ make foo
сс foo.с -о foo
$
Как видите, make
знает, как запустить компилятор, хотя в данном случае она выбирает сс
вместо gcc
(в ОС Linux это нормально, потому что cc
– обычно ссылка на gcc
). Иногда эти встроенные правила называют подразумеваемыми правилами. Стандартные правила используют макросы, поэтому задавая некоторые новые значения для макросов, вы можете изменить стандартное поведение.
$ rm foo
$ make CC=gcc CFLAGS="-Wall -g" foo
gcc -Wall -g foo.с -o foo
$
С помощью опции -p
можно попросить команду make
вывести на экран встроенные правила. Их так много, что мы не можем привести в книге все встроенные правила, ограничимся коротким фрагментом вывода команды make -p
версии GNU, демонстрирующим часть этих правил:
OUTPUT_OPTION = -o $@
COMPILE.с = $(CC) $(CFLAGS) $(CPPFLAGS) $(TARGET_ARCH) -с
%.о: %.с
# commands to execute (built-in) :
$(COMPILE.с) $(OUTPUT_OPTION) $<
Теперь, принимая во внимание описанные встроенные правила, вы можете упростить ваш make-файл, удалив правила для создания объектных файлов и оставив только зависимости, таким образом, соответствующая секция make-файла читается просто:
main.о: main.c a.h
2.о: 2.с a.h b.h
3.o: 3.c b.h c.h
Эту версию можно найти в загружаемом из Интернета программном коде, в файле Makefile4.
Суффиксы и шаблоны правилВстроенные правила, которые вы видели, действуют, используя суффиксы (подобные расширениям файлов в системах Windows и MS-DOS), поэтому команда make
, получая файл с одним окончанием, знает, какое правило применять для создания файла с другим окончанием. Чаще всего используется правило для создания файла, заканчивающегося .о, из файла с окончанием .c. Это правило для применения компилятора, компилирующего исходный файл, но не компонующего.
Порой возникает потребность в создании новых правил. Авторы приучили себя работать с исходными файлами, которые необходимо компилировать несколькими разными компиляторами: двумя в среде MS-DOS и gcc
в ОС Linux. Для того чтобы осчастливить один из компиляторов MS-DOS, исходные файлы на языке С++, а не С должны иметь расширение cpp. К сожалению, у версии команды make
, применяемой в Linux, в то время не было встроенного правила для компиляции файлов с окончанием .cpp. (Существовало правило для суффикса .cc, более распространенного расширения файла на C++ в среде UNIX.)
Следовательно, нужно было либо задавать правило для каждого отдельного исходного файла, либо научить make
новому правилу для создания объектных файлов из файлов с расширением cpp. Учитывая, что в том проекте было довольно большое количество исходных файлов, определение нового правила сэкономило бы много времени на наборе и существенно облегчило бы добавление новых исходных файлов в проект.
Для вставки правила с новым суффиксом сначала добавьте строку в make-файл, информирующую команду make
о новом суффиксе; далее можно написать правило, используя новый суффикс. Команда make
применяет специальную синтаксическую запись
.
для определения общего правила создания файлов с новым суффиксом из файлов с тем же основным именем, но старым суффиксом.
Далее приведен фрагмент make-файла с новым общим правилом для компиляции файлов с суффиксом .срр в файлы с суффиксом .о:
.SUFFIXES: .cpp
.cpp.o:
$ (CC) -xc++ $(CFLAGS) -I$(INCLUDE) -с $<
Особая зависимость .cpp.o:
информирует команду make
о том, что следующие правила предназначены для трансляции файла с суффиксом .cpp в файлы с суффиксом .о. При написании этой зависимости применяются имена специальных макросов, поскольку неизвестны реальные имена файлов, которые будут транслироваться. Для того чтобы понять это правило, нужно просто вспомнить, что символы $<
заменяются начальным именем файла (со старым суффиксом). Имейте в виду, что вы сообщили make
только о том, как получить из файла с суффиксом .cpp файл с суффиксом .о; как из объектного файла получить двоичный исполняемый файл, команда make
уже знает.
После запуска команда make
применяет ваше новое правило для получения из файла bar.cpp файла bar.o; далее она использует свои встроенные правила для превращения файла с суффиксом .о в исполняемый файл. Дополнительный флаг -xc++
должен сообщить программе gcc
о том, что она имеет дело с исходным файлом на языке C++.
В наши дни команда make
знает, как работать с исходными файлами на С++ с расширениями cpp, но данный метод полезен для преобразования файла одного типа в файл другого типа.
Самые последние версии команды make включают в себя альтернативную синтаксическую запись для достижения того же эффекта и многое другое. Например, правила с шаблонами используют знак подстановки %
для сопоставления имен файлов и не полагаются на одни лишь расширения этих имен.
Далее приведено правило с шаблоном, эквивалентное предыдущему правилу с суффиксом .cpp:
%.cpp: %o
$(СС) -xc++ $(CFLAGS) -I$(INCLUDE) -с $<
Когда вы работаете над большими проектами, часто удобно управлять компиляцией нескольких программных единиц с помощью библиотеки. Библиотеки – это файлы, в соответствии с соглашением имеющие расширение a (archive) и содержащие коллекцию объектных файлов. Для работы с библиотеками у команды make есть специальная синтаксическая запись, которая существенно облегчает управление ими.
Синтаксическая запись lib(file.о)
означает объектный файл file.o, хранящийся в библиотеке lib.а. У команды make
есть встроенное правило для управления библиотеками, которое обычно эквивалентно приведенному далее фрагменту:
.с.а:
$(CC) -с $(CFLAGS)
$< $(AR) $(ARFLAGS) $@ $*.о
Макросы $(AR)
и $(ARFLAGS)
подразумевают команду ar
и опции rv
соответственно. Довольно краткая синтаксическая запись информирует команду make
о том, что для включения файла .с в библиотеку .а следует применить два следующих правила:
□ первое правило говорит о том, что команда make
должна откомпилировать исходный файл и сформировать объектный файл;
□ второе правило предписывает применить команду ar
для модификации библиотеки, заключающейся в добавлении нового объектного файла.
Итак, если у вас есть библиотека fud, содержащая файл bas.o, в первом правиле $<
заменяется именем bas.c. Во втором правиле $@
заменяется именем библиотеки fud.а
и $*
заменяется именем bas
.
Выполните упражнение 9.4.
Упражнение 9.4. Управление библиотекой
Правила управления библиотеками очень просто применять на практике. В этом упражнении вы измените свое приложение, сохранив файлы 2.o и 3.o в библиотеке mylib.a. Make-файл потребует лишь нескольких изменений и его новый вариант Makefile5 будет выглядеть следующим образом:
all: myapp
# Какой компилятор
CC = gcc
# Куда установить
INSTDIR = /usr/local/bin
# Где хранятся файлы include
INCLUDE =
# Опции для разработки
CFLAGS = -g -Wall -ansi
# Опции для рабочей версии
# CFLAGS = -O -Wall -ansi
# Локальные библиотеки
MYLIB = mylib.a
myapp: main.o $(MYLIB)
$(CC) -o myapp main.o $(MYLIB)
$(MYLIB): $(MYLIB)(2.o) $(MYLIB)(3.o)
main.o: main.c a.h
2.o: 2.c a.h b.h
3.o: 3.c b.h c.h
clean:
-rm main.o 2.o 3.o $(MYLIB)
install: myapp
@if [ -d $(INSTDIR) ];
then
cp myapp $(INSTDIR);
chmod a+x $(INSTDIR)/myapp;
chmod og-w $(INSTDIR)/myapp;
echo «Installed in $(INSTDIR)»;
else
echo «Sorry, $(INSTDIR) does not exist»;
fi
Обратите внимание на то, как вы разрешили правилам по умолчанию выполнить большую часть работы. Теперь проверьте новую версию make-файла:
$ rm -f myapp *.o mylib.a
$ make -f Makefile5
gcc -g -Wall -ansi -с -o main.о main.c
gcc -g -Wall -ansi -с -o 2.о 2.c
ar rv mylib.a 2.o
a – 2.o
gcc -g -Wall -ansi -с -о 3.o 3.c
ar rv mylib.a 3.o
a – 3.о
gcc -o myapp main.о mylib.a
$ touch c.h
$ make -f Makefile5
gcc -g -Wall -ansi -с -о 3.o 3.c
ar rv mylib.a 3.o
r – 3.о
gcc -o myapp main.о mylib.a
$
Как это работает
Сначала вы удаляете все объектные файлы и библиотеку и разрешаете команде make
создать файл myapp, что она и делает, откомпилировав и создав библиотеку перед тем, как компоновать файл main.о с библиотекой для создания исполняемого файла myapp. Далее вы тестируете зависимость для файла 3.o, которая информирует команду make
о том, что, если меняется файл c.h, файл 3.c следует заново откомпилировать. Она делает это корректно, откомпилировав файл и обновив библиотеку перед перекомпоновкой, создающей новую версию исполняемого файла myapp.
При работе над большими проектами порой бывает удобно отделить от основных файлов файлы, формирующие библиотеку, и поместить их в подкаталог. С помощью команды make
можно сделать это двумя способами.
Во-первых, можно создать в подкаталоге второй make-файл для компиляции файлов, сохранения их в библиотеке и последующего копирования библиотеки на уровень вверх, в основной каталог. При этом в основном make-файле, хранящемся в каталоге более высокого уровня, есть правило формирования библиотеки, в котором описан запуск второго make-файла следующим образом:
mylib.a:
(cd mylibdirectory;$(MAKE))
Это правило гласит, что вы всегда должны пытаться создать mylib.a с помощью команды make
. Когда make
инициирует правило создания библиотеки, она изменяет каталог на mylibdirectory и затем запускает новую команду make
для управления библиотекой. Поскольку для этого запускается новая командная оболочка, программа, применяющая make-файл, не выполняет команду cd
. А командная оболочка, запущенная для выполнения правила построения библиотеки, находится в другом каталоге. Скобки обеспечивают выполнение всего процесса в одной командной оболочке.
Второй способ заключается в применении нескольких макросов в одном make-файле. Дополнительные макросы генерируются добавлением символа D
для каталога или символа F
для имени файла к тем макросам, которые мы уже обсуждали. Вы можете переписать встроенное правило с суффиксами .с.о
.c.o:
$(СС) $(CFLAGS) -с $(@D)/$(
для компиляции файлов в подкаталоге и сохранения в нем объектных файлов. Затем вы обновляете библиотеку в текущем каталоге с помощью зависимости и правила, наподобие приведенных далее:
mylib.a: mydir/2.o mydir/3.о
ar -rv mylib.a $?
Вы должны решить, какой из способов предпочтительнее в вашем проекте. Многие проекты просто избегают применения подкаталогов, но это может привести к непомерному разрастанию исходного каталога. Как видно из только что приведенного краткого обзора, команду make
можно использовать с подкаталогами и сложность возрастает при этом лишь незначительно.
Для GNU-команды make
и GNU-компилятора gcc
существуют две интересные дополнительные опции.
□ Первая – опция -jN
(«jobs») команды make
. Она позволяет make
выполнять N
команд одновременно. Там, где несколько разных частей проекта могут компилироваться независимо, команда make
запускает несколько правил в одно и то же время. В зависимости от конфигурации вашей системы эта возможность может существенно сократить время, затраченное на перекомпиляцию. Если у вас много исходных файлов, может быть стоит воспользоваться этой опцией. Как правило, небольшие числа, например -j3
, – хорошая отправная точка. Если вы делите компьютер с другими пользователями, применяйте эту опцию с осторожностью. Другие пользователи могут не одобрить запуск большого количества процессов при каждой вашей компиляции!
□ Второе полезное дополнение – опция -MM
для gcc
. Она формирует список зависимостей в форме, подходящей для команды make
. В проекте со значительным числом исходных файлов, каждый из которых содержит разные комбинации заголовочных файлов, бывает трудно (но крайне важно) корректно определить зависимости. Если сделать каждый исходный файл зависимым от всех заголовочных файлов, иногда вы будете компилировать файлы напрасно. С другой стороны, если вы пропустите какие-то зависимости, возникнет еще более серьезная проблема, поскольку в этом случае вы не откомпилируете заново те файлы, которые нуждаются в перекомпиляции.
Выполните упражнение 9.5.
Упражнение 9.5. Использование gcc -MM
В этом упражнении вы примените опцию -MM
в программе gcc
для генерации списка зависимостей вашего примера:
$ gcc -MM main.с 2.с 3.с
main.о: main.c a.h
2.о: 2.с a.h b.h
3.o: 3.с b.h c.h
$
Как это работает
Компилятор gcc
просто просматривает исходные файлы, ищет заголовочные файлы и выводит требующиеся строки зависимостей в формате, готовом к вставке в make– файл. Вы должны лишь сохранить вывод во временном файле и затем вставить его в make-файл, чтобы иметь безошибочный набор зависимостей. Если вы пользуетесь копией, полученной от gcc
, для появления ошибок в ваших зависимостях просто нет оснований!
Если вы хорошо знакомы с make-файлами, можно попробовать применить средство makedepend
, которое выполняет функцию, аналогичную опции -MM
, но вставляет полученный список зависимостей в конец реального заданного вами make-файла.
Перед завершением темы make-файлов, быть может, стоит подчеркнуть, что не следует ограничивать применение make-файлов только компиляцией кода и созданием библиотек. Их можно использовать для автоматизации любой задачи, в которой есть последовательность команд, формирующих из входного файла некоторого типа выходной файл. Типичным "некомпиляционным" применением может быть вызов программ awk
, или sed
для обработки некоторых файлов или генерация интерактивного справочного руководства. Вы можете автоматизировать практически любую обработку файлов, если на основании информации о дате и времени модификации файла можете определить, какие из файлов были изменены.
Другая возможность управления вашими сборками или на самом деле другой способ автоматизации задач – утилита ANT. Это средство на базе языка Java, использующее файлы конфигурации, написанные на языке XML. Ее обычно не применяют в ОС Linux для автоматизации создания исполняемых файлов из файлов на языке С, поэтому мы не будем обсуждать ее в книге. Более подробную информацию об ANT можно найти на Web-сайте http://ant.apache.org/.
Управление исходным кодом
Если вы ушли от простых проектов и особенно если несколько человек работает над проектом, управление изменениями, вносимыми в исходные файлы, становится важной составляющей, которая позволяет избежать конфликтных корректировок и отслеживать сделанные изменения.
В среде UNIX есть несколько широко распространенных систем управления исходными файлами:
□ SCCS (Source Code Control System);
□ RCS (Revision Control System);
□ CVS (Concurrent Version System);
□ Subversion.
SCCS первоначально была системой управления исходным кодом, введенной компанией AT&T в версии System V ОС UNIX, а сейчас она – часть стандарта X/Open. RCS была разработана позже как бесплатная замена SCCS и распространяется Фондом бесплатного программного обеспечения (Free Software foundation). RCS функционально очень похожа на SCCS, но с интуитивно более понятным интерфейсом и некоторыми дополнительными опциями, поэтому система SCCS по большей части вытеснена RCS.
Утилиты RCS обычно включены в дистрибутивы Linux или же их можно загрузить из Интернета вместе с исходными файлами с Web-сайта Фонда бесплатного программного обеспечения со страницы http://directory.fsf.org/rcs.html.
CVS – более передовая, чем SCCS или RCS, система, которая может быть инструментом для совместных разработок на базе Интернета. Ее можно найти в большинстве дистрибутивов Linux или по адресу http://www.nongnu.org/cvs/. У этой системы два существенных преимущества по сравнению с RCS: ее можно применять в сетевых соединениях и она допускает параллельные разработки.
Subversion – новое детище, входящее в блок, проектируемый для замены системы CVS когда-нибудь в будущем. Начальную страницу Web-сайта этой системы можно найти по адресу http://www.subversion.org.
В этой главе мы сосредоточимся на системах RCS и CVS; выбор RCS объясняется легкостью ее использования в индивидуальных проектах, хорошей интегрированностью с командой make, a CVS выбрана потому, что это самая популярная форма управления исходным кодом, применяемая в совместных проектах. Мы также кратко сравним команды RCS с командами SCCS, поскольку последняя обладает статусом стандарта POSIX, и некоторые пользовательские команды CVS с командами системы Subversion.