Как make файлом собирать программы в другой директории
У меня есть проект, где структура каталогов, как это:
Как написать файл makefile, который был бы в part/src (или где бы то ни было), который мог бы частично комплировать/связывать исходные файлы c / C++?/ src ?
могу ли я сделать что-то вроде -Я$projectroot/часть1/Ница-я$projectroot/часть1/МКП-я$projectroot/часть2/ГРЦ .
Если это сработает, есть ли более простой способ сделать это. Я видел проекты, где есть makefile в каждом из соответствующая часть? папки. [в этом посте я использовал знак вопроса, как в синтаксисе bash]
традиционный способ-это Makefile в каждом из подкаталогов ( part1 , part2 , etc.) позволяет строить их самостоятельно. Далее, имейте Makefile в корневом каталоге проекта, который строит все. "Корень" Makefile будет выглядеть примерно так:
так как каждая строка в цели make выполняется в своей собственной оболочке, нет необходимости беспокоиться о переходе обратно в дерево каталогов или в другое справочники.
Я предлагаю взглянуть на GNU сделать ручной раздел 5.7; это очень полезно.
Если у вас есть код в одном подкаталоге, зависящий от кода в другом подкаталоге, вам, вероятно, лучше с одним makefile на верхнем уровне.
посмотреть Рекурсивный Сделать Считается Вредным для полного обоснования, но в основном вы хотите, чтобы иметь полную информацию, необходимую для принятия решения о том, нужно ли перестраивать файл, и у него не будет этого, если вы расскажете ему только о трети своего проекта.
ссылка выше, кажется, не достижимый. Тот же документ доступен здесь:
опция VPATH может пригодиться, которая говорит, какие каталоги искать для исходного кода. Однако вам все равно понадобится опция-I для каждого пути включения. Пример:
это автоматически найдет соответствующий partXapi.cpp файлы в любом из vPath указанных каталогов и скомпилировать их. Однако это более полезно, когда каталог src разбит на подкаталоги. Для того, что вы описываете, как говорили другие, вам, вероятно, лучше с makefile для каждой части, особенно если каждая часть может стоять самостоятельно.
вы можете добавить правила в свой корневой файл Makefile для компиляции необходимых файлов cpp в других каталогах. Пример Makefile ниже должен быть хорошим началом в получении вас туда, где вы хотите быть.
если источники распространяются во многих папках, и имеет смысл иметь отдельные файлы Makefile, как предлагалось ранее, рекурсивный make-хороший подход, но для небольших проектов мне легче перечислить все исходные файлы в make-файл С их относительным путем к make-файл такой:
я могу установить VPATH таким образом:
затем я строю объекты:
и построение вывода еще проще:
можно сделать VPATH поколение автоматизирован:
или используя тот факт, что sort удаляет дубликаты (хотя это не важно):
Я думаю, что лучше указать, что использование Make (рекурсивное или нет) - это то, чего обычно вы можете избежать, потому что по сравнению с сегодняшними инструментами трудно учиться, поддерживать и масштабировать.
Это замечательный инструмент, но его прямое использование следует считать устаревшим в 2010+.
Если, конечно, вы не работаете в специальной среде, т. е. с устаревшим проектом и т. д.
используйте IDE,CMake или, если вы тяжело сердцевина, то Autotools.
(отредактировано из-за downvotes, ty Honza для указания)
в parentDir есть куча каталогов с исходными файлами в них: dirA, dirB, dirC. Различные файлы зависят от объектных файлов в других каталогах, поэтому я хотел иметь возможность сделать один файл из одного каталога и сделать эту зависимость, вызвав makefile, связанный с этой зависимостью.
по существу, я сделал один Makefile в parentDir, который имел (среди многого другого) общее правило, похожее на RC's:
каждый подкаталог включал этот makefile верхнего уровня, чтобы наследовать это общее правило. В файле Makefile каждого подкаталога я написал пользовательское правило для каждого файла, чтобы отслеживать все, от чего зависит каждый отдельный файл.
всякий раз, когда мне нужно было сделать файл, я использовал (по существу) это правило для рекурсивного создания любого/всех зависимости. Прекрасно!
Примечание: существует утилита под названием "makepp", что, кажется, делает эту задачу еще более интуитивно, но ради переносимости и не в зависимости от другого инструмента, я решил сделать это таким образом.
Статья будет интересная прежде всего изучающим программирование на 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)
Что делать дальше
После этого краткого инструктажа уже можно пробовать создавать простые мейкфайлы самостоятельно. Дальше надо читать серьезные учебники и руководства. Как финальный аккорд можно попробовать самостоятельно разобрать и осознать такой универсальный мейкфайл, который можно в два касания адаптировать под практически любой проект:
Меня всегда привлекал минимализм. Идея о том, что одна вещь должна выполнять одну функцию, но при этом выполнять ее как можно лучше, вылилась в создание 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 цели.
Обратите внимание на то, что в цели all не указаны команды; все что ей нужно — получить реквизит hello. Зная о рекурсивной природе make, не сложно предположить как будет работать этот скрипт. Так же следует обратить особое внимание на то, что если файл hello уже имеется (остался после предыдущей компиляции) и его реквизиты не были изменены, то команда make ничего не станет пересобирать. Это классические грабли make. Так например, изменив заголовочный файл, случайно не включенный в список реквизитов, можно получить долгие часы головной боли. Поэтому, чтобы гарантированно полностью пересобрать проект, нужно предварительно очистить рабочий каталог:
Для выполнения целей install/uninstall вам потребуются использовать sudo.
Переменные
Все те, кто знакомы с правилом DRY (Don't repeat yourself), наверняка уже заметили неладное, а именно — наш Makefile содержит большое число повторяющихся фрагментов, что может привести к путанице при последующих попытках его расширить или изменить. В императивных языках для этих целей у нас имеются переменные и константы; make тоже располагает подобными средствами. Переменные в make представляют собой именованные строки и определяются очень просто:
Существует негласное правило, согласно которому следует именовать переменные в верхнем регистре, например:
Так мы определили список исходных файлов. Для использования значения переменной ее следует разименовать при помощи конструкции $(); например так:
Ниже представлен мэйкфайл, использующий две переменные: TARGET — для определения имени целевой программы и PREFIX — для определения пути установки программы в систему.
Это уже посимпатичней. Думаю, теперь вышеприведенный пример для вас в особых комментариях не нуждается.
Автоматические переменные
Автоматические переменные предназначены для упрощения мейкфайлов, но на мой взгляд негативно сказываются на их читабельности. Как бы то ни было, я приведу здесь несколько наиболее часто используемых переменных, а что с ними делать (и делать ли вообще) решать вам:
Заключение
В этой статье я попытался подробно объяснить основы написания и работы мэйкфайлов. Надеюсь, что она поможет вам приобрести понимание сути make и в кратчайшие сроки освоить этот провереный временем инструмент.
Утилита make позволяет просто управлять контейнерами, объединив команды для сборки, тестирования и развёртывания в одном конфигурационном файле.
Разработчики многие годы используют утилиту make. При запуске утилита читает файл с описанием проекта (Makefile) и, интерпретируя его содержимое, предпринимает необходимые действия. Файл с описанием проекта представляет собой текстовый конфигурационный файл, где описаны зависимости и команды, которые необходимо выполнить. Он похож на Dockerfile или другой файл конфигурации контейнера: там тоже указаны команды, на основе которых формируются образы для развёртывания контейнеров.
В этой статье я расскажу о том, как управлять контейнерами, используя Makefile. Контейнерный конфигурационный файл описывает образ контейнера, а Makefile описывает процесс сборки проекта, тестирование и развёртывание, а также другие полезные команды.
Цели и структура Makefile
Утилита make по умолчанию установлена в большинстве современных Linux-дистрибутивов, поэтому проблем с её использованием обычно не возникает. И чтобы начать её использовать, нужно создать файл с именем Makefile.
Makefile состоит из набора целей (target), зависимостей (dependency) и команд (commands), необходимых для их выполнения:
Цель — это некий желаемый результат, способ достижения которого описан в Makefile. Под целью подразумевают выполнение некого действия либо получение новой версии файла. А dependency — это некие «исходные данные», условия необходимые для достижения указанной цели. Зависимость может быть результатом выполнения другой цели, либо обычным файлом.
Команды вводят с использованием символа табуляции (пробелы не подойдут). Цель может не содержать команд, но при этом содержать зависимости.
Сначала проверяются и выполняются все зависимости по порядку, в случае завершения какой-либо команды из зависимости с ненулевой ошибкой, выполнение команды прерывается. Далее выполняются все команды перечисленные в самой цели, в случае завершения какой-либо команды с ненулевой ошибкой, выполнение цели прерывается.
В этом вся прелесть Makefile. Вы можете создать набор целей для каждой задачи. В контексте управления контейнерами, это создание образа и его отправка в реестр, тестирование и развёртывание контейнера, а также обновление его сервиса.
Я проиллюстрирую использование Makefile на примере своего личного веб-сайта и покажу как просто можно автоматизировать выполнение перечисленных задач.
Сборка, тестирование и развёртывание
Я создал простой веб-сайт с помощью Hugo, генератора статических сайтов. Он позволяет получить статический HTML из файлов YAML. В качестве веб-сервера я использовал Caddy.
Теперь посмотрим, как Makefile упростит сборку и развёртывание этого проекта на проде.
Первой целью в Makefile будет image_build:
Эта цель вызывает Podman для создания контейнера из его конфигурационного файла (Containerfile), включенного в проект. В приведённой выше команде есть несколько переменных. Переменные в Makefile можно использовать примерно так же, как в простых скриптовых языках программирования. В данном случае мне это нужно для создания «ссылки» на образ, который будет отправлен в удалённый реестр:
Используя эти переменные, цель image_build формирует идентификатор вида us.gcr.io/my-project-name/my-image-name:abc1234.
В конце, в качестве тега образа добавлен хэш соответствующей Git-ревизии.
В нашем случае образ будет помечен тегом :latest. Этот тег нам пригодится для очистки контейнера:
Итак, теперь контейнер создан, и его необходимо проверить, чтобы убедиться, что он соответствует некоторым минимальным требованиям. Для моего личного веб-сайта важно ответить на два вопроса:
- «Запускается ли веб-сервер?»
- «Что он возвращает?»
Проверку можно усложнить. Например, во время процесса сборки можно потребовать, чтобы в ответе содержался хэш Git-ревизии. И тогда мы сможем проверить, содержится ли ответ заданный хэш.
Если с тестами всё хорошо, образ готов к развёртыванию. Я использую для размещения своего веб-сайта сервис Google Cloud Run: скармливаю ему образ, и он мгновенно выдаёт мне URL. Как и многие подобные сервисы, с ним можно общаться через интерфейс командной строки (CLI). С Cloud Run развёртывание сводится к отправке образов (созданных локально) в удалённый реестр контейнеров и к запуску процесса самого развёртывания с помощью инструмента командной строки gcloud.
Теперь создадим цель push. Я используюPodman (но можно вместо него использовать Skopeo или тот же Docker).
После того, как образ будет отправлен, используйте команду gcloud run deploy, чтобы развернуть новейшую версию образа в проекте и «оживить» его.
Давайте снова создадим цель в Makefile. В этом файле я могу создать переменные для аргументов --platform и --region, чтобы мне не нужно было каждый раз запоминать их. Иначе мне пришлось бы вводить их из головы каждый раз, когда я развёртываю новый образ. А я так редко пишу для своего личного блога, что нет никаких шансов, что я запомню эти переменные.
Дополнительные цели
Локальный запуск
При тестировании CSS или других изменений кода мне хочется проверять работу проекта локально, без развёртывания на удалённом сервере. Для этого в мой Makefile я добавлю цель run_local. Она выбирает контейнер в соответствии с моим текущим коммитом и открывает в браузере URL-адрес страницы с локального веб-сервера.
В традиционных реализациях, у программы make нет надежного способа узнать, чем именно является цель. Ведь она может быть как именем действия, так и именем файла. Утилита make просто ищет на диске файл с именем, которое указано в качестве цели. Если такой файл существует, то цель считается именем файла.
Поэтому приходится явно объявлять цели абстрактными (то есть действиями). Для этого достаточно добавить ключевое слово .PHONY.
Создадим абстрактную цель run_local:
Я также использую переменную для имени браузера, поэтому при желании могу тестировать на разных браузерах. Когда я запускаю make run_local, по умолчанию сайт открывается в Firefox. Чтобы протестировать то же самое в Google Chrome, я должен модифицировать вызов make run_local, добавив BROWSER = 'google-chrome'.
Очистка старых контейнеров и образов
При работе с контейнерами очистка старых образов — неприятная рутинная работа, особенно при частых итерациях. Поэтому добавим в Makefile цели для выполнения этих задач. Если контейнер не существует, Podman или Docker вернутся из процесса очистки с кодом 125. Но к сожалению, make ожидает, что каждая команда вернёт 0 (если всё ОК) или прекратит работу (если что-то не так). Поэтому придётся написать на bash вот такую обработку:
Для очистки образов требуется реализовать более сложную логику, но всё это можно сделать в Makefile. Чтобы сделать это легко, я добавлю метку (через Containerfile) к образу (на этапе его создания). Это позволяет легко найти все образы с заданными метками. Самые последние из них можно определить по тегу :latest. И теперь все образы, кроме самых последних (с тегом: latest), могут быть удалены:
Связанные одной целью
На данный момент мой Makefile включает команды для создания и маркировки образов, тестирования, отправки образов, развёртывания новых версий, очистки образов и запуска локальной версии. Выполнять каждую из них с помощью make image_build && make image_tag && make test и так далее значительно проще, чем выполнение каждой из команд, находящихся внутри этих вызовов. Но всё можно упростить ещё больше.
Я хочу, чтобы make в моём проекте по умолчанию делала всё — от создания образа до тестирования, развёртывания и очистки:
Makefile может запускать сразу несколько целей. Сгруппируем цели image_build и image_tag внутри одной цели build. Теперь, чтобы запустить их, нужно просто вызвать make build. Более того, цели build, test, deploy и clean ядополнительно сгруппирую в цель all, что позволит мне запускать их все (в указанной последовательности) за один вызов:
Не только контейнеры
C помощью Makefile можно объединить все команды, необходимые для сборки, тестирования и развёртывания проекта. Так можно упростить и автоматизировать огромное количество задач.
Но Makefile можно использовать и для задач, связанных с разработкой: запуск модульных тестов, компиляция двоичных файлов и формирование контрольных сумм.
Жаль, Makefile не может писать код за нас (подмигивает).
Купить VDS-хостинг с быстрыми NVMе-дисками и посуточной оплатой у хостинга Маклауд.
Оригинал: GNU Make: Manage Your Software Builds
Автор: Mihalis Tsoukalos
Дата публикации: 14 декабря 2016 г.
Перевод: А.Панин
Дата перевода: 30 января 2017 г.
Компилируете программное обеспечение из исходного кода? Если это так, вам просто необходимо разобраться с Make-файлами.
Для чего это нужно?
- Вы сэкономите время, автоматизировав процесс сборки своих программных проектов с помощью Make.
- После создания корректного Make-файла для своего программного проекта, вы едва ли сможете допустить ошибки в процессе его сборки.
Make является мощной системой для автоматизации процесса сборки программного обеспечения, разработанной Стюартом Фельдманом из Bell Labs в апреле 1976 года. GNU Make является стандартной реализацией Make, используемой в Linux и Mac OS X с множеством улучшений, которая, в том числе, необходима для компиляции ядра Linux. Ее основной задачей является автоматическое выявление модифицированных файлов исходного кода сложных приложений и исполнение команд, направленных на их повторную компиляцию.
Для конфигурации make используются так называемые Make-файлы, которые позволяют сохранить группы команд для их последующего исполнения. Давайте рассмотрим содержимое Make-файла более подробно. Но перед этим вы должны принять к сведению тот факт, что GNU Make трактует отступы различных форматах по-разному, то есть, в представлении данного инструмента символ табуляции отличается от 4 или 8 последовательных символов пробела, следовательно, вам придется отнестись к форматированию рассматриваемых файлов с особым вниманием. Это особенно важно, так как каждая строка Make-файла с новой командой должна начинаться с символа табуляции.
Make-файлы могут управлять процессом компиляции программного обеспечения благодаря наличию зависимостей, целей и правил. Правила сообщают GNU Make о том, когда, почему и как нужно исполнять заданные последовательности команд для генерации результирующих файлов на основе файлов исходного кода. Целями являются файлы, которые должны генерироваться с участием GNU Make, причем их имена располагаются слева от символов двоеточий в описаниях правил. Чаще всего каждое из правил имеет по одной цели; однако, в рамках одного правила допускается использование сразу нескольких целей. Зависимости располагаются справа от символов двоеточий в описаниях правил и указывают на то, какие файлы или другие цели могут инициировать исполнение команд, описанных в рамках правила, по причине модификаций.
Впоследствии вы можете переместиться в данную директорию и просто выполнить в ней команду make .
Что же должно случиться?
Теперь вы готовы к исполнению команды make . В случае исполнения команды make program вы должны увидеть аналогичный вывод:
Приведенный в конце вывод утилиты ls позволяет убедиться в том, что все работает в точности так, как ожидалось и был получен желаемый результат.
В случае исполнения команды make clean , позволяющей удалить сгенерированные файлы, должен быть получен аналогичный вывод:
Реализация функции main() находится в файле исходного кода file1.cpp ; исходя из этого, последними компилируемыми файлами должны быть файлы file1.cpp и file1.h . Рассматриваемый проект является настолько простым, что каждый файл с расширением .cpp , за исключением файла file1.cpp , содержит реализацию лишь одного класса, описанного в рамках соответствующего файла с расширением .h , которая используется в рамках файла file1.cpp . Это обстоятельство обуславливает наличие зависимостей, которые описываются в рамках Make-файла.
Важная информация: утилита Make не исследует содержимое файлов проекта для принятия решения о том, стоит ли осуществлять их повторную компиляцию - она всего лишь проверяет метки времени модификации этих файлов.
Отладка Make-файлов
Make-файл может содержать синтаксические или логические ошибки, препятствующие его корректному функционированию. Наиболее полезным в плане отладки Make-файлов параметром утилиты make является параметр -n , который позволяет утилите просто выводить предназначенные для исполнения команды, но не исполнять их. Еще одним полезным параметром является параметр -d , который позволяет утилите выводить большой объем отладочной информации в процессе обычной обработки файла (хотя эта информация и может показаться интересной, она не всегда является полезной).
Последним полезным параметром для отладки Make-файлов является параметр -p , который позволяет утилите make выводить содержимое базы данных, а именно, все правила и значения переменных, извлеченные из Make-файла, перед выполнением необходимых пользователю действий. Если вы хотите получить содержимое базы данных без обработки каких-либо правил и файлов, вам придется воспользоваться следующей командой:
Это наглядная иллюстрация процесса обработки Make-файла, благодаря которой изящность технического решения, положенного в основу утилиты make, становится вполне очевидной
Золотые правила разработки Make-файлов
При разработке нового Make-файла в первую очередь следует описать все макросы (переменные), которые должны содержать полные пути ко всем используемым бинарным файлам системных команд. Это объясняется тем, что вы должны четко знать, какие команды исполняются в процессе сборки проекта, а не полагаться на наличие путей к директориям с необходимыми бинарными файлами системных команд в значении переменной окружения PATH .
Лучше всего начать работу с создания небольшого корректно работающего Make-файла и постепенно добавлять в него новые правила и зависимости. После добавления каждой инструкции вы должны тестировать корректность работы Make-файла, а не продолжать его модификацию. Таким образом вы будете точно знать, какая модификация Make-файла повлекла за собой появление ошибки. Пожалуйста помните о том, что команды из состава правила будут исполняться лишь при модификации целевых файлов, а не всех файлов с исходным кодом проекта.
Не пытайтесь использовать утилиту GNU Make в рамках крупного проекта, если вы все еще изучаете ее возможности; попробуйте внедрить ее в проекты меньшего масштаба перед тем, как начинать работу с более сложными проектами. И наконец, уясните, что переменные полезны и могут существенно сэкономить ваше время, поэтому вам определенно стоит использовать их всегда, когда это возможно.
А это пример практического использования сложного Make-файла в рамках программного проекта на языке C
Различные полезные параметры утилиты make
Если вам понадобится использовать Make-файл с именем, отличающимся от makefile или Makefile , вы можете воспользоваться параметром -f , передав после него имя этого файла. Параметры --file=ИМЯ_ФАЙЛА и --makefile=ИМЯ_ФАЙЛА полностью эквивалентны параметру -f . Параметр -k сообщает утилите make о необходимости осуществления сборки проекта так долго, как это возможно вне зависимости от обнаруженных ошибок. Параметр -t позволяет изменить метки времени модификации файлов вместо выполнения команд для сборки проекта; данный режим работы предназначен для симуляции полной пересборки проекта, что соответствующим образом отразится на результате последующего запуска утилиты make . Параметр --trace позволяет получить информацию о том, почему заданная цель требует пересборки проекта и какие команды будут выполнены в ходе этой операции. Параметр --trace также может использоваться для отладки файлов GNU Make.
Вы можете без каких-либо сложностей прочитать или изменить значение существующей переменной окружения, объявив ее в рамках своего Make-файла. Если вы захотите отключить данный механизм, вы можете просто вызвать утилиту make , передав ей параметр -e .
Пример вывода утилиты GNU Make с параметрами -n, -p и -d, которые используются главным образом для целей отладки Make-файлов
Внутри Make-файла
В первой строке рассматриваемого Make-файла описывается переменная RM , значением которой является полный путь к бинарному файлу утилиты rm , которая используется для удаления файлов. Для того, чтобы получить значение переменной RM и использовать его в рамках вашего Make-файла, вам придется использовать нотацию $(RM) . Мы поговорим о переменных более подробно в следующем разделе.
В строке 3 описывается цель program , имеющая пять зависимостей, причем тремя ее зависимостями являются другие цели ( file2.o , file3.o и file4.o ), а остальными двумя - файл исходного кода на языке C++ и соответствующий заголовочный файл. Цель program имеет лишь одну ассоциированную команду, которая приведена в строке 4. Однако, как становится очевидно при рассмотрении цели clean (также из приведенного выше Make-файла), с любой целью может ассоциироваться более одной команды. В строках с 6 по 10 приведены инструкции по созданию объектных файлов на основе трех классов из файлов с исходным кодом на языке С++ с именами file2.cpp , file3.cpp и file4.cpp соответственно. Последнее правило предназначено для удаления всех временных файлов - использование цели clean является обычной практикой для Make-файлов, так как подобная цель, осуществляющая удаление всех временных файлов без какой-либо модификации основных файлов проекта, позволяет осуществлять полную пересборку проекта.
Параметр -с компилятора из набора GCC используется главным образом для обработки файлов, которые не содержат реализации функции main() , так как он сообщает компилятору C++ из состава GCC о том, что нужно осуществлять лишь предварительную обработку, компиляцию и ассемблирование исходного кода. Параметр -o , используемый в рамках цели program сообщает g++ и gcc о необходимости сохранения результирующего бинарного файла под именем . Если вы попытаетесь задействуете цель, соответствующую файлам в актуальном состоянии, вы получите аналогичный вывод:
В первой части Make-файла описывается множество переменных, которые будут использоваться далее в файле и могут быть разделены на два вида. Некоторые переменные используются для хранения путей к бинарным файлам команд (это позволяет переносить Make-файлы на другие операционные системы). Например:
Другие переменные используются для формирования имен новых файлов на основе различных параметров, таких, как дата и путь к резервной копии файла. Например:
Несложно заметить, что имеется возможность комбинирования множества переменных в рамках одной инструкции для генерации уникальных имен файлов и директорий.
Рассматриваемый Make-файл содержит переменную, хранящую номер версии вашей программы. Значение этой переменной может изменяться либо с помощью параметра командной строки, либо путем редактирования самого Make-файла. Если вы желаете использовать значение переменной, отличное от объявленного в Make-файле (в данном случае это переменная VERSION), вы можете осуществить аналогичный вызов GNU Make:
Однако, данная возможность должна использоваться с особой осторожностью особенно при работе с номерами версий или другими важными параметрами сборки проектов, так как вы в конце концов можете запутаться в версиях своей программы и потерять важные изменения кода.
Переменные GNU Make также называются макросами. Следующее правило является реализацией цели make all :
Это простая цель, которая позволяет создать программный продукт на основе его исходного кода путем передачи управления другому правилу ( executable ). Все программы проекта GNU должны в обязательном порядке поддерживать цель all .
Следующее правило реализует команду make clean , позволяющую удалить все файлы, которые были сгенерированы при предыдущих сборках проекта:
Это очень простое правило, удаляющее все файлы, сгенерированные при предыдущих сборках проекта. Символ @ сообщает GNU Make о том, что в процессе исполнения команды ее вывод не должен отображаться с помощью терминала.
Правило install просто копирует скомпилированные файлы в нужное место. Его реализация выглядит следующим образом:
Цель target должна зависеть от цели executable , так как при отсутствии исполняемого файла вы не сможете установить его в систему!
Для копирования файла в директорию, расположенную за пределами домашней директории, обычно требуются особые привилегии. Директория /tmp является исключением из данного правила; однако, директория /tmp обычно очищается после перезагрузки системы. Если вам нужно установить бинарный файл в директорию /usr/local/bin , которая обычно используется для хранения исполняемых файлов, вам придется выполнить команду make install либо от лица пользователя root, либо с помощью утилиты sudo .
Следующие команды реализуют команду make backup , генерируя архив с резервной копией файлов проекта, уникально идентифицируемый с помощью времени и даты создания:
Хранение резервной копии файлов проекта за пределами его директории является разумной практикой. В приведенном выше Make-файле файл архива сохраняется в директории /tmp , но вы можете использовать вместо нее любую другую директорию.
Другой переменной, практически всегда используемой в процессе компиляции кода на языках C и C++, является переменная CFLAGS , которая хранит флаги, используемые в процессе компиляции. Например, вы можете активировать режим вывода всех предупреждений компилятора и включить информацию о символах в результирующий бинарный файл для упрощения процесса отладки с помощью следующих флагов:
Вы можете изменить значение переменной CFLAGS для активации поддержки отладчиков, механизмов оптимизации кода или генерации машинного кода для отличной архитектуры центрального процессора.
Для того, чтобы избавить разработчиков от создания огромного количества аналогичных правил в make была добавлена поддержка шаблонов. Например, символ % соответствует любой последовательности символов. Этот механизм может пригодится, к примеру, в том случае, если вам понадобится скомпилировать все файлы с исходным кодом на языке C и сохранить объектный код в файлах с соответствующими именами:
В данном случае вам также следует обратить внимание на типичный пример использования переменной CFLAGS . Вы должны самостоятельно принять решение о том, обоснованно ли написание отдельных правил для генерации объектных файлов в ручном режиме или проще воспользоваться соответствующим шаблоном. Вообще говоря, подход с использованием шаблонов является предпочтительным тогда, когда приходится обрабатывать большое количество файлов. Конечно же, в случае использования шаблона вам придется также изменить вашу реализацию команды make clean на аналогичную приведенной ниже:
Следующая цель позволяет вывести список значений всех переменных Make-файла:
Список значений будет достаточно длинным, так как в него также будут включены все значения переменных командной оболочки. Но, несмотря на это, данная цель является достаточно полезной для отладки Make-файла.
Вы можете сделать вывод данной цели чуть более простым с помощью фильтра, ограничивающего объем выводимых данных. Например, если вам нужно получить список значений всех переменных с именами, начинающимися с буквы A, вы можете использовать следующую версию цели, задействующую механизм шаблонов и регулярное выражение:
Несложно заметить, что Make-файл становится достаточно сложным для чтения после добавления целей, правил, зависимостей и переменных, практически таким же, как файл с исходным кодом проекта. По этой причине вы должны стараться структурировать его содержимое и при необходимости добавлять в него комментарии, которые должны начинаться с символа решетки.
Make масштабируется практически до любого уровня сложности проекта. Даже исходный код ядра Linux содержит Make-файлы
Несмотря на то, что основным предназначением утилиты make является автоматизация процесса сборки программных проектов, администраторы Unux-систем, а также их обычные пользователи могут использовать ее и для своих целей. Вы можете использовать данную утилиту для создания резервных копий файлов конфигурации после их обновления, установки новых версий сценариев командной оболочки, генерации новых таблиц поиска Postfix с последующим перезапуском соответствующей службы, создания заданной структуры директорий для каждого из новых пользователей системы, генерации документации и выполнения других подобных действий - ее возможности безграничны; конечно же, вы также можете выработать новые методики использования утилиты GNU Make.
Читайте также: