Archiwum autora: ololukiXP

Jenkins – wyciek pamięci z dysku twardego (winstone .jar) – jak naprawić

Ostatnio zauważyłem, że kończy mi się miejsce na dysku C i przeglądając katalogi tymczasowe zauważyłem sporo plików typu winstoneXXXXXX.jar. Szperając w internecie natrafiłem na buga w Jenkinsie, niestety nie jest on domyślnie poprawiony w instalatorze tylko trzeba ręcznie zaaplikować obejście. Polega na uruchomieniu usługi (service) jenkins.exe z dodatkowym parametrem:

--extractedFilesFolder="c:\TMP-FOLDER-PATH"

Parametr ten należy dodać w pliku jenkins.xml, który znajduje się w tym samym katalogu co jenkins.exe z którego uruchamiana jest usługa.
<arguments>
  --extractedFilesFolder="c:\TMP-FOLDER-PATH"
</arguments>

<arguments>
  --extractedFilesFolder="C:\Windows\Temp"
</arguments>

Więcej o dodawaniu argumentów

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

Otwieranie plików bez rozszerzenia w Notepad++ [Windows]

Programy domyślne w Windowsie są skojarzone z rozszerzeniem pliku i można je dowolnie edytować. Problem pojawia się gdy mamy do czynienia z plikiem bez rozszerzenia, wtedy Windows za każdym razem wyświetla okno do wybrania programu, którym chcemy otworzyć dany plik. Jak pozbyć się tego zbędnego okienka i ustawić program domyślny dla plików bez rozszerzeń? Najprościej dodając następujące wpisy do rejestru:

Windows Registry Editor Version 5.00
[HKEY_CLASSES_ROOT\.]
@="No Extension"
[HKEY_CLASSES_ROOT\No Extension]
[HKEY_CLASSES_ROOT\No Extension\Shell]
[HKEY_CLASSES_ROOT\No Extension\Shell\Open]
[HKEY_CLASSES_ROOT\No Extension\Shell\Open\Command]
@="C:\\Program Files (x86)\\Notepad++\\notepad++.exe %1"

Wystarczy zapisać powyższe linie do pliku tekstowego, zmienić rozszerzenie na .reg i uruchomić.

Źródło

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
...

Podsumowanie konkursu daj się poznać 2017.

Konkurs daj się poznać dobiegł końca. Celem konkursu było rozwijanie projektu programistycznego i pisanie bloga na ten temat. Aplikacja, którą pisałem to obrazki logiczne.

GitHub

W przygotowywanej przeze mnie aplikacji udało się zaimplementować edytor pola rysunkowego i liczb opisujących. Jest też możliwość zapisu i odczytu pola rysunkowego do i z plików w formacie Json. Przy implementacji obsługi plików inspirowałem się tym programem: Simple Drawing App.

Liczba postów oraz commitów rosła wykładniczo w stosunku do mijającego czasu. Może to wynikać z tego, że na początku walczy się z prostymi, ale jednak nieznanymi problemami. Robienie czegoś pierwszy raz zajmuje najwięcej czasu. Dodatkowo można wejść w ślepy zaułek jakiegoś zagadnienia i zmarnować tam trochę czasu – tak było z moją przygodą z wxWidgets. Początkowo chciałem wykorzystać właśnie tę bibliotekę, bo już coś tam miałem w niej napisane, bo licencja trochę bardziej liberalna, bo kontrolki bardziej natywne niż w Qt, ale stary kod nie bardzo chciał mi działać na nowej wersji biblioteki i w końcu zdecydowałem się na użycie Qt i Qt Creatora. Pakiet Qt zadziałał praktycznie od razu, pomijając drobne komplikacji z instalacją debugera. Innym problemem, która zmarnował trochę mojego cennego czasu było zamknięcie dotychczasowego serwera hostingowego, przez co chcąc nie chcąc musiałem walczyć z przenosinami bloga.

Dokumentowanie projektu oraz opisywanie problemów i ich rozwiązań może być przydatne w przyszłości, kiedy będziemy chcieli coś zrobić drugi raz, albo ponownie skonfigurować środowisko po reinstalacji systemu. Wpis na blogu może być wtedy bardzo pomocną ściągą. Tym bardziej, że trudniej go zgubić niż jakieś notatki na kartkach czy w plikach na dysku. Po drugie taki publiczny wpis jednak musi reprezentować sobą jakieś minimum estetyki i być zrozumiały dla czytelników, więc dużo łatwiej będzie w przyszłości odświeżyć sobie wiedzę o jakimś zagadnieniu z blogowego wpisu niż z nabazgranej na szybko karteczki.

Generalnie warto było wziąć udział w konkursie. Uczestnictwo motywowało do mniej lub bardziej systematycznej pracy. Konkurs się kończy ale doświadczenie i wartościowe wpisy na blogu pozostają.

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

Diamentowy problem i dziedziczenie wirtualne

Język C++ oferuje możliwość dziedziczenia wielokrotnego ze wszystkimi wadami i zaletami tego rozwiązania. W przypadku dziedziczenia po kilku klasach mających wspólnego przodka może pojawić się problem z diamentem.

Błąd „base class is ambiguous” świadczy o tym, że dwie klasy, po których dziedziczy nasza „najmłodsza klasa” są potomkami wspólnego przodka. Jeśli obie te klasy dziedziczą po tym przodku niewirtualnie to otrzymamy wspomniany wcześniej błąd, ponieważ klasa dziadka zostanie dodana dwa razy, więc będzie zduplikowana. Rozwiązaniem problemu będzie zastosowane dziedziczenia wirtualnego w naszych dwóch klasach.

W moim projekcie dziedziczenie wirtualne zostało zastosowane przy dziedziczeniu podstawowej klasy pola – RootField. Klasa RootField przechowuje tylko główne, wspólne parametry pola takie jak szerokość i wysokość. Następnie mamy dwie klasy po niej dziedziczące – DrawingAreaField oraz BlocksDescriptionField. Jeden dostarcza metody dla obszaru rysowania (DrawingArea) a drugi dla opisów bloków (RowsDescription i ColumnsDescription). Klasa WholeField łączy te elementy w całość i pozwala odwoływać się do pola roboczego jako całości, np. z poziomu solvera.

class RootField : public QObject
{
	Q_OBJECT
public:
	RootField();
	RootField(size_t width, size_t height) : width(width), height(height) {}
	size_t getWidth() {return width;}
	size_t getHeight() {return height;}
signals:
	void dataChanged();
private:
	size_t width;
	size_t height;
};


class DrawingAreaField : public virtual RootField
{
public:
	virtual Pixel getPixel(AddressOnDrawingArea address) = 0;
	virtual void setPixel(Pixel pixel) = 0;
};


class BlocksDescriptionField : public virtual RootField
{
public:
	virtual BlockDescription getBlockDescription(AddressOnBlocksDescription address) = 0;
	virtual void updateBlockDescription(BlockDescription blockDescription) = 0;
	virtual void insertDescriptionBefore(BlockDescription blockDescription) = 0;
	virtual void addDescriptionAtEnd(BlockDescription blockDescription) = 0;
	virtual void deleteDescription(BlockDescription blockDescription) = 0;
	virtual size_t numberOfBlocksInColumn(size_t columnNumber) = 0;
	virtual size_t columnsDescriptionHeight() = 0;
	virtual bool isDefinedColumnDescriptionAt(size_t line, size_t count) = 0;
};


class WholeField : public DrawingAreaField, public BlocksDescriptionField, virtual public RootField
{
};


class WholeFieldImpl : public WholeField
{
public:
	WholeFieldImpl(size_t width, size_t height) : RootField(width, height)
	{
		array = ArrayOfPixels(width, height);
		columnsDescription = AllLinesDescription(AddressOnBlocksDescription::orientation::VERTICAL, width);
	}
	virtual Pixel getPixel(AddressOnDrawingArea address) override;
	virtual void setPixel(Pixel pixel) override;
	virtual BlockDescription getBlockDescription(AddressOnBlocksDescription address) override;
	virtual void updateBlockDescription(BlockDescription blockDescription) override;
	virtual void insertDescriptionBefore(BlockDescription blockDescription) override;
	virtual void addDescriptionAtEnd(BlockDescription blockDescription) override;
	virtual void deleteDescription(BlockDescription blockDescription) override;
	virtual size_t numberOfBlocksInColumn(size_t columnNumber) override;
	virtual size_t columnsDescriptionHeight() override;
	virtual bool isDefinedColumnDescriptionAt(size_t line, size_t count) override;
private:
	ArrayOfPixels array;
	AllLinesDescription columnsDescription;
};

Kolejnym problemem okazało się wywoływanie konstruktorów parametrycznych z klasy bazowej (dziedziczonej wirtualnie) za pomocą listy inicjalizacyjnej. Okazało się, że wywołanie konstruktora klasy bazowej (dziedziczonej wirtualnie) za pomocą listy inicjalizacyjnej musi znajdować się w najmłodszej klasie – w tej, której obiekt zostanie utworzony za pomocą konstruktora parametrycznego. W sytuacji gdy mamy hierarchię klas, np:

class A {
	int i;
public:
	A(int k) {}
};

class B : virtual public A {
public:
	B(int i) : A(i) {}
};

class C :   public B {
public:
	//C(int i) : B(i) {} // zostanie wywołany tylko konstruktor klasy B, konstruktor A(int k) nie zostanie wywołany, gdyż klasa A jest wirtualnie dziedziczona, zostanie wywołany konstruktor domyślny klasy A
	C(int i) : B(i), A(i) {}	// zostanę wywołane oba konstruktory parametryczne
};

Linki na temat:
multiple-inheritance-from-two-derived-classes
how-can-i-avoid-the-diamond-of-death-when-using-multiple-inheritance
Multiple_inheritance#The_diamond_problem
Virtual_inheritance

Wywoływanie konstruktorów z klasy wirtualnie dziedziczonej za pomocą listy inicjalizacyjnej.
order-of-constructor-call-in-virtual-inheritance

Qt Creator – „Error while saving file: Cannot write file … Disk full?” – rozwiązanie

Czasami podczas próby skompilowania i uruchomienia programu w Qt Creatorze pojawia się błąd „Error while saving file: Cannot write file … Disk full?”. Jest to objaw błędu w Clang Code Model. Pomaga wtedy zabicie procesu clangbackend.exe. Czasami też pojawiają się dziwne pliki w katalogu z plikami źródłowymi o rozszerzeniach złożonych z losowych znaków, np. *.zA5996 – trzeba je usunąć, bo mogą powodować problemy przy kompilacji.