Форум русскоязычного сообщества Ubuntu


Хотите сделать посильный вклад в развитие Ubuntu и русскоязычного сообщества?
Помогите нам с документацией!

Автор Тема: Makefile: как скомпилировать свой первый Hello World  (Прочитано 144147 раз)

0 Пользователей и 1 Гость просматривают эту тему.

Оффлайн VestniK

  • Автор темы
  • Активист
  • *
  • Сообщений: 594
    • Просмотр профиля
Здесь не редко появляются темы о проблемах людей, только начинающих изучать C или C++. Одна из проблем, которая на первых парах мешает процессу обучения, это сборка своих экспериментов. Многие пытаются обойти её, найдя IDE которая сделает всё за них, но так уж повелось, что в линуксе IDE во многом расчитаны на работу с опенсорсными проектами в которых, сборка выполняется не средствами IDE, а с помощью различных универсальных и, как правило, кросплатформенных систем сборки.

Я решил написать небольшую статью, цель которой описать как быстро и просто создать инфраструктуру для работы над своими первыми проектами, которой с лихвой хватит на первые пол годика. При этом я не буду описывать всевозможные фичи утилиты make, которые и без меня отлично описаны здесь: http://www.gnu.org/software/make/manual/

Итак Hello World написан, но что теперь с ним делать?
Конечно же компилировать. В случае если вы пишите на C то компилировать его нужно так:
gcc -o hello hello.c
В случае же с C++ команда будет выглядеть так:
g++ -o hello hello.cpp

Удовлетворившись тем, что прога работает, вы решаете немного усложнить её, написав несколько функций и сложив их в отдельный файл или же если вы учите плюсы, то это будет класс, что не суть важно. Важно, что начиная с этого момента, компиляция выглядит уже не столь простой задачей. Так, программисту C нужно выполнить следующую последовательность комманд:
gcc -c -o myprog.o myprog.c
gcc -c -o myfuncs.o myfuncs.c
gcc -o myprog myprog.o myfuncs.o
а программисту C++:
g++ -c -o myprog.o myprog.cpp
g++ -c -o myclass.o myclass.cpp
g++ -o myprog myprog.o myclass.o
Осознав всю сложность сборки ещё больших проектов, невольно начинаешь задумываться об автоматизации оного процесса.

И тут приходит время первой крови перовго Makefile'а
Для автоматизации процесса сборки с незапамятных времён используется система make. В кратце ознакомиться с синтаксисом скриптов для неё (Makefile'ов) можно почитав вот этот мини мануал (когда-то я начал освоение make именно с него). На всякий случй, для тех кто по ссылкам не ходит, процитирую самую важную техническую деталь make, синаксис описания целей:
Цитировать
target: dependencies
[tab] system command
Отступ при описании тела цели (последовательности команд для сборки этой цели) делается символом табуляции и ничем иным.

Завершив чтение тех пары страниц текста что находятся по ссылке, можно без труда написать Makefile для сборки C++ программы следующего содержания:
all: myprog

myprog: myprog.o myclass.o
g++ -o myprog myprog.o myclass.o

myprog.o: myprog.cpp
g++ -o myprog.o myprog.cpp

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

Я не стану приводить Makefile для сборки C программы, так как написание его тривиально и никто всё равно не станет пользоваться подобной техникой описания явным образом всех целей и всех команд необходимых для сборки.

А теперь подходим к задаче серьёзно
Конечно, используя такой Makefile, уже получаешь некоторые приимущества, но вот перспектива при добавлении нового файла в проект добавлять новую цель выглядит удручающей. Тут к нам на помощь приходят implicit rules а в особенности predefined implicit rules. Это набор правил сборки проекта, который позволяет не писать все цели проекта вручную, а использовать шаблоны для их автоматической генерации.

Чтобы написать по настоящему универсальный Makefile, который без изменений будет кочевать из одного проекта в другой, необходимо определиться с тем какую структуру файлов и каталогов в проекте мы хотим иметь. Когда я работаю с make, я всегда предпочитаю следующую структуру:
  • Имя бинарника, который я хочу собрать, совпадает с именеи папки с сорцами.
  • Все заголовочные файлы и исходники для одного бинрника лежат в одной папке, и эта папка не содержит других исходников.
  • Функция main лежит в сишном или плюсовом файле, имя которого (без расширения) совпадает с именем бинарника который я хочу собрать.

Если писать программу на C++ следуя этим правилам, то для сборки этой программы достаточно положить Makefile следующего содержания:
TARGET=$(shell basename `pwd`)
SOURCES=$(wildcard *.cpp)
OBJECTS=$(SOURCES:%.cpp=%.o)

all: $(TARGET)

$(OBJECTS): $(SOURCES)

$(TARGET): $(OBJECTS)
        $(CXX) -o $(TARGET) $(LDFLAGS) $(OBJECTS) $(LOADLIBES) $(LDLIBS)

clean:
        $(RM) $(OBJECTS) $(TARGET)

.PHONY: all clean
в папку с исходниками и набрать make. Для очистки результатов сборки нужно просто набрать "make clean".

Для тех кто пишет на чистом C Makefile будет выглядеть даже проще:
TARGET=$(shell basename `pwd`)
SOURCES=$(wildcard *.c)
OBJECTS=$(SOURCES:%.c=%.o)

all: $(TARGET)

$(OBJECTS): $(SOURCES)

$(TARGET): $(OBJECTS)

clean:
        $(RM) $(OBJECTS) $(TARGET)

.PHONY: all clean

Обратите особое внимание на то, что эти Makefile'ы не содержат никакой информции о самом проекте. Они просто предполагают, что проект следует тому набору правил, которй я описал выше, и делают сборку.

Большинство IDE в линуксе отлично понимают проекты использующие make для сборки. Всё что нужно, это запустить IDE и импортировать в неё проект на основе Makefile, после чего спокойно работать в приятном окружении поддерживающем code complition, быструю навигацию и прочие плюшки предоставляемые IDE.

Одной стандартной библиотекой сыт не будешь
Приведённые выше универсальные Makefile'ы обладают одним недостатком, они могут собирать только проекты не использующие ничего кроме стандартных библиотек C и C++. Если вдруг захотелось чего-то большего, то их всё же придётся немного модифицировать.

Но, перед тем как написать набор шаманских заклинаний о том как подружить Makefile с внешней библиоткой, разумеется необходимо её установить. Если вы только что свалились с луны винды, то перед установкой следует забыть о существовании офф сайта проекта или по крайней мере о кнопке "Download" на нём. Библиотеки ставятся через apt так же как и остальные программы. Имя пакета, который необходим для написания программы использующей библиотеку "abc", как правило выглядит как "libabc-dev" либо "libabc[N]-dev" где [N] это мажорная часть версии библиотеки (как правило это одна цифра от 0 и до бесконечности).

Подавляющее большинство библиотек в линуксе используют pkg-config для упрощения их использования в проектах. Посмотреть список библиотек информация о которых доступна через pkg-config можно следующей командой:
pkg-config --list-all
Найдя в списке требуемую библиотеку вы подчерпнёте одно очень важно сведение: имя по которому можно получить информацию об этой библиотеки из вашего Makefile'а. Теперь, чтобы получить флаги которые нужно передать компилятору при компиляции программы использующей библеотеки из этого списка, необходимо использовать опцию --cflags, а для получения списка флагов линкеру опцию --libs. Например, в случае если вы хотите использовать libxslt и sqlite в своей программе, вам потребуются следующие команды:
pkg-config --cflags libxslt sqlite3
pkg-config --libs libxslt sqlite3

Теперь приступим к модификациям. Всё что требуется, это добавить нужные флаги компилятора и линкера в соответствующие переменные, используемые в неявных правилах. А именно, нужно добавить флаги компилятора в переменную CFLAGS и флаги линкера в LDFLAGS.  Если вы обратите внимание на структуру написанных мной Makefile'ов, то  заметите, что в начале я объявляю переменные (первые три строчки в обоих C и C++ версиях), а потом перехожу к объявлению целей. После нехитных модификаций Makefile для C++ приложения, использующего библиотеки libxslt и sqlite3, будет выглядеть так:
TARGET=$(shell basename `pwd`)
SOURCES=$(wildcard *.cpp)
OBJECTS=$(SOURCES:%.cpp=%.o)

CFLAGS+=$(shell pkg-config --cflags libxslt sqlite3)
LDFLAGS+=$(shell pkg-config --libs libxslt sqlite3)

all: $(TARGET)

$(OBJECTS): $(SOURCES)

$(TARGET): $(OBJECTS)
        $(CXX) -o $(TARGET) $(LDFLAGS) $(OBJECTS) $(LOADLIBES) $(LDLIBS)

clean:
        $(RM) $(OBJECTS) $(TARGET)

.PHONY: all clean
Думаю, что модификация сишного Makefile'а дело достаточно тривиальное, чтобы не приводить здесь соответствующий листинг.

Маленькая конфетка для заклинателей змей
Если у вас хорошое знание python, то возможно стоит забить на make и использовать scons. Скрипты сборки, написанные на нём, являются полноценными питоновскими скриптами и могут использовать всю мощь этого языка, а исчерпывающая документация, доступная на сайте, ответит на все вопросы по его использованию.

Пример простйшего SConstruct файла для сборки приложения из исходников с использованием всё тех же библиотек libxslt и sqlite3 будет выглядеть так:
env=Environment()
env.ParseConfig('pkg-config --cflags --libs libxslt')
env.ParseConfig('pkg-config --cflags --libs sqlite3')

env.Program('myprog', ['src1.cpp','src2.cpp','src3.cpp'] )

Один из весомых недостатков этой системы сборки заключается в том, что проекты с её использованием несколько сложней подружить с IDE.

Надеюсь эта информация будет полезна для тех кто только приступает к изучению C или C++ и поможет им сэкономить много времени и сил, уходящих на решение второстепенной задачи.

Обсуждение
« Последнее редактирование: 21 Июня 2010, 23:53:37 от Владимир Николаевич »

 

Страница сгенерирована за 0.035 секунд. Запросов: 23.