How to use makefile

How to use makefile

Compiling the source code files can be tiring, especially when you have to include several source files and type the compiling command every time you need to compile. Makefiles are the solution to simplify this task.

Makefiles are special format files that help build and manage the projects automatically.

For example, let’s assume we have the following source files.

The following is the code for main.cpp source file −

The code given below is for hello.cpp source file −

The code for factorial.cpp is given below −

The following is the code for fnctions.h −

The trivial way to compile the files and obtain an executable, is by running the command −

This command generates hello binary. In this example we have only four files and we know the sequence of the function calls. Hence, it is feasible to type the above command and prepare a final binary.

However, for a large project where we have thousands of source code files, it becomes difficult to maintain the binary builds.

The make command allows you to manage large programs or groups of programs. As you begin to write large programs, you notice that re-compiling large programs takes longer time than re-compiling short programs. Moreover, you notice that you usually only work on a small section of the program ( such as a single function ), and much of the remaining program is unchanged.

In the subsequent section, we see how to prepare a makefile for our project.

The make program allows you to use macros, which are similar to variables. Macros are defined in a Makefile as = pairs. An example has been shown below −

Special Macros

Before issuing any command in a target rule set, there are certain special macros predefined −

$@ is the name of the file to be made.

$? is the names of the changed dependents.

For example, we could use a rule as follows −

There are two more special macros used in the implicit rules. They are −

What Is a Makefile and How to Use It

How to use makefile. Смотреть фото How to use makefile. Смотреть картинку How to use makefile. Картинка про How to use makefile. Фото How to use makefile

Introduction

Many developers may recall the first day they started working on a new project. After cloning the main repository, there comes a point when you have to enter a lot of commands with certain flags and in a specific order. In most cases, it’s hard to grasp what is going on without a description of the commands. For example:

These commands are just a small portion of the project deployment process. The commands themselves are extensive and contain several flags, as shown in the example above, making them not only difficult to learn but also difficult to enter manually. Constantly maintaining documentation becomes more challenging as the project grows; it inevitably becomes outdated, and the entry barrier for newcomers increases since no one can remember all of the project’s details. Some of these commands must be used on a daily basis, if not multiple times per day.

Over time, it became clear that we desperately needed a tool that could maintain such commands, and provide convenient shortcuts and self-documentation of the project. This is precisely what Makefile and the make utility have turned into. In this guide, I’ll show you how to reduce the deployment to a few short and straightforward commands using these tools:

What is make and Makefile

Makefile is a file that is stored in the repository alongside the code. It is usually placed at the project’s root. It acts both as documentation and as executable code. The Makefile hides the implementation details and manages the commands, and the make utility runs them from the Makefile in the current directory.

The make has become a standard for many developers, especially for those working on large projects. Examples of makefile can be found in projects such as Kubernetes, Babel, Ansible, and, of course, everywhere on Hexlet.

Makefile syntax

make runs targets from a Makefile that contains the following commands:

However, it’s not enough to just start using a Makefile in a project. To make its implementation more efficient, build a target-oriented command structure and give the targets semantically relevant names. At first, moving commands to a Makefile may result in all commands being merged into a single one with a vague name:

Several actions take place here at once: creating a file with environment variables, preparing the database, generating keys, installing dependencies, and launching the project. Since this is impossible to understand from the comments and target name, it’s best to separate these commands into different independent targets:

Commands will only be executed in the specified order and only if the previous command proves to be successful. Therefore, you can add a setup target to combine all the necessary actions:

Now it is enough to deploy and launch the project with two commands:

The project commands and flags are combined into a Makefile as a result of the Makefile’s work. It ensures the correct execution order, regardless of the languages or technologies involved.

Advanced usage

Fake target

Running commands consecutively and ignoring errors

The easiest (but not the only) way to “cover up” an error is to use a logical OR in the Makefile:

Be cautious applying such hacks so that you don’t shoot yourself in the foot in more complex scenarios.

Variables

Configuration parameters, path indicators, and environment variables are often substituted into commands, and make enables you to handle this as well. Variables can be written directly in the command within the makefile and passed when called:

Variables can be optional and have a default value. They are commonly declared at the beginning of the Makefile.

Conclusion

Because the Makefile can describe multi-line commands consecutively, it may be used as a “universal glue” between language managers and other utilities. The widespread use of this tool and its overall simplicity allows you to quickly implement it into your project without making any changes. However, Makefile can be extremely large and complicated, as shown by the following real-world applications:

Additional materials

Makefile examples from this guide were taken from:

What is a Makefile and how does it work?

How to use makefile. Смотреть фото How to use makefile. Смотреть картинку How to use makefile. Картинка про How to use makefile. Фото How to use makefile

In this article, we’ll explore make and Makefile using basic and advanced examples. Before you start, ensure that make is installed in your system.

Basic examples

Let’s start by printing the classic «Hello World» on the terminal. Create a empty directory myproject containing a file Makefile with this content:

In the example above, say_hello behaves like a function name, as in any programming language. This is called the target. The prerequisites or dependencies follow the target. For the sake of simplicity, we have not defined any prerequisites in this example. The command echo «Hello World» is called the recipe. The recipe uses prerequisites to make a target. The target, prerequisites, and recipes together make a rule.

To summarize, below is the syntax of a typical rule:

As an example, a target might be a binary file that depends on prerequisites (source files). On the other hand, a prerequisite can also be a target that depends on other dependencies:

It is not necessary for the target to be a file; it could be just a name for the recipe, as in our example. We call these «phony targets.»

Going back to the example above, when make was executed, the entire command echo «Hello World» was displayed, followed by actual command output. We often don’t want that. To suppress echoing the actual command, we need to start echo with @ :

Now try to run make again. The output should display only this:

Let’s add a few more phony targets: generate and clean to the Makefile :

Let’s include that at the beginning of our makefile:

This will run the target generate as the default:

The make should call say_hello and generate :

It is a good practice not to call clean in all or put it as the first target. clean should be called manually when cleaning is needed as a first argument to make :

Now that you have an idea of how a basic makefile works and how to write a simple makefile, let’s look at some more advanced examples.

Эффективное использование GNU Make

(C) Владимир Игнатов, 2000

Оглавление

Оглавление

0. Предисловие

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

Для работы я использовал GNU Make версии 3.79.1. Некоторые старые версии GNU Make (например, версия 3.76.1 из дистрибутива Slackware 3.5) могут неправильно работать с примером «традиционного» строения make-файла (по-видимому, они «не воспринимают» старую форму записи шаблонных правил).

1. Моя методика использования GNU Make

1.1. Пример проекта

1.2. «Традиционный» способ построения make-файлов

1.3. Автоматическое построение списка объектных файлов

1.4. Автоматическое построение зависимостей от заголовочных файлов

Перечисление зависимостей «вручную» требует довольно кропотливой работы. Недостаточно просто открыть файл с исходным текстом и перечислить имена всех заголовочных файлов, подключаемых с помощью #include. Дело в том, что одни заголовочные файлы могут, в свою очередь, включать в себя другие заголовочные файлы, так что придется отслеживать всю «цепочку» зависимостей.

Ключ компиляцииНазначение
-MДля каждого файла с исходным текстом препроцессор будет выдавать на стандартный вывод список зависимостей в виде правила для программы make. В список зависимостей попадает сам исходный файл, а также все файлы, включаемые с помощью директив #include и #include «имя_файла». После запуска препроцессора компилятор останавливает работу, и генерации объектных файлов не происходит.
-MMАналогичен ключу -M, но в список зависимостей попадает только сам исходный файл, и файлы, включаемые с помощью директивы #include «имя_файла»
-MDАналогичен ключу -M, но список зависимостей выдается не на стандартный вывод, а записывается в отдельный файл зависимостей. Имя этого файла формируется из имени исходного файла путем замены его расширения на «.d«. Например, файл зависимостей для файла main.cpp будет называться main.d. В отличие от ключа -M, компиляция проходит обычным образом, а не прерывается после фазы запуска препроцессора.
-MMDАналогичен ключу -MD, но в список зависимостей попадает только сам исходный файл, и файлы, включаемые с помощью директивы #include «имя_файла»

После того как файлы зависимостей сформированы, нужно сделать их доступными утилите make. Этого можно добиться с помощью директивы include.

Имеет ли описанная методика недостатки? Да, к сожалению, имеется один недостаток. К счастью, на мой взгляд, не слишком существенный. Дело в том, что утилита 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. «Разнесение» файлов с исходными текстами по директориям

1.6. Сборка программы с разными параметрами компиляции

Вот как выглядит Makefile для этого примера:

Переменная compile_flags получает свое значение из командной строки и, далее, используется при компиляции исходных текстов. Для ускорения работы компилятора, к параметрам компиляции добавляется флажок -pipe. Обратите внимание на необходимость использования директивы override для изменения переменной compile_flags внутри make-файла.

1.7. «Разнесение» разных версий программы по отдельным директориям

Вот, например, как выглядит командный файл make_release, собирающий рабочую версию программы (результаты компиляции помещается в каталог release):

Командный файл для сборки отладочного варианта программы (make_debug) выглядит аналогично. Различие только в имени директории, куда помещаются результаты компиляции (debug) и другом наборе флагов компиляции: Вот окончательная версия make-файла для сборки «гипотетического» проекта текстового редактора:

В этом окончательном варианте я «вынес» имя исполняемого файла программы в отдельную переменную program_name. Теперь для того чтобы адаптировать этот make-файл для сборки другой программы, в нем достаточно изменить всего лишь несколько первых строк.

2. GNU Make

2.1. Две разновидности переменных

Переменная может «менять» свое поведение в зависимости от того, какой из операторов присваивания был к ней применен последним. Одна и та же переменная на протяжении своей жизни вполне может вести себя и как «макрос» и как «текстовая переменная».

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 подробно обсуждается в разделе «The Function wildcard» руководства по GNU Make.

Функция notdir позволяет «убрать» из имени файла имя директории, где он находится. Например, в результате выполнения make-файла:

на экран будет выведено Видно, что из имен файлов убраны «пути» к этим файлам. Функция notdir обсуждается в разделе «Functions for File Names» руководства по GNU Make.

на экран будет выведено

Видно, что во всех словах окончание «.cpp» заменено на «.o«. Функция patsubst имеет второй, более короткий вариант записи для тех случаев, когда надо изменить суффикс слова (например, заменить расширение в имени файла). Более короткий вариант выглядит так: Применяя «короткий» вариант записи предыдущий пример можно записать так:

Функция patsubst обсуждается в разделе «Functions for String Substitution and Analysis» руководства по GNU Make.

2.3. Новый способ задания шаблонных правил

В своих 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. Добавление текста в строку

Если переменная задана с помощью командной строки, то по-прежнему для изменения ее значения внутри make-файла нужно использовать директиву override. В следующем примере предполагается, что переменная compile_flags задана в командной строке:

2.7. Директива include

Основы Make

Установить make

sudo apt install make

sudo yum install make

Так как make входит в состав build-essentials можно установить вместе с этим пакетом

sudo apt install build-essentials

Проверить версию make

GNU Make 4.2.1 Built for x86_64-pc-linux-gnu Copyright (C) 1988-2016 Free Software Foundation, Inc. License GPLv3+: GNU GPL version 3 or later This is free software: you are free to change and redistribute it. There is NO WARRANTY, to the extent permitted by law.

Для чего используются Makefiles

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

Другие языки обычно имеют свои собственные инструменты, которые служат той же цели, что и Make.

Его можно использовать и за пределами программ, когда вам нужна серия инструкций для запуска в зависимости от того, какие файлы изменились.

В этой статье вы узнаете про использование компиляции C/C++.

Вот пример графика зависимостей, который вы можете построить с помощью Make.

Если какие-либо зависимости файла изменятся, то файл будет перекомпилирован:

How to use makefile. Смотреть фото How to use makefile. Смотреть картинку How to use makefile. Картинка про How to use makefile. Фото How to use makefile

Граф зависимостей
wikipedia.org

Формат

target: prerequisites recipe

На русский обычно переводят так

цель: зависимости команды

Типичное применение: какая-то зависимость изменилась → выполнятеся действие в результате которого создаётся таргет файл.

Как и в статье Configure, make, install в примере выше используются стандартные цели (target)

.PHONY

Рассмотрим следующий Makefile

.PHONY: site site: echo «HeiHei.ru»

Если теперь выполнить

Удалите site из первой строки, а всё остальное не трогайте

Вроде бы ничего не изменилось, но теперь создайте файл site рядом с Makefile

touch site
make site

make: ‘site’ is up to date.

Для защиты от таких неприятностей и применяют PHONY

Также PHONY удобен тем, что можно перечислить все цели в самом начале файла.

Посмотреть цели Make-файла

Чтобы получить списко всех целей воспользуйтесь grep и выполните

cat GNUmakefile | grep PHONY:

Пример из C++

Functions.cpp Functions.h Main.cpp

#include #include «Functions.h» int main()

double add( double x, double y)

#pragma once double add(double x, double y);

Эта команда сначала вызывает компиляцию, затем линковку

Создайте Makefile и откройте его в текстовом редакторе. Например, в Vim

touch Makefile
vi Makefile

Makefile будет выглядеть следующим образом

Теперь для компиляции достаточно выполнить

В результате появится исполняемый файл output

В этот пример можно добавить ещё два шага: отдельно следить за компиляцией и убираться после работы.

To запустить скрипт, достаточно выполнить

Если нужно скомпилировать Main execute

Появится файл Main.o но не появятся остальные (Functions.o, output)

Functions.cpp Functions.h Main.cpp Main.o Makefile

На примере команды make Main.o можно понять почему в Make-файлах используется термин цели (target)

Если теперь выполнить make Main.o не будет перекомпилироваться. Будут выполнены только последние два шага.

Выполните make если ещё не выполняли и не делайте после этого clean

Добавим ещё одну функцию в наш проект. Нужно указать её в файлах Functions.*

Вызывать пока не будет, поэтому Main.cpp остаётся без изменений

bool test( bool x)

bool test(bool x);

Обратите внимание: Main.cpp не был перекомпилирован так как в нём нет изменений.

Таже посмотрите на время изменения файла output оно должно измениться.

Не вносите никаких изменений в файлы и execute

make: ‘output’ is up to date.

Перекомпиляция не нужна и поэтому не выполнена

Переменные

Подробнее про переменные в Makefile читайте в статье Работа с переменными в GNUmakefile

В этом примере вы можете увидеть как названия файлов сохранены в переменную для сокращения кода.

Запустить Docker container из Makefile

Читайте также статью

Параметризация Make

?= позволяет переменным быть перезаписанными на существующие переменные окружения

:= перезаписывает значение переменной

BUILD_ID

To добавить переменным уникальности используют BUILD_ID

USER_ID

To получить ID пользователя запустившего GNUmakefile

Какие альтернативы Make существуют

Для Java есть Ant, Maven и Gradle.

Другие языки, такие как Go и Rust, имеют свои собственные инструменты сборки.

Цель Makefile состоит в том, чтобы скомпилировать любые файлы, которые должны быть скомпилированы, основываясь на том, какие файлы изменились.

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

При запуске программы используется самая последняя версия файла.

cc это C compiler

Существует несколько общедоступных компиляторов C

В этой статье использовался gcc

-c это опция, которую разбирали здесь

whoami

В Make файле это может не получиться. Есть два варианта решить проблему

Игнорировать ошибки

Если какая-то команда выполнена с ошибкой выполнение сценария прерывается.

Если в …release/ пусто, то удалять в …master/ make уже не будет.

Вместо этого появится ошибка:

sudo rm /home/$(whoami)/rpms/release/* rm: cannot remove ‘/home/andrei/rpms/release/*’: No such file or directory make: *** [clean-repo] Error 1

rm: cannot remove ‘/home/andrei/rpms/release/*’: No such file or directory
make: [clean-repo] Error 1 (ignored)

make жалуется, но переходит ко второй команде и чистит директорию.

Подпишитесь на Telegram канал @aofeed чтобы следить за выходом новых статей и обновлением старых

Источники информации:

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

Ваш адрес email не будет опубликован. Обязательные поля помечены *