билд-система make возникла в 1976 году, полвека назад, а мы ею всё ещё пользуемся. это оч круто. ну она самодостаточная для простых задач, в данном случае для автоматической рекомпиляции однофайловых программ на C/C++
Makefile для C:
default:
echo no target specified
%.out: %.cpp
clang -lm -O3 -Wall -Wextra -Wpedantic -o $@ $<
$(basename $(wildcard *.cpp)): %: %.out
./$<
Makefile для C++:
default:
echo no target specified
%.out: %.cpp
clang++ -lm -std=c++20 -O3 -Wall -Wextra -Wpedantic -o $@ $<
$(basename $(wildcard *.cpp)): %: %.out
./$<
такими я пользуюсь при решении задачек по информатике, на олимпиадах (выдумал именно такой в марте ‘26 и на паре оставшихся олимпиад по информатике очень смешно всякий раз полунаугад вспоминал как он пишется) и так далее
Makefile это одноимённый конфигурационный текстовый файлик. думаю даже знакомые с таким форматом найдут вышенаписанный достаточно эзотерическим. а я, честно признаться, и в общем плохо понимаю, как Makefile работает. но свой конкретный пример могу объяснить, он, может быть, вам пригодится
в этом файлике перечисляются “цели” (компиляции), затем идут их “зависимости” и на следующих строчках собственно “инструкции” (ВАЖНО: к сожалению, отступы поддерживаются только табуляциями. не думаю, что с сайта всё скопируется в рабочем состоянии)
когда make бегается, он в рабочей директории считывает этот Makefile (которого если нет, ему и делать нечего). если make не предоставлено аргументов, он исполняет цель “по умолчанию” (просто первую в списке), я решил такую выделить отдельно как “default”, чтобы он предупреждал что ты не выбрал цель. но по идее надо вызывать make [TARGET], чтобы он выбрал нужную цель, проверил её зависимости и выполнил инструкции. но где же цели?? вот тут-то и начинается магия: $(wildcard *.c) (буду описывать для C, для C++ аналогично) перечисляет все .c файлы в нашей рабочей директории (допустим, это foo.c и bar.c — раз они здесь, значит мы хотим компилировать и запускать код в них?). $(basename $(wildcard *.c)) выделяет их “имена до расширения” (в нашем примере это будут foo и bar). и make для каждого из значений повторяет написанный дальше шаблон, подставляя значение на место %. получается, например:
foo: foo.out
./$<
bar: bar.out
./$<
то есть у нас теперь есть “цели” foo и bar. мы можем, например, запустить make foo и он выберет эту цель. увидит у неё зависимостью foo.out — это исполняемый файл, которому ещё соответствует цель выше. цель выше тоже шаблонная — %.out% реагирует на любую .out цель, значит и на нашу foo.out тоже, и соотносит ей зависимость foo.c. это уже просто файл на диске, собственно нужный нам исходный код. make проверяет актуальность цели foo.out: если исходник (foo.c) новее исполняемого (foo.out), значит нужно перекомпилировать!: сбегать инструкцию, где на место $@ ставится цель (foo.out) и на место $< ставится первая зависимость (foo.c), получается ничем не примечательная команда для компилятора с нужными флагами оптимизации и предупреждений. а если исполняемый файлик актуален, обновлять его не нужно, то и ничего не делаем. с foo.out готово, возвращаемся в цель foo. там тривиальная инструкция: ./$<, то есть исполнить файлик нашей первой зависимости — foo.out
короче: вышеописанный Makefile позволяет исполнить условный bar.c (или bar.cpp) в вашей рабочей директории, безо всякой преднастройки, просто make bar. он компилирует его с теми флажками, которые вы распишите, компилирует только если нужно. мне кажется это пик удобства
работает на любой ОС, если у вас в “PATH” (грубо говоря, списке директорий, в которых располагаются команды) доступны make и компилятор
без понятия, насколько понятно расписал. comprehension is left as an exercise for the reader. извините, только учусь