- Makefile для самых маленьких
- Установка GNU GCC Компилятора и среды разработки на Ubuntu Linux
- Вам необходимо установить следующие пакеты на Debian и Ubuntu Linux:
- Установка компиляторов с помощью команды apt
- Проверка установки
- Установка страниц dev man на Ubuntu Linux
- Просто о make
- Make- основные сведения
- Простейший Makefile
- Компиляция из множества исходников
- Инкрементная компиляция
- Фиктивные цели
- Переменные
- Автоматические переменные
- Эффективное использование GNU Make
- (C) Владимир Игнатов, 2000
- Оглавление
- Оглавление
- 0. Предисловие
- 1. Моя методика использования GNU Make
- 1.1. Пример проекта
- 1.2. «Традиционный» способ построения make-файлов
- 1.3. Автоматическое построение списка объектных файлов
- 1.4. Автоматическое построение зависимостей от заголовочных файлов
- 1.5. «Разнесение» файлов с исходными текстами по директориям
- 1.6. Сборка программы с разными параметрами компиляции
- 1.7. «Разнесение» разных версий программы по отдельным директориям
- 2. GNU Make
- 2.1. Две разновидности переменных
- 2.2. Функции манипуляции с текстом
- 2.3. Новый способ задания шаблонных правил
- 2.4. Переменная VPATH
- 2.5. Директива override
- 2.6. Добавление текста в строку
- 2.7. Директива include
Makefile для самых маленьких
Не очень строгий перевод материала mrbook.org/tutorials/make Мне в свое время очень не хватило подобной методички для понимания базовых вещей о make. Думаю, будет хоть кому-нибудь интересно. Хотя эта технология и отмирает, но все равно используется в очень многих проектах. Кармы на хаб «Переводы» не хватило, как только появится возможность — добавлю и туда. Добавил в Переводы. Если есть ошибки в оформлении, то прошу указать на них. Буду исправлять.
Статья будет интересная прежде всего изучающим программирование на C/C++ в UNIX-подобных системах от самых корней, без использования IDE.
Компилировать проект ручками — занятие весьма утомительное, особенно когда исходных файлов становится больше одного, и для каждого из них надо каждый раз набивать команды компиляции и линковки. Но не все так плохо. Сейчас мы будем учиться создавать и использовать Мейкфайлы. Makefile — это набор инструкций для программы make, которая помогает собирать программный проект буквально в одно касание.
Для практики понадобится создать микроскопический проект а-ля Hello World из четырех файлов в одном каталоге:
Все скопом можно скачать отсюда
Автор использовал язык C++, знать который совсем не обязательно, и компилятор g++ из gcc. Любой другой компилятор скорее всего тоже подойдет. Файлы слегка подправлены, чтобы собирались gcc 4.7.1
Программа make
Если запустить
make
то программа попытается найти файл с именем по умолчание Makefile в текущем каталоге и выполнить инструкции из него. Если в текущем каталоге есть несколько мейкфайлов, то можно указать на нужный вот таким образом:
make -f MyMakefile
Есть еще множество других параметров, нам пока не нужных. О них можно узнать в ман-странице.
Процесс сборки
Компилятор берет файлы с исходным кодом и получает из них объектные файлы. Затем линковщик берет объектные файлы и получает из них исполняемый файл. Сборка = компиляция + линковка.
Компиляция руками
Самый простой способ собрать программу:
g++ main.cpp hello.cpp factorial.cpp -o hello
Каждый раз набирать такое неудобно, поэтому будем автоматизировать.
Самый простой Мейкфайл
В нем должны быть такие части:
Для нашего примера мейкфайл будет выглядеть так:
Обратите внимание, что строка с командой должна начинаться с табуляции! Сохраните это под именем Makefile-1 в каталоге с проектом и запустите сборку командой make -f Makefile-1
В первом примере цель называется all . Это цель по умолчанию для мейкфайла, которая будет выполняться, если никакая другая цель не указана явно. Также у этой цели в этом примере нет никаких зависимостей, так что make сразу приступает к выполнению нужной команды. А команда в свою очередь запускает компилятор.
Использование зависимостей
Использовать несколько целей в одном мейкфайле полезно для больших проектов. Это связано с тем, что при изменении одного файла не понадобится пересобирать весь проект, а можно будет обойтись пересборкой только измененной части. Пример:
Это надо сохранить под именем Makefile-2 все в том же каталоге
Теперь у цели all есть только зависимость, но нет команды. В этом случае make при вызове последовательно выполнит все указанные в файле зависимости этой цели.
Еще добавилась новая цель clean . Она традиционно используется для быстрой очистки всех результатов сборки проекта. Очистка запускается так: make -f Makefile-2 clean
Использование переменных и комментариев
Переменные широко используются в мейкфайлах. Например, это удобный способ учесть возможность того, что проект будут собирать другим компилятором или с другими опциями.
Это Makefile-3
Переменные — очень удобная штука. Для их использования надо просто присвоить им значение до момента их использования. После этого можно подставлять их значение в нужное место вот таким способом: $(VAR)
Что делать дальше
После этого краткого инструктажа уже можно пробовать создавать простые мейкфайлы самостоятельно. Дальше надо читать серьезные учебники и руководства. Как финальный аккорд можно попробовать самостоятельно разобрать и осознать такой универсальный мейкфайл, который можно в два касания адаптировать под практически любой проект:
Источник
Установка GNU GCC Компилятора и среды разработки на Ubuntu Linux
Как установить компилятор GNU / GCC (C и C ++) и связанные с ним инструменты (например, make, debugger, man pages) в операционной системе Ubuntu Linux с использованием параметров командной строки?
Вам необходимо установить следующие пакеты на Debian и Ubuntu Linux:
Build-essential package — Устанавливает следующую коллекцию для компиляции c / c ++-программ на Ubuntu Linux, включая:
- libc6-dev – стандартная C библиотека.
- gcc – C компилятор.
- g++ – C++ компилятор.
- make – GNU делает утилиту для поддержки групп программ.
- dpkg-dev – Инструменты разработки пакетов Debian.
В принципе, build-essential пакет содержит информационный список пакетов, которые считаются необходимыми для создания пакетов Ubuntu, включая gcc-компилятор, make и другие необходимые инструменты. Этот пакет также зависит от пакетов в этом списке, чтобы упростить установку build-essential пакетов. В этом руководстве вы узнаете об установке компилятора GNU C и компилятора GNU C ++ на Ubuntu Linux.
Установка компиляторов с помощью команды apt
Откройте приложение терминала и введите следующее apt command /apt-get command :
Пример вывода данных:
Рис.01: Как я устанавливаю инструменты разработки на Ubuntu Linux?
Проверка установки
Введите следующую команду:
Рис.02: Поиск установленной версии make и gcc
Установка страниц dev man на Ubuntu Linux
Введите следующую команду:
Чтобы посмотреть запросы библиотеки (функция в библиотеках программы) введите:
Источник
Просто о make
Меня всегда привлекал минимализм. Идея о том, что одна вещь должна выполнять одну функцию, но при этом выполнять ее как можно лучше, вылилась в создание UNIX. И хотя UNIX давно уже нельзя назвать простой системой, да и минимализм в ней узреть не так то просто, ее можно считать наглядным примером количество- качественной трансформации множества простых и понятных вещей в одну весьма непростую и не прозрачную. В своем развитии make прошел примерно такой же путь: простота и ясность, с ростом масштабов, превратилась в жуткого монстра (вспомните свои ощущения, когда впервые открыли мэйкфайл).
Мое упорное игнорирование make в течении долгого времени, было обусловлено удобством используемых IDE, и нежеланием разбираться в этом ‘пережитке прошлого’ (по сути — ленью). Однако, все эти надоедливые кнопочки, менюшки ит.п. атрибуты всевозможных студий, заставили меня искать альтернативу тому методу работы, который я практиковал до сих пор. Нет, я не стал гуру make, но полученных мною знаний вполне достаточно для моих небольших проектов. Данная статья предназначена для тех, кто так же как и я еще совсем недавно, желают вырваться из уютного оконного рабства в аскетичный, но свободный мир шелла.
Make- основные сведения
make — утилита предназначенная для автоматизации преобразования файлов из одной формы в другую. Правила преобразования задаются в скрипте с именем Makefile, который должен находиться в корне рабочей директории проекта. Сам скрипт состоит из набора правил, которые в свою очередь описываются:
1) целями (то, что данное правило делает);
2) реквизитами (то, что необходимо для выполнения правила и получения целей);
3) командами (выполняющими данные преобразования).
В общем виде синтаксис makefile можно представить так:
То есть, правило make это ответы на три вопроса:
Несложно заметить что процессы трансляции и компиляции очень красиво ложатся на эту схему:
Простейший Makefile
Предположим, у нас имеется программа, состоящая всего из одного файла:
Для его компиляции достаточно очень простого мэйкфайла:
Данный Makefile состоит из одного правила, которое в свою очередь состоит из цели — «hello», реквизита — «main.c», и команды — «gcc -o hello main.c». Теперь, для компиляции достаточно дать команду make в рабочем каталоге. По умолчанию make станет выполнять самое первое правило, если цель выполнения не была явно указана при вызове:
Компиляция из множества исходников
Предположим, что у нас имеется программа, состоящая из 2 файлов:
main.c
Makefile, выполняющий компиляцию этой программы может выглядеть так:
Он вполне работоспособен, однако имеет один значительный недостаток: какой — раскроем далее.
Инкрементная компиляция
Представим, что наша программа состоит из десятка- другого исходных файлов. Мы вносим изменения в один из них, и хотим ее пересобрать. Использование подхода описанного в предыдущем примере приведет к тому, что все без исключения исходные файлы будут снова скомпилированы, что негативно скажется на времени перекомпиляции. Решение — разделить компиляцию на два этапа: этап трансляции и этап линковки.
Теперь, после изменения одного из исходных файлов, достаточно произвести его трансляцию и линковку всех объектных файлов. При этом мы пропускаем этап трансляции не затронутых изменениями реквизитов, что сокращает время компиляции в целом. Такой подход называется инкрементной компиляцией. Для ее поддержки make сопоставляет время изменения целей и их реквизитов (используя данные файловой системы), благодаря чему самостоятельно решает какие правила следует выполнить, а какие можно просто проигнорировать:
Попробуйте собрать этот проект. Для его сборки необходимо явно указать цель, т.е. дать команду make hello.
После- измените любой из исходных файлов и соберите его снова. Обратите внимание на то, что во время второй компиляции, транслироваться будет только измененный файл.
После запуска make попытается сразу получить цель hello, но для ее создания необходимы файлы main.o и hello.o, которых пока еще нет. Поэтому выполнение правила будет отложено и make станет искать правила, описывающие получение недостающих реквизитов. Как только все реквизиты будут получены, make вернется к выполнению отложенной цели. Отсюда следует, что make выполняет правила рекурсивно.
Фиктивные цели
На самом деле, в качестве make целей могут выступать не только реальные файлы. Все, кому приходилось собирать программы из исходных кодов должны быть знакомы с двумя стандартными в мире UNIX командами:
Командой make производят компиляцию программы, командой make install — установку. Такой подход весьма удобен, поскольку все необходимое для сборки и развертывания приложения в целевой системе включено в один файл (забудем на время о скрипте configure). Обратите внимание на то, что в первом случае мы не указываем цель, а во втором целью является вовсе не создание файла install, а процесс установки приложения в систему. Проделывать такие фокусы нам позволяют так называемые фиктивные (phony) цели. Вот краткий список стандартных целей:
- all — является стандартной целью по умолчанию. При вызове make ее можно явно не указывать.
- clean — очистить каталог от всех файлов полученных в результате компиляции.
- install — произвести инсталляцию
- uninstall — и деинсталляцию соответственно.
Для того чтобы make не искал файлы с такими именами, их следует определить в Makefile, при помощи директивы .PHONY. Далее показан пример Makefile с целями all, clean, install и uninstall:
Теперь мы можем собрать нашу программу, произвести ее инсталлцию/деинсталляцию, а так же очистить рабочий каталог, используя для этого стандартные make цели.
Обратите внимание на то, что в цели all не указаны команды; все что ей нужно — получить реквизит hello. Зная о рекурсивной природе make, не сложно предположить как будет работать этот скрипт. Так же следует обратить особое внимание на то, что если файл hello уже имеется (остался после предыдущей компиляции) и его реквизиты не были изменены, то команда make ничего не станет пересобирать. Это классические грабли make. Так например, изменив заголовочный файл, случайно не включенный в список реквизитов, можно получить долгие часы головной боли. Поэтому, чтобы гарантированно полностью пересобрать проект, нужно предварительно очистить рабочий каталог:
Для выполнения целей install/uninstall вам потребуются использовать sudo.
Переменные
Все те, кто знакомы с правилом DRY (Don’t repeat yourself), наверняка уже заметили неладное, а именно — наш Makefile содержит большое число повторяющихся фрагментов, что может привести к путанице при последующих попытках его расширить или изменить. В императивных языках для этих целей у нас имеются переменные и константы; make тоже располагает подобными средствами. Переменные в make представляют собой именованные строки и определяются очень просто:
Существует негласное правило, согласно которому следует именовать переменные в верхнем регистре, например:
Так мы определили список исходных файлов. Для использования значения переменной ее следует разименовать при помощи конструкции $( ); например так:
Ниже представлен мэйкфайл, использующий две переменные: TARGET — для определения имени целевой программы и PREFIX — для определения пути установки программы в систему.
Это уже посимпатичней. Думаю, теперь вышеприведенный пример для вас в особых комментариях не нуждается.
Автоматические переменные
Автоматические переменные предназначены для упрощения мейкфайлов, но на мой взгляд негативно сказываются на их читабельности. Как бы то ни было, я приведу здесь несколько наиболее часто используемых переменных, а что с ними делать (и делать ли вообще) решать вам:
Источник
Эффективное использование GNU Make
(C) Владимир Игнатов, 2000
Оглавление
Оглавление
0. Предисловие
В начале каждой главы я кратко описываю, о чем в ней будет вестись речь, и какими знаниями нужно обладать, чтобы успешно воспринять излагаемый в главе материал. Для тех, кто чувствует, что недостаточно хорошо ориентируется в предмете разговора, я указываю на дополнительные главы, с которыми следует предварительно ознакомиться.
Для работы я использовал GNU Make версии 3.79.1. Некоторые старые версии GNU Make (например, версия 3.76.1 из дистрибутива Slackware 3.5) могут неправильно работать с примером «традиционного» строения make-файла (по-видимому, они «не воспринимают» старую форму записи шаблонных правил).
1. Моя методика использования GNU Make
1.1. Пример проекта
В качестве примера я буду использовать «гипотетический» проект — текстовой редактор. Он состоит из нескольких файлов с исходным текстом на языке C++ (main.cpp, Editor.cpp, TextLine.cpp) и нескольких включаемых файлов (main.h,Editor.h, TextLine.h). Если вы имеете доступ в интернет то «электронный» вариант приводимых в книге примеров можно получить на моей домашней страничке по адресу www.geocities.com/SiliconValley/Office/6533 . Если интернет для вас недоступен, то в Приложении D приведены листинги файлов, которые используются в примерах.
1.2. «Традиционный» способ построения make-файлов
В первом примере make-файл построен «традиционным» способом. Все исходные файлы собираемой программы находятся в одном каталоге:
- example_1-traditional /
- main.cpp
- main.h
- Editor.cpp
- Editor.h
- TextLine.cpp
- TextLine.h
- Makefile
Предполагается, что для компиляции программы используется компилятор GCC, и объектные файлы имеют расширение «.o». Файл Makefile выглядит так: Первое правило заставляет make перекомпоновывать программу при изменении любого из объектных файлов. Второе правило говорит о том, что объектные файлы зависят от соответствующих исходных файлов. Каждое изменение файла с исходным текстом будет вызывать его перекомпиляцию. Следующие несколько правил указывают, от каких заголовочных файлов зависит каждый из объектных файлов. Такой способ построения make-файла мне кажется неудобным потому что:
- Требуется «явно» перечислять все объектные файлы, из которых компонуется программа
- Требуется «явно» перечислять, от каких именно заголовочных файлов зависит тот или иной объектный файл
- Исполняемый файл программы помещается в «текущую» директорию. Если мне нужно иметь несколько различных вариантов программы (например, отладочный и рабочий), то каждый раз при переходе от одного варианта к другому требуется полная перекомпиляция программы во избежание нежелательного «смешивания» разных версий объектных файлов.
Видно, что традиционный способ построения make-файлов далек от идеала. Единственно, чем этот способ может быть удобен — своей «совместимостью». По-видимому, с таким make-файлом будут нормально работать даже самые «древние» или «экзотические» версии make (например, nmake фирмы Microsoft). Если подобная «совместимость» не нужна, то можно сильно облегчить себе жизнь, воспользовавшись широкими возможностями утилиты GNU Make. Попробуем избавиться от недостатков «традиционного» подхода.
1.3. Автоматическое построение списка объектных файлов
не сработает, так как будут учтены только существующие в данный момент объектные файлы. Я использую чуть более сложный способ, который основан на предположении, что все файлы с исходным текстом должны быть скомпилированы и скомпонованы в собираемую программу. Моя методика состоит из двух шагов:
- Получить список всех файлов с исходным текстом программы (всех файлов с расширением «.cpp«). Для этого можно использовать функцию wildcard.
- Преобразовать список исходных файлов в список объектных файлов (заменить расширение «.cpp» на расширение «.o«). Для этого можно воспользоваться функцией patsubst.
Следующий пример содержит модифицированную версию make-файла:
- example_2-auto_obj /
- main.cpp
- main.h
- Editor.cpp
- Editor.h
- TextLine.cpp
- TextLine.h
- Makefile
Файл Makefile теперь выглядит так:
Список объектных файлов программы строится автоматически. Сначала с помощью функции wildcard получается список всех файлов с расширением «.cpp«, находящихся в директории проекта. Затем, с помощью функции patsubst, полученный таким образом список исходных файлов, преобразуется в список объектных файлов. Make-файл теперь стал более универсальным — с небольшими изменениями его можно использовать для сборки разных программ.
1.4. Автоматическое построение зависимостей от заголовочных файлов
Перечисление зависимостей «вручную» требует довольно кропотливой работы. Недостаточно просто открыть файл с исходным текстом и перечислить имена всех заголовочных файлов, подключаемых с помощью #include. Дело в том, что одни заголовочные файлы могут, в свою очередь, включать в себя другие заголовочные файлы, так что придется отслеживать всю «цепочку» зависимостей.
Утилита GNU Make не сможет самостоятельно построить список зависимостей, поскольку для этого придется «заглядывать» внутрь файлов с исходным текстом — а это, разумеется, лежит уже за пределами ее «компетенции». К счастью, трудоемкий процесс построения зависимостей можно автоматизировать, если воспользоваться помощью компилятора GCC. Для совместной работы с make компилятор GCC имеет несколько опций:
Ключ компиляции | Назначение |
-M | Для каждого файла с исходным текстом препроцессор будет выдавать на стандартный вывод список зависимостей в виде правила для программы make. В список зависимостей попадает сам исходный файл, а также все файлы, включаемые с помощью директив #include и #include «имя_файла». После запуска препроцессора компилятор останавливает работу, и генерации объектных файлов не происходит. |
-MM | Аналогичен ключу -M, но в список зависимостей попадает только сам исходный файл, и файлы, включаемые с помощью директивы #include «имя_файла» |
-MD | Аналогичен ключу -M, но список зависимостей выдается не на стандартный вывод, а записывается в отдельный файл зависимостей. Имя этого файла формируется из имени исходного файла путем замены его расширения на «.d«. Например, файл зависимостей для файла main.cpp будет называться main.d. В отличие от ключа -M, компиляция проходит обычным образом, а не прерывается после фазы запуска препроцессора. |
-MMD | Аналогичен ключу -MD, но в список зависимостей попадает только сам исходный файл, и файлы, включаемые с помощью директивы #include «имя_файла» |
Как видно из таблицы компилятор может работать двумя способами — в одном случае компилятор выдает только список зависимостей и заканчивает работу (опции -M и -MM). В другом случае компиляция происходит как обычно, только в дополнении к объектному файлу генерируется еще и файл зависимостей (опции -MD и -MMD). Я предпочитаю использовать второй вариант — он мне кажется более удобным и экономичным потому что:
- При изменении какого-либо из исходных файлов будет построен заново лишь один соответствующий ему файл зависимостей
- Построение файлов зависимостей происходит «параллельно» с основной работой компилятора и практически не отражается на времени компиляции
Из двух возможных опций -MD и -MMD, я предпочитаю первую потому что:
- С помощью директивы #include я часто включаю не только «стандартные», но и свои собственные заголовочные файлы, которые могут иногда меняться (например, заголовочные файлы моей прикладной библиотеки LIB).
- Иногда бывает полезно взглянуть на полный список включаемых в модуль заголовочных файлов, в том числе и «стандартных».
После того как файлы зависимостей сформированы, нужно сделать их доступными утилите make. Этого можно добиться с помощью директивы include.
Обратите внимание на использование функции wildcard. Конструкция будет правильно работать только в том случае, если в каталоге будет находиться хотя бы один файл с расширением «.d«. Если таких файлов нет, то make аварийно завершится, так как потерпит неудачу при попытке «построить» эти файлы (у нее ведь нет на этот счет ни каких инструкций!). Если же использовать функцию wildcard, то при отсутствии искомых файлов, эта функция просто вернет пустую строку. Далее, директива include с аргументом в виде пустой строки, будет проигнорирована, не вызывая ошибки. Теперь можно составить новый вариант make-файла для моего «гипотетического» проекта:
- example_3-auto_depend /
- main.cpp
- main.h
- Editor.cpp
- Editor.h
- TextLine.cpp
- TextLine.h
- Makefile
Вот как выглядит Makefile из этого примера: После завершения работы make директория проекта будет выглядеть так:
- example_3-auto_depend /
- iEdit
- main.cpp
- main.h
- main.o
- main.d
- Editor.cpp
- Editor.o
- Editor.d
- Editor.h
- TextLine.cpp
- TextLine.o
- TextLine.d
- TextLine.h
- Makefile
Файлы с расширением «.d» — это сгенерированные компилятором GCC файлы зависимостей. Вот, например, как выглядит файл Editor.d, в котором перечислены зависимости для файла Editor.cpp: Теперь при изменении любого из файлов — Editor.cpp, Editor.h или TextLine.h, файл Editor.cpp будет перекомпилирован для получения новой версии файла Editor.o.
Имеет ли описанная методика недостатки? Да, к сожалению, имеется один недостаток. К счастью, на мой взгляд, не слишком существенный. Дело в том, что утилита make обрабатывает make-файл «в два приема». Сначала будет обработана директива include и в make-файл будут включены файлы зависимостей, а затем, на «втором проходе», будут уже выполняться необходимые действия для сборки проекта.
Получается что для «текущей» сборки используются файлы зависимостей, сгенерированные во время «предыдущей» сборки. Как правило, это не вызывает проблем. Сложности возникнут лишь в том случае, если какой-нибудь из заголовочных файлом по какой-либо причине прекратил свое существование. Рассмотрим простой пример. Предположим, у меня имеются файлы main.cpp и main.h:
Файл main.h: В таком случае, сформированный компилятором файл зависимостей main.d будет выглядеть так: Теперь, если я переименую файл main.h в main_2.h, и соответствующим образом изменю файл main.cpp,
Файл main.cpp: то очередная сборка проекта окончится неудачей, поскольку файл зависимостей main.d будет ссылаться на не существующий более заголовочный файл main.h.
Выходом в этой ситуации может служить удаление файла зависимостей main.d. Тогда сборка проекта пройдет нормально и будет создана новая версия этого файла, ссылающаяся уже на заголовочный файл main_2.h:
При переименовании или удалении какого-нибудь «популярного» заголовочного файла, можно просто заново пересобрать проект, удалив предварительно все объектные файлы и файлы зависимостей.
1.5. «Разнесение» файлов с исходными текстами по директориям
осталось работоспособным, я использую переменную VPATH, в которой перечисляются все директории, где могут располагаться исходные тексты. В следующем примере я поместил файлы Editor.cpp и Editor.h в каталог Editor, а файлы TextLine.cpp и TextLine.h в каталог TextLine:
- example_4-multidir /
- main.cpp
- main.h
- Editor /
- Editor.cpp
- Editor.h
- TextLine /
- TextLine.cpp
- TextLine.h
- Makefile
Вот как выглядит Makefile для этого примера:
По сравнению с предыдущим вариантом make-файла он претерпел следующие изменения:
- Для хранения списка директорий с исходными текстами я завел отдельную переменную source_dirs, поскольку этот список понадобится указывать в нескольких местах.
- Шаблон поиска для функции wildcard (переменная search_wildcards) строится «динамически» исходя из списка директорий source_dirs
- Используется переменная VPATH для того, чтобы шаблонное правило могло искать файлы исходных текстов в указанном списке директорий
- Компилятору разрешается искать заголовочные файлы во всех директориях с исходными текстами. Для этого используется функция addprefix и флажок -I компилятора GCC.
- При формировании списка объектных файлов, из имен исходных файлов «убирается» имя каталога, где они расположены (с помощью функции notdir)
1.6. Сборка программы с разными параметрами компиляции
Вот как выглядит Makefile для этого примера:
Переменная compile_flags получает свое значение из командной строки и, далее, используется при компиляции исходных текстов. Для ускорения работы компилятора, к параметрам компиляции добавляется флажок -pipe. Обратите внимание на необходимость использования директивы override для изменения переменной compile_flags внутри make-файла.
1.7. «Разнесение» разных версий программы по отдельным директориям
Для решения этой проблемы я помещаю результаты компиляции каждой версии программы в свой отдельный каталог. Так, например, отладочная версия программы (включая все объектные файлы) помещается в каталог debug, а рабочая версия программы — в каталог release:
- example_6-multiconfig-multidir /
- debug /
- release /
- main.cpp
- main.h
- Editor /
- Editor.cpp
- Editor.h
- TextLine /
- TextLine.cpp
- TextLine.h
- Makefile
- make_debug
- make_release
Главная сложность заключалась в том, чтобы заставить программу make помещать результаты работы в разные директории. Попробовав разные варианты, я пришел к выводу, что самый легкий путь — использование флажка —directory при вызове make. Этот флажок заставляет утилиту перед началом обработки make-файла, сделать каталог, указанный в командной строке, «текущим».
Вот, например, как выглядит командный файл make_release, собирающий рабочую версию программы (результаты компиляции помещается в каталог release):
Команда mkdir введена для удобства — если удалить каталог release, то при следующей сборке он будет создан заново. В случае «составного» имени каталога (например, bin/release) можно дополнительно использовать флажок -p. Флажок —directory заставляет make перед началом работы сделать указанную директорию release текущей. Флажок —makefile укажет программе make, где находится make-файл проекта. По отношению к «текущей» директории release, он будет располагаться в «родительском» каталоге.
Командный файл для сборки отладочного варианта программы (make_debug) выглядит аналогично. Различие только в имени директории, куда помещаются результаты компиляции (debug) и другом наборе флагов компиляции: Вот окончательная версия make-файла для сборки «гипотетического» проекта текстового редактора:
В этом окончательном варианте я «вынес» имя исполняемого файла программы в отдельную переменную program_name. Теперь для того чтобы адаптировать этот make-файл для сборки другой программы, в нем достаточно изменить всего лишь несколько первых строк.
После запуска командных файлов make_debug и make_release директория с последним примером выглядит так:
- example_6-multiconfig-multidir /
- debug /
- iEdit
- main.o
- main.d
- Editor.o
- Editor.d
- TextLine.o
- TextLine.d
- release /
- iEdit
- main.o
- main.d
- Editor.o
- Editor.d
- TextLine.o
- TextLine.d
- main.cpp
- main.h
- Editor /
- Editor.cpp
- Editor.h
- TextLine /
- TextLine.cpp
- TextLine.h
- makefile
- make_debug
- make_release
- debug /
Видно, что объектные файлы для рабочей и отладочной конфигурации программы помещаются в разные директории. Туда же попадают готовые исполняемые файлы и файлы зависимостей.
В этой главе я изложил свою методику работы с make-файлами. Остальные главы носят более или менее «дополнительный» характер.
- В Приложении A я описываю проблемы, которые могут возникнуть при редактировании make-файлов в разных операционных системах
- В Приложении B я описываю свой личный способ организации дерева каталогов для сложных проектов.
- В Приложении C я делюсь некоторыми мыслями по поводу использования компилятора GCC
2. GNU Make
GNU Make — это версия программы make распространяемая Фондом Свободного Программного Обеспечения (Free Software Foundation — FSF) в рамках проекта GNU ( www.gnu.org ). Получить самую свежую версию программы и документации можно на «домашней страничке» программы www.gnu.org/software/make либо на страничке Paul D. Smith — одного из авторов GNU Make ( www.paulandlesley.org/gmake).
Программа GNU Make имеет очень подробную и хорошо написанную документацию, с которой я настоятельно рекомендую ознакомиться. Если у вас нет доступа в интернет, то пользуйтесь документацией в формате Info, которая должна быть в составе вашего дистрибутива Linux. Будьте осторожны с документацией в формате man-странички (man make) — как правило, она содержит лишь отрывочную и сильно устаревшую информацию.
2.1. Две разновидности переменных
GNU Make поддерживает также и второй, новый способ задания переменной — с помощью оператора ‘:=‘: В этом случае переменная работает подобно «обычным» текстовым переменным в каком-нибудь из языков программирования. Вот приблизительный аналог этого выражения на языке C++: Значение переменной вычисляется в момент обработки оператора присваивания. Если, например, записать то при обработке такого make-файла на экран будет выдана строка «one two».
Переменная может «менять» свое поведение в зависимости от того, какой из операторов присваивания был к ней применен последним. Одна и та же переменная на протяжении своей жизни вполне может вести себя и как «макрос» и как «текстовая переменная».
Все свои make-файлы я пишу с применением оператора ‘:=‘. Этот способ кажется мне более удобным и надежным. Вдобавок это более эффективно, так как значение переменной не вычисляется заново каждый раз при ее использовании. Подробнее о двух способах задания переменных можно прочитать в документации на GNU Make в разделе «The Two Flavors of Variables» .
2.2. Функции манипуляции с текстом
Утилита GNU Make содержит большое число полезных функций, манипулирующих текстовыми строками и именами файлов. В частности в своих make-файлах я использую функции addprefix, addsuffix, wildcard, notdir и patsubst. Для вызова функций используется синтаксис
Функция addprefix рассматривает второй параметр как список слов разделенных пробелами. В начало каждого слова она добавляет строку, переданную ей в качестве первого параметра. Например, в результате выполнения make-файла: на экран будет выведено
Видно, что к каждому имени директории добавлен префикс «../../«. Функция addprefix обсуждается в разделе «Functions for File Names» руководства по GNU Make.
Функция addsuffix работает аналогично функции addprefix, только добавляет указанную строку в конец каждого слова. Например, в результате выполнения make-файла:
на экран будет выведено
Видно, что к каждому имени директории добавлен суффикс «/*.cpp«. Функция addsuffix обсуждается в разделе «Functions for File Names» руководства по GNU Make.
Функция wildcard «расширяет» переданный ей шаблон или несколько шаблонов в список файлов, удовлетворяющих этим шаблонам. Пусть в директории Editor находится файл Editor.cpp, а в директории TextLine — файл TextLine.cpp:
- wildcard_example /
- Editor /
- Editor.cpp
- TextLine /
- TextLine.cpp
- makefile
- Editor /
Тогда в результате выполнения такого make-файла:
на экран будет выведено
Видно, что шаблоны преобразованы в списки файлов. Функция wildcard подробно обсуждается в разделе «The Function wildcard» руководства по GNU Make.
Функция notdir позволяет «убрать» из имени файла имя директории, где он находится. Например, в результате выполнения make-файла:
на экран будет выведено Видно, что из имен файлов убраны «пути» к этим файлам. Функция notdir обсуждается в разделе «Functions for File Names» руководства по GNU Make.
Функция patsubst позволяет изменить указанным образом слова, подходящие под шаблон. Она принимает три параметра — шаблон, новый вариант слова и исходную строку. Исходная строка рассматривается как список слов, разделенных пробелом. Каждое слово, подходящее под указанный шаблон, заменяется новым вариантом слова. В шаблоне может использоваться специальный символ ‘%’, который означает «любое количество произвольных символов». Если символ ‘%’ встречается в новом варианте слова (втором параметре), то он заменяется текстом, соответствующим символу ‘%’ в шаблоне. Например, в результате выполнения make-файла:
на экран будет выведено
Видно, что во всех словах окончание «.cpp» заменено на «.o«. Функция patsubst имеет второй, более короткий вариант записи для тех случаев, когда надо изменить суффикс слова (например, заменить расширение в имени файла). Более короткий вариант выглядит так: Применяя «короткий» вариант записи предыдущий пример можно записать так:
Функция patsubst обсуждается в разделе «Functions for String Substitution and Analysis» руководства по GNU Make.
2.3. Новый способ задания шаблонных правил
GNU Make поддерживает более универсальный подход — с использованием шаблонов имен файлов. Для задания шаблона используется символ ‘%’, который означает «последовательность любых символов произвольной длины». Символ ‘%’ в правой части правила заменяется текстом, который соответствует символу ‘%’ в левой части. Пользуясь новой формой записи, приведенный выше пример можно записать так:
В своих make-файлах я пользуюсь новой формой записи шаблонных правил, потому что считаю ее более удобной (шаблонные и нешаблонные правила теперь имеют аналогичный синтаксис) и универсальной (можно задавать не только файлы, отличающиеся своими расширениями).
2.4. Переменная VPATH
Переменная VPATH описывается в главе «VPATH: Search Path for All Dependencies» руководства по GNU Make. На страничке Paul D. Smith есть статья под названием «How Not to Use VPATH» ( paulandlesley.org/gmake/vpath.html), в которой обсуждается «неправильный» стиль использования переменной VPATH.
2.5. Директива override
2.6. Добавление текста в строку
При использовании этого оператора, «тип» переменной (см. раздел 2.1 «Две разновидности переменных») не меняется — «макросы» остаются «макросами», а «текстовые переменные» по-прежнему остаются таковыми.
Если переменная задана с помощью командной строки, то по-прежнему для изменения ее значения внутри make-файла нужно использовать директиву override. В следующем примере предполагается, что переменная compile_flags задана в командной строке:
2.7. Директива include
В директиве include могут быть указаны одно или несколько имен файлов, разделенных пробелами. В качестве имен файлов можно использовать шаблоны: Указанные в директиве файлы должны существовать — иначе make предпримет попытку «создать» их, а при невозможности этого достигнуть, выдаст сообщение об ошибке. Директива include с пустым списком файлов: просто игнорируется.
Источник