26 July 2016

Автоматизированная сборка C++ проектов в Gitlab CI

Новый адрес: MKlimenko.github.io

Для начала: GitLab это git-сервис с открытым исходным кодом. Около года назад, когда я спросил о наличии каких-либо развёрнутых систем контроля версий, админы дали мне доступ к нашему серверу git. Достаточно долго я использовал лишь малую часть его возможностей, в основном просто push, чтобы сохранить проделанную работу. Однако, этому было суждено измениться. Сейчас я работаю над достаточно сложным проектом, который включает в себя несколько типов программных продуктов (программы для DSP и ARM, API, графическое приложение), которые в свою очередь требуют множество сред и тулчейнов для сборки. И после того как пришлось ждать три часа, пока скачается и установится Qt (привет корпоративным прокси!), я решил, что было бы здорово настроить build-сервер, который бы при каждом новом коммите стягивал последние исходники и собирал их. Беглое гугление рассказало про Jenkins, но в тот день мне было лениво его настраивать. Позднее в тот же день я зашел в веб-интерфейс нашего git и один пункт меню приковал моё внимание: “Builds”. Если не вдаваться в детали, это интегрированный планировщик задач, который запускается при каждом новом коммите (а так же merge request).

Чтобы начать использовать CI (continuous integration), необходимо всего-либо расположить в корне репозитория один файл. Он предоставляет много возможностей, например, произвести В, только если А пройдёт успешно/неуспешно и т.д. Я не слишком серьезно настроил эти скрипты, в моём случае перед сборкой скачиваются подмодули для получения последних версий библиотек, после чего осуществляется сборка. Если сборка провалилась, автор коммита получает письмо на почту, что он сломал билд. Когда я тестировал различные возможности CI, я получал по 50 писем в час (простите, админы).

В сети много примеров содержимого файла .gitlab-ci.yml для популярных языков, однако я не нашёл пример для проектов Visual Studio (кроме C#, но это другая песня) или Qt. Если вы пришли сюда за этим, надеюсь вам это поможет.

MSBuild:

 Job_name: 
  script: 
  - 'setlocal' 
  - 'chcp 65001' 
  - 'call "%VS120COMNTOOLS%..\..\vc\vcvarsall.bat" x86_amd64' 
  - 'msbuild.exe make\vs120\Project_name.sln /t:Rebuild /p:Configuration=Release /p:Platform="x64" /m' 
  - 'if not exist "%BUILDS%\Project_name" (mkdir "%BUILDS%\Project_name")' 
  - 'copy make\vs120\x64\Release\Project_name.exe "%BUILDS%\Project_name"' 

Строка “Job_name:” является обязательной и обозначает, как будет называться задача runner-а. Также под этим именем она будет отображена в веб-интерфейсе.

  - 'chcp 65001'” необходмо для корректного отображения кириллицы, печатаемой при выводе MSBuild.

  - 'call "%VS120COMNTOOLS%..\..\vc\vcvarsall.bat" x86_amd64'” добавляет необходимые переменные окружения чтобы cmd нашёл MSBuild, компилятор и т.д.

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

Достаточно просто, да? Давайте перейдём к более интересным вещам, а в частности проектам из Qt.

Another_Job_name: 
  script: 
  - 'setlocal' 
  - 'chcp 65001' 
  - 'call "%VS120COMNTOOLS%..\..\vc\vcvarsall.bat" x86_amd64' 
  - 'cd make\qt5' 
  - 'call "%QT_ROOT_x86_64%\bin\qmake.exe" Qt_project.pro -r -spec win32-msvc2013' 
  - 'call "%QT_CREATOR%\bin\jom.exe" -f Makefile.Release'  
  - 'rd /s/q deploy' 
  - 'mkdir deploy' 
  - 'copy release\Qt_project.exe deploy' 
  - 'set curr_dir=%cd%' 
  - 'cd /d "%QT_ROOT_x86_64%\bin"' 
  - 'windeployqt.exe "%curr_dir%\deploy\Qt_project.exe" -no-translations' 
  - 'cd /d %curr_dir%' 
  - 'if not exist "%BUILDS%\Qt_project" (mkdir "%BUILDS%\Qt_project")' 
  - 'xcopy /s /y deploy "%BUILDS%\Qt_project"' 

Вот это уже интереснее. Несколько схожих команд, затем вызывается qmake для генерации makefile-ов из файла проекта .rpo, а затем jom (многопоточный make) производит сборку Release-версии.

Приложения, написанные в Qt требуют множества библиотек во время работы, соответственно для распространения нам необходимо, чтобы они все находились в директории с программой. Вместе с Qt поставляется утилита windeployqt, которая анализирует исполняемый файл и кладёт все его зависимости рядом с ним. До сих пор не понимаю почему, но мне не удалось заставить её работать без смены директории (ругалась на Python2.7), хотя какая разница. xcopy используется для копирования всего содержимого внутренней директории в каталог с общим доступом.

Осмысление того, как всё необходимо осуществлять с qmake, jom и windeployqt заняло некоторое время, но опять же, ничего слишком сложного. А теперь я вам представляю скрипт для сборки проектов под ARM.

 ARM_project_job: 
  script: 
  - 'setlocal' 
  - 'chcp 65001' 
  - 'set command=""%DS-5_DIR%\sw\eclipse\eclipsec.exe" -nosplash --launcher.suppressErrors -application org.eclipse.cdt.managedbuilder.core.headlessbuild -data "%ARM_WORKSPACE%" -import make\eclipse\arm_project -cleanBuild arm_project"'  
  - 'echo "%command%" | "%DS-5_DIR%\bin\cmdsuite.exe" 2> error.txt' 
  - 'for %%A in (error.txt) do set fileSize=%%~zA' 
  - 'del /f /q error.txt'  
  - 'if not %fileSize%==0 (exit /b 1)' 
  - 'if not exist "%BUILDS%\arm_project" (mkdir "%BUILDS%\arm_project")' 
  - 'copy make\eclipse\arm_project\Release\arm_project.axf "%BUILDS%\arm_project"' 

Самая интересная часть тут это перенаправление вывода %command% в cmdsuite.exe. Cmdsuite это командная строка DS-5, т.н. batch job, в котором происходит некоторая внутренняя магия с лицензиями и конфигурированием баз данных. Я называю это магией в связи с тем, что мне не удалось экспортировать все переменные окружения и настройки в основную командную строку. Вопрос в том, как передать команду в созданную командную строку из родителя? Каким-то образом это работает через символ |, т.н. pipe. Сама по себе команда — это просто вызов Eclipse без логотипа с командой для сборки проекта в режиме командной строки. Внимание. Если у вас в workspace уже имеется одноимённый проект, вы не сможете его импортировать и собрать. После этого я осуществляю переадресацию вывода ошибок сборки в текстовый файл с последующим чтением в основном потоке (командной строке). Это необходимо в связи с тем, что вывод batch job подавлен и недоступен для анализа из gitlab CI. Если файл не пуст, принимается решение о наличии ошибок и возвращается код 1, что отмечает коммит, как не прошедший сборку. Иначе в общую директорию кладётся новейшая программа под ARM.


Automated builds for C++ projects via GitLab CI

New address: MKlimenko.github.io

First of all: GitLab is an open-source git service. About a year ago, when I asked for any type of source control admins gave me an access to our git server. For a long time I've used only a few of its features, just straightforward git pushes to save my work. However, things were about to change very soon. Currently I'm working on a very complicated project, which involves multiple types of projects (DSP, ARM, API, UI), which requires a lot of SDK’s and IDE’s to build. And when we were forced to wait for three hours to install Qt (hello to corporate proxies), I’ve decided that it would be great to make a dedicated build server, which will pull every new commit from git and handle all the builds. Brief search told me about Jenkins, but I was too lazy to set this thing up. Later that day I’ve visited our corporate git webpage and one menu entry has caught my attention: “Builds”. Simply put it’s an integrated build tasker, which engages when new commit is pushed.

To engage the CI you only have to put a file into the root of the repository. There are many possibilities, like perform B only if A fails/succeeds, etc. I’m not so good at it and just update all of the submodules prior to the builds (to acquire the latter libraries), and then perform the builds. If the build job fails, the last pusher is notified that he broke the build. When I was testing this feature, I was receiving something around 50 emails an hour.


There are many examples of .gitlab-ci.yml files for popular languages, but I was unable to find an example of Visual Studio and Qt projects builds. Maybe this will help you:
MSBuild:

 Job_name:  
  script:  
  - 'setlocal'  
  - 'chcp 65001'  
  - 'call "%VS120COMNTOOLS%..\..\vc\vcvarsall.bat" x86_amd64'  
  - 'msbuild.exe make\vs120\Project_name.sln /t:Rebuild /p:Configuration=Release /p:Platform="x64" /m'  
  - 'if not exist "%BUILDS%\Project_name" (mkdir "%BUILDS%\Project_name")'  
  - 'copy make\vs120\x64\Release\Project_name.exe "%BUILDS%\Project_name"'  

“Job_name:” string is required and this is how your build/test job will be displayed on the server website.

“  - 'chcp 65001'” is required to display correctly the Cyrillic symbols from MSBuild output.

“  - 'call "%VS120COMNTOOLS%..\..\vc\vcvarsall.bat" x86_amd64'” adds required environment variables, to help shell executor to find MSBuild, C++ compiler etc.

The last two strings help me to store the latter build in the shared folder on the server. It helps a lot when it comes to sharing some of the software I develop.

Easy, huh? Let’s switch to something more interesting, Qt projects!

Another_Job_name:  
  script:  
  - 'setlocal'  
  - 'chcp 65001'  
  - 'call "%VS120COMNTOOLS%..\..\vc\vcvarsall.bat" x86_amd64'  
  - 'cd make\qt5'  
  - 'call "%QT_ROOT_x86_64%\bin\qmake.exe" Qt_project.pro -r -spec win32-msvc2013'  
  - 'call "%QT_CREATOR%\bin\jom.exe" -f Makefile.Release'   
  - 'rd /s/q deploy'  
  - 'mkdir deploy'  
  - 'copy release\Qt_project.exe deploy'  
  - 'set curr_dir=%cd%'  
  - 'cd /d "%QT_ROOT_x86_64%\bin"'  
  - 'windeployqt.exe "%curr_dir%\deploy\Qt_project.exe" -no-translations'  
  - 'cd /d %curr_dir%'  
  - 'if not exist "%BUILDS%\Qt_project" (mkdir "%BUILDS%\Qt_project")'  
  - 'xcopy /s /y deploy "%BUILDS%\Qt_project"'  

Ah, now that’s interesting. Several similar commands, then we call qmake to create Makefiles from .pro, and then jom (multithreaded make) builds the Release version.

Qt application requires a lot of libraries to run, so we need them all to be present in the final folder. There’s a tool called windeployqt, which analyzes the executable and puts everything right next to it. Somewhy I wasn’t able to make it work without changing the folder, but who cares. xcopy is used to copy everything inside the internal deploy folder to The deploy folder.


It took me a while to realize how to call qmake, jom and windeployqt, but again, nothing too difficult. So I present to you the ARM project build script:

 ARM_project_job:  
  script:  
  - 'setlocal'  
  - 'chcp 65001'  
  - 'set command=""%DS-5_DIR%\sw\eclipse\eclipsec.exe" -nosplash --launcher.suppressErrors -application org.eclipse.cdt.managedbuilder.core.headlessbuild -data "%ARM_WORKSPACE%" -import make\eclipse\arm_project -cleanBuild arm_project"'   
  - 'echo "%command%" | "%DS-5_DIR%\bin\cmdsuite.exe" 2> error.txt'  
  - 'for %%A in (error.txt) do set fileSize=%%~zA'  
  - 'del /f /q error.txt'   
  - 'if not %fileSize%==0 (exit /b 1)'  
  - 'if not exist "%BUILDS%\arm_project" (mkdir "%BUILDS%\arm_project")'  
  - 'copy make\eclipse\arm_project\Release\arm_project.axf "%BUILDS%\arm_project"'  

The interesting part here is the piping the %command% to the cmdsuite.exe. Cmdsuite is a DS-5 command prompt, batch job with some internal magic about licensing and configuring databases. I call it magic I failed at trying to export all of the environment variables to the system. The problem is how to pass the command to the batch job? Somehow piping works. The command itself is just a call for the eclipse without logo, ordering it to import the acquired project and build it in headless mode. Attention! If you have a project with the same name imported into your DS-5 workspace on that PC, eclipse won’t be able to import it and, therefore, to build it. Then I redirect the output of the build to the text file and read it later in the main thread (command prompt). This is required, because the batch job output is suppressed and isn’t available for gitlab CI to analyze. If the file is not empty, I presume that there are errors and exit with error code 1, which marks the build as failed. Otherwise, I have the latest build ARM executable in the shared folder.                 

8 July 2016

Управление потоками в Qt

Я писал небольшой пост о том, как осуществляется удалённый сброс по питанию, но он как-то слишком долго пишется. Так что пока я поделюсь некоторыми соображениями на тему многопоточности в приложениях с UI. Предположим, что мы разрабатываем простое клиент-серверное приложение, от которого требуется вызывать некую callback-функцию каждый раз, когда приходят новые данные. Отсюда следует необходимость постоянного прослушивания порта, что невозможно реализовать в основном потоке, если есть и другие задания. Тут и пригождается многопоточность: мы создаём слушающий поток, в то время как главный ожидает команд от пользователя.

Существует два способа создания параллельных приложений на C++: на основе потоков и на основе задач, std::thread и std::async соответственно. Лично я предпочитаю std::thread по той простой причине, что std::async требует модификатора std::launch::async для полноценной отвязки потока. Хотя тут дело скорее в том, что я просто не оценил по достоинству все возможности std::async в действии.

Предположим, у нас есть некий класс:

class Foo {
private:
       std::thread RxThread;
       //…
       void InfiniteRead(std::function<void(uint8_t*, size_t &)> callback) {
             for (;;) {
                    //Read Some Data
                    if(read) {
                           callback(data, size_of_data);
                    }
             }
       }
       //…
public:
       void Init() {
             RxThread = std::thread(&Foo::InfiniteRead, this, callback);
       }
}

Вуаля, мы породили поток, который в вечном цикле слушает порт и вызывает некую функцию при наличии данных. Проблемы начинаются, когда становится необходимо обновить UI в соответствии с полученными данными. Считается плохим тоном изменять что-либо в интерфейсе из других потоков по той простой причине, что из потока неизвестно, происходит ли обновление интерфейса в данный момент, что приводит к очень противным и трудно отлавливаемым ошибкам.

В Qt следует использовать event-ы для обновления графического интерфейса из другого (как бы) потока. Сначала, следует создать класс, который будет эти самые события обрабатывать.

class MyEvent : public QEvent{
public:
    struct event_msg{
        //some custom struct with data
    };

  MyEvent(const event_msg& message) : QEvent(QEvent::User) {_message = message;}
 ~MyEvent() {}

  event_msg message() const {return _message;}

private:
  event_msg _message;
};

Далее объявляется простой метод в заголовочном файле класса графического интерфейса:

bool event(QEvent* event);

Этому методу соответствует следующая имплементация:

bool UI_Class::event(QEvent* event){
    if (event->type() == QEvent::User){

        MyEvent* postedEvent = static_cast<MyEvent*>(event);
        //some code
    }
    return QWidget::event(event);
}

В перегруженном методе обработки событий мы проверяем тип события и, если он совпадает с тем, что создали мы, производятся некоторые команда. Иначе событие отправляется дальше по пищевой цепочке. Ну и напоследок вкратце о том, как посылать эти события:

void blabla(){
    //...
    MyEvent::event_msg event;
    //fill event_msg with data
    MyEvent* e = new MyEvent(event);
    QCoreApplication::postEvent(parent, e);
    }

Готово, вот так просто. Ещё очень хорошей идеей является уведомление потока через std::condition_variable о том, что были получены некоторые данные. 

Managing threads in Qt

I was writing a little post about performing a remote power reset, but it is not quite there yet, so I’ll share with you some of the ideas about multithreading in UI applications. Let’s say we’re developing a simple client-server application that is required to use a callback function every time there is new data available. Therefore, we need to listen the port for the incoming data constantly, which is impossible in the main thread. Here comes the multithreading: we have to spawn a listener thread, while our main thread is waiting for the instructions.

There are two ways to create a parallel application in C++: thread-based and task-based. std::thread vs std::async. I personally prefer the std::thread way, simply because std::async need the std::launch::async policy specified for the true asynchronous calculations.

For example, let’s use a class:

class Foo {
private:
       std::thread RxThread;
       //…
       void InfiniteRead(std::function<void(uint8_t*, size_t &)> callback) {
             for (;;) {
                    //Read Some Data
                    if(read) {
                           callback(data, size_of_data);
                    }
             }
       }
       //…
public:
       void Init() {
             RxThread = std::thread(&Foo::InfiniteRead, this, callback);
       }
}

Voila! We’ve spawned a thread, which constantly reads data and calls some function on received data. Problems start when we need to update the UI according to the received data. It is a bad habit to change anything in the UI from other threads, because you never know if it’s being updated from the main thread right now.

One should use events in Qt to update the UI from (sort of) another thread. Firstly you should declare a class, which would handle the events:

class MyEvent : public QEvent{
public:
    struct event_msg{
        //some custom struct with data
    };

  MyEvent(const event_msg& message) : QEvent(QEvent::User) {_message = message;}
 ~MyEvent() {}

  event_msg message() const {return _message;}

private:
  event_msg _message;


};

Then you declare a simple method in the UI header file:

bool event(QEvent* event);

With the following implementation:

bool UI_Class::event(QEvent* event){
    if (event->type() == QEvent::User){

        MyEvent* postedEvent = static_cast<MyEvent*>(event);
        //some code
    }
    return QWidget::event(event);
}

In the overridden event method, we check the event type, and if it is the one we’ve made, we execute some commands. Otherwise, we send the event further down the food chain. And the last thing is how to send such events:

    void blabla(){
        ...
        MyEvent::event_msg event;
        //fill event_msg with data
        MyEvent* e = new MyEvent(event);
        QCoreApplication::postEvent(parent, e);
    }


Done. As simple as that. Also, it’s very good idea to notify the thread via the std::condition_variable, that you’ve received some data.