Qt
21 May 2012

Qt Build System: спасательный круг для сборки


Примечание(17.06.2013): синтаксис примеров в статье не соответствует последним версиям. Сверяйтесь с документацией. Также в марте был выпущен Qt Creator 2.7 с поддержкой QBS.
Не так давно команда разработчиков Qt представила новую систему сборки — QBS. Разумеется, основной реакцией было «Да чем вас QMAKE не устраивал», «Просто адаптируйте CMAKE», «Ещё одна система сборки [xkcd, стандарты]». Обещанные преимущества новой системы: гибкость, понятный синтаксис для всех разработчиков (QML — javascript-подобный декларативный язык), скорость билдов (чистых и инкрементальных), а также лёгкая расширяемость.
Всё это мы где-то уже слышали, поэтому в данной статье мы попытаемся разобраться, как разработчики пришли к этой системе, рассмотрим простейшие примеры, изучим основные конструкции и посмотрим, какая поддержка на уровне IDE присутствует на настоящий момент.

История


Сейчас мы вместе постараемся повторить путь Йорга Борнемана (Jörg Bornemann), разработчика QBS.

Критика make (и производных)

В статье Peter Miller 1999 года рассматривается проблематика рекурсивного make и вообще удобства makefile'ов как таковых.
  • Традиционная для UNIX система сборки make выдает непозволительно долгое время сборки для больших проектов (особенно для инкрементальных сборок);
  • Использование рекурсивого вызова make из корневой директории приводит к нарушениям зависимостей между модулями, для устранения которого приходится запускать сборку два или три раза (и увеличение времени сборки, естественно);
  • Использование единого makefile устраняет недостатки, но поддержка становится невыносимой. О какой-либо достаточной поддержки изменений makefile с помощью IDE речи не идёт.


В статье Adrian Neagu 2005 года рассматриваются более общие проблемы make:
  • Портируемость make сильно раздута, так как make работает не сам по себе, в makefile могут быть указаны десятки других утилит, которые специфичны для платформы;
  • Масштабируемость ужасна, при использовании рекурсивного make (то же мнение, что в предыдущей статье), и недостаточна при использовании include или других расширений. В частности, производительность сборки падает очень быстро.


В статье «Что не так с GNU Make»
Более подробно расписаны минусы языка и те же самые проблемы что и ранее (не буду повторяться)
  • Неочевидные и откровенно ущербные элементы языка (чего стоят только ifeq, ifneq, ifdef, и ifndef)
  • Проблемы типов (работа только со строками)
  • Плохое разрешение зависимостей в случае изменения опций командной строки
  • Проблемы с одновременным запуском и распараллеливанием
  • Легко «убить» дерево сборки внезапной остановкой программы.
  • И много других мелких косяков, которые свидетельствуют о неподходящей архитектуре под современные нужды (по мнению автора).


В короткой заметке Ian Lance Taylor, 2007 г. рассмотрен основной недостаток Cmake как замены Make — Он. Слишком. Сложен. Это адская смесь нескольких языков, поддерживать её (для действительно сложных систем) могут только гуру разработки и отладки (отлаживать её тоже проблематично). Ещё одним недостатком является то, что за гибкость (даже ГИБКОСТЬ), приходится расплачиваться потерей производительность в сгенерированных скриптах.

Что не так с QMake?

В инфраструктуре Qt на сегодняшний день широко используется система сборки Qmake, которая поддерживается официально и все ещё дорабатывается. Зачем же возникла надобность «just another one» системы сборки?

В статье Marius Storm-Olsen анализирует следующие недостатки qmake:
  • Свой, самобытный синтаксис языка.

    Попробуйте посмотреть на QTDIR/mkspecs/features/exclusive_builds.prf:
    DEFINES += QT_NO_CAST_TO_ASCII
    !macx:DEFINES += QT_USE_FAST_OPERATOR_PLUS QT_USE_FAST_CONCATENATION
    
    unix {
        CONFIG(debug, debug|release):OBJECTS_DIR = $${OUT_PWD}/.obj/debug-shared
        CONFIG(release, debug|release):OBJECTS_DIR = $${OUT_PWD}/.obj/release-shared
        CONFIG(debug, debug|release):MOC_DIR = $${OUT_PWD}/.moc/debug-shared
        CONFIG(release, debug|release):MOC_DIR = $${OUT_PWD}/.moc/release-shared
        RCC_DIR = $${OUT_PWD}/.rcc
        UI_DIR = $${OUT_PWD}/.uic
    }
    


    Понять такое сразу, даже «поллитры» не хватит. Хотя с этим еще CMake может потягаться.
    Пусть программист знает С++, Pascal, Javascript, win&unix shell и SQL — все эти знания окажутся бесполезными в освоении системы сборки.
    От себя могу добавить, что много хлопот мне доставил символ "#" в путях и возня с $$LITERAL_HASH (зачем придумывать способ экранирования спецсимволов языка? Введём свои макросы лучше).
  • Поддержка IDE.
    Qt creator поддерживает добавление новых файлов в проект, но стоит чуть отойти от «привычного» ему синтаксиса, он просто может добавить в конец новую констркцию HEADERS или FILES.
  • Отсутствие сборки как таковой в qmake.
    Qmake не собирает ваш проект. Название обманчиво. Он лишь генерирует Makefile, а сборку осуществляет одна из утилит Make (*nix — gnu make, win- nmake, jom или mingw-make).
  • Отсутствие прозрачной поддержки кросс-компиляции, сборки пакетов и деплоймента.

Наконец, мы подошли к тому моменту, как в рассылке Qt обсуждалась новая система сборки и требования для неё:
  • Она должна быть ОЧЕНЬ быстрой (незаметное время чтобы произвести stat() всех файлов);
    Изменение узла где-то в глубине дерева сборки не должно приводить к изменению конфигурации всего дерева;
  • У неё должен быть простой и понятный язык, который может быть легко понят людьми и обслужен скриптами;
    Язык должен быть хорошо специализирован;
    Он должен быть декларативным в своей структуре, с небольшими императивными фрагментами, жёстко изолированными от остальных;
    Язык должен основываться на свойствах, а не на командах. Никаких CFLAGS. (cmake на полпути к этому);
  • Должна быть полная поддержка модульности. Также любой подпроект должен иметь возможность легко отделяться от родительского (и присоединяться к другому). Т. е., грубо говоря, взяли папку с конфигурацией сборки, вставили в другой проект -там тоже все хорошо собирается (без учёта зависимостей конечно);
  • Чёткая и ясная поддержка для IDE;
  • Должная быть возможность сборки разных конфигураций под разные платформы в один проход.


Представляем «спасательный круг» — Qt Build System


15 февраля 2012 года в Qt labs Йорг Борнеман представил общественности проект QBS. Сейчас он находится даже не в состоянии альфы, а где-то посередине между прототипом и альфа-версией. В оправдание можно сказать, что Qt Creator уже собирается с её помощью.
Основные принципы проектирования новой системы:
  • Декларативность — используется QML синтаксис;
  • Расширяемость — есть возможность писать свои модули для сборки, либо кодогенераторы и т. п. (ниже мы попробуем написать свой модуль для сборки проектов Delphi);
  • Скорость;
  • Сборка напрямую — это не qmake, это не «посредник». Qbs сама вызывает компиляторы, компоновщик, и что там еще понадобится.

Чего пока нет: проверки конфигурации системы(используется запрос к qmake для конфигурирования тулчейнов), сборки пакетов, деплоймента, запуска тестов и самое главное — поддержки каких-либо ide, кроме особой ветки QtCreator-а.

Начнём пробовать!

Автор советует собрать qbs из исходников, но можно иметь ввиду, для пользователей win и linux есть бинарные сборки
win — собрано под MSVC2010. Я бы тоже посоветовал собрать версию из git ( у меня проблемы были с плагинами и MOC).
В бинарном виде qbs зависит от библиотек QtCore и QtScript (+Concurrent для Qt5)
Создадим Hello World проект на С++:
main.cpp
#include <iostream>
using namespace std;
int main()
{
	cout << "Hello world!" << endl;
	return 0;
}


Создадим также минимальный проект на qbs:
hello.qbp
import qbs.base 1.0
CppApplication {
     name: "HelloWorld"
     files: "main.cpp"
}

LINUX:
1. Открываем в текущей папке шелл.
2. Добавляем путь, куда вы распаковали/собрали qbs, в PATH (вернее, bin папку)
PATH=~/qbs/bin/:$PATH
3. запускам qbs:
qbs
Found project file /home/mapron/статья/example-3/hello.qbp
loading project took:  69 ms 
build graph took:  18 ms 
for debug:
  - [hpp, application] HelloWorld as debug

compiling main.cpp
linking HelloWorld
Build done.

После этого программа сборки запустит разбор проекта и соберет в папке build/debug исполняемый файл (ну мы же не указали пока папку назначения?)
WINDOWS:
4. Поскольку под Windows, как правило компилятор и qt не находятся в PATH, qbs может попросить вас запустить «qbs platform probe», что необходимо сделать.
5. затем возможно, понадобится запустить qbs config update или подобное (если он попросит, возможно это исправили уже).
6. после этого при запуске qbs [build] — вы получите собранный бинарник «HelloWorld.exe»

Продолжаем изучать

Теперь попробуем освоить сборку чего-то более сложного, и заодно, разобраться с языком.
Для начала, создадим новую папку и скопируем туда папки «2dpainting» и «collidingmice» из папки examples с Qt.
Почему сразу два? Мы создадим конфигурацию для сборки сразу двух продуктов.
Продукт — это целевой «выход» системы сборки, подобие «.pro» файла при сборке Qmake. В одном проекте qbs может быть несколько продуктов.
Для начала создадим проект «examples.qbp»

//Подключаем стандартные библиотеки в стиле QML
import qbs.base 1.0 
import qbs.fileinfo 1.0 as FileInfo
Project {   // основной элемент файла - проект. 
    moduleSearchPaths: "qbs" // Папка для поиска дополнительных модулей, таких как cpp и qt
    // Один проект может состоять из нескольких продуктов - конечных целей сборки.
    Product {
        name: "2dpainting"       // Название выходного файла (без суффикса, он зависит от цели)
        type: "application"     // Тип - приложение, т.е. исполняемый файл.
        Depends { name: "cpp" } // этот продукт зависит от компилятора C++
        Depends { name: "Qt"; submodules: ["core", "gui", "opengl"] }   // И Qt модулей QtCore, QtGui, QtOpengl
        files: [ // список файлов в данном проекте. пока они в поддиректории, организуем позже.
            "2dpainting/glwidget.h",
            "2dpainting/helper.h",
            "2dpainting/widget.h",
            "2dpainting/window.h",
            "2dpainting/glwidget.cpp",
            "2dpainting/helper.cpp",
            "2dpainting/main.cpp",
            "2dpainting/widget.cpp",
            "2dpainting/window.cpp",
        ]
    }
}


Откроем консоль и попробуем собрать, выведется ошибка «ERROR: Error while setting up build environment: qt.core.incPath not set. Set qt.core.incPath or qt.core.path in your profile. »
Закроем пока глаза на не слишком юзер-френдли способ конфигурации, и создадим файл «qbs.config» с таким содержимым (WINDOWS):
modules.qbs.platform: MSVC2010
profile: default
profiles.default.qt.core.path: C:/QtSDK/Desktop/Qt/4.8.1/msvc2010/


либо под LINUX (ubuntu):
modules.qbs.platform: gcc
profile: default
profiles.default.qt.core.binPath: /usr/bin/
profiles.default.qt.core.libPath: /usr/lib/qt4
profiles.default.qt.core.incPath: /usr/include/qt4 
profiles.default.qt.core.mkspecsPath: /usr/share/qt4/mkspecs

и запустить qbs config --import qbs.config
После этого qbs нормально сможет собрать проект и поместит выходной файл в папку build/debug.
Для того чтобы собрать проект в релиз, выполните «qbs build release».
Для очистки всех файлов сборки (т. е. Папки «build») выполните «qbs clean».
Теперь попробуем организовать расширяемую структуру для двух проектов. Создадим в подкаталогах «2dpainting» и «collidingmice» файлы-тёзки своих каталогов с расширением «.qbs» с таким содержимым:

import qbs.base 1.0 
Product {
    name: "2dpainting"
    type: "application"
    Depends { name: "cpp" }
    Depends { name: "Qt"; submodules: ["core", "gui", "opengl"] }
    files: [ // чтоб не раздувать по вертикали, напишу в две колонки.
        "glwidget.h",     "helper.h",
        "widget.h",       "window.h",
        "glwidget.cpp",   "helper.cpp",
        "main.cpp",       "widget.cpp",
        "window.cpp",
    ]
}


import qbs.base 1.0 
Product {
        name: "collidingmice"
        type: "application"
        Depends { name: "cpp" }
        Depends { name: "Qt"; submodules: ["core", "gui", "opengl"] }
        files: [ "mouse.h","main.cpp", "mouse.cpp" ,"mice.qrc"]
    }


Т.е. мы разбиваем код на независимые продукты, которые могут собираться параллельно. Внесём изменения в examples.qbp:
//Подключаем стандартные библиотеки в стиле QML
import qbs.base 1.0 
import qbs.fileinfo 1.0 as FileInfo
Project {   // основной элемент файла - проект. 
    moduleSearchPaths: "qbs"
    // Один проект может состоять из нескольких продуктов - конечных целей сборки.
    // указываем связанные файлы с помощью references. Внимание: это не жестко заданный порядок!
    // Порядок задается с помощью зависимостей, о них позже
    references: [
           "2dpainting/2dpainting.qbs",
           "collidingmice/collidingmice.qbs",
       ]
}


Можете снова запустить «qbs». Обратите внимание, для «.qrc» файла будет автоматически вызван «rcc» и все это слинковано вместе. Все файлы при этом указываются одним списком, без разделения на HEADERS, SOURCES и т. д., как было в qmake.

Как это все работает?

Для начала рекомендую бегло ознакомиться со справкой
Основные концепции языка: Проект (Project), Продукт (Product), Артефакт (Artifact), Модуль (Module), Правило (Rule), Группа(Group), Зависимость (Depends), Тег (Tag).
Продукт — это аналог pro или vcproj, т. е. одна цель для сборки.
Проект — это набор ваших продуктов вместе с зависимостями, воспринимаемый системой сборки как одно целое. Один проект — один граф сборки.
Тег — система классификации файлов. Например «*.cpp» => «cpp»
Правило — Преобразование файлов проекта, отмеченных определенными тегами. Генерирует другие файлы, называемые Артефактами. Как правило, это компиляторы или другие системы сборки.
Артефакт — файл, над который является выходным для правила (и возможно, входным для други правил). Это обычно «obj», «exe» файлы.
У многих QML-объектов есть свойство condition, которое отвечает за то, будет собираться он или нет. А если нам необходимо разделить так файлы? Для этого их можно объединить в группу (Group)
Group { 
            condition: qbs.targetOS == "windows" 
            files: [ "file1", ...]
}

примерно таким образом.

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


Закономерный вопрос, примеры это здорово, а как можно отправиться в свободное плавание с этой системой? Можно:
  • Читать документацию;
  • Скачать исходники Qt Creator и посмотреть как QBS используется там;
  • Изучать исходники самого QBS (на крайний случай).


Заключение


QBS предоставляет нам следующие плюсы:
  • Удобный и понятный формат конфигов сборки
  • Высокую скорость
  • Чёткую модульную архитектуру

Но, к сожалению имеет следующие (временные минусы)
  • Наличие большого количества багов (это ещё не альфа);
  • Отсутствие поддержки IDE (ждем в Qt Creator);
  • Отсутствие системы сборки пакетов, инсталляторов (в планах).


К сожалению, формат статьи не позволяет раскрыть все тонкости, в дальнейших планах раскрыть тему:
  1. Создание собственного модуля (он включён в архив с примерами);
  2. Написание правил для сборки;
  3. Создание js-модулей и их включение;
  4. Создание своих типов продуктов;
  5. Работа с глобальным конфигом и конфиги модулей;
  6. Написание своего плагина (для парсинга зависимостей .drpoj файла).

Все это может быть в этой статье, и что-то что напишут в комментариях.

Ссылка на архив со всеми примерами к статье:
narod.ru/disk/49759080001.18d6748f8ef86e26c6dea2e2c5ed7d13/examples.zip.html
В архиве БОНУС! не упомянутый в статье пример модуля для сборки DELPHI 2007 проекта (планируется сделать разбор в следующей статье).

Ссылки


labs.qt.nokia.com/2012/02/15/introducing-qbs — представление
doc-snapshot.qt-project.org/qbs — документация
qt.gitorious.org/qt-labs/qbs — git
bugreports.qt-project.org/browse/QBS — багтрекер.

Примечание: Qt Build Salvation (Спасение для сборки Qt) — внутреннее имя QBS, в README дерева исходников.

+47
39.5k 143
Comments 49