Основы Make
Установить make
sudo apt install make
или для rpm
sudo yum install make
Так как make входит в состав build-essentials можно установить вместе с этим пакетом
sudo apt install build-essentials
Проверить версию make
/usr/bin/make --version
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 <http://gnu.org/licenses/gpl.html> This is free software: you are free to change and redistribute it. There is NO WARRANTY, to the extent permitted by law.
Для чего используются Makefiles
Make-файлы используются, чтобы помочь решить, какие части большой
программы должны быть перекомпилированы.
В подавляющем большинстве случаев компилируются файлы
C
или
C++
.
Другие языки обычно имеют свои собственные инструменты, которые служат той же цели, что и Make.
Его можно использовать и за пределами программ, когда вам нужна серия инструкций для запуска
в зависимости от того, какие файлы изменились.
В этой статье вы узнаете про использование компиляции C/C++.
Вот пример графика зависимостей, который вы можете построить с помощью Make.
Если какие-либо зависимости файла изменятся, то файл будет перекомпилирован:
Формат
Makefile состоит из правил (rules).
Первым указывается название цели (target), затем зависимости (prerequisites)
и действие (recipe - набор действий/команд), которое нужно выполнить.
Зависимости нужны не всегда и указываются по необходимости. Для простоты на первом этапе можно
думать о зависимостях как о файлах, которые нужно проверить: если ни один не изменился - заново
компилировать не нужно.
Отступы по умолчанию нужно ставить табуляцией. Если хотите поменять на другой символ - задайте .RECIPEPREFIX
target: prerequisites
recipe
На русский обычно переводят так
цель: зависимости
команды
Типичное применение: какая-то зависимость изменилась → выполнятеся действие в результате которого создаётся таргет файл.
output: main.o message.o
g++ main.o message.o -o output
clean:
rm *.o output
Как и в статье Configure, make, install в примере выше используются стандартные цели (target)
Про опции -o и -c читайте статью «Компиляция в C++
Опция | Назначение |
---|---|
-c | Указывает компилятору не делать линковку и создавать .o файлы для каждого исходника |
-o filename | Меняет название output файла со стадартного на указанный |
-S | Directs the compiler to produce an assembly source file but not to assemble the program. |
Дополнительная информация (на английском ): gnu.org: Rule-Introduction
Если файл вам не нужен, например, вы просто хотите выполнить какие-то команды - можно использовать .PHONY
.PHONY
PHONY это одна из
Специальных встроенных целей
(gnu.org - Special Built-in Target Names)
Рассмотрим следующий
Makefile
.PHONY: site
site:
echo "HeiHei.ru"
Если теперь выполнить
make site
echo "HeiHei.ru"
HeiHei.ru
Удалите site из первой строки, а всё остальное не трогайте
make site
echo "HeiHei.ru"
HeiHei.ru
Вроде бы ничего не изменилось, но теперь создайте файл site рядом с Makefile
touch site
make site
make: 'site' is up to date.
Так как таргет теперь реальный - make не нашёл изменений и ничего не сделал. Из-за такого простого
совпадения имени цели (target) и какого-то файла в директории может перестать работать скрипт.
Для защиты от таких неприятностей и применяют PHONY
Также PHONY удобен тем, что можно перечислить все цели в самом начале файла.
Если не злоупотреблять этой возможностью - можно улучшить читаемость кода, особенно в небольших файлах.
Посмотреть цели Make-файла
Если вы создали Make-файл с большим количеством PHONY целей и забыли название нужно - не обязательно продираться через весь файл
Чтобы получить список всех целей воспользуйтесь
grep
и выполните
cat GNUmakefile | grep PHONY:
Пример из C++
Рассмотрим пример из статьи о заголовочных файлах .h
Есть три файла
ls
Functions.cpp Functions.h Main.cpp
Main.cpp
#include <iostream>
#include "Functions.h"
int main() {
double b = add(1.3, 4.5);
cout << "1.3 + 4.5 is " << b << "\n";
return 0;
}
Functions.cpp
double add(double x, double y)
{
return x + y;
}
Functions.h
#pragma once
double add(double x, double y);
Если один из этих файлов изменился - нужно перекомпилировать проект. Для начала будем пользоваться командой
g++ -o output Main.cpp Functions.cpp
Эта команда сначала вызывает компиляцию, затем линковку
Создайте Makefile и откройте его в текстовом редакторе. Например, в Vim
touch Makefile
vi Makefile
Makefile будет выглядеть следующим образом
output: Main.cpp Functions.cpp Functions.h
g++ -o output Main.cpp Functions.cpp
Теперь для компиляции достаточно выполнить
make output
Или просто
make
В результате появится исполняемый файл output
В этот пример можно добавить ещё два шага: отдельно следить за компиляцией и убираться после работы.
Если вам не понятно что происходит в этом файле - изучите статью
«Компиляция в C++
.PHONY: clean
output: Main.o Functions.o
g++ Main.o Functions.o -o output
Main.o: Main.cpp
g++ -c Main.cpp
Functions.o: Functions.cpp
g++ -c Functions.cpp
clean:
rm *.o output
To запустить скрипт, достаточно выполнить
make
g++ -c Main.cpp
g++ -c Functions.cpp
g++ -o output Main.o Functions.o
Если нужно скомпилировать Main execute
make Main.o
g++ -c Main.cpp
ls
Появится файл Main.o но не появятся остальные (Functions.o, output)
Functions.cpp Functions.h Main.cpp Main.o Makefile
На примере команды make Main.o можно понять почему в Make-файлах используется термин цели (target)
make
Main.o
говорит - создай файл
Main.o
а инструкция в Makefile определяет правило по которому это нужно сделать.
Если теперь выполнить make Main.o не будет перекомпилироваться. Будут выполнены только последние два шага.
g++ -c Functions.cpp
g++ -o output Main.o Functions.o
Выполните make если ещё не выполняли и не делайте после этого clean
Добавим ещё одну функцию в наш проект. Нужно указать её в файлах Functions.*
Вызывать пока не будет, поэтому
Main.cpp
остаётся без изменений
Functions.cpp
bool test(bool x)
{
return x;
}
Functions.h
bool test(bool x);
make
g++ -c Functions.cpp g++ -o output Main.o Functions.o
Обратите внимание:
Main.cpp
не был перекомпилирован так как в нём нет изменений.
Таже посмотрите на время изменения файла
output
оно должно измениться.
Не вносите никаких изменений в файлы и execute
make
make: 'output' is up to date.
Перекомпиляция не нужна и поэтому не выполнена
Переменные
Подробнее про переменные в Makefile читайте в статье
Работа с переменными в GNUmakefile
В этом примере вы можете увидеть как названия файлов сохранены в переменную для сокращения кода.
.PHONY: clean
objects = Main.o Functions.o
output: $(objects)
g++ -o output $(objects)
Main.o: Main.cpp
g++ -c Main.cpp
Functions.o: Functions.cpp
g++ -c Functions.cpp
clean:
rm *.o output
Запустить Docker container из Makefile
Читайте также статью
«Introduction в Docker»
Пример
.PHONY: docker
docker:
docker-compose -f docker/dev/docker-compose.yml build
Параметризация Make
?= позволяет переменным быть перезаписанными на существующие переменные окружения
:= перезаписывает значение переменной
PROJECT_NAME ?= myproject
ORG_NAME ?= heihei
REPO_NAME ?= myproject
#Filenames
DEV_COMPOSE_FILE := docker/dev/docker-compose.yml
REL_COMPOSE_FILE := docker/release/docker-compose.yml
.PHONY: test release
test:
docker-compose -f $(DEV_COMPOSE_FILE) build
docker-compose -f $(DEV_COMPOSE_FILE) up agent
docker-compose -f $(DEV_COMPOSE_FILE) up test
release:
docker-compose -f $(REL_COMPOSE_FILE) build
docker-compose -f $(REL_COMPOSE_FILE) up agent
docker-compose -f $(REL_COMPOSE_FILE) run --rm app manage.py collectstatic --noinput
docker-compose -f $(REL_COMPOSE_FILE) run --rm app manage.py migrate --noinput
docker-compose -f $(REL_COMPOSE_FILE) up test
clean:
docker-compose -f $(DEV_COMPOSE_FILE) kill
docker-compose -f $(DEV_COMPOSE_FILE) rm -f
docker-compose -f $(REL_COMPOSE_FILE) kill
docker-compose -f $(DEV_COMPOSE_FILE) rm -f
BUILD_ID
Чтобы добавить переменным уникальности используют BUILD_ID
# Docker Compose Project Names
REL_PROJECT := $(PROJECT_NAME)$(BUILD_ID)
DEV_PROJECT := $(REL_PROJECT)dev
USER_ID
To получить ID пользователя запустившего GNUmakefile
USER_ID = $(shell id -u ${USER})
Какие альтернативы Make существуют
Популярными альтернативными системами сборки C/C++ являются
SCons, CMake, Bazel и Ninja. Некоторые редакторы кода, такие как
Microsoft Visual Studio
, имеют свои собственные встроенные инструменты сборки.
Для
Java
есть Ant,
Maven
и Gradle.
Другие языки, такие как
Go
и Rust, имеют свои собственные инструменты сборки.
Интерпретируемые языки, такие как
Python
,
Ruby
и
JavaScript
, не требуют аналога для создания файлов.
Цель Makefile состоит в том, чтобы скомпилировать любые файлы, которые
должны быть скомпилированы, основываясь на том, какие файлы изменились.
Но когда файлы на интерпретируемых языках меняются, ничего не нужно перекомпилировать.
При запуске программы используется самая последняя версия файла.
Что означает cc -c
cc это C compiler
Существует несколько общедоступных компиляторов C
В этой статье использовался
gcc
-c это опция, которую разбирали
здесь
whoami
В обычном
Bash скрипте
достаточно написать $(whoami) и это будет равносильно подстановке вывода whoami
В Make файле это может не получиться. Есть два варианта решить проблему
`whoami`
И
$$(whoami)
Игнорировать ошибки
Если какая-то команда выполнена с ошибкой выполнение сценария прерывается.
Рассмотрим пример
RPM_DIR=/home/$$(whoami)/rpms/ .PHONY: clean-repo clean-repo: @sudo rm $(RPM_DIR)release/* @sudo rm $(RPM_DIR)master/*
Если в …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
Избежать этой проблемы можно поставив - перед командой
RPM_DIR=/home/$$(whoami)/rpms/ .PHONY: clean-repo clean-repo: @-sudo rm $(RPM_DIR)release/* @-sudo rm $(RPM_DIR)master/*
[andrei@localhost ~]$ make clean-repo
rm: cannot remove ‘/home/andrei/rpms/release/*’: No such file or directory
make: [clean-repo] Error 1 (ignored)
make жалуется, но переходит ко второй команде и чистит директорию.
Цель из других целей
Если нужно запустить несколько целей сразу, можно вызывать из новой цели
all-targets: target1 target2 target3
Несколько make-файлов в одной директории
Если в одной директории находится два и более make-файлов с совпадающими целями, вызывать из нужного файла помогает опция -f
Пример проекта
make ├── GNUmakefile.beget └── GNUmakefile.heihei
# GNUmakefile.beget .PHONY: url url: echo "https://beget.com"
# GNUmakefile.heihei .PHONY: url url: echo "https://heihei.ru"
make -f GNUmakefile.beget url
echo "https://beget.com" https://beget.com
make -f GNUmakefile.heihei url
echo "https://heihei.ru" https://heihei.ru