Що робить команда make?
Створення даного тексту не мало на меті дати повний опис роботи команди make і покликане лише познайомити читача з найбільш простим застосуванням цієї утиліти для автоматизації деяких дій, що супроводжують програмування в операційній системі UNIX.
Створення програми часто починається з невеликого однофайлового проекту. Проходить деякий час і проект, як снігова грудка, починає обростати файлами, заголовками, бібліотеками, що підключаються, необхідними опціями компіляції. і для його складання стає вже недостатнім сказати "gcc-o file file.c". Коли ж, через кілька днів, одного разу набраний магічний рядок, що містить всі необхідні для складання проекту параметри компілятора, таємниче зникає в надрах історії вашого командного інтерпретатора, народжується природне бажання увічнити свої знання у вигляді, наприклад, шелл скрипта. Потім можливо захочеться зробити цей скрипт керованим параметрами, щоб його можна було використовувати для різних цілей. Однак, диво юнікса полягає в тому, що якщо вам щось знадобилося, значить хтось це вже зробив, і настав час згадати про існування команди make.
Розглянемо нескладну програму на C. Нехай програма prog складається з пари файлів коду main.c і supp.c і файлу заголовків defs.h, що використовується в кожному з них. Відповідно, для створення prog необхідно з пар ( main.c defs.h ) і ( supp.c defs.h ) створити об'єктні файли main.o і supp.o , а потім об'єднати їх у prog . При складанні вручну, вийде щось на кшталт:
cc -c main.c defs.h cc -c supp.c defs.h cc -o prog main.o supp.o
Якщо ми згодом змінимо defs.h, нам знадобиться повна перекомпіляція, а якщо змінимо supp.c, то повторну компіляцію main.о можна і не виконувати.Здавалося б, якщо для кожного файлу, який ми повинні отримати в процесі компіляції вказати, на основі яких файлів і за допомогою якої команди він створюється, то стала б у нагоді програма, яка по-перше, збирає з цієї інформації правильну послідовність команд для отримання необхідних результатів. файлів і, по-друге, ініціює створення необхідного файлу тільки у випадку, якщо такого файлу не існує, або він старший, ніж файли від яких він залежить. Це саме те, що робить команда make! Усю інформацію про проект make черпає з файлу Makefile, який зазвичай знаходиться в тому ж каталозі, що й вихідні файли проекту.
Найпростіший Makefile складається з синтаксичних конструкцій всього двох типів: цілей та макровизначень.
Мета в Makefile - це файл(и), побудова якого передбачається у процесі компіляції проекту. Опис мети складається з трьох частин: імені мети, списку залежностей та списку команд інтерпретатора sh, потрібних для побудови мети. Ім'я мети – непустий список файлів, які передбачається створити. Список залежностей - список файлів, у тому числі будується мета. Ім'я мети та список залежностей становлять заголовок мети, записуються в один рядок і поділяються двокрапкою. Список команд записується з наступного рядка, причому всі команди починаються з символу обов'язкового табуляції. Можливий багаторядковий запис заголовка або команд через застосування символу \ для екранування кінця рядка. При виклику команди make , якщо її аргументом явно не вказана мета, оброблятиметься перша знайдена в Makefile мета, ім'я якої не починається з символу ".". Прикладом для простого Makefile може стати вже згадувана програма prog:
prog: main.o supp.o cc -o prog main.o supp.o main.o supp.o: defs.h
У наведеному прикладі можна помітити ряд особливостей: у імені другої мети вказано два файли і для цієї ж мети не вказано команду компіляції, крім того, ніде явно не вказано залежність об'єктних файлів від "*.c"-файлів. Справа в тому, що команда make має визначені правила для отримання файлів із певними суфіксами. Так, для мети - об'єктного файлу (суфікс ".o") при виявленні відповідного файлу з суфіксом ".c", буде викликаний компілятор "сс-с" із зазначенням у параметрах цього ".c"-файлу та всіх файлів-залежностей.Більш того, в цьому випадку явно не вказані ".c"-файли make самостійно внесе до списку залежностей і реагуватиме їх зміна так само, як і для явно зазначених залежностей. Втім, ніщо не заважає вказати для цієї мети альтернативну команду компіляції.
Ви, мабуть, помітили, що у наведеному Makefile одні й самі об'єктні файли перераховуються кілька разів. А якщо до них додасться ще один? Для спрощення таких ситуацій make підтримує макровизначення.
Макровизначення має вигляд "ЗМІННА = ЗНАЧЕННЯ". ЗНАЧЕННЯ може бути довільною послідовністю символів, включаючи прогалини та звернення до значень вже певних змінних. Надалі, в будь-якому місці Makefile, де зустрінеться звернення до змінної-макровизначення, замість неї буде підставлено її поточне значення. Звертання до значення змінної в будь-якому місці Makefile виглядає як $(ЗМІННА) (дужки обов'язкові, якщо ім'я змінної довше одного символу). Значення ще певних змінних - порожній рядок. З урахуванням сказаного, можна перетворити наш Makefile :
OBJS = main.o supp.o prog: $(OBJS) cc -o prog $(OBJS) $(OBJS): defs.h
Тепер припустимо, що до проекту додався другий заголовний файл supp.h, який включається тільки до supp.c. Тоді Makefile збільшиться ще на один рядок:
Таким чином, один цільовий файл може вказуватися в кількох цілях. При цьому повний список залежностей для файлу буде складений зі списків залежностей всіх цілей, в яких він бере участь, однак створення файлу буде проводитися лише один раз.
У нашому прикладі ми групували цілі за принципом загальних залежностей, проте існує й альтернативний спосіб-групувати залежності по одній меті. У цьому випадку Makefile виглядатиме трохи інакше, проте його суть не зміниться.
OBJS = main.o supp.o prog: $(OBJS) cc -o prog $(OBJS) main.o: defs.h supp.o: defs.h supp.h
Зазвичай Makefile пишеться так, щоб простий запуск make призводив до компіляції проекту, однак, крім компіляції, Makefile може використовуватися і для виконання інших допоміжних дій, які безпосередньо не пов'язані зі створенням будь-яких файлів. До таких дій відноситься очищення проекту від усіх результатів компіляції або виклик процедури інсталяції проекту в системі. Для виконання подібних дій у Makefile можуть бути вказані додаткові цілі, звернення до яких буде здійснюватися вказівкою їхнього імені аргументом виклику make (наприклад, "make install"). Подібні допоміжні цілі звуться фальшивих , що пов'язані з відсутністю у проекті файлів, відповідних їх іменам. Фальшива мета може містити список залежностей і містити список команд для виконання. Оскільки фальшива мета не має відповідного файлу у проекті, при кожному зверненні до неї make намагатиметься її побудувати. Однак, можливе виникнення конфліктної ситуації, коли в каталозі проекту опиниться файл з ім'ям, що відповідає імені фальшивої мети. Якщо для цього імені не визначені файлові залежності, він завжди вважатиметься актуальним (up to date) і мета виконуватися не буде.Для запобігання таким ситуаціям make підтримує "вбудовану" змінну ".PHONY", якій можна присвоїти список імен цілей, які завжди повинні вважатися фальшивими.
Тепер можна навести приклад повного Makefile, придатного для роботи з проектом prog і взяти до уваги деякі найчастіше застосовувані прийоми:
OBJS = main.o supp.o BINS = prog PREFIX = /usr/local INSTALL = install INSOPTS = -s -m 755 -o 0 -g 0 CC = gcc .PHONY = all clean install all: $(BINS) prog: $(OBJS) $(CC) -o prog $(OBJS) main.o: defs.h supp.o: defs.h supp.h clean: rm -f $(BINS) rm -f $(OBJS) rm - f *~ install: all for $i in $(BINS) ; do \ $(INSTALL) $(INSOPTS) $$i $(PREFIX)/bin ; \done
Отже, у нас з'явилися три фальшиві цілі: all, clean і install. Мета all зазвичай використовується як псевдонім для складання складного проекту, що містить кілька результуючих файлів (виконуваних, бібліотек, що розділяються, сторінок документації і т.п.). Ціль clean використовується для повного очищення каталогу проекту від результатів компіляції та "сміття" - резервних файлів, створюваних текстовими редакторами (вони зазвичай закінчуються символом "~"). Мета install використовується для інсталяції проекту в операційній системі (наведений приклад розрахований на встановлення лише файлів, що виконуються). Слід зазначити повсюдне використання макровизначень - крім того, цей прийом підвищує читабельність. Зверніть також увагу на визначення змінної $(CC) - це вбудована змінна make і вона неявно "спрацює" при компіляції об'єктних файлів.
Викладений матеріал охоплює далеко не всі здібності make. Наприклад, у тексті Makefile можна застосовувати команди умовного виконання та різноманітні функції для маніпуляції з рядками. Make підтримує великий набір вбудованих змінних, а також метазмінні , що набувають різних значень залежно від контексту застосування. Наприклад, $* відповідає імені цільового файлу без суфіксів, а $^ - повному списку залежностей цієї мети.
На закінчення найбільш допитливих читачів можна надіслати до опису make у форматі info.
Ваші коментарі з приводу даного опусу, а також повідомлення про виявлені неточності буду радий отримати за адресою: [email protected]
Що робить команда make?
Команда make дозволяє задіяти однойменну утиліту, призначену для компіляції програмного забезпечення вихідних кодів. Ця команда затребувана переважно програмістами і системними адміністраторами, але може бути корисною і для звичайних користувачів, які бажають зібрати те чи інше програмне забезпечення з вихідних кодів замість встановлення бінарних пакетів. Утиліта, що розглядається, використовує файли опису цілей складання під назвою Makefile і автоматично створює список файлів вихідного коду, які мають бути скомпільовані, заощаджуючи тим самим час програмістів, які постійно модифікують вихідний код своїх продуктів. Підтримуються будь-які компілятори, які можуть запускатися за допомогою терміналу, причому в рамках файлів опису цілей складання можуть описуватися цілі, призначені для встановлення ПЗ (зазвичай називаються "install") та очищення оточення збірки (зазвичай називаються "clean"). Часто файли Makefile генеруються автоматично, причому у разі перед компіляцією ПЗ необхідно виконувати сценарій командної оболонки configure.sh, що здійснює їх генерацію та налаштування складального оточення.
Базовий синтаксис команди виглядає так:
$ make [параметри] [мета] .
Команда приймає назви цілей як аргументи, причому у разі відсутності назви мети вважається, що потрібно використовувати ціль "all"зазвичай включає всі інші цілі. Для опису цілей використовуються спеціальні файли з іменами Makefile, причому ці файли мають досить суворий синтаксис (наприклад, у яких забороняється змішувати символи табуляції та пропуску). Щодо параметрів, то найбільш важливими є параметр -f для вказівки нестандартного імені файлу опису цілей, параметр -C для зміни директорії перед складання ПЗ, параметр -d для виведення налагоджувальних повідомлень, параметр -e для зміни змінних оточення, а також параметр -B для безумовного складання всіх цілей.
Приклади використання
Як приклад буде використовуватися примітивна програма мовою C, що виводить повідомлення «It works!». Вона складатиметься з трьох файлів вихідного коду та файлу з описом цілей збирання. Файл з описом цілей збирання створений вручну і не вимагає попередньої конфігурації.
Вміст файлу main.c:
int main(int argc, char **argv)
{
core();
return 0;
}
Вміст файлу core.h:
#ifndef _CORE_H_
#define _CORE_H_
Вміст файлу core.c:
void core(void)
{
printf("It works!n");
}
Вміст файлу Makefile:
GCC = gcc-g-Wall
OBJ = core.o main.o
test: $(OBJ)
$(GCC) $(LDFLAGS) $(OBJ) -o test
clean:
rm -f *.o
rm -f test
Складання програми з вихідних кодів
Для складання програми достатньо виконати команду make без будь-яких параметрів:
$ make
gcc -g -Wall -c core.c -o core.o
gcc -g -Wall -c main.c -o main.o
gcc -g -Wall core.o main.o -o test
$./test
It works!
Очевидно, що програма була зібрана та коректно функціонує.
Складання програми з налагодженням системи збирання
Якщо ви бажаєте налагодити систему збирання, ви можете використати параметр -d команди make:
$ make -d
GNU Make 4.3
Ця програма зібрана для x86_64-pc-linux-gnu
Copyright (C) 1988-2020 Free Software Foundation, Inc.
Ліцензія GPLv3+: GNU GPL версії 3 або новіша
Це вільне програмне забезпечення: ви можете вільно змінювати його та
поширювати. НЕМАЄ НІЯКИХ ГАРАНТІЙ поза межами, допустимими законом.
Читання make-файлів.
Читання make-файлу "Makefile".
Оновлення make-файлів.
Обробка цільового файлу Makefile.
Пошук неявного правила для Makefile.
Спроба застосування правила із зразком «Makefile».
Спроба застосування неявної залежності Makefile.o.
Спроба застосування правила із зразком «Makefile».
Спроба застосування неявної залежності Makefile.c.
.
Цільовий файл "test" успішно перестворено.
Оновлення цілей, від яких залежить цільовий файл "all", завершено.
Необхідно перезбирати мету «all».
Цільовий файл "all" успішно перестворено.
$./test
It works!
Програма також успішно зібрана та коректно функціонує, але в даному випадку виводиться величезний обсяг налагоджувальної інформації, що стосується роботи утиліти make.
Складання окремих цілей
Для складання окремої мети слід передати назвою цієї мети команді make. У нашому випадку слушною метою є мета з назвою "test":
$ make test
gcc -g -Wall -c core.c -o core.o
gcc -g -Wall -c main.c -o main.o
gcc -g -Wall core.o main.o -o test
$./test
It works!
І знову програма успішно зібрана, причому у нашому випадку мета "all" автоматично передбачає збирання мети "test"тому ніяких відмінностей з використанням команди make без параметрів не спостерігатиметься.
Для очищення робочого оточення слід ініціювати збирання цілі "clean":
$ make clean
rm -f *.o
rm -f test
В результаті будуть видалені об'єктні файли і результат виконуваний файл програми.
Складання програми у заданій директорії
Для складання програми у заданій директорії слід використовувати параметр -C та шлях до цієї директорії. Для демонстрації перейдемо до директорії на рівень вище і спробуємо перезбирати програму.
$ cd ..
$ make -C ./test/
make: вхід до каталогу "/home/alex/code/test"
gcc -g -Wall -c core.c -o core.o
gcc -g -Wall -c main.c -o main.o
gcc -g -Wall core.o main.o -o test
make: вихід з каталогу "/home/alex/code/test"
$ ./test/test
It works!
Зрозуміло, і в цьому випадку програму успішно зібрали.
Складання програми з нестандартним ім'ям файлу опису цілей збирання
Якщо програма поставляється з файлом опису цілей збирання з нестандартним ім'ям (зазвичай розробники використовують різні суфікси для додавання підтримки різних компіляторів або платформ), ви можете використовувати параметр -f та передати утиліті make Ім'я цього файлу. Як приклад перейменуємо файл Makefile в Makefile.gcc:
$mv Makefile Makefile.gcc
$ make
make: *** Не задані цілі та не знайдено make-файл. Зупин.
І в цьому випадку збірка не представлятиме жодних складнощів:
$ make -f Makefile.gcc
gcc -g -Wall -c core.c -o core.o
gcc -g -Wall -c main.c -o main.o
gcc -g -Wall core.o main.o -o test
$./test
It works!
Таким чином ви можете використовувати декілька файлів опису цілей збирання в рамках одного проекту.
Використання утиліти Make і файлів Makefile у Linux [Посібник]
Це повний посібник для початківців з використання команди make у Linux.
Що таке утиліта make?
Утиліта make - одна з найзручніших утиліт для програміста. Його основна мета – компілювати середній та великий програмний проект. Утиліта make настільки корисна та універсальна, що її використовує навіть ядро Linux!
Щоб зрозуміти корисність утиліти make, потрібно спочатку зрозуміти, навіщо вона взагалі потрібна.
У міру того, як ваше програмне забезпечення стає більшим, ви починаєте все більше і більше покладатися на зовнішні залежності (тобто бібліотеки). Ваш код починає розбиватися на кілька файлів і Бог знає, що знаходиться в кожному файлі. Компіляція кожного файлу та розумне зв'язування їх разом для створення необхідних двійкових файлів стає складним завданням.
"Але я можу створити для цього сценарій Bash!"
Чому так, ти можеш! Більше сили вам! Але зі зростанням вашого проекту вам доведеться мати справу з поступовими перебудовами. Як ви з цим справлятиметеся в цілому, щоб логіка залишалася вірною навіть при збільшенні кількості файлів?
Все це робить утиліта make.Так що давайте не будемо винаходити велосипед і подивимося, як встановити та ефективно використовувати утиліту make.
Встановлення утиліти make
Утиліта make вже доступна у власних репозиторіях майже всіх дистрибутивів Linux.
Щоб встановити make в Debian, Ubuntu та їх похідних, використовуйте менеджер пакетів apt таким чином:
sudo apt install make
Щоб встановити make у дистрибутивах Linux на основі Fedora та RHEL, використовуйте менеджер пакетів dnf таким чином:
sudo dnf install make
Щоб встановити make в Arch Linux та його похідних, використовуйте менеджер пакетів pacman наступним чином:
sudo pacman -Sy make
Тепер, коли утиліта make встановлена, можна приступити до її розбору на прикладах.
Створення базового make-файлу
Утиліта make компілює ваш код на основі інструкцій, вказаних у make-файлі в каталозі верхнього рівня репозиторію коду вашого проекту.
Нижче наведено структуру каталогів мого проекту:
$ tree make-tutorial make-tutorial └── src ├── calculator.c ├── greeter.c ├── main.c └── userheader.h 1 directory, 4 files
Нижче наведено вміст вихідного файлу main.c :
#include #include "userheader.h" int main()
Далі слідує вміст вихідного файлу greeter.c :
#include #include "userheader.h" void greeter_func()
Нижче наведено вміст вихідного файлу calculator.c :
#include #include "userheader.h" int add(int a, int b) < return (a + b); >int sub(int a, int b) < if (a >b) return (a - b); else if (a < b) return (b - a); else return 0; >int mul(int a, int b) < return (a * b); > double div (int a, int b) < if (a > b) return ((double) a / (double) b); else if (a < b) return ((double) b / (double) a); else return 0; >
Нарешті, нижче показано вміст заголовного файлу userheader.h :
#ifndef USERHEADER_DOT_H #define USERHEADER_DOT_H void greeter_func(); int add(int a, int b); int sub(int a, int b); int mul(int a, int b); double div(int a, int b); #endif /* USERHEADER_DOT_H */
Основи make-файлу
Перш ніж ми створимо простий make-файл, погляньмо на синтаксис make-файлу. Базовий будівельний блок Makefile складається з одного або кількох «правил» та «змінних».
Правила у make-файлі
Давайте спочатку подивимося правила в make-файлі. Правило make-файлу має наступний синтаксис:
target: prerequisites recipe.
Макроси/змінні в make-файлі
У C та C++ основною функцією мови є змінні. Вони дозволяють зберігати значення, які ми можемо використовувати в багатьох місцях. Це допомагає нам використовувати те саме ім'я змінної там, де це необхідно. Додатковою перевагою є те, що нам потрібно внести лише одну зміну, якщо потрібно змінити значення.
Аналогічно, make-файл може містити змінні. Їх іноді називають макросами. Синтаксис оголошення змінної в Makefile наступний:
Змінна і значення, що зберігаються в ній, поділяються знаком рівності ( = ). Декілька значень розділяються пробілами між собою.
Зазвичай змінні використовуються зберігання різних елементів, необхідні компіляції. Допустимо, ви хочете включити виявлення переповнення буфера під час виконання і включити повну ASLR для файлу, що виконується; цього можна досягти, зберігши всі прапори компілятора в одній змінній, наприклад CFLAGS.
Нижче наведено демонстрацію цього:
CFLAGS = -D_FORTIFY_SOURCE=2 -fpie -Wl,-pie
Ми створили змінну під назвою CFLAGS (прапори компілятора) і додали всі наші прапори компілятора.
Щоб використовувати нашу змінну, ми можемо укласти її в круглі дужки, що починаються зі знака долара, наприклад:
gcc $(CFLAGS) -c main.c
Наведений вище рядок у нашому make-файлі додасть усі вказані нами прапори компілятора та скомпілює файл main.c у міру необхідності.
Автоматичні змінні
Утиліта make має кілька автоматичних змінних, які ще більше полегшують повторення. Ці змінні зазвичай використовують у рецепті правила.
Ось деякі з автоматичних змінних:
Ім'я цільового правила. Зазвичай використовується для визначення імені вихідного файлу.
Назва першої попередньої умови.
Імена всіх попередніх вимог, які новіші за цільовий. тобто файли, змінені після останньої компіляції коду.
Імена всіх попередніх вимог із пробілами між ними.
Повний список автоматичних змінних можна знайти в офіційній документації GNU Make.
Неявні змінні
Як і автоматичні змінні, описані вище, make також є змінні, які мають задане використання. Оскільки раніше я використовував макрос/змінну CFLAGS для зберігання прапорів компілятора, існують інші змінні, які мають передбачуване застосування.
Це можна розглядати не як "зарезервовані ключові слова", а скоріше як "загальний консенсус" щодо іменування змінних.
Ці звичайні змінні такі:
Зробіть службовий еквівалент змінної PATH Bash. Шляхи поділяються знаком двокрапки ( : ). За замовчуванням тут пусто.
Це асемблер. За замовчуванням використовується асемблер as.
Програма компіляції файлів C. За замовчуванням використовується cc . (Зазвичай cc вказує на gcc.)
Програма компіляції файлів C++. За промовчанням використовується компілятор g++.
Програма запускає препроцесор C. За замовчуванням встановлено значення $ (CC) -E .
Програма, яка перетворює лексичні граматики на вихідний код. За замовчуванням використовується lex. (Вам слід змінити це на flex.)
Програма, яка аналізує вихідний код. За замовчуванням використовується lint.
Команда видалення файлу. За замовчуванням використовується rm-f. (Будь ласка, зверніть увагу на це сильне увага!)
Він містить усі прапори асемблера.
Він містить усі прапори компілятора C (cc).
Він містить усі прапори компілятора C++ (g++).
Він містить усі прапори препроцесора C.
Вкажіть цілі, які не схожі на ім'я файлу. Прикладом може бути мета «зробити чистою»; де clean - Значення .PHONY
Коментарі в make-файлі
Коментарі в make-файлі аналогічні коментарям у сценарії оболонки. Вони починаються з символу решітки/решітки ( # ), а вміст вказаного рядка (після символ решітки/решітки) розглядається утилітою make як коментар і ігнорується.
Нижче наведено приклад, який демонструє це:
CFLAGS = -D_FORTIFY_SOURCE=2 -fpie -Wl,-pie # The '-D_FORTIFY_SOURCE=2' flag enables run-time buffer overflow detection # The flags '-fpie -Wl,-pie'
Початковий варіант make-файлу
Тепер, коли я описав основний синтаксис елементів make-файлу, а також дерево залежностей мого простого проекту, давайте тепер напишемо дуже простий Make-файл для компіляції нашого коду та зв'язування всього разом.
Почнемо з налаштування змінних CFLAGS, CC і VPATH, які необхідні для нашої компіляції. (Це не повний make-файл. Ми будемо створювати його поступово.)
CFLAGS = -Wall -Wextra CC = gcc VPATH = src
Після цього давайте визначимо наші правила побудови. Я створю 3 правила для кожного файлу .c. Мій виконуваний файл буде називатися make_tutorial, але ваш може бути будь-яким!
CFLAGS = -Wall -Wextra CC = gcc VPATH = src make_tutorial : main.o calculator.o greeter.o $(CC) $(CFLAGS) $? -o $@ main.o : main.c $(CC) $(CFLAGS) -c $? -o $@ calculator.o : calculator.c $(CC) $(CFLAGS) -c $? -o $@ greeter.o : greeter.c $(CC) $(CFLAGS) -c $? -o $@
Як бачите, я компілюю всі файли .c в об'єктні файли (.o) і наприкінці зв'язую їх разом.
Коли ми запускаємо команду make, вона починається з першого правила (make_tutorial). Це полягає у створенні остаточного виконуваного двійкового файлу з тим самим ім'ям. Він має 3 обов'язкові об'єктні файли для кожного файлу .c .
Кожне наступне правило після правила make_tutorial створює об'єктний файл із однойменного вихідного файлу. Я можу зрозуміти, як це складно.Отже, розберемо кожну з цих автоматичних і неявних змінних і зрозуміємо, що вони означають.
- $ (CC) : викликає компілятор GNU C (gcc).
- $ (CFLAGS) : неявна змінна для передачі прапорів компілятора, таких як -Wall і т.д.
- $? : імена всіх необхідних файлів, які нові цільового. У правилі для main.o$? розшириться до main.c IF main. c був змінений після створення main.o.
- $@ : це цільове ім'я. Я використовую це, щоб не вводити ім'я правила двічі. У правилі для main.o $@ розширюється до main.o.
Нарешті, параметри -c та -o — це параметри gcc для компіляції/складання вихідних файлів без зв'язування та вказівка імені вихідного файлу відповідно. Ви можете перевірити це, виконавши команду man 1 gcc у терміналі.
Тепер спробуємо запустити цей make-файл і сподіваємося, що він спрацює з першої спроби!
$ make gcc -Wall -Wextra -c src/main.c -o main.o gcc -Wall -Wextra -c src/calculator.c -o calculator.o gcc -Wall -Wextra -c src/greeter.c -o greeter.o gcc -Wall -Wextra main.o calculator.o greeter.o -o make_tutorial
Якщо придивитися, кожен крок компіляції містить усі прапори, які ми вказали у неявній змінній CFLAGS. Ми також бачимо, що вихідні файли були автоматично отримані з каталогу src. Це сталося автоматично, оскільки ми вказали "src" у неявній змінній VPATH.
Спробуємо запустити двійковий файл make_tutorial і перевіримо, чи все працює так, як задумано.
$ ./make_tutorial Hello, user! I hope you є ready for today's basic Mathematics class! Adding 5 and 10 дозавжди gives us '15'. Subtracting 10 з 32 результатів у '22'. If 43 is multiplied with 2, we get '86'. Результат розподілу будь-якого з number like 78 with 2 is a whole number like '39.000000'.
Поліпшення make-файлу
Що можна поліпшити?
Давайте запустимо команду ls, ви самі в цьому переконаєтесь;)
$ ls --group-directories-first -1 src calculator.o greeter.o main.o Makefile make_tutorial
Чи бачите ви артефакти збирання (об'єктні файли)? Так, вони можуть захаращувати ситуацію в гірший бік. Давайте скористаємося нашим каталогом збирання та зменшимо цей безлад.
Нижче наведено модифікований файл:
CFLAGS = -Wall -Wextra CC = gcc VPATH = src:build make_tutorial : main.o calculator.o greeter.o $(CC) $(CFLAGS) $? -o $@ build/main.o : main.c mkdir build $(CC) $(CFLAGS) -c $? -o $@ build/calculator.o : calculator.c $(CC) $(CFLAGS) -c $? -o $@ build/greeter.o : greeter.c $(CC) $(CFLAGS) -c $? -o $@
Тут я зробив одну просту зміну: додав рядок build/ перед кожним правилом, що генерує об'єктний файл. Це помістить кожен об'єктний файл у каталог build. Я також додав build в змінну VPATH .
Якщо придивитися, наша перша мета компіляції - make_tutorial. Але це не буде мета, яка педантично буде першою. Першою метою, рецепт якої виконується, є main.o (або, швидше, build/main.o). Тому я додав команду «mkdir build» як рецепт в ціль main.o.
Якби я не створював каталог «build», я отримав би таку помилку:
$ make gcc -Wall -Wextra -c src/main.c -o build/main.o Спосіб повідомлення: Fatal error: не можна створювати build/main.o: Немає такого файлу або directory make: *** [Makefile: 12: build/main.o] Error 1
Тепер, коли ми змінили наш make-файл, видалімо поточні артефакти збірки разом зі скомпільованим двійковим файлом і перезапустимо утиліту make.
$ rm -v *.o make_tutorial removed 'calculator.o' removed 'greeter.o' removed 'main.o' removed 'make_tutorial' $ make mkdir build gcc -Wall -Wextra -c main.o gcc -Wall -Wextra -c src/calculator.c -o build/calculator.o gcc -Wall -Wextra -c src/greeter.c -o build/greeter.o gcc -Wall -Wextra build/main. o build/calculator.o build/greeter.o -o make_tutorial
Це скомпільовано чудово! Якщо придивитися, ми вже вказали каталог build у змінній VPATH , що дозволяє утиліті make шукати наші об'єктні файли всередині каталогу build.
Наші вихідні файли та файли заголовків були автоматично знайдені в каталозі src, а артефакти збірки (об'єктні файли) зберігалися всередині і були пов'язані з каталогом build, як ми і припускали.
Додавання цілей .PHONY
Ми можемо зробити це покращення ще на крок далі. Давайте додамо мети «зробити чистим» та «зробити працювати».
Нижче наведено наш остаточний make-файл:
CFLAGS = -Wall -Wextra CC = gcc VPATH = src:build build/bin/make_tutorial : main.o calculator.o greeter.o mkdir build/bin $(CC) $(CFLAGS) $? -o $@ build/main.o : main.c mkdir build $(CC) $(CFLAGS) -c $? -o $@ build/calculator.o : calculator.c $(CC) $(CFLAGS) -c $? -o $@ build/greeter.o : greeter.c $(CC) $(CFLAGS) -c $? -o $@ .PHONY = clean clean : rm -rvf build .PHONY = run run: make_tutorial ./build/bin/make_tutorial
Все в цілях складання таке ж, за винятком зміни, в якому я вказую, що хочу, щоб двійковий файл, що виконується make_tutorial був поміщений в каталог build/bin/ .
Потім я встановив для змінної .PHONY значення clean , щоб вказати, що clean не є файлом, про який мусить турбуватися утиліта make. Це. фальшиво. З метою clean я вказую, що необхідно видалити, щоб «очистити все».
Я роблю те саме для мети run. Якщо ви розробник Rust, вам сподобається цей шаблон. Як і команда cargo run, я використовую команду make run для запуску скомпілованого двійкового файлу.
Щоб ми могли запустити двійковий файл make_tutorial, він має існувати. Тому я додав його в необхідну умову для мети run.
Давайте спочатку запустимо make clean, а потім безпосередньо запустимо make run!
$ make clean rm -rvf build removed 'build/greeter.o' removed 'build/main.o' removed 'build/calculator.o' removed 'build/bin/make_tutorial' removed directory 'build/bin' removed directory 'build ' $ make run mkdir build gcc -Wall -Wextra -c src/main.c -o build/main.o gcc -Wall -Wextra -c src/calculator.c -o build/calculator.o gcc -Wall -Wextra - c src/greeter.c -o build/greeter.o mkdir build/bin gcc -Wall -Wextra build/main.o build/calculator.o build/greeter.o -o build/bin/make_tutorial ./build/bin/ make_tutorial Hello, user! I hope you є ready for today's basic Mathematics class! Adding 5 and 10 дозавжди gives us '15'. Subtracting 10 з 32 результатів у '22'. If 43 is multiplied with 2, we get '86'. Результат розподілу будь-якого з number like 78 with 2 is a whole number like '39.000000'.
Як ви бачите тут, ми не запустили команду make для першої компіляції нашого проекту. Після запуску make run компіляція була виконана. Давайте розберемося, як це сталося.
При запуску команди make run утиліта make спочатку перевіряє ціль run . Обов'язковою умовою мети run є наш двійковий файл, який ми компілюємо. Тому наш двійковий файл make_tutorial компілюється першим.
make_tutorial має власні попередні вимоги, які розміщуються в каталозі build/ . Після компіляції цих об'єктних файлів компілюється наш двійковий файл make_tutorial; нарешті, утиліта Make повертається до мети run та виконується двійковий файл ./build/bin/make_tutorial .
така елегантність, вау
Висновок
У цій статті розглядаються основи make-файлу - файлу, від якого залежить утиліта make, для спрощення компіляції вашого репозиторію програмного забезпечення. Для цього ми починаємо з базового Makefile і збираємо його зі зростанням наших потреб.
