В статье рассматриваются инструменты для генерации make-файлов и проектов таких IDE, как Visual Studio. Статья будет интересна тем, кто лишь недавно опрометчиво выбрал тяжкий труд программиста.
Преимущества разбиения исходных текстов разрабатываемой программы на множество отдельных файлов очевидны. Однако, после такого разбиения усложняется процесс компиляции программы. Например, пусть мы разрабатываем программу, исходые коды которой разбиты на два файла: main.c и fun.c. Тогда скомпилировать их можно было бы такой командой (предположим, что работа выполняется в GNU/Linux):
gcc main.c fun.c -o superproga |
Недостатки такого подхода следующие:
Для решения указанных проблем, а также автоматизации процесса сборки больших программных проектов когда-то в древности была разработана утилита make. Эта утилита читает специальный файл (обычно он называется Makefile) и выполняет указанные команды, если в этом есть необходимость. Основу Makefile составляют строки следующего вида:
цель: зависимости [tab] команды |
prog: main.o fun.o gcc main.o fun.o -o prog fun.o: fun.c fun.h gcc -c fun.c main.o: main.c gcc -c main.c |
make prog |
Теперь если изменить лишь какой-то один файл и выполнить команду make, можно увидеть, что компилируется только этот измененный файл 2.
Вариант make из проекта GNU - gmake - является довольно продвинутым инструментом, существенно упрощающим тяжелую жизнь программистов. Наш Makefile можно немного изменить, сделав более универсальным:
SRCS = main.c fun.c OBJS = ${SRCS:%.c=%.o} .SUFFIXES: .c .o .c.o: gcc -c $< -o $@ prog: ${OBJS} gcc ${OBJS} -o prog fun.o: fun.h |
Следует подчеркнуть, что хотя make разрабатывалась для автоматизации процесса сборки программ, она может с успехом применяться и для других целей. Ведь никто не запрещает в качестве команд указывать что-угодно. Например, следующее правило позволяет перегенерировать документацию по проекту, если она устарела:
doc: fun.h doxygen |
Современные интегрированные среды разработки (IDE 3) также позволяют создавать проекты и выполнять компиляцию только изменившихся файлов. Как правило, эти средства имеют якобы более удобный интерфейс, позволяя создавать проект с помощью команд меню или мышкой, но гораздо меньшие возможности, так как умеют делать только то, что запрограммировали авторы IDE.
Подробную документацию об утилите make можно найти на соответствующих man- и info-страницах, а также на просторах Internet. Очень хорошее введение лежит здесь.
В добрые старые времена4 функциональности make было вполне достаточно. Но с тех пор все ужасно улучшилось. Во-первых, программы обладают гораздо большей функциональностью и, как результат, требуют для своей работы гораздо больше библиотек. Было бы неплохо определять наличие отсутствия :) библиотеки еще на этапе компиляции. Во-вторых, операционные системы стали более разнородными. И если в нормальных *nix и Linux всего несколько каталогов, в которых находятся библиотеки (/usr/lib, /usr/local/lib) и другие файлы, необходимые для сборки, то во всяких Windows, программы устанавливаются куда попало. И определить, куда на машине известного Василия Пупкина установлена, например, библиотека expat довольно тяжело. Все это приводит к тому, что написание универсального, а тем более переносимого, Makefile-а становится довольно сложной задачей.
В соответствии с принципом "Пишите программы, которые пишут программы" 5 ленивые программисты написали несколько генераторов Makefile-файлов. Одним из первых таких генераторов был набор утилит autoconf и automake. Вот в этой статье рассказывается об использовании данных утилит.
Позже появились такие утилиты, как qmake, bakefile, CMake и ряд других. qmake является ачстью библиотеки Qt и заточена на использование именно этой библиотеки. bakefile в настоящее время имеет версию 0.2. Насколько мне известно, с помощью этой утилиты собирается библиотека wxWidgets . CMake является довольно продвинутым и развитым инструментом. С его помощью собираются несколько крупных проектов, например, KDE. Следующий текст я скопипестил с официального сайта этой утилиты:
A Summary Of Features CMake is an extensible, open-source system that has several powerful features. These include:
|
По этой причине6 рассмотрим эту утилиту более подробно.
CMake читает специальный файл (обычно он называется CMakeLists.txt) и выполняет указанные команды, создавая Makefile. Команды во входном файле имеют следующий вид:
команда(аргументы) |
Для нашего примера CMakeLists.txt будет иметь следующий вид:
PROJECT (prog) SET (PROJECT_SOURCE_LIST main.c fun.c) ADD_EXECUTABLE(prog ${PROJECT_SOURCE_LIST}) |
Для генерации Makefile необходимо запустить Cmake.
Версия утилиты для Windows имеет стандартный графический интерфейс:
Исполняемый файл называется CMakeSetup.exe.
Версия для *nix может иметь curses-интерфейс.
В этом
случае исполняемый файл называется ccmake.
В *nix при запуске утилиты в командной строке следует указать каталог, в котром содержится подготовленный командный файл. В случае Windows этот каталог можно указать из окна утилиты. После запуска утилиты надо выполнить дополнительное конфигурирование, нажимая клавишу 'c' (в лучае *nix-версии) или кнопку 'Configure' (в Windows). Возможно надо будет выполнить несколько нажатий. При этом при работе в Windows можно указать тип создаваемого проекта: проект для Visual Studio разных версий или варианты make-файла. После того, как конфигурирование выполнено можно сгенерировать make-файл или проект для IDE, нажав, ставшие доступными, клавишу 'g' или кнопку 'Ok'.
Существует также неинтерактивная утилита cmake, которая молча выполняет свою работу. Ее удобно использовать, когда файл CMakeLists.txt уже должным образом настроен и необходимо лишь время от времени добавлять в проект файлы. Также эта утилита может использоваться в скриптах. Например, можно сочинить скрипт, который после обновления файлов из репозитария CVS или SVN проверяет, менялись ли файлы CMakeLists.txt, и при необходимости запускает указанную утилиту.
CMake умеет создавать Makefile-ы для проектов, исходные тексты которых располагаются в нескольких каталогах. Для этого следует создать CMakeLists.txt в каждом каталоге с исходниками, а в файле, лежащем в корневом каталоге проекта, перечислить подкаталоги. Вот фрагмент CMakeLists.txt одного из моих проектов с исходниками, которые для удобства размещены в нескольких каталогах:
PROJECT (dmt) SUBDIRS(dmt sysed tests) |
Cmake, как и другие генераторы, позволяет при формировании Makefile-ов выполнять поиск необходимых для проекта библиотек. Рассмотрим эти возможности на примере. Предположим, нашей программе крайне необходима библиотека expat. CMake содержит следующую команду для поиска библиотеки:
FIND_LIBRARY(VAR libname [path1 path2...]) |
Также нам необходимо найти каталог, в котором располагается файл expat.h. Позже этот каталог будет добавлен к другим include-каталогам проекта. Поиск каталога выполняет команда:
FIND_PATH(VAR filename [path1 path2...]) |
Поместив эти команды в CMakeLists.txt, мы решим нашу задачу. Однако, можно поступить по-другому. Дело в том, что CMake позволяет создавать дополнительные модули, которые могут выполнять часто встречающиеся задачи. В подкаталоге Modules (например, /usr/local/share/CMake/Modules) находится большое количество таких дополнительных модулей. Написав такой модуль 7, мы
# Find the native EXPAT include and library FIND_PATH (EXPAT_INCLUDE_DIR expat.h /usr/include /usr/local/include "C:/Program Files/Expat/Include" ) IF (WIN32) SET(LIBEXPAT_NAME libexpat) ELSE (WIN32) SET(LIBEXPAT_NAME expat) ENDIF (WIN32) FIND_LIBRARY (EXPAT_LIBRARY ${LIBEXPAT_NAME} /usr/lib /usr/local/lib "c:/Program Files/Expat/Libs" ) SET (EXPAT_FOUND "NOTFOUND") IF (EXPAT_LIBRARY AND EXPAT_INCLUDE_DIR) SET (EXPAT_FOUND "FOUND") ENDIF (EXPAT_LIBRARY AND EXPAT_INCLUDE_DIR) MARK_AS_ADVANCED (EXPAT_INCLUDE_DIR EXPAT_LIBRARY) #Log FILE (WRITE "${PROJECT_BINARY_DIR}/findexpat.log" "Expat found: ${EXPAT_FOUND}\n") FILE (APPEND "${PROJECT_BINARY_DIR}/findexpat.log" "Expat library name: ${LIBEXPAT_NAME}\n") FILE (APPEND "${PROJECT_BINARY_DIR}/findexpat.log" "Expat include: ${EXPAT_INCLUDE_DIR}\n") FILE (APPEND "${PROJECT_BINARY_DIR}/findexpat.log" "Expat lib: ${EXPAT_LIBRARY}\n") |
Подключается модуль в файл CMakeLists.txt командой
INCLUDE(FindExpat) |
В качестве еще одного примера приведу фрагмент CMakeLists.txt, который применяется в одном из моих проектов:
#wxWidgets build related stuff SET(WXW_USE_DEBUG OFF) SET(WXW_USE_UNICODE ON) IF (WIN32) SET(WXW_USE_SHARED OFF) ELSE (WIN32) SET(WXW_USE_SHARED ON) ENDIF (WIN32) SET(WXW_USE_UNIV OFF) SET(WXW_USE_MONO OFF) SET(WXW_FILE_VERSION "26") SET(WXW_VERSION "2.6") #CMake Options SET(CMAKE_VERBOSE_MAKEFILE TRUE) #Additional libraries INCLUDE (FindwxW) INCLUDE (FindExpat) IF (NOT EXPAT_FOUND) MESSAGE (FATAL_ERROR "Can't find expat library" ) ENDIF (NOT EXPAT_FOUND) SET(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS}${WXWIDGETS_CXX_FLAGS}") SET(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS}${WXWIDGETS_EXE_LINKER_FLAGS}") ADD_DEFINITIONS( ${WXWIDGETS_DEFINITIONS} ${PROJECT_DEFINITIONS} ) # # The include dirs # INCLUDE_DIRECTORIES(${PROJECT_SOURCE_DIR}) INCLUDE_DIRECTORIES(${PROJECT_SOURCE_DIR}/common) # Add project include paths as a separated list SET(PROJECT_INCLUDE_PATH_LIST ${PROJECT_INCLUDE_PATH}) SEPARATE_ARGUMENTS(PROJECT_INCLUDE_PATH_LIST) INCLUDE_DIRECTORIES(${PROJECT_INCLUDE_PATH_LIST}) INCLUDE_DIRECTORIES(${WXWIDGETS_INCLUDE_DIR}) INCLUDE_DIRECTORIES(${EXPAT_INCLUDE_DIR}) # # This is needed to tell CMake what directories contain the libraries we need. This will # allow us to just specify the lib files without prepending them with a full path to that # library # LINK_DIRECTORIES( ${WXWIDGETS_LINK_DIRECTORIES} ) # Add project dirs as a separated list SET(PROJECT_LINK_DIRECTORIES_LIST ${PROJECT_LINK_DIRECTORIES}) SEPARATE_ARGUMENTS(PROJECT_LINK_DIRECTORIES_LIST) LINK_DIRECTORIES( ${PROJECT_LINK_DIRECTORIES_LIST} ) #tmpled options # sources of tmpled SET( PROJECT_SOURCE_FILES sysedmainframe.cpp sysedapp.cpp constredpage.cpp) SET( PROJECT_SOURCE_FILES ${PROJECT_SOURCE_FILES} tmpledprefdlg.cpp) #... SET( PROJECT_SOURCE_FILES ${PROJECT_SOURCE_FILES} ${PROJECT_SOURCE_DIR}/common/titledtextctrl.cpp) #SET( PROJECT_SOURCE_FILES ${PROJECT_SOURCE_FILES} ${PROJECT_SOURCE_DIR}/common/dirchooser.cpp) #executable module of tmpled ADD_EXECUTABLE (tmpled WIN32 ${PROJECT_SOURCE_FILES}) # # Here we specify what libraries are linked to our project # # First, add the WX libs TARGET_LINK_LIBRARIES(tmpled ${WXWIDGETS_LIBRARIES}) # Then, we add the project specific libs SET(PROJECT_LIBRARIES_LIST ${PROJECT_LIBRARIES}) # Make the project libs a list so CMake will do the right things SEPARATE_ARGUMENTS(PROJECT_LIBRARIES_LIST) TARGET_LINK_LIBRARIES(tmpled ${PROJECT_LIBRARIES_LIST}) TARGET_LINK_LIBRARIES(tmpled ${EXPAT_LIBRARY}) |
CMake является довольно мощным генератором Makefile-ов и файлов проектов для IDE. В статье мне удалось лишь намекнуть на ее возможности. Более подробную информацию об использовании этого инструмента можно найти в довольно подробной документации. У буржуинов также вышла бумажная книга об использовании CMake. Ссылки на эту книгу есть на официальном сайте CMake.
Надеюсь, эта статья поможем начинающим программистам в их нелегком пути к совершенству.
1Некоторые редакторы, например vim умеют самостоятельно выставлять при редактировании Makefile табуляции.
2Для ленивых замечу, что нам достаточно зменить не содержимое файла, а лишь дату его модификации. Что можно сделать командой touch.
3Если мне не изменяет мой склероз, первой подобной IDE был Turbo Pascal фирмы Borland.
4"...когда ставки были высокими, души - смелыми, мужчины - настоящими мужчинами, женщины - настоящими женщинами, а маленькие мохнатые зверюшки с Альфа Центавра - настоящими маленькими мохнатыми зверюшками с Альфа Центавра" - читайте Дугласа Адамса :)
5См. Брайн У. Керниган, Роб Пайк "Практика программирования"
6Ну, и потому, что я ее знаю лучше всего (не считая qmake).
7Одно из правил unix-way гласит: "Прежде чем писать какую-то программу, посмотри, а не написал ли кто-то уже нечто подобное." После написания этого модуля, я в Internet нашел подобные модули.