Пользователь
0,1
рейтинг
18 октября 2012 в 14:52

Разработка → Makefile для самых маленьких tutorial

Не очень строгий перевод материала mrbook.org/tutorials/make Мне в свое время очень не хватило подобной методички для понимания базовых вещей о make. Думаю, будет хоть кому-нибудь интересно. Хотя эта технология и отмирает, но все равно используется в очень многих проектах. Кармы на хаб «Переводы» не хватило, как только появится возможность — добавлю и туда. Добавил в Переводы. Если есть ошибки в оформлении, то прошу указать на них. Буду исправлять.

Статья будет интересная прежде всего изучающим программирование на C/C++ в UNIX-подобных системах от самых корней, без использования IDE.

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

Для практики понадобится создать микроскопический проект а-ля Hello World из четырех файлов в одном каталоге:
main.cpp
#include <iostream>
#include "functions.h"

using namespace std;

int main(){
    print_hello();
    cout << endl;
    cout << "The factorial of 5 is " << factorial(5) << endl;
    return 0;
}


hello.cpp
#include <iostream>
#include "functions.h"

using namespace std;

void print_hello(){
   cout << "Hello World!";
}


factorial.cpp
#include "functions.h"

int factorial(int n){
    if(n!=1){
	return(n * factorial(n-1));
    }
    else return 1;
}


functions.h
void print_hello();
int factorial(int n);


Все скопом можно скачать отсюда
Автор использовал язык C++, знать который совсем не обязательно, и компилятор g++ из gcc. Любой другой компилятор скорее всего тоже подойдет. Файлы слегка подправлены, чтобы собирались gcc 4.7.1

Программа make

Если запустить
make
то программа попытается найти файл с именем по умолчание Makefile в текущем каталоге и выполнить инструкции из него. Если в текущем каталоге есть несколько мейкфайлов, то можно указать на нужный вот таким образом:
make -f MyMakefile
Есть еще множество других параметров, нам пока не нужных. О них можно узнать в ман-странице.

Процесс сборки

Компилятор берет файлы с исходным кодом и получает из них объектные файлы. Затем линковщик берет объектные файлы и получает из них исполняемый файл. Сборка = компиляция + линковка.

Компиляция руками

Самый простой способ собрать программу:
g++ main.cpp hello.cpp factorial.cpp -o hello
Каждый раз набирать такое неудобно, поэтому будем автоматизировать.

Самый простой Мейкфайл

В нем должны быть такие части:
цель: зависимости
[tab] команда

Для нашего примера мейкфайл будет выглядеть так:
all:
	g++ main.cpp hello.cpp factorial.cpp -o hello

Обратите внимание, что строка с командой должна начинаться с табуляции! Сохраните это под именем Makefile-1 в каталоге с проектом и запустите сборку командой make -f Makefile-1
В первом примере цель называется all. Это цель по умолчанию для мейкфайла, которая будет выполняться, если никакая другая цель не указана явно. Также у этой цели в этом примере нет никаких зависимостей, так что make сразу приступает к выполнению нужной команды. А команда в свою очередь запускает компилятор.

Использование зависимостей

Использовать несколько целей в одном мейкфайле полезно для больших проектов. Это связано с тем, что при изменении одного файла не понадобится пересобирать весь проект, а можно будет обойтись пересборкой только измененной части. Пример:
all: hello

hello: main.o factorial.o hello.o
	g++ main.o factorial.o hello.o -o hello

main.o: main.cpp
	g++ -c main.cpp

factorial.o: factorial.cpp
	g++ -c factorial.cpp

hello.o: hello.cpp
	g++ -c hello.cpp

clean:
	rm -rf *.o hello

Это надо сохранить под именем Makefile-2 все в том же каталоге

Теперь у цели all есть только зависимость, но нет команды. В этом случае make при вызове последовательно выполнит все указанные в файле зависимости этой цели.
Еще добавилась новая цель clean. Она традиционно используется для быстрой очистки всех результатов сборки проекта. Очистка запускается так: make -f Makefile-2 clean

Использование переменных и комментариев

Переменные широко используются в мейкфайлах. Например, это удобный способ учесть возможность того, что проект будут собирать другим компилятором или с другими опциями.
# Это комментарий, который говорит, что переменная CC указывает компилятор, используемый для сборки
CC=g++
#Это еще один комментарий. Он поясняет, что в переменной CFLAGS лежат флаги, которые передаются компилятору
CFLAGS=-c -Wall

all: hello

hello: main.o factorial.o hello.o
	$(CC) main.o factorial.o hello.o -o hello

main.o: main.cpp
	$(CC) $(CFLAGS) main.cpp

factorial.o: factorial.cpp
	$(CC) $(CFLAGS) factorial.cpp

hello.o: hello.cpp
	$(CC) $(CFLAGS) hello.cpp

clean:
	rm -rf *.o hello

Это Makefile-3
Переменные — очень удобная штука. Для их использования надо просто присвоить им значение до момента их использования. После этого можно подставлять их значение в нужное место вот таким способом: $(VAR)

Что делать дальше

После этого краткого инструктажа уже можно пробовать создавать простые мейкфайлы самостоятельно. Дальше надо читать серьезные учебники и руководства. Как финальный аккорд можно попробовать самостоятельно разобрать и осознать такой универсальный мейкфайл, который можно в два касания адаптировать под практически любой проект:
CC=g++
CFLAGS=-c -Wall
LDFLAGS=
SOURCES=main.cpp hello.cpp factorial.cpp
OBJECTS=$(SOURCES:.cpp=.o)
EXECUTABLE=hello

all: $(SOURCES) $(EXECUTABLE)
	
$(EXECUTABLE): $(OBJECTS) 
	$(CC) $(LDFLAGS) $(OBJECTS) -o $@

.cpp.o:
	$(CC) $(CFLAGS) $< -o $@

Makefile-4
Успехов!
Вадим Марков @BubaVV
карма
98,7
рейтинг 0,1
Реклама помогает поддерживать и развивать наши сервисы

Подробнее
Реклама

Самое читаемое Разработка

Комментарии (32)

  • +13
    Вот бы кто automake и autoconf разжевал.
    • +26
      I saw a book entitled «Die GNU Autotools» and I thought «My feelings exactly». Turns out the book was in German. [via]
      • –13
    • +3
      Если вы начинаете новый проект, а не вынуждены поддерживать существующее — обходите automake и autoconf за милю :) Есть множество альтернатив — cmake, scons итд — разработчиков которых хотя бы не хочется четвертовать.
      • 0
        Нельзя ли поподробней, что такого ужасного в GNU Autotools? Я использовал их в нескольких проектах с самого начала и по большому счету ни разу не пожалел, хотя приходилось писать небольшие куски на m4, а также возникали определенные неудобства, когда хотелось странного.
        • 0
          m4 ужасен. divert — это вообще за гранью добра и зла.

          Для небольших проектов, где ничего особенного не нужно, все выглядит неплохо, но… Попробуйте на досуге разобраться в autoconf-е php ;)
          • 0
            Я бы сказал, что m4 странен, а divert мне не понадобился (повезло наверно), поэтому я не знаю, что это такое. Сколь-нибудь нетривиальную макруху приходилось писать для Charm++ и для CUDA. В первом случае, насколько я помню, было всё гладко, а во втором осталось какое-то несовершенство, но терпимое. Правда, мои коллеги боялись даже заглядывать во все эти тексты и осторожно ковыряли только am-мейкфайлы. Наверно, действительно, порог вхождения высок.
    • 0
      есть куча переведенных туториалов
  • +4
    Если уж заикнулись про зависимости, рассказали бы про makedepend
  • 0
    • +5
      Нет конечно, вы что
      • 0
        Неправильно выразился. На основе толстых талмудов сделать компактный легкоусвояемый туториал вроде вот этого. Или первый комментатор использовал sarcasm mode on?
        • 0
          1. Это сделать полезно и нужно
          2. Такие вопросы надоели уже
          • 0
            Вопросы про реферат или про сарказм? Я еще атмосферу ресурса не очень улавливаю, поэтому спрашиваю глупости
            • +3
              На ресурсе часто можно встретить вопросы из разряда «А нужна ли статья про..» и, по моим наблюдениям, на такие вопросы всегда отвечают, что да, нужна, делайте. Зачем тогда такие бесполезные вопросы? Если можете сделать хорошую статью о чем-то, то просто сделайте.
  • +5
    Для цели clean все же лучше указывать точку перед о (rm -rf *.o)
    • +1
      Исправил в посте и в архиве проекта
    • +5
      А для цели fun просто писать * без ".o"
      • 0
        Скорее для цели mazo :)
  • +8
    Вместо

    main.o: main.cpp
        g++ -c main.cpp
    
    factorial.o: factorial.cpp
        g++ -c factorial.cpp
    
    hello.o: hello.cpp
        g++ -c hello.cpp

    Лучше писать:

    .SUFFIXES: .cpp .o
    
    .cpp.o:
    	$(CC) $(CFLAGS) -c -o $@ $<
    

    $@ — имя .o-файла
    $< — имя .cpp-файла

    Такое правило будет автоматом работать для всех .o-файлов, указанных в качестве зависимостей цели.
    • –2
      Ровно до тех пор, пока не появится необходимость подключить проекту объектный модуль. Соответственно, для этого модуля не будет .cpp-файла. Более того, может появится необходимость подключить один единственный файл .c и make тоже поломается.
      • +1
        Вы даже не проверяли то, о чём говорите.

        Ровно до тех пор, пока не появится необходимость подключить проекту объектный модуль. Соответственно, для этого модуля не будет .cpp-файла.
        Только что создал файл x.c, вручную скомпилировал в z.o, и добавил z.o в список целей сборки одного моего личного проекта — собирается нормально, никаких проблем у make не возникло со сборкой. Я даже успешно вызвал из кода на D функцию из z.o.

        Более того, может появится необходимость подключить один единственный файл .c и make тоже поломается.
        Опять же, никаких проблем:

        .d.o:
        	$(D_COMPILER) $(D_COMPILER_FLAGS) -c -of$@ $<
        
        .c.o:
        	$(CC) $(CFLAGS) -c -o $@ $<
        

        Убираю из списка целей z.o, добавляю x.o — всё прекрасно собирается.
  • +2
    Если изменятся заголовочные файлы, ваш мейкфайл ничего не пересоберёт.
  • –1
    Я очень порекомендую вам premake (среди тулзов cmake, qmake, premake он мне понравился больше всего). Хотя последняя версия вышла 16 ноября 2010, так что, возможно, он не очень живой.
  • +5
    программа попытается найти файл с именем по умолчание makefile

    Это юникс, здесь важен регистр букв. make пытается найти файл с именем по умолчанию Makefile
    • 0
      позорно протупил. Конечно же так правильно. Исправляю
    • +2
      Если речь идет о GNU Make, то по умолчанию проверяются три файла, по порядку: GNUmakefile -> makefile -> Makefile.

      Другое дело, что в документации рекомендуется использовать именно Makefile, чтобы в листинге директории видеть его сверху. Пруф.
  • 0
    До сих пор пользовался скриптом на баше, чтобы компилировать большое количество файлов. Спасибо за перевод, давно хотел изучить этот вопрос.
  • 0
    Еще бы показали, как одинм make-файлом собирать проекты написаные частично на С, частично на С++.
  • –2
    как_нарисовать_сову.jpg
  • –1
    .cpp.o:
    $(CC) $(CFLAGS) $< -o $@

    Makefile-4
    Успехов!

    Пока в комментах не разживали, что такое $< и $@ ничего не было понятно. Да и после того всё придельно ясно не стало. Зачем вообще было упоминать эти токены, если они не рассказали?
  • +2
    В первом примере цель называется all. Это цель по умолчанию для мейкфайла, которая будет выполняться, если никакая другая цель не указана явно.
    Тут злостно нарушена причинно-следственная связь!

    На самом деле, «по умолчанию» цель выбирается не «all», а просто первая цель в Makefile'е.
    Называться-то она может вообще как угодно:
    firsttarget:
    	echo "The first one is the default"
    
    all:
    	echo "All is the default"
    

    Проверяем:
    $ make
    echo "The first one is the default"
    The first one is the default

    Понимаю, что это перевод, но своя голова-то тоже не бывает лишней :-)

    Из той же серии:
    all:
        g++ main.cpp hello.cpp factorial.cpp -o hello
    

    — правилом хорошего тона вроде как считается называть цель так же, как называется результат выполняния команд (hello).
    Могу поверить, что стилистическая ошибка в последнем примере обусловлена непониманием, описанным выше.

Только зарегистрированные пользователи могут оставлять комментарии. Войдите, пожалуйста.