До сих пор мы для сборки нашей программы пользовались командным файлом, объединяющим в себе команды для препроцессинга, компиляции, ассемблирования, компоновки файлов в исполняемый файл. Так тоже делать можно и зачастую так и делается, если дело касается проектов, которые содержат в себе малое количество модулей, а также не требующих какой-то автоматизации процесса сборки.
Но для более грамотной работы с проектом существуют сценарии, работа с которыми осуществляется в утилите Make.
Make — это утилита, предназначенная для сборки проектов, автоматически определяющая, какие части большой программы должны быть перекомпилированы, и выполняет необходимые для этого действия.
Утилита Make работает с файлами сценариев, в которых описывается, какие цели мы перед собой ставим при сборке нашего проекта, а также то, каким путём мы этих целей будем добиваться. Также в файлах сценариев поддерживается очень много всего, что описать в рамках одного занятия невозможно. Поэтому в данном уроке мы проведём только очень краткое знакомство с написанием данных сценариев, создадим такой сценарий, который поможет нам собрать наш проект и увидим, что изменилось по сравнению с использованием командных файлов для сборки проектов, какие у нас появились новые возможности благодаря использованию утилиты Make.
Так как мы работаем с системой сборки MinGW, основанной на системе GNU, соответственно, и утилита Make будет также из этой системы.
Авторами GNU make являются Richard Stallman и Roland McGrath. Начиная с версии 3.76, разработкой программы руководит Paul D. Smith.
Чтобы использовать Make, мы даём одноимённую команду в командной строке, либо добавляем её в среду разработки. Так как данная команда расположена в комплекте MinGW в виде исполняемого файла mingw32-make.exe, то мы будем использовать одноимённую команду «mingw32-make». Можно конечно сделать дубликат в папке с данной утилитой и назвать её make.exe для упрощения ввода команды, только мы этого делать не будем во избежание конфликтов имён с другими подобными утилитами, которые вполне могут быть использованы в наших операционных системах. В качестве параметра к данной команде используется обычно имя файла сценария и при этом перед именем данного файла используется ключ -f. Но если назвать файл сценария Makefile или makefile, то никаких ключей и параметров не потребуется, так как это имя файла сценария по умолчанию для make.
В файле сценария обязательно должно присутствовать хотя бы одно правило.
Правило состоит из цели, зависимостей или пререквизитов, обязательных для достижения цели, а также одной или нескольких команд, которые для достижения данной цели будут выполняться.
Команды перечисляются каждая с новой строки. Перед каждой такой командой мы используем табуляцию. Без этого сценарий работать не будет.
В качестве команд используются обычные команды оболочки Shell. Все используемые команды будут выводиться в командной строке. Если же мы ходим, чтобы тексты команд не выводились в консоли, то мы используем перед командой символ '@'.
Когда мы запускаем утилиту make с командной строки, то помимо имени файла с ключом мы можем указать имя цели, которую мы хотим выполнить в сценарии. Если не ввести имя цели, то выполнится либо цель с именем all либо самая первая в файле сценария цель.
В качестве пререквизитов (зависимостей) как правило используются другие цели, представляющие собой, как правило, имена файлов. Также могут использоваться и фиктивные цели. Но об этом не в данном уроке.
Чтобы было немного понятнее, давайте поработаем всё-таки с утилитой Make на практике.
Создадим проект, как и прежде, из проекта прошлого занятия с именем MYPROG19 и присвоим ему имя MYPROG20.
Файл build.cmd можно будет теперь удалить. clean.cmd пока оставим.
Тексты исходников не трогаем. Задача наше сегодня — не писать новый код, а использовать существующий, но собрать его мы должны уже с помощью утилиты Make.
Все лишние файлы из папки с нашей программой должны быть удалены, должны остаться только файлы с исходными текстами. Если это не так, то дадим команду clean.
Откроем файл main.c и закомментируем последний код
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
/* char str1[35] = {}; int a = 0b00111000, b = 0b10000010, c = 0b01000001; int_to_binary(a, str1); printf ("Value is %s\r\n", str1); int_to_binary(b, str1); printf ("Value is %s\r\n", str1); int_to_binary(c, str1); printf ("Value is %s\r\n", str1); printf("==========\r\n"); int res = a | b ^ c; int_to_binary(res, str1); printf ("Value is %s\r\n", str1); */ |
А в самом начале вот этот код раскомментируем
1 2 3 4 5 6 7 8 9 |
int main() { float xf = 8; float yf = 3; float zf = 2; float res = xf + yf + zf; printf ("Value is %.5f\n", res); res = xf + yf - zf; printf ("Value is %.5f\n", res); |
Создадим файл с именем Makefile в каталоге с нашей программой добавим в него вот такой текст
1 2 3 |
main.o: main.c gcc -O0 -g3 -Wall -c main.c gcc -masm=intel -g3 -Wall -c main.c -S |
Мы описали здесь наше первое правило. Не забываем про табуляторы перед командами.
Имя цели у нас main.o, следовательно нашей целью является создание одноимённого файла. Зависимостью служит файл main.c, который должен существовать.
Затем с новой строки, используя табуляцию, мы вводим знакомые нам команды, которые сформируют нам необходимые файлы.
Казалось бы, зачем всё это нужно, если безо всякой цели данные команды и так нам всё сделают. А это мы увидим позже.
Сохраним наш первый файл сценария и введём команду mingw32-make в нашей консоли
Мы видим текст наших команд в консоли.
Также мы видим, что в каталоге с нашей программой появились сформированные файлы
Но у нас нет исполняемого файла. Его нет, так как мы его не создавали.
Для этого будет отдельное правило, которое мы напишем выше нашего предыдущего правила. А выше мы его напишем для того, чтобы выполнилась именно цель этого правила.
1 2 |
myprog20.exe: main.o gcc -o myprog20.exe main.o |
В качестве цели у нас будет имя исполняемого файла нашей программы. Пререквизитом или зависимостью будет цель main.o, чтобы данная цель тоже выполнилась. А затем идёт команда компоновки. Хотя у нас в папке с программой есть ещё модули, но мы их пока не используем. Недаром я расскомментировал самый первый код, где мы не используем функции, реализация которых находится в других модулях.
Сохраним наш сценарий и запустим утилиту, только перед этим дадим команду clean, чтобы остальные файлы у нас тоже сформировались
Мы видим, что у нас выполнились все три команды, то есть оба правила.
Посмотрим, что в папке с проектом также появился и исполняемый файл
Запустим его, чтобы убедиться, что всё работает
Отлично! Сценарий работает!
Теперь давайте удалим только исполняемый файл myprog20.exe и запустим утилиту
И что же мы видим? А видим мы то, что у нас выполнилась только команда линковки и создания исполняемого файла. Объектный файл, а также ассемблерный, заново не пересоздаются, так как в правиле их создания не произошло изменение зависимости, которой является файл main.c. Вот оно — поистине главное преимущество утилиты make. Она не пересоздаёт то, что не изменилось. На всякий случай можно запустить программу на выполнение, которая, я уверен, что нормально выполнится.
Давайте теперь в файле main.c закомментируем наш раскомментированный код и раскомментируем следующий
1 2 3 4 5 6 7 |
float xf = 8; float yf = 3; float zf = 2; float res = xf + yf * zf; printf ("Value is %.5f\n", res); res = xf - yf / zf; printf ("Value is %.5f\n", res); |
Сохраним изменения и запустим утилиту
У нас опять выполнились все три команды, так как содержимое файла main.c изменилось, вследствие чего изменились и объектный с ассемблерном файлом, а так как изменился объектный файл, являющийся зависимостью первой целью, то первое правило тоже выполнилось. Теперь мы, надеюсь, начали немного понимать смысл сценария. Если бы не было правил, а были бы просто команды, то они бы выполнялись всегда независимо от изменений. Это ничего, когда мало файлов, а вот когда их огромное количество, то сценарий даёт огромный выигрыш во времени сборки.
Теперь закомментируем предыдущий код и раскомментируем следующий
1 2 3 4 5 6 7 |
float xf = 8; float yf = 3; float zf = 2; float res = xf + my_div(yf, zf); printf ("Value is %.5f\n", res); res = xf / my_sum(yf, zf); printf ("Value is %.5f\n", res); |
Теперь мы должны подключить к сборке следующий модуль, так как используются его функции. Сохраним изменения в main и перейдём к Makefile.
Во-первых, чтобы немного отвлечься, давайте познакомимся с использованием переменных в сценариях.
Переменная в сценариях представляет собой имя, определенное в файле сценария для представления строки текста, называемой значением переменной. Далее, по запросу, эти значения могут быть подставлены в нужные места файла сценария (например, в имена целей, имена зависимостей, команды и т.д.).
Переменная может представлять собой что угодно, например, список файлов, набор передаваемых компилятору опций, имя запускаемой программы, список каталогов с исходными файлами, каталог для выходных файлов и много другое.
Именем переменной может быть любая последовательность символов, не содержащая :, #, = и начальных или конечных пробелов. Однако, рекомендуется избегать использования имен переменных, содержащих символы, отличные от букв, цифр и символа подчеркивания. Во-первых, такие имена в будущем могут получить какое-либо специальное значение, и, во-вторых, не все интерпретаторы командной строки смогут передать (через переменные среды) такие переменные «порожденным» копиям make.
Имена переменных чувствительны к регистру.
Чтобы обратиться к переменной, мы используем символ $ и скобки, в которые заключено имя переменной. Например, чтобы использовать переменную с именем VAR_NAME, мы пишем $(VAR_NAME).
Давайте создадим переменную для имени файла с компилятором, так как данное имя может в любой момент измениться, а оно используется повсюду во многих местах файла сценария.
Добавим переменную в самом верху нашего сценария Makefile
1 |
CC = gcc |
Теперь вместо gcc в нашем сценарии мы будем использовать нашу переменную и Makefile теперь примет вот такой вид
1 2 3 4 5 6 7 8 |
CC = gcc myprog20.exe: main.o $(CC) -o myprog20.exe main.o main.o: main.c main.h ariph.h utils.h $(CC) -O0 -g3 -Wall -c main.c $(CC) -masm=intel -g3 -Wall -c main.c -S |
Добавим в самом низу Makefile правило для сборки нашего арифметического модуля
1 2 3 |
ariph.o: ariph.c $(CC) -O0 -g3 -Wall -c ariph.c $(CC) -masm=intel -g3 -Wall -c ariph.c -S |
Также добавим в главное правило ещё одну зависимость — объектный файл модуля. Также добавим этот объектный файл и в команду этого правила
myprog20.exe: main.o ariph.o
$(CC) -o myprog20.exe main.o ariph.o
Сохраним сценарий, очистим от файлов с помощью команды clean наш каталог и запустим утилиту
Всё собралось. Проверим работоспособность
Всё работает.
Закомментируем предыдущий код и раскомментируем следующий
1 2 3 4 5 6 7 8 9 10 |
char str1[35] = {}; int a = 0b00111000, b = 0b10000010; int res = a | b >> 1; int_to_binary(a, str1); printf ("Value is %s\r\n", str1); int_to_binary(b, str1); printf ("Value is %s\r\n", str1); printf("==========\r\n"); int_to_binary(res, str1); printf ("Value is %s\r\n", str1); |
А правило для сборки данного модуля мы будем писать в следующей части нашего занятия, в котором мы также изучим ещё кое-какие тонкости написания сценариев, а также напишем цель для очистки проекта от лишних файлов.
Предыдущий урок Программирование на C Следующая часть
Смотреть ВИДЕОУРОК в RuTube (нажмите на картинку)
Смотреть ВИДЕОУРОК в YouTube (нажмите на картинку)
Добавить комментарий