Утилита make

Зачем нужна make?

Утилита make определяет, какие файлы проекта необходимо перестроить для получения готовой программы. Она позволяет собирать огромные и сложные проекты со множеством зависимостей. Для использования make нужно подготовить специальный Makefile с определённой структурой. Работа с make зависит от содержимого Makefile. Makefile представляет собой обычный текстовый файл, который можно распространять вместе с проектом, редактировать в обычном редакторе.

Компиляция без make

Использование компилятора без make:

gcc -o имя_программы имя1.с имя2.с ...

или раздельная компиляция каждого модуля:

gcc -c имя1.c
gcc -c имя2.с
gcc -o имя_программы имя1.о имя2.о

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

Структура make-файла

Мakefile состоит из так называемых ''правил'', имеющих вид:

имя-результата: исходные-имена ...
    команды
     ...
     ...

имя-результата - это обычно имя файла, генерируемого программой, например, исполняемый или объектный файл. ''Результатом'' может быть действие никак не связанное с процессом компиляции, например, clean - очистка.

исходное-имя - это имя файла, используемого на вводе, необходимое, чтобы создать файл с именем-результата.

команда - это действие, выполняемое утилитой make. Правило может включать более одной команды, В начале каждой команды надо вставлять отступ (символ ''Tab''). Команда выполняется, если если один из файлов в списке исходные-имена изменился. Допускается написание правила содержащего команду без указания зависимостей. Например, можно создать правило clean, удаляющее объектные файлы проекта, без указания имен.

Стандартные правила

К числу стандартных правил относятся:

Простой пример

В качестве примера рассмотрим небольшой проект filestat, состоящий из 2-х исходных файлов:

main.c
fstat.c

main.c содержит описание main, а также вызов функции StatDir, определённой в файле fstat.c

Содержимое main.c

 #include <stdio.h>
 #include <dirent.h>
 int StatDir(char *dname);
 int main(int argc, char **argv)
 {
    if(argc != 2)  {
       printf("Usage: filestat dirname\n");
       return 1;
    }
    printf("Using directory %s\n", argv[1]);   
    printf("Files: %d\n",StatDir(argv[1]));  
    return 0;
 }

Содержимое fstat.c

 #include <stdio.h>
 #include <dirent.h>
 #include <string.h>
 int StatDir(char *dname) {
    int count=0;
    DIR *dir,*subDir;
    struct dirent *entry;
    struct dirent *subEntry;
    char path[1024];
    dir = opendir(dname);
    if(dir == 0)
       return 0;
    while(entry = readdir(dir))   {
       if(entry->d_type == DT_DIR && (!strcmp(entry->d_name,".") || !strcmp(entry->d_name,"..")))
           continue;         
       if(entry->d_type == DT_DIR)
       {
          printf("%s\n", entry->d_name); 
          strcpy(path, dname);
          strcat(path, "/");
          strcat(path, entry->d_name);
          count+=StatDir(path);
       }
       else {
          printf("  %s\n", entry->d_name);
          count++;
       }
    }
    closedir(dir);
    return count;
 }

Приведём пример make-файла для рассматриваемого проекта:

 filestat: main.o fstat.o
    gcc $(CFLAGS) -o filestat main.o fstat.o
 main.o: main.c
    gcc $(CFLAGS) -c main.c
 fstat.o: fstat.c
    gcc $(CFLAGS) -c fstat.c
 clean:
    rm -f *.o filestat

По-умолчанию работает первое правило в файле (в рассматриваемом примере filestat). Если дать команду make, то утилита построит программу в соответствием с make-файлом:

$ make
gcc  -c main.c
gcc  -c fstat.c
gcc  -o filestat main.o fstat.o

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

Через переменную CFLSGS в программу можно передавать дополнительные параметры, например

make CFLAGS=-g3

Для очистки содержимого каталога используем другое правило:

$make clean
rm -f *.o filestat

Макросы

Макросы являются средством замены одних данных на другие (по принципу препроцессора). Часто можно увидеть такое использование макросов

CC=gcc

Далее в тексте Makefile можно использовать запись команды

$(CC) -c main.c

Аналогично можно задавать названия опций, значения параметров и т.д.

Для вывода на экран значения текущих макросов используется команда

make -p

Приведём пример небольшого Make-файла с макросами

CC = gcc
FILES = in_one.c in_two.c
OUT_EXE = out_executable
    
build: $(FILES)
        $(CC) -o $(OUT_EXE) $(FILES)

Другой пример с большим числом правил:

CC = gcc
FILES = in_one.c in_two.c
OUT_EXE = out_executable

build: $(FILES)
        $(CC) -o $(OUT_EXE) $(FILES)

clean:
        rm -f *.o core

rebuild: clean build

Пример более сложного Makefile

 # Makefile to compare sorting routines
 BASE = /home/blufox/base
 CC = gcc
 CFLAGS = -O –Wall
 EFILE = $(BASE)/bin/compare_sorts
 INCLS = -I$(LOC)/include
 LIBS = $(LOC)/lib/g_lib.a \
                      $(LOC)/lib/h_lib.a
 LOC = /usr/local
    
 OBJS = main.o    another_qsort.o    chk_order.o \
              compare.o    quicksort.o
 
 $(EFILE): $(OBJS)
    @echo "linking …"
    @$(CC) $(CFLAGS) –o $@ $(OBJS) $(LIBS)
 
 $(OBJS): compare_sorts.h
    $(CC) $(CFLAGS) $(INCLS) –c $*.c
 
 # Clean intermediate files
 clean:
    rm *~ $(OBJS)