Archiwa tagu: qt

Jenkins – konfiguracja projektu Qt [Windows]

Jenkins jest narzędziem do automatycznego budowania i testowania aplikacji. Ten wpis opisuje jak skonfigurować Jenkinsa do budowania i testowaniu aplikacji napisanych w C++ i Qt.
Docelowo Jenkins będzie pobierał projekt z gita, zbuduje go i odpali projekt z testami jednostkowymi, który jest aplikacją konsolową.

Nowy projekt.
Z menu na stronie głównej wybieramy New Item, podajemy nazwę projektu i klikamy Freestyle project i OK.

W karcie Source Code Management wybieramy git i podajemy adres repozytorium, np.
https://github.com/ololuki/Nonograms

Dobrze jest zaznaczyć checkbox „Delete workspace before build starts” (wymagany plugin „Workspace Cleanup”). Dzięki temu projekt zostanie za każdym razem wyczyszczony i zbudowany od zera, co pozwoli uniknąć dziwnych błędów kompilacji w przypadku dużych zmian w projekcie.

W karcie build dodajemy Add build step -> Execute Windows batch command
Dla kompilatora Visual C++ 2015 x64:

:: Konfiguracja środowiska
call "C:\Program Files (x86)\Microsoft Visual Studio 14.0\VC\vcvarsall.bat" amd64

:: Uruchomienie qmake
"D:\Qt\5.8\msvc2015_64\bin\qmake.exe" "nonograms.pro" -spec win32-msvc2015 "CONFIG+=debug" "CONFIG+=qml_debug"

:: Przerwanie skryptu w przypadku błędu
if errorlevel 1 (
   echo Error: %errorlevel%
   exit /b %errorlevel%
)

:: Kompilacja projektu
"D:\Qt\Tools\QtCreator\bin\jom.exe" -f "Makefile"

:: Przerwanie skryptu w przypadku błędu
if errorlevel 1 (
   echo Error: %errorlevel%
   exit /b %errorlevel%
)

:: Dodanie bibliotek Qt do PATHa, aby uruchomić aplikację z testami
SET PATH=%PATH%;D:\Qt\5.8\msvc2015_64\bin

:: Uruchomienie aplikacji konsolowej z testami
"bin\NonogramsTests.exe"

Zadania w pliku wsadowym w cmd.exe są domyślnie wykonywane linia po linii bez sprawdzania błędów, więc jeśli jedno z zadań się nie powiedzie to pozostałe i tak mogą zostać wykonane. Trzeba więc ręcznie zadbać o zatrzymanie skryptu w przypadku błędu jednego z zadań poprzez sprawdzenie wartości zmiennej „errorlevel”.

Automatyczne sprawdzanie zmian w repozytorium.

Aby Jenkins automatycznie sprawdzał czy nie ma nowych zmian można użyć zakładki „Build Triggers”, zaznaczyć checkbox „Poll SCM” i w polu „Schedule” wpisać:

H/15 * * * *

gdzie 15 oznacza liczbę minut pomiędzy kolejnymi odpytaniami repozytorium.

Ręczne budowanie konkretnej wersji.

Aby umożliwić ręczne budowanie konkretnej wersji projektu za pomocą Jenkinsa w zakładce „General” zaznaczamy opcję „This project is parametrized” i w polu Name podajemy nazwe parametru, np. „commitId” – w naszym przepadku jest to hash commitu. Aby umożliwić budowanie wersji o podanym hashu w zakładce „Source Code Management” w polu „Branches to build” wpisujemy:

${commitId}

Od tej chwili po wejściu do projektu zamiast przycisku „Build Now” pojawi się przycisk „Build with Parameters” umożliwiający wpisanie hasha commita do zbudowania.

Qt Test – przykładowy projekt z testami wykorzystujący bibliotekę statyczną dla wspólnego kodu

Tworząc bardziej rozbudowany projekt aplikacji wykorzystujący testy jednostkowe można natknąć się na problem struktury projektu. Pierwszym podejściem może być stworzenie dwóch projektów: aplikacji oraz testów. W tym rozwiązaniu pliki z kodem produkcyjnym dodajemy do projektu aplikacji, natomiast pliki testów do projektu test. Jednak projekt testowy także musi mieć dostęp do plików nagłówkowych oraz źródłowych aplikacji. Jeśli potrzebne pliki projektu app dodamy także do projektu test to zauważymy dwa problemy:
1. Każdy z tych plików źródłowych będzie kompilowany dwa razy
2. Qt Creator wyświetli ostrzeżenie „Multiple parse contexts are available for this file. Choose preferred one from the editor toolbar.”

Aby uniknąć powyższych problemów warto kompilować część wspólną do biblioteki statycznej a następnie linkować tę bibliotekę w projektach app i test. W poniższym przykładzie biblioteka statyczna (projekt core) zawiera klasy, które są jednocześnie używane w projekcie app i testowane w projekcie tests.

Link do kompletnego projektu: Qt-simple-examples/ProjectStructureQTestWithStaticLib

Qt Unit Test – projekt z kilkoma klasami testowymi

Mając chwilowe problemy z dotychczasowym frameworkiem do testów jednostkowych postanowiłem przyjrzeć się frameworkowi wbudowanemu w Qt: Qt Test.

Pierwszy problem pojawia się gdy po stworzeniu nowego projektu Qt Unit Test w Qt Creatorze próbujemy go odpalić w konfiguracji debug. Kończy się to błędem „moc: Too many input files specified: „. Problem wynika ze spacji w ścieżce. Można go rozwiązać usuwając jedną linię z pliku *.pro:

#DEFINES += SRCDIR=\\\"$$PWD/\\\"

Druga sprawa dotyczy architektury frameworka Qt Unit Test. Domyślny projekt testowy stworzony w Qt Creatorze zawiera tylko jedną klasę testową. W plikach źródłowych Qt każda klasa testowa jest osobnym projektem, a te projekty połączone są w strukturę drzewa za pomocą projektów subdirs. Domyślnie funkcja main w projektach testowych jest implementowana poprzez makro

QTEST_APPLESS_MAIN(UnittestTest)

i pozwala na dodanie tylko jednej klasy z testami jednostkowymi. Aby móc stworzyć projekt zawierający kilka klas z testami i jedną funkcją main wystarczy tę funkcję main ręcznie zaimplementować:

int main()
{
	QVector<QObject*> tests;
	
	tests.append(new FooTest);
	tests.append(new BarTest);
	
	int result = 0;
	for (int i = 0; i < tests.length(); i++)
	{
		result = QTest::qExec(tests[i]);
		if (result) break;
	}
	qDeleteAll(tests);
	return result;
}

Link do kompletnego projektu: Qt-simple-examples/QTestMultipleTestClasses

Innym sprawą, która skłoniła mnie do przejścia z Catcha na Qt Unit Test był problem związany z obsługą biblioteki Catch przez Qt Creatora. Podczas zmiany nazwy metody lub klasy za pomocą polecenia Refactor -> Reaname Symbol Under Cursor, Qt Creator widzi jedynie użycia zmienianej metody lub klasy w projekcie aplikacji, natomiast nie widzi wystąpień tych nazw w projekcie test (który używa Catcha). Gdy w projekcie test użyta zostanie biblioteka Qt Test to Qt Creator nie będzie miał już problemów i zmieni nazwy wszystkich wystąpień edytowanej nazwy.

Qt test – unresolved external symbol metaObject – rozwiązanie

Dotychczasowy framework do testów jednostkowych Catch nie chciał zbytnio współpracować z klasami dziedziczącymi po QObject. Przy próbie kompilacji zwracał błąd: „LNK2001: unresolved external symbol „public: virtual struct QMetaObject const * __cdecl BlocksDescriptionField::metaObject(void)const”. Rozwiązaniem okazało się dodanie plików nagłówkowych w pliku *.pro:

...
HEADERS += ../app/field/BlocksDescriptionField.h
...

Qt Deployment, czyli tworzenie wersji release ze wszystkimi plikami *.dll

QT Creator domyślnie tworzy aplikacje z linkowaniem dynamicznym (shared). Jeśli korzystamy z darmowej, otwartoźródłowej wersji QT to jest to zgodnie z licencją jedyna opcja. W związku z tym razem z publikowanym plikiem *.exe projektu trzeba dostarczyć skompilowane pliki biblioteki od których jest on zależny. Pomoże nam w tym windeployqt.exe (Qt Deployment Tool) znajdujący się w katalogu Qt\Qt5.8.0\5.8\msvc2015_64\bin. Odpalamy ten plik w cmd.exe, jako argument podając ścieżkę do pliku wykonywalnego projektu, np. C:\QT Projects\nonograms\app\release\project1.exe. Po tej operacji w folderze release znajdują się wszystkie pliki potrzebne do działania aplikacji, a nawet więcej. Aby usunąć niepotrzebne pliki wystarczy odpalić aplikację project1.exe i w czasie jej działania spróbować usunąć pozostałe pliki z folderu. Niepotrzebne pliki powinny zostać usunięte a wymagane przez aplikację zostaną pominięte. Sposób ten opisany jest tutaj: Deploy an Application on Windows

cd C:\Qt\Qt5.8.0\5.8\msvc2015_64\bin
windeployqt.exe "C:\QT Projects\nonograms\app\release\nonograms.exe"

Więcej na stronie QT: Qt for Windows – Deployment

Lepsze podpowiadanie składni kodu w Qt Creatorze, czyli ClangCodeModel

Qt Creator domyślnie ma włączone „jakieś” podpowiadanie kodu, które cośtam podpowiada, jednak nie jest ono zbyt dobre. Problem pojawia się, gdy przeciążymy operator[], aby zwracał obiekt z naszego kontenera. Przykład:

// mamy przykładową klasę:
class Pixel
{
public:
	Pixel(int x, int y, bool isVisible = true) : x(x), y(y), visible(isVisible) {}
	bool isVisible(){return visible;}
	void makeVisible() {visible = true;}
	void makeInvisible() {visible = false;}
private:
	int x;
	int y;
	bool visible;
};

// i kontener na obiekty tej klasy:
class LineOfPixels : private std::vector < Pixel >
{
public:
	LineOfPixels(){}
	LineOfPixels(std::vector < Pixel > vectorToCopy) : std::vector< Pixel >(vectorToCopy) {}
	size_t size() {return std::vector < Pixel > ::size();}
	
	Pixel& getPixelAt(int pixelNumber) {return this->at(pixelNumber);}
	Pixel& operator[](const int pixelNumber) {return std::vector < Pixel > ::operator[](pixelNumber);}
};

int main()
{
	int size = 2;
	LineOfPixels lineOfPixels(std::vector <Pixel>(size, Pixel(0, 0)));
	lineOfPixels[1].makeInvisible();
	lineOfPixels.getPixelAt(1).makeVisible();
	
	return 0;
}

Po instalacji Qt Creatora opcja Clang Code Model domyślnie jest wyłączona. Jeśli teraz w programie chcemy odwołać się do obiektu klasy Pixel przez zwykły getter:

lineOfPixels.getPixelAt(1).makeInvisible();

to podpowiadanie składni zadziała normalnie pokazując listę dostępnych składowych na każdym poziomie. Natomiast gdy zrobimy to samo przez przeciążony operator[] jak tutaj:

lineOfPixels[1].makeInvisible();

to podpowiadanie składni zgłupieje i nic nam nie pomoże. Po wpisaniu „lineOfPixels[1].” nie otrzymamy żadnej podpowiedzi.
Aby podpowiadanie składni działało poprawnie w każdych warunkach, trzeba włączyć plugin ClangCodeModel, zaznaczając opcję w Help → About Plugins… → C++ → ClangCodeModel.

Link: Parsing C++ Files with the Clang Code Model

QT Creator – dodanie niestandardowego widgetu w edytorze interfejsu graficznego

Dodanie customowego widgetu. Z menu po lewej stronie edytora interfejsu przeciągamy obiekt „Widget” w docelowe miejsce. Klikamy prawym przyciskiem myszy na widget → „Promote to …”. W oknie Promoted Widgets wpisujemy do „Promoted class name” nazwę naszej klasy, np. DrawingAreaView, natomiast w polu „Header file” ścieżkę pliku nagłówkowego, np. widgets/DrawingAreaView.h. Klikamy Add, zaznaczamy nowo dodany wpis i klikamy Promote. Nasz widget został dodany w miejsce zwykłego QWidgetu.

Layout dla każdego (QWidgetu) czyli co zrobić, gdy kontrolki wewnątrz layoutu nie skalują się wraz z oknem

Dziś trochę o layoutach i definiowaniu interfejsu graficznego. Po dodaniu kilku kontrolek do layoutu zauważyłem, że podczas skalowania okna znajdujące się w jego wnętrzu kontrolki nie zmieniają swojego rozmiaru i kształtu. Efekt, który chciałem osiągnąć to skalowanie kontrolek wewnątrz okna wraz ze zmianą rozmiaru okna. Chwila w kwakania w Duck Duck Go zaprowadziła mnie do rozwiązania problemu. Mianowicie każde okno (QMainWindow) i każdy obszar typu QScrollArea powinien mieć ustawiony layout. W edytorze interfejsu po prawej stronie mamy okno z drzewem widżetów. Dla nowo stworzonego projektu zaraz pod elementem MainWindow znajduje się centralWidget. Jest to serce całego interfejsu, najważniejszy widget w oknie. Po jego lewej stronie znajduje się ikonka layoutu z czerwonym znacznikiem informującym, że layout nie został ustawiony. Aby ustawić layout wystarczy kliknąć prawym przyciskiem myszy na MainWindow → Lay Out → Lay Out Horizontaly (bądź inny wybrany przez nas layout). Można też tego dokonać klikając na MainWindow a następnie ikonkę Lay Out Horizontaly na pasku ponad oknem edycji GUI albo używając skrótu klawiszowego CTRL + H.

Po ustawieniu layoutu zawartość okna powinna zmieniać rozmiar razem z nim:

Simple Drawing Application – prosta aplikacja QT5 znaleziona w Internecie

Znalazłem w Internecie całkiem schludnie napisaną a jednocześnie w miarę prostą aplikację w QT 5 z użyciem widgetów. Aplikacja ta korzysta z frameworka testów jednostkowych Catch. Autor inspirował się książką Roberta C. Martina – Agile Software Development, Principles, Patterns, and Practices. Osobiście czytałem inną książkę Roberta C. Martina – Czysty kod, którą przy okazji mogę polecić gdyż w prosty sposób przedstawia jak pisać czytelny i łatwy do zrozumienia kod.
Link do aplikacji na GitHubie:
https://github.com/bruceoutdoors/DrawingApp

Stworzenie projektu, commit i push na githuba

Nadszedł czas aby skupić się na kodzie programu i zapełnić święcące dotychczas pustkami repozytorium projektu: Obrazki logiczne. Poniżej przedstawiam krok po kroku tworzenie nowego projektu w Qt Creatorze i umieszczanie go na githubie.

Krok po kroku

W Qt Creatorze stworzyłem nowy projekt jako Qt Widgets Application i od razu dodałem do gita. W karcie „projects” odznaczyłem „Shadow build” dla wszystkich konfiguracji po to aby mieć wszystkie pliki wynikowe w podkatalogach debug i release w głównym katalogu projektu. Do akcji związanych z gitem używam Git Gui (Qt Creator → Tools → Git → Git Tools → Git Gui). Przy pierwszym uruchomieniu Git Gui trzeba go skonfigurować. Wchodzimy w Edit → Options… i podajemy User Name oraz Email Address – nazwa użytkownika i e-mail konta na githubie. Ustawiamy kodowanie znaków na UTF-8, resztę można pozostawić bez zmian. Klikamy Save i wracamy do ekranu głównego. Jeśli w oknie Unstaged changes pojawiły się jakieś pliki, których nie chcemy zamieszczać w repozytorium (np. pliki generowane przez IDE lub kompilator) to musimy stworzyć plik .gitignore w głównym katalogu projektu. W pliku .gitignore będą znajdowały się wyrażenia regularne określające pliki wykluczone z repozytorium. Zawartość pliku .gitignore dla projektu stworzonego w Qt:

/debug
/release
*.user
*.stash
Makefile
Makefile.Debug
Makefile.Release
ui_*.h

Po stworzeniu i uzupełnieniu pliku .gitignore wracamy do Git Gui. Po kliknięciu Rescan niechciane pliki powinny zniknąć. Teraz w oknie Unstaged changes powinny znajdować się jedynie pliki źródłowe i główny plik projektu. Kliknięcie Stage Changed przenosi pliki do okna Staged Changes, które zawiera wszystkie pliki, które zostaną dodane do przyszłego commitu. Wypełniamy opis zmian i klikamy commit, który dodaje pliki do lokalnego repozytorium. Zaznaczenie opcji Ammend Last Commit pozwala na modyfikację ostatniego commitu – dzięki temu można uniknąć niepotrzebnego rozrostu listy edycji, w sytuacji gdy zapomnieliśmy dodać jakiegoś pliku lub źle opisaliśmy poprzedni commit. Jednak warto być tutaj ostrożnym gdyż komenda ammend zastępuje całkowicie poprzedni commit bez śladu w historii zmian. Powinno się unikać komendy Ammend w publicznych repozytoriach.

Przed pierwszym wysłaniem kodu do githuba trzeba skonfigurować zdalne repozytorium. W Git Gui klikamy Remote → Add. W polu Name wpisujemy nazwę repozytorium np. „nonograms”, a w polu Location adres repozytorium „https://github.com/ololuki/nonograms”. Po kliknięciu Add możemy wykonać pierwszy „Push”. Po kliknięciu przycisku Push w oknie Push, trzeba będzie podać nazwę użytkownika i hasło z githuba. Jeśli wszystko poszło jak trzeba nasz kod pojawi się na githubie.

Możliwe problemy

Jeśli używamy oprogramowania SPICE to mogą pojawić się problemy z lokalizacją plików ustawień gita. Domyślnie znajdują się one w katalogu domowym użytkownika ~/ czyli w Windowsie w C:\Users\username\ natomiast SPICE (orCAD) wprowadza zmienną środowiskową HOME z dziwną ścieżką, na przykład C:\Users\LUK\AppData\Roaming\SPB_16.6 więc po zainstalowaniu orCADa pliki ustawień gita a także kilku innych programów mogą trafić do tego śmiesznego katalogu. Aby to naprawić wystarczy przenieść zawartość katalogu znajdującego się pod zmienną środowiskową HOME do naszego katalogu domowego (C:\Users\username\) a następnie usunąć zmienną środowiskową HOME z systemu. Po wszystkim konieczne jest ponowne uruchomienie komputera.