LG.BALUKATION's Weblog

Ничего, это тоже кое-что… А при желании из него можно сделать что угодно

make

Posted by LG.BALUKATION на 2008/05/27

Вроде бы в минувшую пятницу я запалился, что жахну пост о make. Я не являюсь экспертом иль даж хотяб паверюзером в этой проге, просто когда-то мне было интересно что за хрень там вечно пишется и хотелось научиться самому писать простенькие Makefile =)

Итак, не секрет, что для сбора и развёртки более-менее мелкой проги обычно надо выполнить простую последовательность действий. Проблема в том, что эти действия приходится выполнять часто — для каждого испытания проги и это несколько утомляет…

Объёмы кода растут и часто его уже не сваливают в один файл, разделяя на несколько связанных модулей или быть может даже библиотеки. Тут уже приходится не только заботится о компиляции каждого файла, но и связывать всё это дело в некую общую сущность. Интересной мыслью является ещё и отслеживание изменений относительно прошлых сборок, чтобы компилить только обновлённые модули и тем самым экономить время при пересборке. Однако этот подход естественно обладает и недостатками, так что юзают его не всегда.

Традиционно в сях нет решения этих проблем (например в паскале есть кое что для раздельной компиляции, а в сях и асме нету). Зато есть наборы стороннего софта, призванного покрыть эти недостатки. Всякие там IDE обычно поддерживают так называемые «проекты», описывающие кроме самого исходного кода и ресурсов так же необходимые для сборки действия. Однако есть более традиционная технология, названная make — часто даже проекты в IDE всего-лишь «обёртки» над make.

Существует множество разновидностей make и все они обычно не совместимы в деталях, но в общих моментах они как правило идентичны. Лично я вроде бы и не пользуюсь «фирменными» наворотами, так что рассказанное тут должно быть совместимо со всеми вариациями make. Если что — у меня GNU Make 3.81 (в системе Gentoo Linux).

Технически make — это интерпретатор некоего скриптового языка, весьма похожего на язык shell. При запуски утилиты, она ищет в текущем каталоге сценарий сборки (традиционно именуемый Makefile или makefile) и выполняет его. Имя сценария можно указать и явно с помощью флага f, но в данном посте эта фича рассмотрена не будет.

Сценарий сборки представляет собой особо оформленный текстовый файл. В этом файле прописываются переменные, цели, их зависимости и собственно сами команды, необходимые для выполнения целей.

Переменными являются всякие нужные переменные окружения, которые в общем-то и не обязательны. Но в большом проекте удобней пользоваться ими, да и портируемость это обычно повышает. Они представляют собой строки символов, порой являющимися результатом выполнения неких команд. Записываются как пары ‘ИМЯ = знаение’, традиции подразумевают использование в имени именно заглавных букв (как например в константах или директивах препроцессора).

Целью может быть как весь проект, так и отдельная его часть или например файл. У цели могут быть зависимости — это те цели, которые должны быть выполнены раньше текущей. Например, вполне логично прописать, что цель проекта зависит от целей всех модулей проекта, а они в свою очередь зависят от файлов с кодом этих модулей и быть может чего-то ещё.

Подразумевается, что при запуске make, цель будет явно указана — иначе make попробует выполнить цель, описанную в файле первой. Естественно, при этом будут рассмотрены все зависимости. Традиционно первой цели дают имена вроде «all» или «build» и эта цель описывает весь проект — чтобы запустив make без явного указания цели можно было получить готовое приложения, а не например только какой-нить его модуль =) Ещё очень часто встречаются цели вроде «clean» (очистка проекта — удаление всех временных файлов, объектного кода, бинарника и т. п.) и «install» (установка приложения, развёртывание уже собранной проги в системе).

При описании цели следует её имя, потом после двоеточия через пробел список зависимостей, а начиная со следующей строчки идут операторы. Имя может совпадать с именем какого-нить файла. Зависимости перечисляют требуемые цели (или файлы!). Если хоть одна из этих целей ещё не выполнена, то make сперва дождётся её выполнения и лишь потом выполнит цель. В случае, когда имена целей/зависимостей соответствуют файлам, то make смотрит на даты… Пусть например у нас будет цель с именем «target.o», зависящая от цели с файлом «target.cpp» и телом, обеспечивающим компиляцию файла target.cpp в объектный модуль target.o. Тогда если файл target.o уже существует (т. е. цель вроде бы и выполнена), но дата этого файла старше даты последнего изменения target.cpp (т. е. после компиляци в файле что-нить подправили), будут выполнены команды пересборки этого модуля. Если же объектный файл новее файла с исходным кодом, пересборки выполнено не будет.

Этот механизм позволяет собрать один раз весь проект и потом, в процессе правки, автоматически пересобирать только изменённые модули. Но стоит быть осторожным, т. к. серьёзные изменения в одном модуле могут сделать невозможной работу других и тогда проект перестанет быть работоспособным. Например, если другие модули используют реализованные в текущем функции, а правка изменила имена и/или параметры этих функций. В таких ситуациях надо сделать очистку проекта (удалить все объектные файлы и бинарник), после чего заново собрать весь проект. Можно вообще не привязывать имена целей к файлам, тогда проект целиком будет собираться каждый раз из текущего исходного кода без всякого учёта — менялся он или нет. Такой подход тратит больше времени на каждую перекомпиляцию, но и описанную чуть выше проблему в нём несколько проще обнаружить и решить.

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

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

Примеров приведу два, оба опенсорц и уже давно валяются у меня на блоге:

1) мой плагин к X-Chat для MPD, давно заброшен и м/б даж уже не работающий (давно в IRC не был, соответственно и плагин не юзаю). Но это, как и код его не имеет сейчас значения — смотрим на сборку. Сценарий должен компилировать исходный файл в разделяемую библиотеку, которую можно будет подключать к X-Chat.

В сценарии всего две цели — первая (т. е. цель по-умолчанию) собирает библиотеку в текущем каталоге, вторя (очистка) её удаляет. Сборка занимает всего одну строчку (вызов gcc с нужными опциями), зависимостей нет, переменные тоже не используются. Эту строку можно набирать самому в консоли, можно прописать в shell-сценарий, но сделано оно в make-файле. Как видите всё очень просто: цель + её оператор, потом следующая цель…

2) мой диплом — нечто вроде кросс компилятора ассемблера для i8080/КР580. Это уже более сложная программа, состоящая из четырёх модулей =) Код в данном случае тоже давно заброшен и не имеет особого значения, важно лишь как оно собирается — это хоть и посложнее предыдущего, но и более жизненно (примерно так же можно собирать всякие лабы или курсачи).

Файлы в дипломе раскиданы по нескольким директориям. Исходный код берётся из ./src/*.cpp и компилируется в ./obj/*.obj, после чего результат линкуется в готовую программу ./bin/a, из которой удаляется лишняя (отладочная) информация. Соответственно надо скомпилировать 4 файла, а потом из полученного ещё и собрать программу. Традиционно, сценарий предусматривает и возможность очистки…

Первыми строками файла идут комментарии, что типа накодил это я, а не какой-нить «сосед Гена» и если прога вам понравится, пивом поить следует именно меня B-) Комментарии подчиняются тем же правилам, что и в сценариях оболочки — начинаются с символа # (шарп, решётка) и действуют на всю строку.

Потом идёт объявление используемых переменных. Очень часто имя компилятора помещают в переменную CC (C Compiler), а его опции в CFLAGS (для Си) и CXXFLAGS (для С++ соответственно). Можно делать и более изощрённые вариации с использованием других утилит (например с результатами поиска нужной библиотеки или заголовочных файлов).

После объявления переменных идёт описание главной цели. Сама по себе она лишь линкует готовые модули, от которых и зависит. Если при запуске готовых модулей не обнаружится (или некоторые из них будут старше своего исходного кода), они будут собраны заново. Каждому модулю соответствует цель, которая зависит от исходного кода и/или других целей (модулей).

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

Надеюсь, я доходчиво обьяснил азы make. На самом деле это гораздо более гибкая технология сборки, но лично мне дома вполне хватает перечисленного сдесь.

Copyleft (c) 2008-05-27, Oleg «LG.BALUKATION» Baluk

Advertisements

Один ответ to “make”

  1. D_Alex said

    интересно.. будем учиться, спасибо =)

Добавить комментарий

Заполните поля или щелкните по значку, чтобы оставить свой комментарий:

Логотип WordPress.com

Для комментария используется ваша учётная запись WordPress.com. Выход / Изменить )

Фотография Twitter

Для комментария используется ваша учётная запись Twitter. Выход / Изменить )

Фотография Facebook

Для комментария используется ваша учётная запись Facebook. Выход / Изменить )

Google+ photo

Для комментария используется ваша учётная запись Google+. Выход / Изменить )

Connecting to %s