Довольно часто приходится писать небольшие проекты для тестирования того или иного участка кода. Компиляция этих проектов не всегда тривиальна, и запуск компиляции из командной строки утомляет. К тому же не хочется создавать проекты в IDE. И тут на помощь программистам приходят Makefile’ы. Пользователям UNIX-like систем хорошо знакомы файлы для сборки приложений, имеющие названия Makefile. Но и писать всякий раз Makefile’ы тоже не удобно. Поэтому я решил написать универсальный Makefile. На столько универсальный, на сколько это возможно.

К примеру, мы хотим написать такой код:

#include <iostream>
#include <string>
#include <boost/thread.hpp>

boost::mutex gMutex1;
boost::mutex gMutex2;


void MyThread(const std::string & name, size_t iter_count,
size_t step_size)
{
for(size_t i = 0; i < iter_count;)
{
gMutex1.lock();
gMutex2.lock();

for(size_t j = 0; j < step_size && i < iter_count; ++j, ++i)
{
if(0 == j)
gMutex1.unlock();
std::cout << name << ": " << i + 1 << " from " << iter_count <<
std::endl;
}
std::cout << std::endl;
gMutex2.unlock();
}
}

int main()
{
boost::thread thread_one(MyThread, "one", 12, 3);
boost::thread thread_two(MyThread, "two", 8, 4);
thread_one.join();
thread_two.join();
return 0;
}

Сразу же видно, что без подключения библиотеки boost_thread тут не обойтись. Не буду утомлять Вас и приведу полный код Makefile’а. Пусть наша программа записана в файл main.cpp

TARGET_NAME = test
DEFAULT_BUILD_TYPE = debug
#DEFAULT_BUILD_TYPE = release
COMPILER = g++
HEADERS =
SOURCES = main.cpp
OBJECTS = *.o

# Common options
COMMON_COMPILER_OPTS =
COMMON_LINKER_OPTS = -lboost_thread
COMMON_COMMON_OPTS = -Wall

# Debug options
DEBUG_COMPILER_OPTS = $(COMMON_COMPILER_OPTS)
DEBUG_LINKER_OPTS = $(COMMON_LINKER_OPTS)
DEBUG_COMMON_OPTS = $(COMMON_COMMON_OPTS) -g -O0

# Release options
RELEASE_COMPILER_OPTS = $(COMMON_COMPILER_OPTS)
RELEASE_LINKER_OPTS = $(COMMON_LINKER_OPTS)
RELEASE_COMMON_OPTS = $(COMMON_COMMON_OPTS) -O4

# Debug build steps
DEBUG_BUILD = $(COMPILER) $(DEBUG_COMMON_OPTS) $(DEBUG_COMPILER_OPTS) -c $(SOURCES)
DEBUG_LINK = $(COMPILER) $(DEBUG_COMMON_OPTS) $(DEBUG_LINKER_OPTS) $(OBJECTS) -o $(TARGET_NAME)

# Release build steps
RELEASE_BUILD = $(COMPILER) $(RELEASE_COMMON_OPTS) $(RELEASE_COMPILER_OPTS) -c $(SOURCES)
RELEASE_LINK = $(COMPILER) $(RELEASE_COMMON_OPTS) $(RELEASE_LINKER_OPTS) $(OBJECTS) -o $(TARGET_NAME)

all: $(DEFAULT_BUILD_TYPE)

debug: debug_build debug_link

release: release_build release_link

debug_build: $(HEADERS) $(SOURCES)
$(DEBUG_BUILD)

debug_link: debug_build
$(DEBUG_LINK)

release_build: $(HEADERS) $(SOURCES)
$(RELEASE_BUILD)

release_link: release_build
$(RELEASE_LINK)

clean:
rm -rf $(TARGET_NAME)
rm -rf $(OBJECTS)

Так как при вставке в этот блог строк, начинающихся со знака табуляции они (знаки табуляции) заменяются на пробелы, прошу Вас заменить четыре пробела на один знак табуляции. Утилита make требует именно табуляцию.
Итак, что мы здесь имеем. В начале файла большое количество переменных. По порядку:
TARGET_NAME — имя исполняемого файла;
DEFAULT_BUILD_TYPE — тип компиляции по умолчанию. Может принемать два значения: debug и release;
COMPILER — команда, вызывающая компилятор;
HEADERS — перечисляем здесь все заголовочные файлы через пробел.
SOURCES — а здесь перечисляем все компилируемые модули (*.cpp);
OBJECTS — тут мы вписываем *.o,- это маска для всех объектных файлов.
Далее идёт секция общих опций для отладочной и релизной компиляций:
COMMON_COMPILER_OPTS — общие опции компилятора;
COMMON_LINKER_OPTS — общие опции линковщика;
COMMON_COMMON_OPTS — общие опции общие для компилятора и для линковщика, чтобы два раза не писать;
Опции отладочного режима:
DEBUG_COMPILER_OPTS — отладочные опции компилятора;
DEBUG_LINKER_OPTS — отладочные опции линковщика;
DEBUG_COMMON_OPTS — отладочные опции общие для компилятора и линковщика;
Опции релиза:
RELEASE_COMPILER_OPTS — релизные опции компилятора;
RELEASE_LINKER_OPTS — релизные опции линковщика;
RELEASE_COMMON_OPTS — релизные опции общие для компилятора и линковщика.
Следующие переменные DEBUG_BUILD, DEBUG_LINK, RELEASE_BUILD и RELEASE_LINK складывают команды на компиляцию и линковку из предыдущих переменных. Их редактировать не нужно.
Итак, первая цель, цель по умолчанию, по традиции называется all. Цель all скомпилирует и слинкует программу в режиме, заданном переменной DEFAULT_BUILD_TYPE.
Отдельные цели debug и release компилируют и линкуют соответствующие конфигурации программы.
Цели debug_build и release_build только компилируют, но не линкуют приложение, а цели debug_link и release_link, напротив, только линкуют программу.
Цель clean очищает каталог от объектных файлов и файла приложения. Обратите внимание на то, что использованы команды rm. Для успешной работы в ОС Windows их следует заменить на команды del или установить в систему соответствующую программу, например с пакетом MSYS.

В опциях линкера мы указали библиотеку boost_thread. Но я не указал каталог, в котором она находится. Я этого не сделал потому, что в ОС Linux, которую я использую, эта библиотека находится в стандартной директории, путь до которой известен компилятору. Пользователям ОС Window придётся указать путь в опции -L (например -LC:boostlib). Аналогичным образом стоит поступить и с каталогом, содержащим заголовочный файл boost/thread.hpp. Для этого в опциях компилятора указываем путь до этого каталога в параметре -I.
Указываем имя программы: test. Для ОС Windows не обязательно указывать расширение .exe, компилятор g++ допишет его сам.
Дописываем имя нашего файла с исходными текстами: main.cpp. Теперь всё готово для компиляции. Откройте консоль и перейдите в каталог с Makefile’ом и файлом main.cpp и запустите команду make. Пользователям ОС Windows придётся установить комплект программ MinGW и поместить путь до подкаталога bin в переменную окружения PATH. Кроме того, команда для ОС Windows будет не make, а mingw32-make. После успешной компиляции проекта (для компиляции моего примера Вам понадобится библиотека boost), можно запускать программу.

sergey@debian:~/temp/cpp$ make
g++ -Wall -g -O0 -c main.cpp
g++ -Wall -g -O0 -lboost_thread *.o -o test
sergey@debian:~/temp/cpp$ ./test
one: 1 from 12
one: 2 from 12
one: 3 from 12

two: 1 from 8
two: 2 from 8
two: 3 from 8
two: 4 from 8

one: 4 from 12
one: 5 from 12
one: 6 from 12

two: 5 from 8
two: 6 from 8
two: 7 from 8
two: 8 from 8

one: 7 from 12
one: 8 from 12
one: 9 from 12

one: 10 from 12
one: 11 from 12
one: 12 from 12

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