https://community.kde.org/api.php?action=feedcontributions&user=Gjditchfield&feedformat=atomKDE Community Wiki - User contributions [en]2024-03-28T08:52:15ZUser contributionsMediaWiki 1.40.2https://community.kde.org/index.php?title=Policies/Library_Code_Policy&diff=94328Policies/Library Code Policy2022-08-13T18:24:27Z<p>Gjditchfield: /* Deprecation of API */</p>
<hr />
<div>This document describes some of the recommended conventions that should be applied in the KDE libraries (not applications). Respecting these guidelines helps create a consistent API and also may help ease maintainence of the libraries later. While these conventions are not mandatory, they are important guidelines, and should be respected unless you have a good reason to disregard them.<br />
<br />
As an introduction, you should read the document [http://wiki.qt-project.org/index.php?title=API_Design_Principles Qt-Style C++ API Design Principles].<br />
<br />
For KDE Frameworks, it is recommended to follow the [[Policies/Frameworks_Coding_Style | KDE Frameworks Coding Style]].<br />
<br />
== Naming Conventions ==<br />
In KDE, we basically follow the same naming conventions as Qt.<br />
<br />
Class names start with a capital K. The rest is in camel case. Function names start with a lower case, but the first letter of each successive word is capitalized.<br />
<br />
Unless dealing with central libraries (kdecore, kdeui), classes should be in the library namespace. In that case, it is the namespace which starts with K and the classes inside may not start with it. New libraries should choose their namespace.<br />
<br />
The prefix 'set' is used for setters, but the prefix ''''get'''' is not used for accessors. Accessors are simply named with the name of the property they access. The exception is for accessors of a boolean which may start with the prefix ''''is''''.<br />
<br />
Acronyms are lowercased too. Example:<br />
<tt>KUrl</tt> instead of <tt>KURL</tt> and <tt>isNssEnabled()</tt> instead of <tt>isNSSEnabled()</tt><br />
<br />
Accessors should usually be <tt>const</tt>.<br />
<br />
This example shows some possible functions names<br />
<source lang="cpp-qt"><br />
public:<br />
void setColor(const QColor& c);<br />
QColor color() const;<br />
void setDirty(bool b);<br />
bool isDirty() const;<br />
<br />
private Q_SLOTS:<br />
void slotParentChanged();<br />
</source><br />
Make one public class for every .h file. Add the <tt>_EXPORT</tt> macro related to the library they are in.<br />
Private classes should be declared in the .cpp file, or in a _p.h file.<br />
<br />
== D-Pointers ==<br />
In order to more easily maintain binary compatibility, there shouldn't be private members in a public class. For more information about binary compatibility, read [[Policies/Binary_Compatibility_Issues_With_C++|Binary Compatibility Issues With C++]].<br />
<br />
By convention, the private class will be named the same as the public class, with <tt>Private</tt> appended to the name.<br />
<source lang="cpp-qt"><br />
class KFooPrivate;<br />
class KFoo<br />
{<br />
public:<br />
/* public members */<br />
private:<br />
const QScopedPointer<KFooPrivate> d;<br />
};<br />
</source><br />
In the .cpp file:<br />
<source lang="cpp-qt"><br />
class KFooPrivate<br />
{<br />
public:<br />
int someInteger;<br />
};<br />
<br />
KFoo::KFoo() : d(new KFooPrivate)<br />
{<br />
/* ... */<br />
}<br />
<br />
KFoo::~KFoo()<br />
{ <br />
// You must define a non-inline destructor in the .cpp file, even if it is empty<br />
// else, a default one will be built in a place where KFooPrivate is only forward<br />
// declare, leading to an error in the destructor of QScopedPointer<br />
}<br />
</source><br />
Notice that the member d is <tt>const</tt> to avoid modifying it by mistake. This example also uses {{qt|QScopedPointer}} for the d-pointer. It's not a requirement, you can use just your private class directly, but then you need to take care of deleting it properly in the destructor. {{qt|QScopedPointer}} does that for you automatically, see the docs for more details.<br />
<br />
If you are implementing an implicitly shared class, you should consider using {{qt|QSharedData}} and {{qt|QSharedDataPointer}} for d. if you do, remember you need to implement explicit constructors, destructors and assignment operators (operator=).<br />
<br />
Sometimes, complex code may be moved to a member method of the Private class itself. Doing this may give the compiler an extra register to optimize the code, since you won't be using "d" all the time. Also, remember to '''inline''' such methods if they are called only from one place.<br />
<br />
=== Shared D-Pointers ===<br />
<br />
If your class hierarchy is large and/or deep, you may want to try the concept of shared d-pointers. You'll be trading the added complexity for a smaller memory footprint in the main object (there will be only one "d" variable in it). Other advantages include:<br />
<br />
:* direct access to the private data of the whole hierarchy (in other words, the Private classes are in fact "protected", not "private")<br />
:* access to the parent's d-pointer methods<br />
<br />
The latter advantage is especially useful if your class has moved the code from the main class to the Private class. If that's the case, you should be calling the Private methods instead: since they are not exported, they will create simpler relocations in the final library (or none at all). By simply calling the Private method instead of the public one, you contribute to a faster load-time of your library.<br />
<br />
To implement a "shared d-pointer", you need to:<br />
:# define a '''protected''' variable (d_ptr) in the least derived class of your hierarchy<br />
:# in each class of the hierarchy, define a '''private''' function called d_func() that reinterpret_casts that d_ptr to the current class's Private class<br />
:# use Q_D(Foo) at the beginning of the functions to have access to a variable "d"<br />
:# the private classes derive from one another just like the public hierarchy; they also have virtual destructors<br />
:# add one extra, protected constructor that takes the private class as a parameter<br />
:# in each constructor for all derived classes, call the parent's constructor that takes the d pointer as a parameter<br />
<br />
There's an example of such a construct in a [https://techbase.kde.org/Policies/Library_Code_Policy/Shared_D-Pointer_Example separate page].<br />
<br />
=== Q_DECLARE_PRIVATE ===<br />
<br />
This is a handy macro that hides the ugly stuff for you. It creates the <tt>d_func()</tt> function for you, using the variable called <tt>d_ptr</tt>. If yours has that name, you can use this macro. If it has another name, maybe you should create a macro to make your code look nicer.<br />
<br />
=== Q-Pointers ===<br />
<br />
Q-pointers are like d-pointers, but work in the reverse direction: they are in the Private class and they point to the public class. Needless to say, this is only possible for classes that don't share their d-pointers. Examples of classes that might benefit from q-pointers are all those derived from QObject, while classes with implicit sharing are those that potentially can't use it.<br />
<br />
Q-pointers are especially useful if your class has moved most of the code to the Private class as recommended. In that case, you may need to emit signals from the Private class. You would do it as:<br />
<br />
<source lang="cpp-qt"><br />
emit q->signalName();<br />
</source><br />
(You need to declare the Private class a friend of your public one; Q_DECLARE_PRIVATE does that for you)<br />
<br />
Q-pointers may also use the shared q-pointer technique just like [[#Shared D-Pointers|d-pointers]] can. What's more, Qt also provides a macro called <tt>Q_DECLARE_PUBLIC</tt> and one <tt>Q_Q</tt> to hide the ugly parts of the implementation.<br />
<br />
== Inline Code ==<br />
For binary compatibility reasons, try to avoid inline code in headers. Specifically no inline constructor or destructor.<br />
<br />
If ever you add inline code please note the following:<br />
* Installed headers should compile with the following preprocessor defines: <tt>QT_NO_CAST_FROM_ASCII</tt>, <tt>QT_NO_CAST_TO_ASCII</tt>, <tt>QT_NO_KEYWORD</tt>. So don't forget {{qt|QLatin1String}}.<br />
* No C casts in the header. Use <tt>static_cast</tt> if types are known. Use <tt>qobject_cast</tt> instead of <tt>dynamic_cast</tt> if types are QObject based. dynamic_cast is not only slower, but is also unreliable across shared libraries.<br />
* In general, check your code for [https://techbase.kde.org/Development/Tutorials/Common_Programming_Mistakes common mistakes].<br />
<br />
These recommendations are also true for code that is not in headers.<br />
<br />
== Flags ==<br />
Try to avoid meaningless boolean parameters in functions. Example of a bad boolean argument:<br />
<source lang="cpp-qt"><br />
static QString KApplication::makeStdCaption( const QString &caption,<br />
bool withAppName,<br />
bool modified);<br />
</source><br />
<br />
Because when you read code that uses the above function, you can't easily know the significance of the parameters<br />
<br />
<source lang="cpp-qt"><br />
window->setCaption(KApplication::makeStdCaption( "Document Foo",<br />
true, true));<br />
</source><br />
<br />
The solution is to use {{qt|QFlags}}. If the options only apply to one function, call the <tt>enum FunctionNameOption</tt> and the QFlags typedef <tt>FunctionNameOptions</tt>. Do that even if there is only one option, this will allow you to add more options later and keep the binary compatibility.<br />
<br />
So a better API would be:<br />
<source lang="cpp-qt"><br />
class KApplication<br />
{<br />
public:<br />
/* [...] */<br />
enum StandardCaptionOption {<br />
/**<br />
* Indicates to include the application name<br />
*/<br />
WithApplicationName = 0x01,<br />
/**<br />
* Note in the caption that there is unsaved data<br />
*/<br />
Modified = 0x02<br />
};<br />
Q_DECLARE_FLAGS(StandardCaptionOptions, <br />
StandardCaptionOption)<br />
<br />
/**<br />
* Builds a caption using a standard layout.<br />
*<br />
* @param userCaption The caption string you want to display<br />
* @param options a set of flags from MakeStandartCaptionOption<br />
*/<br />
static QString makeStandardCaption(const QString& userCaption,<br />
const StandardCaptionOptions& options = WithApplicationName);<br />
/* [...] */<br />
};<br />
Q_DECLARE_OPERATORS_FOR_FLAGS(KApplication::StandardCaptionOptions)<br />
</source><br />
<br />
== Const References ==<br />
Each object parameter that is not a basic type (int, float, bool, enum, or pointers) should be passed by reference-to-const. This is faster, because it is not required to do a copy of the object. Do that even for objects that are already implicitly shared, like QString:<br />
<br />
<source lang="cpp-qt"><br />
QString myMethod( const QString& foo,<br />
const QPixmap& bar,<br />
int number );<br />
</source><br />
<br />
'''Note:''' Avoid const references for return types though. Returning for example<br />
<source lang="cpp-qt"><br />
const QList<int> &someProperty() const;<br />
</source><br />
means exposing the internal data structure for someProperty() and it's very difficult to change it in the future while preserving binary compatibility. Especially for implicitly shared objects the one refcount that one avoids by returning a const reference is often not worth it the exposure of implementation.<br />
<br />
There are cases where it makes sense, where performance is absolutely critical and the implementation is very fixed. So think twice about it and consider returning a value instead:<br />
<source lang="cpp-qt"><br />
QList<int> someProperty() const;<br />
</source><br />
<br />
== Signals and Slots ==<br />
In the libraries, use <tt>Q_SIGNALS</tt> and <tt>Q_SLOTS</tt> instead of <tt>signals</tt> and <tt>slots</tt>. They are syntactically equivalent and should be used to avoid conflicts with boost signals, and with python's use of "slots" in its headers.<br />
<br />
== Properties ==<br />
Consider using <tt>Q_PROPERTY</tt> for properties. The reason is that properties (especially those marked <tt>SCRIPTABLE</tt>) will be accessible through the javascript interface.<br />
<br />
If you follow the propname / setPropname naming scheme, moc sets a special flag for the {{qt|QMetaProperty}}.<br />
<br />
== Explicit Constructors ==<br />
For each constructor (other than the copy constructor), check if you should make the constructor <tt>explicit</tt> in order to minimize wrong use of the constructor.<br />
<br />
Basically, each constructor that may take only one argument should be marked <tt>explicit</tt> unless the whole point of the constructor is to allow implicit casting.<br />
<br />
== Avoid including other headers in headers ==<br />
Try to reduce as much as possible the number of includes in header files. This will generally help reduce the compilation time, especially for developers when just one header has been modified. It may also avoid errors that can be caused by conflicts between headers.<br />
<br />
If an object in the class is only used by pointer or by reference, it is not required to include the header for that object. Instead, just add a forward declaration before the class.<br />
<br />
In this example, the class KFoo uses KBar by reference, so we do not need to include KBar's header:<br />
<source lang="cpp-qt"><br />
#include <kfoobase.h><br />
class KBar;<br />
class KFoo : public KFooBase<br />
{<br />
public:<br />
/* [...] */<br />
void myMethod(const KBar& bar);<br />
};<br />
</source><br />
<br />
== Getting #includes right ==<br />
<br />
There are two types of #include statements: <tt>#include <foo.h></tt> and <tt>#include "foo.h"</tt>.<br />
<br />
Say we have the file <tt>xyz.h</tt> in <tt>/usr/include/mylib/</tt> that contains the following:<br />
<source lang="cpp-qt"><br />
#include <header1.h><br />
#include "header2.h"<br />
</source><br />
<br />
The preprocessor will search for the file <tt>header1.h</tt> in all the paths given as <tt>-I</tt> arguments and then replace the line with the contents of that file.<br />
<br />
For line 2 the preprocessor tries to use the file /usr/include/mylib/header2.h first and if it does not exist search for the file like it did for <tt>header1.h</tt>. The important part to note here is that the preprocessor does not look in the directory of the source file that includes <tt>xyz.h</tt> but in the directory where <tt>xyz.h</tt> resides.<br />
<br />
Now, which include statement is the one to use? After all you can specify every directory you want using <tt>-I</tt> (or rather CMake's <tt>include_directories()</tt>) and thus could use <tt>#include <...></tt> everywhere.<br />
<br />
=== As application developer ===<br />
* Include headers from '''external''' libraries using '''angle brackets'''.<br />
<source lang="cpp-qt"><br />
#include <iostream><br />
#include <QDate><br />
#include <zlib.h><br />
</source><br />
* Include headers from your '''own project''' using '''double quotes'''.<br />
<source lang="cpp-qt"><br />
#include "myclass.h"<br />
</source><br />
Rationale: ''The header files of external libraries are obviously not in the same directory as your source files. So you need to use angle brackets.''<br />
<br />
''Headers of your own application have a defined relative location to the source files of your application. Using KDE4's cmake macros your source directory is the first include switch to the compiler and therefore there's no difference in using angle brackets or double quotes. If you work with a different buildsystem that does not include the current source directory or disable CMAKE_INCLUDE_CURRENT_DIR then all includes (inside your application) using angle brackets will break.''<br />
<br />
''Ideally the buildsystem would not need to specify <tt>-I&lt;source directory&gt;</tt> though as that can break with library headers that have the same filename as a header of your project (i.e.: If a library has the header file <tt>foo.h</tt> and your project has a different file with the same filename the compiler will always pick the header from your project instead of the one from the library because the source directory of the project is specified first.)''<br />
<br />
=== As library developer ===<br />
* Include headers from '''external''' libraries using '''angle brackets'''.<br />
<source lang="cpp-qt"><br />
#include <iostream><br />
#include <QDate><br />
#include <zlib.h><br />
</source><br />
* Include headers of your '''own library''' and libraries that belong to it using '''double quotes'''.<br />
<source lang="cpp-qt"><br />
#include "xyz.h" // same library and same directory<br />
</source><br />
<br />
Rationale: ''The header files of external libraries are obviously not in a fixed location relative to your source files. So you need to use angle brackets.''<br />
<br />
''Headers of your own libraries have a fixed relative location in the filesystem. Therefore you'' can ''use double quotes. You should use double quotes because otherwise the include statement could include a different header file than expected. An example how angle brackets can break the build:''<br />
<br />
''<tt>/usr/include/libxyz/xyz.h</tt> includes <tt>foo.h</tt> using angle brackets and expects to have it replaced with the contents of the file <tt>/usr/include/libzyx/foo.h</tt>. Assuming there's another library that also ships a <tt>foo.h</tt> file in the directory <tt>/usr/include/anotherlib/</tt>. If the application that uses both libraries compiles with "<tt>g++ -I/usr/include/libxyz -I/usr/include/anotherlib ...</tt>" libxyz will work as expected. If the application compiles with "<tt>g++ -I/usr/include/anotherlib -I/usr/include/libxyz ...</tt>" the header <tt>xyz.h</tt> will include the file <tt>/usr/include/anotherlib/foo.h</tt> instead of the file that is shipped with libxyz. The same problem can appear if an application has a header file of the same name as a library and specifies <tt>-I./</tt> as the first include directory.''<br />
<br />
=== Qt includes ===<br />
<br />
Always include the Qt forwarding class header without it's module as in :<br />
<source lang="cpp-qt"><br />
#include <QDate> //correct<br />
</source><br />
<br />
<source lang="cpp-qt"><br />
#include <QtCore/QDate> //incorrect, no need to specify the module QtCore<br />
#include <QtCore> //incorrect, do not include the top-level Qt module<br />
</source><br />
<br />
=== Include order ===<br />
<br />
Another important aspect of include management is the include order. Typically, you have a class named Foo, a file foo.h and a file foo.cpp . The rule is :<br />
<br />
: ''In your file foo.cpp, you should include "foo.h" as the first include, before the system includes.''<br />
<br />
The rationale behind that is to make your header standalone. <br />
<br />
Let's imagine that your foo.h looks like this:<br />
<source lang="cpp-qt"><br />
class Foo<br />
{<br />
public:<br />
Bar getBar();<br />
<br />
};<br />
</source><br />
<br />
And your foo.cpp looks like this:<br />
<source lang="cpp-qt"><br />
#include "bar.h"<br />
#include "foo.h"<br />
</source><br />
<br />
Your foo.cpp file will compile, but it will not compile for other people using foo.h without including bar.h . Including "foo.h" first makes sure that your foo.h header works for others.<br />
<br />
=== Include guards ===<br />
Header files should use guards to protect against possible multiple inclusion.<br />
Your myfoo.h header should look like this:<br />
<source lang="cpp-qt"><br />
#ifndef MYFOO_H<br />
#define MYFOO_H<br />
... <stuff>...<br />
#endif /* MYFOO_H */<br />
</source><br />
<br />
To be even more careful, you may want to encode a namespace or subdirectory name (e.g. KFOO) into the guard macro name, for example: MYFOO_H, KFOO_MYFOO_H, or _KFOO_MYFOO_H_ are all acceptable macro names. By convention, the macro name should be all uppercase; but that is not a firm requirement.<br />
<br />
== Static Objects ==<br />
Global static objects in libraries should be avoided. You never know when the constructor will be run or if it will be run at all.<br />
; Wrong<br />
<source lang="cpp-qt"><br />
static QString foo; // wrong - object might not be constructed<br />
static QString bar("hello"); // as above<br />
static int foo = myInitializer(); // myInitializer() might not be called<br />
</source><br />
In particular, <b>never</b> construct QObject-derived objects this way. On Windows (MS Visual C++) the object's internals will be left uninitialized within a library and will lead to crashes ([http://www.kdedevelopers.org/node/2889 more info]).<br />
<source lang="cpp-qt"><br />
static QFile myFile("abc"); // QFile inherits QObject<br />
</source><br />
Also, when using KUniqueApplication and using static objects that may query DBus during their initialization, the application can dead-lock.<br />
; Correct<br />
<source lang="cpp-qt"><br />
static const int i = 42;<br />
static const int ii[3] = {1, 2, 3};<br />
static const char myString[] = "hello";<br />
static const MyStruct s = {3, 4.4, "hello"};<br />
Q_GLOBAL_STATIC(QFile, myFile);<br />
</source><br />
<br />
You can use [https://doc.qt.io/qt-5/qglobalstatic.html <tt>Q_GLOBAL_STATIC</tt>] macro (and for QObject-derived objects you should) to create global static objects which will be initialized the first time you use them.<br />
<br />
== Signal and Slot Normalization ==<br />
Since <tt>QObject::connect</tt> uses a string-based comparison<br />
of the function signature, it requires some normalization to take<br />
place. It does that automatically for you, but it takes some CPU<br />
time, so, if it doesn't hurt your code's readability, normalize<br />
manually your SIGNAL and SLOT entries.<br />
<br />
For example, you may have the following code:<br />
<source lang="cpp-qt"><br />
QObject::connect(this, SIGNAL( newValue(const QString&,<br />
const MyNamespace::Type&) ),<br />
other, SLOT( value(const QString &) ));<br />
</source><br />
It would be preferable to write as follows:<br />
<source lang="cpp-qt"><br />
QObject::connect(this, SIGNAL(newValue(QString,MyNamespace::Type)),<br />
other, SLOT(value(QString)));<br />
</source><br />
<br />
Note the absence of extra whitespace and the<br />
reduction of pass-by-reference-to-const parameters to simple<br />
pass-by-value ones. The normalization may involve other<br />
transformations, but these are the most common ones. Also note that<br />
types in namespaces should always use the full qualified name.<br />
To be sure what the proper normalization is, read the {{path|.moc}} file<br />
generated for the class.<br />
<br />
'''Note''': If you are unsure about the normalization, don't do it. Let<br />
QObject do it for you (the performance penalty is negligible in most cases).<br />
<br />
== External Dependencies ==<br />
When a library pulls in a new external dependency due to a new feature or a change in implementation, it is highly preferred to make that dependency optional if at all possible. For some core features in a library this may not be possible, but usually it is even if the cost is a degradation in features when built without that dependency.<br />
<br />
All dependencies should be adequately documented in the build system so that they appear in the summary post-configure.<br />
<br />
For libraries in the '''kdelibs module''', this is a hard requirement: '''no new non-optional dependencies may be added, all new dependencies must be made optional at configure time'''. Exceptions may be granted for unusual circumstances by sending a request to the kde-core-devel at kde.org mailing list and getting a consensus decision to grant an exception there.<br />
<br />
== Documentation ==<br />
Every class and method should be well documented. Read the [[Policies/Library Documentation Policy|KDE Library Documentation Policy]] for the guidelines to follow when documenting your code.<br />
<br />
Also don't forget the license headers and copyrights in each file. As stated in the [[Policies/Licensing Policy|Licensing Policy]], kdelibs code must be licensed under the LGPL, BSD, or X11 license.<br />
<br />
== Deprecation of API ==<br />
<br />
If an item of the API is no longer recommended for use, usually with a better replacement, it gets tagged as such both in the API documentation as well for the compiler (with C++ and the current tools that data duplication is needed), noting the very version the item got deprecated and what recommended replacement there is, if.<br />
<br />
For example, you may have the following definition of a function which is published:<br />
<source lang="cpp-qt"><br />
#include <foo_export.h><br />
FOO_EXPORT void foo();<br />
</source><br />
<br />
Where "FOO_EXPORT" is the macro holding the visibility attribute, as defined in the included file "foo_export.h". That file usually is generated with a macro from CMake's [https://cmake.org/cmake/help/latest/module/GenerateExportHeader.html GenerateExportHeader] or KDE's enhanced variant of it, [https://api.kde.org/ecm/module/ECMGenerateExportHeader.html ECMGenerateExportHeader]<br />
<br />
When using export macro headers generated by ECMGenerateExportHeader, one would use the version-based macro "FOO_DEPRECATED_VERSION" and additionally the "FOO_ENABLE_DEPRECATED_SINCE" macro, like this (no combined macro available here, and duplication of data for the different tools also needed here):<br />
<source lang="cpp-qt"><br />
#include <foo_export.h><br />
#if FOO_ENABLE_DEPRECATED_SINCE(5, 0)<br />
/**<br />
* @deprecated Since 5.0. Use bar().<br />
*/<br />
FOO_EXPORT<br />
FOO_DEPRECATED_VERSION(5, 0, "Use bar()")<br />
void foo();<br />
#endif<br />
</source><br />
<br />
The "FOO_BUILD_DEPRECATED_SINCE" macro can control compilation of the<br />
implementation:<br />
<source lang="cpp-qt"><br />
#include "foo.h"<br />
#if FOO_BUILD_DEPRECATED_SINCE(5, 0)<br />
void foo()<br />
{<br />
}<br />
#endif<br />
</source><br />
<br />
{{Note|Keep any export macro before the deprecation macro or other attribute macros.}}<br />
{{Note|Make sure that the version at which the deprecation happened is listed with the <tt>DEPRECATION_VERSIONS</tt> argument of the <tt>ecm_generate_export_header</tt> call.}}<br />
<br />
The API documenation generation tool need some support to correctly handle these macros. With [https://api.kde.org/ecm/module/ECMAddQch.html ECMAddQch], the <tt>ecm_add_qch</tt> call should includes this:<br />
<source lang="cmake"><br />
ecm_add_qch(FOO_QCH<br />
# [...]<br />
INCLUDE_DIRS<br />
# [...]<br />
/path/to/dir/where/generated/export/header/is/found <br />
BLANK_MACROS<br />
# [...]<br />
"FOO_DEPRECATED_VERSION(x, y, t)"<br />
"FOO_DEPRECATED_VERSION_BELATED(x, y, xt, yt, t)"<br />
"FOO_ENUMERATOR_DEPRECATED_VERSION(x, y, t)"<br />
"FOO_ENUMERATOR_DEPRECATED_VERSION_BELATED(x, y, xt, yt, t)"<br />
)<br />
</source><br />
<br />
With [https://api.kde.org/frameworks/kapidox/html/index.html KApidox], the file "docs/Doxyfile.local" should have an entry like this:<br />
<source lang="doxygen"><br />
PREDEFINED += \<br />
"FOO_ENABLE_DEPRECATED_SINCE(x, y)=1" \<br />
"FOO_BUILD_DEPRECATED_SINCE(x, y)=1" \<br />
"FOO_DEPRECATED_VERSION(x, y, t)=" \<br />
"FOO_DEPRECATED_VERSION_BELATED(x, y, xt, yt, t)=" \<br />
"FOO_ENUMERATOR_DEPRECATED_VERSION(x, y, t)=" \<br />
"FOO_ENUMERATOR_DEPRECATED_VERSION_BELATED(x, y, xt, yt, t)="<br />
</source><br />
<br />
<br />
When using GenerateExportHeader, the export header file provides a macro "FOO_DEPRECATED" to do that for the compiler, as well as a convenience macro "FOO_EXPORT_DEPRECATED" which covers both visibility & deprecation attributes, for shorter code:<br />
<source lang="cpp-qt"><br />
#include <foo_export.h><br />
/**<br />
* @deprecated Since 5.0. Use bar().<br />
*/<br />
FOO_EXPORT_DEPRECATED void foo();<br />
</source><br />
<br />
Again the API documenation generation tool need some support. With ECMAddQch, the <tt>ecm_add_qch</tt> call should includes this:<br />
<source lang="cmake"><br />
ecm_add_qch(FOO_QCH<br />
# [...]<br />
INCLUDE_DIRS<br />
# [...]<br />
/path/to/dir/where/generated/export/header/is/found <br />
BLANK_MACROS<br />
FOO_EXPORT<br />
FOO_DEPRECATED<br />
FOO_DEPRECATED_EXPORT<br />
)<br />
</source><br />
<br />
With KApidox, the file "docs/Doxyfile.local" should have an entry like this:<br />
<source lang="doxygen"><br />
PREDEFINED += \<br />
"FOO_EXPORT=" \<br />
"FOO_DEPRECATED=" \<br />
"FOO_EXPORT_DEPRECATED="<br />
</source><br />
<br />
== <tt>auto</tt> Keyword ==<br />
<br />
Optionally, you can use the <tt>auto</tt> keyword in the following cases. If in doubt, for example if using <tt>auto</tt> could make the code less readable, do not use <tt>auto</tt>. Keep in mind that code is read much more often than written.<br />
<br />
* When it avoids repetition of a type in the same statement.<br />
<br />
<source lang="cpp-qt"><br />
auto something = new MyCustomType;<br />
auto keyEvent = static_cast<QKeyEvent *>(event);<br />
auto myList = QStringList({ "FooThing", "BarThing" });<br />
</source><br />
<br />
* When assigning iterator types.<br />
<br />
<source lang="cpp-qt"><br />
auto it = myList.const_iterator();<br />
</source><br />
<br />
Author: [mailto:ogoffart@kde.org Olivier Goffart] March 2006<br />
<br />
[[Category:Policies]]</div>Gjditchfieldhttps://community.kde.org/index.php?title=Infrastructure/GitLab&diff=93952Infrastructure/GitLab2022-04-26T14:32:10Z<p>Gjditchfield: /* Switching the target branch of a Merge Request */</p>
<hr />
<div>KDE uses a GitLab instance at https://invent.kde.org for code review (as well as hosting and other important collaboration tasks). This page is intended to serve as a general-purpose introduction to the most important aspects: submitting and reviewing Merge Requests.<br />
<br />
{{Info|The KDE community does not generally use GitLab for bug reporting. Please continue to submit bug reports on https://bugs.kde.org. Task management is transitioning to Invent/GitLab but most projects still use https://phabricator.kde.org for now.}}<br />
<br />
<br />
== Workflow ==<br />
The sanest and easiest way to submit code to KDE is by following a typical '''feature branch workflow''': keep your master branch synchronized with the origin repository, and make all changes on separate branches. '''Each Merge Request needs its own private, temporary branch.''' Once your Merge Request has been merged, delete the feature branch, and make another new branch for the next Merge Request. In this way, you can be working on multiple changes at once without them colliding with one another because each one lives on its own branch.<br />
<br />
<br />
== Logging in ==<br />
Navigate to https://invent.kde.org/users/sign_in and log in using the username and password for your KDE Identity account. If you don't have one, you can [https://identity.kde.org/index.php?r=registration/index sign up for one here].<br />
<br />
=== Setting up git ===<br />
<br />
You will need to set up git to use you account details to help identify your work: <br />
<br />
<syntaxhighlight lang="bash"><br />
git config user.name <Your Real Name><br />
git config user.email <Your identity.kde.org email><br />
</syntaxhighlight><br />
<br />
(You can set it up globally with --global)<br />
<br />
In order to authenticate yourself when pushing code changes, you need to add a ssh key to your GitLab profile as [https://invent.kde.org/help/ssh/README described here].<br />
<br />
=== Setting up your account email address ===<br />
<br />
By default your GitLab account will inherit your email addresses from KDE Identity, and allow you to choose which address is used for commits made by GitLab on your behalf and where notifications are sent to. Please note that the 'users.noreply.invent.kde.org' address should not be used as it will cause certain functions to stop working.<br />
<br />
== Submitting a Merge Request ==<br />
Contributing to KDE code using GitLab involves '''submitting a Merge Request'''. A Merge Request is a request to merge some of your code into the project's permanent source code repo. Here's how:<br />
<br />
=== Build the project from source and make your change ===<br />
First you need to check out the project, compile it from source, and make some changes that you would like to submit to KDE! Instructions for doing this can be found at [[Get Involved/development]]. You will wind up with a checkout of the project at <tt>~/kde/src/[the project name]</tt> with some local changes applied to it.<br />
<br />
{{Info|If you prefer a different organizational structure for source code on your machine, you can of course check out the local copy of your KDE repos wherever you want. However for the purposes of this documentation, we will assume that they are located inside <tt>~/kde/src/</tt>}}<br />
<br />
=== Fork the project ===<br />
{{Note|If you have commit access, you can prefix your branches with "<tt>work/</tt>" and push them directly to origin without having to fork, and safely ignore this section.}}<br />
<br />
Once you have made some local changes that you would like to submit to KDE, you need to create a personal fork of the project and push your changes to the forked copy.<br />
<br />
Navigate to https://invent.kde.org/kde and locate the project. If it is not visible in the list, you can use the search field. Once you find the project, click on it:<br />
<br />
[[File:Find_the_project.png|600px]]<br />
<br />
On the project's page, click on the "Fork" button in the top-right corner of the screen:<br />
<br />
[[File:Click_the_Fork_button.png|300px]]<br />
<br />
This will take you to a page asking you some details about how you want to fork the project. Select your own name in the "Project URL" section and "Public" in the "Visibility" section:<br />
<br />
[[File:Choose_the_fork_namespace.png|700px]]<br />
<br />
After a moment, the system will finish creating the fork and take you to the page for your fork. On that page, click on the blue "Clone" button in the top-right corner:<br />
<br />
[[File:Click_the_Clone_button.png|300px]]<br />
<br />
In the pop-up that appears, click on the "copy" button to the right of the upper text field. This will copy the URL for the fork onto your clipboard.<br />
<br />
[[File:Copy_the_URL.png|300px]]<br />
<br />
=== Add the fork to your source checkout ===<br />
Next, open your terminal app and navigate to the location where the project's repo lives (i.e. <tt>~/kde/src/[the project name]</tt>).<br />
<br />
You need to add your fork as a '''remote''' to the existing repo:<br />
<br />
{{Input|1=<nowiki><br />
git remote add fork [the URL you copied to your clipboard]<br />
</nowiki>}}<br />
<br />
Run <tt>git remote -v</tt>. You should see something like this:<br />
<br />
<pre><br />
$ git remote -v<br />
fork git@invent.kde.org:ngraham/kid3.git (fetch)<br />
fork git@invent.kde.org:ngraham/kid3.git (push)<br />
origin https://invent.kde.org/kde/kid3.git (fetch)<br />
origin https://invent.kde.org/kde/kid3.git (push)<br />
</pre><br />
<br />
This means you have two remotes set up for your repo: "origin" points to the original repo, and "fork" points to your fork of it.<br />
<br />
=== Make a branch and commit ===<br />
Now that you have your fork set up, it's time to create a branch to track your work and make a commit.<br />
<br />
{{Input|1=<nowiki><br />
git checkout -b my_awesome_feature<br />
git add [the files you changed]<br />
git commit<br />
</nowiki>}}<br />
<br />
{{Note|If you have commit access and are pushing a branch directly to origin, remember to prefix your branch with "<tt>work/</tt>". Branches with this prefix can be force-pushed to, and keywords like <tt>BUG:</tt> are not evaluated}}<br />
<br />
=== Write a good commit message ===<br />
Please follow [https://chris.beams.io/posts/git-commit/#seven-rules commit message best practices]: write a descriptive title in the form of an imperative sentence (e.g. "Fix button disappearing when view is changed") and on the next line, write at least one sentence describing your change and why it is necessary, adding more details if necessary.<br />
<br />
If your patch is intended to fix a Bugzilla ticket, include the following on its own line:<br />
<pre><br />
BUG: 385942<br />
</pre><br />
(The number should be just the Bugzilla ticket number, '''not the full URL''')<br />
<br />
For example a commit message might read:<br />
<br />
<pre><br />
Close memory leak in GC<br />
<br />
The GC was doing a bad job and leaking memory. Explicitly call delete where necessary.<br />
<br />
BUG: 385942<br />
FIXED-IN: 5.0.0<br />
</pre><br />
<br />
[https://community.kde.org/Policies/Commit_Policy#Special_keywords_in_GIT_and_SVN_log_messages Here is more information] about other special messages that interact with Bugzilla tickets.<br />
<br />
{{Note|These keywords will only work if the email address in your <tt>~/.config/git/config</tt> file matches the email address used for your https://bugs.kde.org account (See [https://techbase.kde.org/Development/Git/Configuration#Basic_Settings this page] for more information).}}<br />
<br />
=== Push to your fork ===<br />
At this point you have a branch in your local repo called "my_awesome_feature" (Hopefully in reality it is named something a bit more appropriate!) that has a commit on it with your work. Now push it to your fork:<br />
<br />
{{Input|1=<nowiki><br />
git push fork my_awesome_feature<br />
</nowiki>}}<br />
<br />
This will produce a message somewhat like this:<br />
<br />
<pre><br />
$ git push fork my_awesome_feature<br />
Enumerating objects: 5, done.<br />
Counting objects: 100% (5/5), done.<br />
Delta compression using up to 4 threads<br />
Compressing objects: 100% (3/3), done.<br />
Writing objects: 100% (3/3), 303 bytes | 303.00 KiB/s, done.<br />
Total 3 (delta 2), reused 0 (delta 0)<br />
remote: This commit is available for viewing at:<br />
remote: https://invent.kde.org/ngraham/kid3/commit/23a702439f494806cf3cfe14f212df58a0075bba<br />
remote: <br />
remote: To create a merge request for my_awesome_feature, visit:<br />
remote: https://invent.kde.org/ngraham/kid3/merge_requests/new?merge_request%5Bsource_branch%5D=my_awesome_feature<br />
remote: <br />
To invent.kde.org:ngraham/kid3.git<br />
* [new branch] my_awesome_feature -> my_awesome_feature<br />
</pre><br />
<br />
=== Create the Merge Request ===<br />
Notice the "To create a merge request for my_awesome_feature..." message in the output of the push command (explained in the previous section). You can copy-and-paste the URL shown below it into a web browser. On some terminal apps, such as Konsole and Yakuake, you can ctrl+click on the link to go right there!<br />
<br />
You will be taken to a web page that looks like this:<br />
<br />
[[File:Merge_Request_info.png|800px]]<br />
<br />
In the Description section, write at least one sentence describing your change and why it is necessary, adding more details if needed. For Merge Requests that change something about the appearance or user interface, it's customary to include a screenshot of how the interface looks after your change has been applied. Bonus points for including a "Before" image too, so reviewers can easily compare them.<br />
<br />
If your patch is intended to fix a Bugzilla ticket, include the ticket number at the bottom of the description, just like how you did in the commit message (as explained in [[Infrastructure/GitLab#Write_a_good_commit_message]]):<br />
<pre><br />
BUG: 385942<br />
FIXED-IN: 5.21<br />
</pre><br />
<br />
{{Note|The <code>BUG:</code> keyword must be added both in the Merge Request description--which notifies the bug ticket of the Merge Request creation--and in the message of the first commit--which closes the bug ticket once the Merge Request is merged.<br><br />
In case you don't want to close the bug, but only add the information to it you can use <code>CCBUG:</code>. This can be the case if there are commits required before the bugfix can be done.}}<br />
<br />
Once you're done with that, click the "Submit Merge Request" button!<br />
<br />
[[File:Submit_Merge_Request.png|400px]]<br />
<br />
=== What happens next? ===<br />
After you've submitted your Merge Request, KDE developers who work with the software in question will review it and provide feedback. This can often take a few days. However, if nobody has responded after a week, it's likely that the review was overlooked (sorry about that!) and it's appropriate to make a comment saying, "Ping!" or something to that effect.<br />
<br />
Once the Merge Request is accepted, KDE Developers will merge it for you!<br />
<br />
=== Making changes to a Merge Request ===<br />
Oftentimes, reviewers will request changes before the Merge Request can be merged. To accomplish this, you make the requested changes locally, then create a new commit including your changes. First, stage all changed files:<br />
<br />
{{Input|1=<nowiki><br />
git add -u<br />
</nowiki>}}<br />
<br />
Now make a new commit with the staged files:<br />
<br />
{{Input|1=<nowiki><br />
git commit<br />
</nowiki>}}<br />
<br />
Then push the local branch with the new commit on it up to the remote branch:<br />
{{Input|1=<nowiki><br />
git push fork<br />
</nowiki>}}<br />
<br />
=== Rebasing a Merge Request ===<br />
When other changes have been made to the project's source code repo since you submitted your Merge Request, you will need to ''rebase'' the Merge Request to incorporate those changes.<br />
<br />
Confirm that you have the origin setup correctly by issuing <code>git remote -v</code>:<br />
<br />
{{Input|1=<syntaxhighlight lang="bash"><br />
fork git@invent.kde.org:developer/projectfork.git (fetch)<br />
fork git@invent.kde.org:developer/projectfork.git (push)<br />
origin https://invent.kde.org/applicationgroup/originalproject.git (fetch)<br />
origin https://invent.kde.org/applicationgroup/originalproject.git (push)<br />
</syntaxhighlight>}}<br />
<br />
It doesn't matter whether the original project is of type <code>https://</code> or <code>git@</code>, as you'll just fetch it.<br />
<br />
If you instead see:<br />
<br />
{{Input|1=<syntaxhighlight lang="bash"><br />
origin git@invent.kde.org:developer/projectfork.git (fetch)<br />
origin git@invent.kde.org:developer/projectfork.git (push)<br />
</syntaxhighlight>}}<br />
<br />
You'll need to rename your origin to fork or any similar name that is easily recognizable and add the original project as your new origin:<br />
<br />
{{Input|1=<syntaxhighlight lang="bash"><br />
# Rename your origin to "fork"<br />
git remote rename origin fork<br />
# Add new origin project<br />
git remote add origin https://invent.kde.org/applicationgroup/originalproject.git<br />
</syntaxhighlight>}}<br />
<br />
Now you may proceed:<br />
<br />
{{Input|1=<syntaxhighlight lang="bash"><br />
# First, make sure you are on the branch for your Merge Request<br />
git checkout mybranch<br />
# Now fetch the new changes in the repository<br />
git fetch<br />
# And finally rebase it<br />
git pull --rebase origin master<br />
</syntaxhighlight>}}<br />
<br />
At this point, there may be merge conflicts. If there are, git will tell you which files have conflicts. You can then open each file and resolve the conflict by exiting the contents to keep only the appropriate change; for that you can either make each change manually or use <code>git mergetool</code>, which opens a diff tool of your choice (if there's any installed).<br />
<br />
If you use a diff tool like '''meld''', for instance, the origin will be shown on the left, your fork will be shown on the right, and you may pick which changes to apply to the middle file, the new one.<br />
<br />
After resolving the existing conflicts, run <code>git add [file path]</code> on each conflicted file once all the conflicts have been resolved. Lastly, run <code>git rebase --continue</code> to finish the rebase.<br />
<br />
Now, you need to overwrite the version of the Merge Request on your remote branch with the version on your local branch. To do this, you have to force-push:<br />
<br />
{{Input|1=<syntaxhighlight><br />
git push --force fork<br />
</syntaxhighlight>}}<br />
<br />
{{Note|1=<br />
In case the content of files on the original project you're attempting to merge have been reorganized before your changes have been merged, when attempting a rebase, a conflict resolution loop might occur and you'd need to rebase and resolve conflicts several times. In such a case, run <code>git rerere</code> immediately before and after resolving those conflicts.<br />
}}<br />
<br />
=== Cherry-picking a Merge Request's commit to another branch ===<br />
If a Merge Request was labeled with the "cherry-pick" label, it should be manually cherry-picked to the branch corresponding to target milestone after it is merged to master. To do this, click on the link to the commit that was generated:<br />
<br />
[[File:Click on this to get to the commit.png|700px]]<br />
<br />
On that page, click the "Options" button and then "Cherry-Pick" from its dropdown menu:<br />
<br />
[[File:Cherry-pick it.png|700px]]<br />
<br />
In the dialog that appears, select the branch you want to cherry-pick the commit to. And then uncheck the "checkbox marked "Start a new merge request with these changed" '''Always uncheck this checkbox!'''<br />
<br />
[[File:Cherry-pick options.png|500px]]<br />
<br />
You can also manually cherry-pick the commit using the git command line. When doing so, always add the <code>-x</code> option to <code>git-cherry-pick</code>.<br />
<br />
== Testing someone else's Merge Request ==<br />
First you'll need a development environment set up. If you haven't done that yet, it's time to do so. Follow the instructions on [[Get_Involved/development#Set_up_your_development_environment]]. It is also advisable to set up the <tt>git mr</tt> tool, which makes testing Merge Requests a breeze. Here's how to install it, depending on your operating system:<br />
<br />
''' Arch / Manjaro '''<br />
{{Input|1=<nowiki><br />
yay -S git-mr<br />
</nowiki>}}<br />
<br />
'''Debian/Ubuntu/KDE Neon'''<br />
{{Input|1=<nowiki><br />
sudo apt install git-extras<br />
</nowiki>}}<br />
<br />
''' Fedora '''<br />
{{Input|1=<nowiki><br />
sudo dnf install git-extras<br />
</nowiki>}}<br />
<br />
''' OpenSUSE '''<br />
{{Input|1=<nowiki><br />
sudo zypper install git-mr<br />
</nowiki>}}<br />
<br />
=== Check out the Merge Request and compile the software ===<br />
First check out or enter the source repository for the software that's being patched. For example, let's say you want to test a Merge Request for Okular. If you've never built it before, check it out and build it once first:<br />
{{Input|1=<nowiki><br />
kdesrc-build okular<br />
</nowiki>}}<br />
<br />
Now go to its source directory:<br />
{{Input|1=<nowiki><br />
cd ~/kde/src/okular<br />
</nowiki>}}<br />
<br />
Find the Merge Request's ID. For example, for https://invent.kde.org/kde/okular/merge_requests/80, the ID is <tt>80</tt>.<br />
<br />
...and apply the Merge Request:<br />
{{Input|1=<nowiki><br />
git mr 80<br />
</nowiki>}}<br />
<br />
Now it's time to compile and run the software to make sure that the Merge Request does what it says it does and doesn't cause any regressions! Compile the patched source code:<br />
{{Input|1=<nowiki><br />
kdesrc-build okular --no-src --resume-from okular<br />
</nowiki>}}<br />
Those arguments will tell <tt>kdesrc-build</tt> to not update the source code and to not build any dependencies.<br />
<br />
If it didn't compile, that's reason alone to reject the Merge Request! Go to the web page for the Merge Request and report your findings.<br />
<br />
=== Perform QA ===<br />
If it did compile, then it's time to perform QA, because it's important to thoroughly test Merge Requests to ensure that bad code and regressions don't slip in. '''This is the entire purpose of having a review infrastructure'''; it is very important.<br />
<br />
First make sure the unit tests all pass:<br />
{{Input|1=<nowiki><br />
cd ~kde/build/kde/applications/okular<br />
ctest<br />
</nowiki>}}<br />
<br />
If any tests fail, report this through a comment on the Merge Request's web page.<br />
<br />
Does everything all still work for you? If not, return to the web page and request changes, writing a detailed comment explaining what didn't work. It is permissible to do this even if you have not been specified as a reviewer! '''Anyone can reject a Merge Request on the grounds that it does not work, does not do what it says it does, or causes regressions.'''<br />
<br />
Next, try to break the Merge Request. Here are some ideas:<br />
# Remove the program's configuration file (<tt>~/.config/<program name>rc</tt> ) and re-open it<br />
# Try the program with a HiDPI scale factor (or without one) or with a different default font size<br />
# If it's a new feature, feed it unexpected input<br />
# Test related functionality<br />
<br />
A good Merge Request will handle corner cases and variations in configuration. The price of configurability is vigilant testing! We owe it to our users to test using many configurations, not just the defaults or our personal settings.<br />
<br />
=== Perform code review ===<br />
A good overview for how to review changes can be found at https://google.github.io/eng-practices/review/reviewer/.<br />
<br />
=== Engage with the author and other reviewers ===<br />
After you have run the program and evaluated the Merge Request, it's time to leave some review comments on the webpage. If you have been specified as a reviewer, or are a member of a group that has been specified as a reviewer, it is permissible to Accept the Merge Request. But keep in mind that reviewing involves responsibility: you are giving a thumbs-up to code that will be run potentially by millions of people. If you accept and land a Merge Request that causes regressions, you will be expected to help fix it if the original author cannot or has disappeared. It's important to take the reviewer role seriously.<br />
<br />
<br />
== Advanced topics ==<br />
<br />
=== Curating your merge request commit history ===<br />
<br />
For large or complex merge requests, it is strongly recommended to separate the pieces of your proposed change into individual commits--one for each component of the proposed change. For example, perhaps you are working on a feature that consists of multiple logically separable elements that nonetheless all live in the same source repo, or perhaps you are first doing some code refactoring, then you add a backend feature, then finally you add a front-end user interface for it.<br />
<br />
For this workflow, specifically mention in the merge request description that you would like reviewers to review individual commits separately, and not squash when merging.<br />
<br />
If you need to later make changes to your Merge Request, do not add new commits; instead, combine the new changes with one of the existing commits using git's interactive rebasing feature. To learn how to do this, see https://git-rebase.io/<br />
<br />
{{Note|Using Gitlab's "Apply suggestion" feature will create new commits in your Merge Request which must be manually squashed into one of the existing commits.}}<br />
<br />
=== Using work branches instead of forks ===<br />
<br />
If you have a [[Infrastructure/Get a Developer Account|developer account]], you don't need to use forks to submit merge requests. Instead, make sure your branch name begins with <code>work/</code> and push it straight to the main repo. <code>work/</code> branches are treated specially, as you can use <code>git push --force</code> with them. By convention, it is recommended to add your KDE username to the branch, so the final name would end up looking like <code>work/your-username/my-awesome-feature</code>.<br />
<br />
To make it easier creating new <code>work</code> branches (instead of typing work/your-username/branch-name every time), you can use a Git script to do that; for example, if you create a shell script somewhere in your <code>PATH</code>, and name it e.g. <code>git-work</code> (the script name must start with '''git-'''), and make the script executable:<br />
{{Input|1=<nowiki><br />
#!/usr/bin/bash<br />
if [ -z "$1" ]; then<br />
echo "Cannot create a work branch with an empty name after the last /"<br />
exit 1<br />
fi<br />
<br />
git checkout -t origin/master -b work/your-user-name/$1<br />
</nowiki>}}<br />
<br />
you can then use:<br />
<br />
{{Input|1=<nowiki><br />
git work branch-name<br />
</nowiki>}}<br />
<br />
and this will create a branch ''work/your-username/branch-name'', that tracks ''origin/master'', and switch to the newly created branch.<br />
<br />
=== Switching the target branch of a Merge Request ===<br />
Sometimes you will submit a merge request that is targeting the master branch, but will later be asked to target the stable branch instead because it is a bugfix. Or perhaps you have targeted the stable branch but the commit is considered too invasive and you are asked to target the master branch instead. In either of these circumstances, you will need to re-target your Merge Request. Here's how:<br />
<br />
{{Input|1=<nowiki><br />
git checkout [the local branch for your merge request]<br />
git fetch<br />
git rebase -i origin/[the name of the remote branch you want to target; for example release/19.12]<br />
</nowiki>}}<br />
<br />
This will open a text editor showing a list of commits, each on a separate line. Delete all of the lines of text except for the ones corresponding to new commits you have added as part of your merge request. Next, fix any merge conflicts if there are any.<br />
<br />
Then force-push the branch up to gitlab again:<br />
<br />
{{Input|1=<nowiki><br />
git push --force fork<br />
</nowiki>}}<br />
<br />
Finally, edit the merge request by clicking the "Edit" button in the top-right corner of the page and choose the desired different target branch:<br />
<br />
[[File:Editing_merge_request_target_branch.png]]<br />
<br />
Note that after merging a Merge Request to the stable branch, you are expected to merge that branch back to <tt>master</tt> afterwards:<br />
{{Input|1=<nowiki><br />
git fetch origin<br />
git checkout release/19.12<br />
git pull<br />
git checkout master<br />
git pull<br />
git merge -s recursive -Xours release/19.12<br />
git diff origin/master # Are the changes what you expected?<br />
git push<br />
</nowiki>}}<br />
<br />
{{warning|If at any time you feel nervous or run into trouble, ask your reviewers for help. This can be tricky, and undoing bad merges is a pain in the neck.}}<br />
<br />
=== Pushing commits to somebody else's fork ===<br />
Sometimes someone will say "hey let's work on my branch together." So you will be pushing commits not to origin, and not to your fork, but to someone else's fork. Let's say you want to work on joliveira's "gsoc2019_numberFormat" branch.<br />
<br />
First you would need to add the URL for his fork as a remote:<br />
<br />
<pre><br />
$ cd ~/kde/src/okular<br />
$ git remote add joliveira_fork git@invent.kde.org:joliveira/okular.git<br />
$ git remote -v<br />
aacid_fork git@invent.kde.org:aacid/okular.git (fetch)<br />
aacid_fork git@invent.kde.org:aacid/okular.git (push)<br />
joliveira_fork git@invent.kde.org:joliveira/okular.git (fetch)<br />
joliveira_fork git@invent.kde.org:joliveira/okular.git (push)<br />
origin https://invent.kde.org/kde/okular.git (fetch)<br />
origin https://invent.kde.org/kde/okular.git (push)<br />
</pre><br />
<br />
Notice how there are now multiple forks set up as remotes.<br />
<br />
Next, you need to fetch all the repo metadata from the new remote:<br />
<br />
{{Input|1=<nowiki><br />
git fetch joliveira_fork<br />
</nowiki>}}<br />
<br />
This will download the list of branches. The next step is to switch to the one you want to collaborate on:<br />
<br />
{{Input|1=<nowiki><br />
git checkout --track joliveira_fork/gsoc2019_numberFormat<br />
</nowiki>}}<br />
<br />
This will create a local branch named "gsoc2019_numberFormat" from the contents of the remote branch joliveira_fork/gsoc2019_numberFormat and that also "tracks" it. This means that if someone else pushes changes to a remote version of that branch, you can run <tt>git pull --rebase</tt> while on your local "gsoc2019_numberFormat" branch to bring it up to date.<br />
<br />
Next, make your changes, add and commit. Then push the changes to the remote joliveira_fork remote:<br />
<br />
{{Input|1=<nowiki><br />
git push joliveira_fork gsoc2019_numberFormat<br />
</nowiki>}}<br />
<br />
=== Generating "eternal" URLs to commits or objects in a repository ===<br />
<br />
History has taught that no system used by KDE around the code repositories stays forever.<br />
Quickgit, CGit, Phabricator & Co. came and at one point were replaced again. Sadly also taking with them the service-specific URLs (and host names).<br />
<br />
To give documentation, blog posts, commit messages and other long-living documents a way to reference commits or objects in the repository like directories or files, at given branches or at given tags, the service commits.kde.org exists. It maps and forwards URLs to the respective current service URLs.<br />
<br />
The pattern for URLs to commits is this:<br />
<pre><br />
https://commits.kde.org/<repo-id>/<commit-id><br />
</pre><br />
<br />
Example:<br />
<pre><br />
https://commits.kde.org/kcoreaddons/d2f4d353327b322ee6bfcc303169190ae44393f0<br />
</pre><br />
<br />
The pattern for URLs to objects is like this:<br />
<pre><br />
https://commits.kde.org/<repo-id>[?[path=<pathToFileOrDirectory]&[branch=<branch>|tag=<tag>]]<br />
</pre><br />
<path> should be without a leading /. It defaults to the top-level directory if not set. Either a branch or tag can be passed at which the objects should be shown. It defaults to the main branch (master usually).<br />
<br />
Examples:<br />
<pre><br />
https://commits.kde.org/kcoreaddons?path=src # points to src/ directory in master branch<br />
https://commits.kde.org/kcoreaddons?path=README.md&tag=v5.0.0 # points to README.md file at tag v5.0.0<br />
https://commits.kde.org/kdelibs?path=kdecore/tests&branch=KDE/3.5 # points to kdecore/tests directory in branch KDE/3.5<br />
</pre><br />
<br />
There currently is no service to generate commit.kde.org URLs from URLs for the actual system. This has to be done manually.<br />
<br />
=== Creating a merge request using the command line ===<br />
<br />
Gitlab supports git push options that can push your branch and create a merge request in one go. For example:<br />
{{Input|1=<nowiki><br />
git push -o merge_request.create -o merge_request.target=master -o merge_request.remove_source_branch -o merge_request.assign=foo<br />
</nowiki>}}<br />
<br />
replace <code>foo</code> with your actual user name. This command will push the branch, create a merge request, targeting the <code>master</code> branch, enable the "delete branch on merge" option, and assign the merge request to user <code>foo</code>.<br />
<br />
You can change any of those parameters to suit your usage. For more details on Gitlab push options see [https://docs.gitlab.com/ee/user/project/push_options.html this].<br />
<br />
If you have a branch with several commits, most likely you'll still need to tweak the merge request description ...etc.<br />
<br />
Of course that's a lot to type, so it's easier to use a Git alias, for example this command add an alias <code>push-mr</code> to your Git config:<br />
<br />
{{Input|1=<nowiki><br />
git config --global alias.push-mr 'push -o merge_request.create -o merge_request.target=master -o merge_request.remove_source_branch -o merge_request.assign=foo'<br />
</nowiki>}}<br />
<br />
then you can use <code>git push-mr</code> like any other Git command. For more information about Git aliases see [https://git-scm.com/book/en/v2/Git-Basics-Git-Aliases this]</div>Gjditchfieldhttps://community.kde.org/index.php?title=KDE_PIM/Docker&diff=93820KDE PIM/Docker2022-02-28T22:56:27Z<p>Gjditchfield: /* Fixing build errors */</p>
<hr />
<div>To make developing KDE PIM as easy as possible we have prepared a Docker image based on the KDE Neon distribution. It contains all the dependencies necessary to compile KDE PIM, the environment for running self-compiled KDE PIM and pre-configured tool (kdesrc-build) used to build latest KDE PIM from sources.<br />
<br />
== Set up Docker ==<br />
If you use KDE Neon, Ubuntu or Debian, run the following commands to install Docker and add yourself to the <code>docker</code> group, so you can use it:<br />
<br />
sudo apt install docker.io xserver-xephyr<br />
sudo usermod -aG docker $(whoami)<br />
newgrp docker<br />
<br />
{{Warning|It's important to note that any user added to the docker group is root equivalent. More information [https://github.com/docker/docker/issues/9976 here] and [https://docs.docker.com/engine/security/security/ here].}}<br />
<br />
On other distributions, please follow your distro's guide on how to set up Docker there, since the steps can differ slightly on different distributions.<br />
<br />
== Preparations ==<br />
Clone the git repository with the Dockerfile and support scripts.<br />
<br />
git clone git@invent.kde.org:pim/kdepim-docker.git<br />
cd kdepim-docker<br />
<br />
== Making OpenGL work in the container ==<br />
Several parts of KDE PIM depend on OpenGL - this is due to our use of QtWebEngine, which is based on Blink and has a hard dependency on OpenGL for rendering web pages. There's no way around that and so we need to make OpenGL work in the container. Unfortunately, that is not a very straightforward process and it differs for each GPU vendor and drivers used.<br />
<br />
=== NVIDIA proprietary drivers ===<br />
The easiest way is to use NVIDIA's nvidia-docker from [https://github.com/NVIDIA/nvidia-docker nvidia-docker Github]. You can follow the instructions on the Github page regarding how to install it. The nvidia-docker will automatically find your installed NVIDIA drivers and will expose them into the Docker container at runtime, so you don't have to rebuild your container whenever you upgrade your NVIDIA drivers.<br />
<br />
Note that if you do this, you must pass <code>-n</code> switch to the <code>build.sh</code> and <code>run.sh</code> scripts from the kdepim-docker.git repository.<br />
<br />
=== Nouveau (NVIDIA opensource drivers) ===<br />
TODO<br />
<br />
=== Intel ===<br />
Works out of the box<br />
<br />
=== AMD/ATI ===<br />
TODO<br />
<br />
== Building Docker image ==<br />
<br />
In order to build the Docker image, run the <code>build.sh</code> script. If you are<br />
using proprietary NVIDIA drivers, run the script with the <code>-n</code> switch.<br />
<br />
The command will create a Docker image called kdepim:dev.<br />
<br />
== Running the Docker container ==<br />
<br />
Create a directory where you want the source code, build folders, and everything else related to KDE PIM development to be stored. This is also where runtime data and configuration of Akonadi, Kontact and other apps you run inside the container will be stored. You will then expose this directory to the Docker container at runtime. The content of the directory will be available in the container in its<br />
<code>/home/neon/kdepim</code> directory.<br />
<br />
mkdir $HOME/kdepim-dev<br />
<br />
To run the container, use the <code>run.sh</code> script. When you run it for the first time, you must give it the path to the storage directory:<br />
<br />
run.sh $HOME/kdepim-dev<br />
<br />
If you are using proprietary NVIDIA drivers, run the script with the <code>-n</code> switch:<br />
<br />
run.sh -n $HOME/kdepim-dev<br />
<br />
The first run will create a container named after the storage directory. After the first run you can just use the name instead of the full path:<br />
<br />
run.sh kdepim-dev<br />
<br />
If you want to get another terminal window opened inside the same container, just run <code>run.sh</code> again. It will automatically create a new terminal on the already running container.<br />
<br />
{{note|In some systems, the container's <code>/home/neon/kdepim</code> directory may not be writable. If so, edit <code>run.sh</code> to remove the parameter <code>:rw,z</code> from the <code>-v</code> options. Then delete the container with the command <code>docker rm kdepim-dev</code>, and run the container again.}}<br />
<br />
=== Running Multiple Containers ===<br />
<br />
You can have more than one container, each with its own state. For example, KDE PIM bug fixing mostly occurs on the current Release git repository branches, while new development occurs on the master branches. Switching between them (with <code>kdesrc-build --branch</code>) requires lengthy recompilation. Instead of switching, you can have one container for each.<br />
<br />
mkdir $HOME/kdepim-stable # Storage for second container.<br />
run.sh $HOME/kdepim-stable # Create second container.<br />
<br />
== Building and updating KDE PIM ==<br />
<br />
Once inside the container, you can use the following commands to compile the<br />
entire KDE PIM suite:<br />
<br />
sudo apt update<br />
sudo apt full-upgrade<br />
kdesrc-build kde-pim<br />
<br />
This will take a lot of time the first time, but all subsequent builds will be<br />
faster thanks to incremental builds and use of ccache. You can also use a specific repository name instead of the<br />
<code>kde-pim</code> group.<br />
<br />
Check the [https://kdesrc-build.kde.org kdesrc-build documentation] for more<br />
details about using kdesrc-build. For a start, you may want to customize the <code>-j</code> option in the <code>make-options</code> option group in /home/neon/.kdesrc-buildrc.<br />
<br />
kdesrc-build will clone all the repositories into <code>/home/neon/kdepim/src/kde/pim</code>.<br />
Build directories (where you can run <code>make</code> and <code>ctest</code> manually) are in <code>/home/neon/kdepim/build/kde/pim</code>.<br />
Two command-line aliases, <code>cb</code> and <code>cs</code>, switch back and forth between them.<br />
<br />
The binaries are installed to <code>/home/neon/kdepim/install</code>. The environment<br />
of the container is adjusted to work with the custom installation prefix.<br />
<br />
=== Fixing build errors ===<br />
If kdesrc-build cannot build from a repository, it will write a log file in <code>/home/neon/kdepim/logs/latest/''repository''/error.log</code>.<br />
<br />
It is often the case that some system has added a dependency on some package that is not installed.<br />
In that case, error.log will contain messages like<br />
<br />
Could not find a package configuration file provided by "Qt5Location" with<br />
any of the following names:<br />
<br />
Qt5LocationConfig.cmake<br />
qt5location-config.cmake<br />
<br />
If the system you are trying to build is normally distributed in some KDE Neon package, you can try to install all of its dependencies at once:<br />
<br />
sudo apt update<br />
sudo apt build-dep <em>package</em><br />
<br />
Another way to find a missing package is to visit [https://packages.ubuntu.com/ Ubuntu Packages Search] and use the "Search the contents of packages" form to search for the cmake file, then use <code>sudo apt install</code> to install the package.<br />
<br />
== Development tools ==<br />
<br />
There's [https://www.kdevelop.org KDevelop] and [https://www.qt.io/ide/ QtCreator]<br />
preinstalled in the container and you can run them from there. You can also use<br />
them from outside of the container, but code completion might not work perfectly then.<br />
<br />
You can also use any other IDE of your choice either by installing it into the container<br />
with apt-get or use it from outside of the container.<br />
<br />
== Contributing ==<br />
<br />
You can find more details in the [[KDE_PIM/Development|Development]] section of our wiki. If you have any issues or questions, feel free to stop by on our IRC channel (#kontact) on Libera Chat, or talk to us on the [[KDE_PIM/Development#Mailing_Lists|kde-pim mailinglist]].</div>Gjditchfieldhttps://community.kde.org/index.php?title=KDE_PIM/Docker&diff=93819KDE PIM/Docker2022-02-28T16:40:40Z<p>Gjditchfield: /* Building and updating KDE PIM */</p>
<hr />
<div>To make developing KDE PIM as easy as possible we have prepared a Docker image based on the KDE Neon distribution. It contains all the dependencies necessary to compile KDE PIM, the environment for running self-compiled KDE PIM and pre-configured tool (kdesrc-build) used to build latest KDE PIM from sources.<br />
<br />
== Set up Docker ==<br />
If you use KDE Neon, Ubuntu or Debian, run the following commands to install Docker and add yourself to the <code>docker</code> group, so you can use it:<br />
<br />
sudo apt install docker.io xserver-xephyr<br />
sudo usermod -aG docker $(whoami)<br />
newgrp docker<br />
<br />
{{Warning|It's important to note that any user added to the docker group is root equivalent. More information [https://github.com/docker/docker/issues/9976 here] and [https://docs.docker.com/engine/security/security/ here].}}<br />
<br />
On other distributions, please follow your distro's guide on how to set up Docker there, since the steps can differ slightly on different distributions.<br />
<br />
== Preparations ==<br />
Clone the git repository with the Dockerfile and support scripts.<br />
<br />
git clone git@invent.kde.org:pim/kdepim-docker.git<br />
cd kdepim-docker<br />
<br />
== Making OpenGL work in the container ==<br />
Several parts of KDE PIM depend on OpenGL - this is due to our use of QtWebEngine, which is based on Blink and has a hard dependency on OpenGL for rendering web pages. There's no way around that and so we need to make OpenGL work in the container. Unfortunately, that is not a very straightforward process and it differs for each GPU vendor and drivers used.<br />
<br />
=== NVIDIA proprietary drivers ===<br />
The easiest way is to use NVIDIA's nvidia-docker from [https://github.com/NVIDIA/nvidia-docker nvidia-docker Github]. You can follow the instructions on the Github page regarding how to install it. The nvidia-docker will automatically find your installed NVIDIA drivers and will expose them into the Docker container at runtime, so you don't have to rebuild your container whenever you upgrade your NVIDIA drivers.<br />
<br />
Note that if you do this, you must pass <code>-n</code> switch to the <code>build.sh</code> and <code>run.sh</code> scripts from the kdepim-docker.git repository.<br />
<br />
=== Nouveau (NVIDIA opensource drivers) ===<br />
TODO<br />
<br />
=== Intel ===<br />
Works out of the box<br />
<br />
=== AMD/ATI ===<br />
TODO<br />
<br />
== Building Docker image ==<br />
<br />
In order to build the Docker image, run the <code>build.sh</code> script. If you are<br />
using proprietary NVIDIA drivers, run the script with the <code>-n</code> switch.<br />
<br />
The command will create a Docker image called kdepim:dev.<br />
<br />
== Running the Docker container ==<br />
<br />
Create a directory where you want the source code, build folders, and everything else related to KDE PIM development to be stored. This is also where runtime data and configuration of Akonadi, Kontact and other apps you run inside the container will be stored. You will then expose this directory to the Docker container at runtime. The content of the directory will be available in the container in its<br />
<code>/home/neon/kdepim</code> directory.<br />
<br />
mkdir $HOME/kdepim-dev<br />
<br />
To run the container, use the <code>run.sh</code> script. When you run it for the first time, you must give it the path to the storage directory:<br />
<br />
run.sh $HOME/kdepim-dev<br />
<br />
If you are using proprietary NVIDIA drivers, run the script with the <code>-n</code> switch:<br />
<br />
run.sh -n $HOME/kdepim-dev<br />
<br />
The first run will create a container named after the storage directory. After the first run you can just use the name instead of the full path:<br />
<br />
run.sh kdepim-dev<br />
<br />
If you want to get another terminal window opened inside the same container, just run <code>run.sh</code> again. It will automatically create a new terminal on the already running container.<br />
<br />
{{note|In some systems, the container's <code>/home/neon/kdepim</code> directory may not be writable. If so, edit <code>run.sh</code> to remove the parameter <code>:rw,z</code> from the <code>-v</code> options. Then delete the container with the command <code>docker rm kdepim-dev</code>, and run the container again.}}<br />
<br />
=== Running Multiple Containers ===<br />
<br />
You can have more than one container, each with its own state. For example, KDE PIM bug fixing mostly occurs on the current Release git repository branches, while new development occurs on the master branches. Switching between them (with <code>kdesrc-build --branch</code>) requires lengthy recompilation. Instead of switching, you can have one container for each.<br />
<br />
mkdir $HOME/kdepim-stable # Storage for second container.<br />
run.sh $HOME/kdepim-stable # Create second container.<br />
<br />
== Building and updating KDE PIM ==<br />
<br />
Once inside the container, you can use the following commands to compile the<br />
entire KDE PIM suite:<br />
<br />
sudo apt update<br />
sudo apt full-upgrade<br />
kdesrc-build kde-pim<br />
<br />
This will take a lot of time the first time, but all subsequent builds will be<br />
faster thanks to incremental builds and use of ccache. You can also use a specific repository name instead of the<br />
<code>kde-pim</code> group.<br />
<br />
Check the [https://kdesrc-build.kde.org kdesrc-build documentation] for more<br />
details about using kdesrc-build. For a start, you may want to customize the <code>-j</code> option in the <code>make-options</code> option group in /home/neon/.kdesrc-buildrc.<br />
<br />
kdesrc-build will clone all the repositories into <code>/home/neon/kdepim/src/kde/pim</code>.<br />
Build directories (where you can run <code>make</code> and <code>ctest</code> manually) are in <code>/home/neon/kdepim/build/kde/pim</code>.<br />
Two command-line aliases, <code>cb</code> and <code>cs</code>, switch back and forth between them.<br />
<br />
The binaries are installed to <code>/home/neon/kdepim/install</code>. The environment<br />
of the container is adjusted to work with the custom installation prefix.<br />
<br />
=== Fixing build errors ===<br />
If kdesrc-build cannot build from a repository, it will write a log file in <code>/home/neon/kdepim/logs/latest/''repository''/error.log</code>.<br />
<br />
It is often the case that some system has added a dependency on some package that is not installed.<br />
In that case, error.log will contain messages like<br />
<br />
Could not find a package configuration file provided by "Qt5Location" with<br />
any of the following names:<br />
<br />
Qt5LocationConfig.cmake<br />
qt5location-config.cmake<br />
<br />
To find the package, visit [https://packages.ubuntu.com/ Ubuntu Packages Search] and use the "Search the contents of packages" form to search for the cmake file, then use <code>sudo apt install</code> to install the package.<br />
<br />
== Development tools ==<br />
<br />
There's [https://www.kdevelop.org KDevelop] and [https://www.qt.io/ide/ QtCreator]<br />
preinstalled in the container and you can run them from there. You can also use<br />
them from outside of the container, but code completion might not work perfectly then.<br />
<br />
You can also use any other IDE of your choice either by installing it into the container<br />
with apt-get or use it from outside of the container.<br />
<br />
== Contributing ==<br />
<br />
You can find more details in the [[KDE_PIM/Development|Development]] section of our wiki. If you have any issues or questions, feel free to stop by on our IRC channel (#kontact) on Libera Chat, or talk to us on the [[KDE_PIM/Development#Mailing_Lists|kde-pim mailinglist]].</div>Gjditchfieldhttps://community.kde.org/index.php?title=KDE_PIM/Docker&diff=93238KDE PIM/Docker2021-09-29T20:41:37Z<p>Gjditchfield: Multiple containers</p>
<hr />
<div>To make developing KDE PIM as easy as possible we have prepared a Docker image based on the KDE Neon distribution. It contains all the dependencies necessary to compile KDE PIM, the environment for running self-compiled KDE PIM and pre-configured tool (kdesrc-build) used to build latest KDE PIM from sources.<br />
<br />
== Set up Docker ==<br />
If you use KDE Neon, Ubuntu or Debian, run the following commands to install Docker and add yourself to the <code>docker</code> group, so you can use it:<br />
<br />
sudo apt install docker.io xserver-xephyr<br />
sudo usermod -aG docker $(whoami)<br />
newgrp docker<br />
<br />
{{Warning|It's important to note that any user added to the docker group is root equivalent. More information [https://github.com/docker/docker/issues/9976 here] and [https://docs.docker.com/engine/security/security/ here].}}<br />
<br />
On other distributions, please follow your distro's guide on how to set up Docker there, since the steps can differ slightly on different distributions.<br />
<br />
== Preparations ==<br />
Clone the git repository with the Dockerfile and support scripts.<br />
<br />
git clone git@invent.kde.org:pim/kdepim-docker.git<br />
cd kdepim-docker<br />
<br />
== Making OpenGL work in the container ==<br />
Several parts of KDE PIM depend on OpenGL - this is due to our use of QtWebEngine, which is based on Blink and has a hard dependency on OpenGL for rendering web pages. There's no way around that and so we need to make OpenGL work in the container. Unfortunately, that is not a very straightforward process and it differs for each GPU vendor and drivers used.<br />
<br />
=== NVIDIA proprietary drivers ===<br />
The easiest way is to use NVIDIA's nvidia-docker from [https://github.com/NVIDIA/nvidia-docker nvidia-docker Github]. You can follow the instructions on the Github page regarding how to install it. The nvidia-docker will automatically find your installed NVIDIA drivers and will expose them into the Docker container at runtime, so you don't have to rebuild your container whenever you upgrade your NVIDIA drivers.<br />
<br />
Note that if you do this, you must pass <code>-n</code> switch to the <code>build.sh</code> and <code>run.sh</code> scripts from the kdepim-docker.git repository.<br />
<br />
=== Nouveau (NVIDIA opensource drivers) ===<br />
TODO<br />
<br />
=== Intel ===<br />
Works out of the box<br />
<br />
=== AMD/ATI ===<br />
TODO<br />
<br />
== Building Docker image ==<br />
<br />
In order to build the Docker image, run the <code>build.sh</code> script. If you are<br />
using proprietary NVIDIA drivers, run the script with the <code>-n</code> switch.<br />
<br />
The command will create a Docker image called kdepim:dev.<br />
<br />
== Running the Docker container ==<br />
<br />
Create a directory where you want the source code, build folders, and everything else related to KDE PIM development to be stored. This is also where runtime data and configuration of Akonadi, Kontact and other apps you run inside the container will be stored. You will then expose this directory to the Docker container at runtime. The content of the directory will be available in the container in its<br />
<code>/home/neon/kdepim</code> directory.<br />
<br />
mkdir $HOME/kdepim-dev<br />
<br />
To run the container, use the <code>run.sh</code> script. When you run it for the first time, you must give it the path to the storage directory:<br />
<br />
run.sh $HOME/kdepim-dev<br />
<br />
If you are using proprietary NVIDIA drivers, run the script with the <code>-n</code> switch:<br />
<br />
run.sh -n $HOME/kdepim-dev<br />
<br />
The first run will create a container named after the storage directory. After the first run you can just use the name instead of the full path:<br />
<br />
run.sh kdepim-dev<br />
<br />
If you want to get another terminal window opened inside the same container, just run <code>run.sh</code> again. It will automatically create a new terminal on the already running container.<br />
<br />
{{note|In some systems, the container's <code>/home/neon/kdepim</code> directory may not be writable. If so, edit <code>run.sh</code> to remove the parameter <code>:rw,z</code> from the <code>-v</code> options. Then delete the container with the command <code>docker rm kdepim-dev</code>, and run the container again.}}<br />
<br />
=== Running Multiple Containers ===<br />
<br />
You can have more than one container, each with its own state. For example, KDE PIM bug fixing mostly occurs on the current Release git repository branches, while new development occurs on the master branches. Switching between them (with <code>kdesrc-build --branch</code>) requires lengthy recompilation. Instead of switching, you can have one container for each.<br />
<br />
mkdir $HOME/kdepim-stable # Storage for second container.<br />
run.sh $HOME/kdepim-stable # Create second container.<br />
<br />
== Building and updating KDE PIM ==<br />
<br />
Once inside the container, you can use the following commands to compile the<br />
entire KDE PIM suite:<br />
<br />
sudo apt update<br />
sudo apt full-upgrade<br />
kdesrc-build kde-pim<br />
<br />
This will take a lot of time the first time, but all subsequent builds will be<br />
faster thanks to incremental builds and use of ccache. You can also use a specific repository name instead of the<br />
<code>kde-pim</code> group.<br />
<br />
Check the [https://kdesrc-build.kde.org kdesrc-build documentation] for more<br />
details about using kdesrc-build. For a start, you may want to customize the <code>-j</code> option in the <code>make-options</code> option group in /home/neon/.kdesrc-buildrc.<br />
<br />
kdesrc-build will clone all the repositories into <code>/home/neon/kdepim/src/kde/pim</code>.<br />
Build directories (where you can run <code>make</code> manually) are in <code>/home/neon/kdepim/build/kde/pim</code>.<br />
Two command-line aliases, <code>cb</code> and <code>cs</code>, switch back and forth between them.<br />
<br />
The binaries are installed to <code>/home/neon/kdepim/install</code>. The environment<br />
of the container is adjusted to work with the custom installation prefix.<br />
<br />
=== Fixing build errors ===<br />
If kdesrc-build cannot build from a repository, it will write a log file in <code>/home/neon/kdepim/logs/latest/''repository''/error.log</code>.<br />
<br />
It is often the case that some system has added a dependency on some package that is not installed.<br />
In that case, error.log will contain messages like<br />
<br />
Could not find a package configuration file provided by "Qt5Location" with<br />
any of the following names:<br />
<br />
Qt5LocationConfig.cmake<br />
qt5location-config.cmake<br />
<br />
To find the package, visit [https://packages.ubuntu.com/ Ubuntu Packages Search] and use the "Search the contents of packages" form to search for the cmake file, then use <code>sudo apt install</code> to install the package.<br />
<br />
== Development tools ==<br />
<br />
There's [https://www.kdevelop.org KDevelop] and [https://www.qt.io/ide/ QtCreator]<br />
preinstalled in the container and you can run them from there. You can also use<br />
them from outside of the container, but code completion might not work perfectly then.<br />
<br />
You can also use any other IDE of your choice either by installing it into the container<br />
with apt-get or use it from outside of the container.<br />
<br />
== Contributing ==<br />
<br />
You can find more details in the [[KDE_PIM/Development|Development]] section of our wiki. If you have any issues or questions, feel free to stop by on our IRC channel (#kontact) on Libera Chat, or talk to us on the [[KDE_PIM/Development#Mailing_Lists|kde-pim mailinglist]].</div>Gjditchfieldhttps://community.kde.org/index.php?title=KDE_PIM/Docker&diff=93237KDE PIM/Docker2021-09-29T19:15:54Z<p>Gjditchfield: /* Building and updating KDE PIM */</p>
<hr />
<div>To make developing KDE PIM as easy as possible we have prepared a Docker image based on the KDE Neon distribution. It contains all the dependencies necessary to compile KDE PIM, the environment for running self-compiled KDE PIM and pre-configured tool (kdesrc-build) used to build latest KDE PIM from sources.<br />
<br />
== Set up Docker ==<br />
If you use KDE Neon, Ubuntu or Debian, run the following commands to install Docker and add yourself to the <code>docker</code> group, so you can use it:<br />
<br />
sudo apt install docker.io xserver-xephyr<br />
sudo usermod -aG docker $(whoami)<br />
newgrp docker<br />
<br />
{{Warning|It's important to note that any user added to the docker group is root equivalent. More information [https://github.com/docker/docker/issues/9976 here] and [https://docs.docker.com/engine/security/security/ here].}}<br />
<br />
On other distributions, please follow your distro's guide on how to set up Docker there, since the steps can differ slightly on different distributions.<br />
<br />
== Preparations ==<br />
Clone the git repository with the Dockerfile and support scripts.<br />
<br />
git clone git@invent.kde.org:pim/kdepim-docker.git<br />
cd kdepim-docker<br />
<br />
== Making OpenGL work in the container ==<br />
Several parts of KDE PIM depend on OpenGL - this is due to our use of QtWebEngine, which is based on Blink and has a hard dependency on OpenGL for rendering web pages. There's no way around that and so we need to make OpenGL work in the container. Unfortunately, that is not a very straightforward process and it differs for each GPU vendor and drivers used.<br />
<br />
=== NVIDIA proprietary drivers ===<br />
The easiest way is to use NVIDIA's nvidia-docker from [https://github.com/NVIDIA/nvidia-docker nvidia-docker Github]. You can follow the instructions on the Github page regarding how to install it. The nvidia-docker will automatically find your installed NVIDIA drivers and will expose them into the Docker container at runtime, so you don't have to rebuild your container whenever you upgrade your NVIDIA drivers.<br />
<br />
Note that if you do this, you must pass <code>-n</code> switch to the <code>build.sh</code> and <code>run.sh</code> scripts from the kdepim-docker.git repository.<br />
<br />
=== Nouveau (NVIDIA opensource drivers) ===<br />
TODO<br />
<br />
=== Intel ===<br />
Works out of the box<br />
<br />
=== AMD/ATI ===<br />
TODO<br />
<br />
== Building Docker image ==<br />
<br />
In order to build the Docker image, run the <code>build.sh</code> script. If you are<br />
using proprietary NVIDIA drivers, run the script with the <code>-n</code> switch.<br />
<br />
The command will create a Docker image called kdepim:dev.<br />
<br />
== Running the Docker container ==<br />
<br />
Create a directory where you want the source code, build folders, and everything else related to KDE PIM development to be stored. This is also where runtime data and configuration of Akonadi, Kontact and other apps you run inside the container will be stored. You will then expose this directory to the Docker container at runtime. The content of the directory will be available in the container in its<br />
<code>/home/neon/kdepim</code> directory.<br />
<br />
mkdir $HOME/kdepim-dev<br />
<br />
To run the container, use the <code>run.sh</code> script. When you run it for the first time, you must give it the path to the storage directory:<br />
<br />
run.sh $HOME/kdepim-dev<br />
<br />
If you are using proprietary NVIDIA drivers, run the script with the <code>-n</code> switch:<br />
<br />
run.sh -n $HOME/kdepim-dev<br />
<br />
The first run will create a container named after the storage directory. After the first run you can just use the name instead of the full path:<br />
<br />
run.sh kdepim-dev<br />
<br />
If you want to get another terminal window opened inside the same container, just run <code>run.sh</code> again. It will automatically create a new terminal on the already running container.<br />
<br />
{{note|In some systems, the container's <code>/home/neon/kdepim</code> directory may not be writable. If so, edit <code>run.sh</code> to remove the parameter <code>:rw,z</code> from the <code>-v</code> options. Then delete the container with the command <code>docker rm kdepim-dev</code>, and run the container again.}}<br />
<br />
== Building and updating KDE PIM ==<br />
<br />
Once inside the container, you can use the following commands to compile the<br />
entire KDE PIM suite:<br />
<br />
sudo apt update<br />
sudo apt full-upgrade<br />
kdesrc-build kde-pim<br />
<br />
This will take a lot of time the first time, but all subsequent builds will be<br />
faster thanks to incremental builds and use of ccache. You can also use a specific repository name instead of the<br />
<code>kde-pim</code> group.<br />
<br />
Check the [https://kdesrc-build.kde.org kdesrc-build documentation] for more<br />
details about using kdesrc-build. For a start, you may want to customize the <code>-j</code> option in the <code>make-options</code> option group in /home/neon/.kdesrc-buildrc.<br />
<br />
kdesrc-build will clone all the repositories into <code>/home/neon/kdepim/src/kde/pim</code>.<br />
Build directories (where you can run <code>make</code> manually) are in <code>/home/neon/kdepim/build/kde/pim</code>.<br />
Two command-line aliases, <code>cb</code> and <code>cs</code>, switch back and forth between them.<br />
<br />
The binaries are installed to <code>/home/neon/kdepim/install</code>. The environment<br />
of the container is adjusted to work with the custom installation prefix.<br />
<br />
=== Fixing build errors ===<br />
If kdesrc-build cannot build from a repository, it will write a log file in <code>/home/neon/kdepim/logs/latest/''repository''/error.log</code>.<br />
<br />
It is often the case that some system has added a dependency on some package that is not installed.<br />
In that case, error.log will contain messages like<br />
<br />
Could not find a package configuration file provided by "Qt5Location" with<br />
any of the following names:<br />
<br />
Qt5LocationConfig.cmake<br />
qt5location-config.cmake<br />
<br />
To find the package, visit [https://packages.ubuntu.com/ Ubuntu Packages Search] and use the "Search the contents of packages" form to search for the cmake file, then use <code>sudo apt install</code> to install the package.<br />
<br />
== Development tools ==<br />
<br />
There's [https://www.kdevelop.org KDevelop] and [https://www.qt.io/ide/ QtCreator]<br />
preinstalled in the container and you can run them from there. You can also use<br />
them from outside of the container, but code completion might not work perfectly then.<br />
<br />
You can also use any other IDE of your choice either by installing it into the container<br />
with apt-get or use it from outside of the container.<br />
<br />
== Contributing ==<br />
<br />
You can find more details in the [[KDE_PIM/Development|Development]] section of our wiki. If you have any issues or questions, feel free to stop by on our IRC channel (#kontact) on Libera Chat, or talk to us on the [[KDE_PIM/Development#Mailing_Lists|kde-pim mailinglist]].</div>Gjditchfieldhttps://community.kde.org/index.php?title=KDE_PIM/Docker&diff=93236KDE PIM/Docker2021-09-29T19:04:21Z<p>Gjditchfield: Moved directory creation to first-time run.</p>
<hr />
<div>To make developing KDE PIM as easy as possible we have prepared a Docker image based on the KDE Neon distribution. It contains all the dependencies necessary to compile KDE PIM, the environment for running self-compiled KDE PIM and pre-configured tool (kdesrc-build) used to build latest KDE PIM from sources.<br />
<br />
== Set up Docker ==<br />
If you use KDE Neon, Ubuntu or Debian, run the following commands to install Docker and add yourself to the <code>docker</code> group, so you can use it:<br />
<br />
sudo apt install docker.io xserver-xephyr<br />
sudo usermod -aG docker $(whoami)<br />
newgrp docker<br />
<br />
{{Warning|It's important to note that any user added to the docker group is root equivalent. More information [https://github.com/docker/docker/issues/9976 here] and [https://docs.docker.com/engine/security/security/ here].}}<br />
<br />
On other distributions, please follow your distro's guide on how to set up Docker there, since the steps can differ slightly on different distributions.<br />
<br />
== Preparations ==<br />
Clone the git repository with the Dockerfile and support scripts.<br />
<br />
git clone git@invent.kde.org:pim/kdepim-docker.git<br />
cd kdepim-docker<br />
<br />
== Making OpenGL work in the container ==<br />
Several parts of KDE PIM depend on OpenGL - this is due to our use of QtWebEngine, which is based on Blink and has a hard dependency on OpenGL for rendering web pages. There's no way around that and so we need to make OpenGL work in the container. Unfortunately, that is not a very straightforward process and it differs for each GPU vendor and drivers used.<br />
<br />
=== NVIDIA proprietary drivers ===<br />
The easiest way is to use NVIDIA's nvidia-docker from [https://github.com/NVIDIA/nvidia-docker nvidia-docker Github]. You can follow the instructions on the Github page regarding how to install it. The nvidia-docker will automatically find your installed NVIDIA drivers and will expose them into the Docker container at runtime, so you don't have to rebuild your container whenever you upgrade your NVIDIA drivers.<br />
<br />
Note that if you do this, you must pass <code>-n</code> switch to the <code>build.sh</code> and <code>run.sh</code> scripts from the kdepim-docker.git repository.<br />
<br />
=== Nouveau (NVIDIA opensource drivers) ===<br />
TODO<br />
<br />
=== Intel ===<br />
Works out of the box<br />
<br />
=== AMD/ATI ===<br />
TODO<br />
<br />
== Building Docker image ==<br />
<br />
In order to build the Docker image, run the <code>build.sh</code> script. If you are<br />
using proprietary NVIDIA drivers, run the script with the <code>-n</code> switch.<br />
<br />
The command will create a Docker image called kdepim:dev.<br />
<br />
== Running the Docker container ==<br />
<br />
Create a directory where you want the source code, build folders, and everything else related to KDE PIM development to be stored. This is also where runtime data and configuration of Akonadi, Kontact and other apps you run inside the container will be stored. You will then expose this directory to the Docker container at runtime. The content of the directory will be available in the container in its<br />
<code>/home/neon/kdepim</code> directory.<br />
<br />
mkdir $HOME/kdepim-dev<br />
<br />
To run the container, use the <code>run.sh</code> script. When you run it for the first time, you must give it the path to the storage directory:<br />
<br />
run.sh $HOME/kdepim-dev<br />
<br />
If you are using proprietary NVIDIA drivers, run the script with the <code>-n</code> switch:<br />
<br />
run.sh -n $HOME/kdepim-dev<br />
<br />
The first run will create a container named after the storage directory. After the first run you can just use the name instead of the full path:<br />
<br />
run.sh kdepim-dev<br />
<br />
If you want to get another terminal window opened inside the same container, just run <code>run.sh</code> again. It will automatically create a new terminal on the already running container.<br />
<br />
{{note|In some systems, the container's <code>/home/neon/kdepim</code> directory may not be writable. If so, edit <code>run.sh</code> to remove the parameter <code>:rw,z</code> from the <code>-v</code> options. Then delete the container with the command <code>docker rm kdepim-dev</code>, and run the container again.}}<br />
<br />
== Building and updating KDE PIM ==<br />
<br />
Once inside the container, you can use the following commands to compile the<br />
entire KDE PIM suite:<br />
<br />
sudo apt update<br />
sudo apt full-upgrade<br />
kdesrc-build kde-pim<br />
<br />
This will take a lot of time the first time, but all subsequent builds will be<br />
faster thanks to incremental builds and use of ccache. You can also use a specific repository name instead of the<br />
<code>kde-pim</code> group.<br />
<br />
Check the [https://kdesrc-build.kde.org kdesrc-build documentation] for more<br />
details about using kdesrc-build. For a start, you may want to customize the <code>-j</code> option in the <code>make-options</code> option group in /home/neon/.kdesrc-buildrc.<br />
<br />
kdesrc-build will clone all the repositories into /home/neon/kdepim/src/kde/pim.<br />
Build directories (where you can run <code>make</code> manually) are in /home/neon/kdepim/build/kde/pim.<br />
The binaries are installed to /home/neon/kdepim/install. The environment<br />
of the container is adjusted to work with the custom installation prefix.<br />
<br />
=== Fixing build errors ===<br />
If kdesrc-build cannot build from a repository, it will write a log file in /home/neon/kdepim/logs/latest/''repository''/error.log.<br />
<br />
It is often the case that some system has added a dependency on some package that is not installed.<br />
In that case, error.log will contain messages like<br />
<br />
Could not find a package configuration file provided by "Qt5Location" with<br />
any of the following names:<br />
<br />
Qt5LocationConfig.cmake<br />
qt5location-config.cmake<br />
<br />
To find the package, visit [https://packages.ubuntu.com/ Ubuntu Packages Search] and use the "Search the contents of packages" form to search for the cmake file, then use <code>sudo apt install</code> to install the package.<br />
<br />
== Development tools ==<br />
<br />
There's [https://www.kdevelop.org KDevelop] and [https://www.qt.io/ide/ QtCreator]<br />
preinstalled in the container and you can run them from there. You can also use<br />
them from outside of the container, but code completion might not work perfectly then.<br />
<br />
You can also use any other IDE of your choice either by installing it into the container<br />
with apt-get or use it from outside of the container.<br />
<br />
== Contributing ==<br />
<br />
You can find more details in the [[KDE_PIM/Development|Development]] section of our wiki. If you have any issues or questions, feel free to stop by on our IRC channel (#kontact) on Libera Chat, or talk to us on the [[KDE_PIM/Development#Mailing_Lists|kde-pim mailinglist]].</div>Gjditchfieldhttps://community.kde.org/index.php?title=KDE_PIM/Docker&diff=93235KDE PIM/Docker2021-09-29T19:03:01Z<p>Gjditchfield: How to run a container by name instead of by directory path.</p>
<hr />
<div>To make developing KDE PIM as easy as possible we have prepared a Docker image based on the KDE Neon distribution. It contains all the dependencies necessary to compile KDE PIM, the environment for running self-compiled KDE PIM and pre-configured tool (kdesrc-build) used to build latest KDE PIM from sources.<br />
<br />
== Set up Docker ==<br />
If you use KDE Neon, Ubuntu or Debian, run the following commands to install Docker and add yourself to the <code>docker</code> group, so you can use it:<br />
<br />
sudo apt install docker.io xserver-xephyr<br />
sudo usermod -aG docker $(whoami)<br />
newgrp docker<br />
<br />
{{Warning|It's important to note that any user added to the docker group is root equivalent. More information [https://github.com/docker/docker/issues/9976 here] and [https://docs.docker.com/engine/security/security/ here].}}<br />
<br />
On other distributions, please follow your distro's guide on how to set up Docker there, since the steps can differ slightly on different distributions.<br />
<br />
== Preparations ==<br />
First, clone the git repository with the Dockerfile and support scripts.<br />
<br />
git clone git@invent.kde.org:pim/kdepim-docker.git<br />
cd kdepim-docker<br />
<br />
Next, create a directory where you want the sources code, build folders and everything else related to KDE PIM development to be stored. This is also where runtime data and configuration of Akonadi, Kontact and other apps you run inside the container will be stored. We will then expose this directory to the Docker container at runtime.<br />
<br />
mkdir ~/kdepim-dev<br />
<br />
== Making OpenGL work in the container ==<br />
Several parts of KDE PIM depend on OpenGL - this is due to our use of QtWebEngine, which is based on Blink and has a hard dependency on OpenGL for rendering web pages. There's no way around that and so we need to make OpenGL work in the container. Unfortunately, that is not a very straightforward process and it differs for each GPU vendor and drivers used.<br />
<br />
=== NVIDIA proprietary drivers ===<br />
The easiest way is to use NVIDIA's nvidia-docker from [https://github.com/NVIDIA/nvidia-docker nvidia-docker Github]. You can follow the instructions on the Github page regarding how to install it. The nvidia-docker will automatically find your installed NVIDIA drivers and will expose them into the Docker container at runtime, so you don't have to rebuild your container whenever you upgrade your NVIDIA drivers.<br />
<br />
Note that if you do this, you must pass <code>-n</code> switch to the <code>build.sh</code> and <code>run.sh</code> scripts from the kdepim-docker.git repository.<br />
<br />
=== Nouveau (NVIDIA opensource drivers) ===<br />
TODO<br />
<br />
=== Intel ===<br />
Works out of the box<br />
<br />
=== AMD/ATI ===<br />
TODO<br />
<br />
== Building Docker image ==<br />
<br />
In order to build the Docker image, run the <code>build.sh</code> script. If you are<br />
using proprietary NVIDIA drivers, run the script with the <code>-n</code> switch.<br />
<br />
The command will create a Docker image called kdepim:dev.<br />
<br />
== Running the Docker container ==<br />
<br />
Create a directory where you want the source code, build folders, and everything else related to KDE PIM development to be stored. This is also where runtime data and configuration of Akonadi, Kontact and other apps you run inside the container will be stored. You will then expose this directory to the Docker container at runtime. The content of the directory will be available in the container in its<br />
<code>/home/neon/kdepim</code> directory.<br />
<br />
mkdir $HOME/kdepim-dev<br />
<br />
To run the container, use the <code>run.sh</code> script. When you run it for the first time, you must give it the path to the storage directory:<br />
<br />
run.sh $HOME/kdepim-dev<br />
<br />
If you are using proprietary NVIDIA drivers, run the script with the <code>-n</code> switch:<br />
<br />
run.sh -n $HOME/kdepim-dev<br />
<br />
The first run will create a container named after the storage directory. After the first run you can just use the name instead of the full path:<br />
<br />
run.sh kdepim-dev<br />
<br />
If you want to get another terminal window opened inside the same container, just run <code>run.sh</code> again. It will automatically create a new terminal on the already running container.<br />
<br />
{{note|In some systems, the container's <code>/home/neon/kdepim</code> directory may not be writable. If so, edit <code>run.sh</code> to remove the parameter <code>:rw,z</code> from the <code>-v</code> options. Then delete the container with the command <code>docker rm kdepim-dev</code>, and run the container again.}}<br />
<br />
== Building and updating KDE PIM ==<br />
<br />
Once inside the container, you can use the following commands to compile the<br />
entire KDE PIM suite:<br />
<br />
sudo apt update<br />
sudo apt full-upgrade<br />
kdesrc-build kde-pim<br />
<br />
This will take a lot of time the first time, but all subsequent builds will be<br />
faster thanks to incremental builds and use of ccache. You can also use a specific repository name instead of the<br />
<code>kde-pim</code> group.<br />
<br />
Check the [https://kdesrc-build.kde.org kdesrc-build documentation] for more<br />
details about using kdesrc-build. For a start, you may want to customize the <code>-j</code> option in the <code>make-options</code> option group in /home/neon/.kdesrc-buildrc.<br />
<br />
kdesrc-build will clone all the repositories into /home/neon/kdepim/src/kde/pim.<br />
Build directories (where you can run <code>make</code> manually) are in /home/neon/kdepim/build/kde/pim.<br />
The binaries are installed to /home/neon/kdepim/install. The environment<br />
of the container is adjusted to work with the custom installation prefix.<br />
<br />
=== Fixing build errors ===<br />
If kdesrc-build cannot build from a repository, it will write a log file in /home/neon/kdepim/logs/latest/''repository''/error.log.<br />
<br />
It is often the case that some system has added a dependency on some package that is not installed.<br />
In that case, error.log will contain messages like<br />
<br />
Could not find a package configuration file provided by "Qt5Location" with<br />
any of the following names:<br />
<br />
Qt5LocationConfig.cmake<br />
qt5location-config.cmake<br />
<br />
To find the package, visit [https://packages.ubuntu.com/ Ubuntu Packages Search] and use the "Search the contents of packages" form to search for the cmake file, then use <code>sudo apt install</code> to install the package.<br />
<br />
== Development tools ==<br />
<br />
There's [https://www.kdevelop.org KDevelop] and [https://www.qt.io/ide/ QtCreator]<br />
preinstalled in the container and you can run them from there. You can also use<br />
them from outside of the container, but code completion might not work perfectly then.<br />
<br />
You can also use any other IDE of your choice either by installing it into the container<br />
with apt-get or use it from outside of the container.<br />
<br />
== Contributing ==<br />
<br />
You can find more details in the [[KDE_PIM/Development|Development]] section of our wiki. If you have any issues or questions, feel free to stop by on our IRC channel (#kontact) on Libera Chat, or talk to us on the [[KDE_PIM/Development#Mailing_Lists|kde-pim mailinglist]].</div>Gjditchfieldhttps://community.kde.org/index.php?title=KDE_PIM/Docker&diff=93234KDE PIM/Docker2021-09-29T18:33:16Z<p>Gjditchfield: /* Preparations */</p>
<hr />
<div>To make developing KDE PIM as easy as possible we have prepared a Docker image based on the KDE Neon distribution. It contains all the dependencies necessary to compile KDE PIM, the environment for running self-compiled KDE PIM and pre-configured tool (kdesrc-build) used to build latest KDE PIM from sources.<br />
<br />
== Set up Docker ==<br />
If you use KDE Neon, Ubuntu or Debian, run the following commands to install Docker and add yourself to the <code>docker</code> group, so you can use it:<br />
<br />
sudo apt install docker.io xserver-xephyr<br />
sudo usermod -aG docker $(whoami)<br />
newgrp docker<br />
<br />
{{Warning|It's important to note that any user added to the docker group is root equivalent. More information [https://github.com/docker/docker/issues/9976 here] and [https://docs.docker.com/engine/security/security/ here].}}<br />
<br />
On other distributions, please follow your distro's guide on how to set up Docker there, since the steps can differ slightly on different distributions.<br />
<br />
== Preparations ==<br />
First, clone the git repository with the Dockerfile and support scripts.<br />
<br />
git clone git@invent.kde.org:pim/kdepim-docker.git<br />
cd kdepim-docker<br />
<br />
Next, create a directory where you want the sources code, build folders and everything else related to KDE PIM development to be stored. This is also where runtime data and configuration of Akonadi, Kontact and other apps you run inside the container will be stored. We will then expose this directory to the Docker container at runtime.<br />
<br />
mkdir ~/kdepim-dev<br />
<br />
== Making OpenGL work in the container ==<br />
Several parts of KDE PIM depend on OpenGL - this is due to our use of QtWebEngine, which is based on Blink and has a hard dependency on OpenGL for rendering web pages. There's no way around that and so we need to make OpenGL work in the container. Unfortunately, that is not a very straightforward process and it differs for each GPU vendor and drivers used.<br />
<br />
=== NVIDIA proprietary drivers ===<br />
The easiest way is to use NVIDIA's nvidia-docker from [https://github.com/NVIDIA/nvidia-docker nvidia-docker Github]. You can follow the instructions on the Github page regarding how to install it. The nvidia-docker will automatically find your installed NVIDIA drivers and will expose them into the Docker container at runtime, so you don't have to rebuild your container whenever you upgrade your NVIDIA drivers.<br />
<br />
Note that if you do this, you must pass <code>-n</code> switch to the <code>build.sh</code> and <code>run.sh</code> scripts from the kdepim-docker.git repository.<br />
<br />
=== Nouveau (NVIDIA opensource drivers) ===<br />
TODO<br />
<br />
=== Intel ===<br />
Works out of the box<br />
<br />
=== AMD/ATI ===<br />
TODO<br />
<br />
== Building Docker image ==<br />
<br />
In order to build the Docker image, run the <code>build.sh</code> script. If you are<br />
using proprietary NVIDIA drivers, run the script with the <code>-n</code> switch.<br />
<br />
The command will create a Docker image called kdepim:dev.<br />
<br />
== Running the Docker container ==<br />
<br />
To run the container, use the <code>run.sh</code> script:<br />
<br />
run.sh $HOME/kdepim-dev<br />
<br />
If you are using proprietary NVIDIA drivers, run the script with the <code>-n</code> switch:<br />
<br />
run.sh -n $HOME/kdepim-dev<br />
<br />
The content of the directory will be available in the container in the<br />
/home/neon/kdepim directory.<br />
<br />
If you want to get another terminal window opened inside the same container, just run run.sh again. It will automatically create a new terminal on the already running container.<br />
<br />
''Note:''<br />
<ul>In some systems, the container's /home/neon/kdepim directory may not be writable. If so, edit <code>run.sh</code> to remove the parameter <code>:rw,z</code> from the <code>-v</code> options. Then delete the container with the command <code>docker rm kdepim-dev</code>, and run the container again.<br />
</ul><br />
<br />
== Building and updating KDE PIM ==<br />
<br />
Once inside the container, you can use the following commands to compile the<br />
entire KDE PIM suite:<br />
<br />
sudo apt update<br />
sudo apt full-upgrade<br />
kdesrc-build kde-pim<br />
<br />
This will take a lot of time the first time, but all subsequent builds will be<br />
faster thanks to incremental builds and use of ccache. You can also use a specific repository name instead of the<br />
<code>kde-pim</code> group.<br />
<br />
Check the [https://kdesrc-build.kde.org kdesrc-build documentation] for more<br />
details about using kdesrc-build. For a start, you may want to customize the <code>-j</code> option in the <code>make-options</code> option group in /home/neon/.kdesrc-buildrc.<br />
<br />
kdesrc-build will clone all the repositories into /home/neon/kdepim/src/kde/pim.<br />
Build directories (where you can run <code>make</code> manually) are in /home/neon/kdepim/build/kde/pim.<br />
The binaries are installed to /home/neon/kdepim/install. The environment<br />
of the container is adjusted to work with the custom installation prefix.<br />
<br />
=== Fixing build errors ===<br />
If kdesrc-build cannot build from a repository, it will write a log file in /home/neon/kdepim/logs/latest/''repository''/error.log.<br />
<br />
It is often the case that some system has added a dependency on some package that is not installed.<br />
In that case, error.log will contain messages like<br />
<br />
Could not find a package configuration file provided by "Qt5Location" with<br />
any of the following names:<br />
<br />
Qt5LocationConfig.cmake<br />
qt5location-config.cmake<br />
<br />
To find the package, visit [https://packages.ubuntu.com/ Ubuntu Packages Search] and use the "Search the contents of packages" form to search for the cmake file, then use <code>sudo apt install</code> to install the package.<br />
<br />
== Development tools ==<br />
<br />
There's [https://www.kdevelop.org KDevelop] and [https://www.qt.io/ide/ QtCreator]<br />
preinstalled in the container and you can run them from there. You can also use<br />
them from outside of the container, but code completion might not work perfectly then.<br />
<br />
You can also use any other IDE of your choice either by installing it into the container<br />
with apt-get or use it from outside of the container.<br />
<br />
== Contributing ==<br />
<br />
You can find more details in the [[KDE_PIM/Development|Development]] section of our wiki. If you have any issues or questions, feel free to stop by on our IRC channel (#kontact) on Libera Chat, or talk to us on the [[KDE_PIM/Development#Mailing_Lists|kde-pim mailinglist]].</div>Gjditchfieldhttps://community.kde.org/index.php?title=Guidelines_and_HOWTOs/Debugging/Using_Error_Messages&diff=92572Guidelines and HOWTOs/Debugging/Using Error Messages2021-07-19T18:59:45Z<p>Gjditchfield: /* Improving Logging Output */</p>
<hr />
<div>= Qt 5 / KDE Frameworks 5 =<br />
<br />
<tt>kDebug()</tt> and friends have been deprecated in KDE Frameworks 5, and you should use Qt's built-in debugging instead. We recommend that you use [https://doc.qt.io/qt-5/qloggingcategory.html QLoggingCategory], particularly for libraries and plugins. Note that this is only available in Qt 5.2 and later.<br />
<br />
== Controlling Messages ==<br />
The source code for a library, plugin, or program named "Foo" may contain statements like<br />
<syntaxhighlight lang="cpp-qt"><br />
qCDebug(LOG_FOO) << "Log something:" << someVariable;<br />
</syntaxhighlight><br />
Here, <tt>LOG_FOO</tt> is a ''logging category''. If debug-level messages have been enabled for that logging category, then the statement will write a message to <tt>stderr</tt>.<br />
<br />
Some source file will define the logging category:<br />
<syntaxhighlight lang="cpp-qt"><br />
Q_LOGGING_CATEGORY(LOG_FOO, "some.namespace.foo")<br />
</syntaxhighlight><br />
Here, <tt>"some.namespace.foo"</tt> is the ''category name''. Once you know the category's name, you can<br />
set the <tt>QT_LOGGING_RULES</tt> environment variable to enable debug-level messages for the category:<br />
<syntaxhighlight lang="bash"><br />
QT_LOGGING_RULES="some.namespace.foo.debug=true"<br />
</syntaxhighlight><br />
<br />
Logging rules can be more complex than the example above. They can specify wildcards in the category name, enable or disable more than one message level, and control more than one logging category.<br />
They can also be stored in various configuration files. Please see the [https://doc.qt.io/qt-5/qloggingcategory.html#logging-rules QLoggingCategory documentation] for details.<br />
<br />
If you run the application from within a terminal application, like [http://www.kde.org/applications/system/konsole/ Konsole], you will see the logging output in that terminal window. If you use an Integrated Development Environment like [https://kde.org/applications/en/development/org.kde.kdevelop KDevelop] it will display the output in its windows. Otherwise, the messages will usually appear in {{path|~/.xsession-errors}} or {{path|~/.X.err}} if you use a login manager like KDM, or on the console you ran <tt>startx</tt> from if you started X that way. Also check the systemd log, since the application could be its direct child process.<br />
<br />
== Adding Logging to Your Code ==<br />
For a library or plugin called "Foo", you should have a common header that contains the following declaration (e.g. called "foo-debug.h")<br />
<syntaxhighlight lang="cpp-qt"><br />
#include <QLoggingCategory><br />
Q_DECLARE_LOGGING_CATEGORY(LOG_FOO)<br />
</syntaxhighlight> and exactly one source file containing <syntaxhighlight lang="cpp-qt"><br />
#include "foo-debug.h"<br />
Q_LOGGING_CATEGORY(LOG_FOO, "some.namespace.foo")<br />
</syntaxhighlight><br />
<br />
You should treat the category name (<tt>"some.namespace.foo"</tt> in the example) as something like reverse DNS; it cannot contain spaces, and dots indicate a hierarchy. For example, KDE PIM category names all start with <tt>"org.kde.pim."</tt>.<br />
<br />
To simplify the setup, you can use the ECM macro <tt>[https://api.kde.org/ecm/module/ECMQtDeclareLoggingCategory.html ecm_qt_declare_logging_category]</tt>, which generates the respective source files for you:<br />
<syntaxhighlight lang="cmake"><br />
include(ECMQtDeclareLoggingCategory)<br />
ecm_qt_declare_logging_category(FOO_SRCS<br />
HEADER foo-debug.h<br />
IDENTIFIER "LOG_FOO"<br />
CATEGORY_NAME "some.namespace.foo"<br />
)<br />
</syntaxhighlight><br />
<br />
Logging lines then look like <syntaxhighlight lang="cpp-qt"><br />
#include "foo-debug.hpp"<br />
qCDebug(LOG_FOO) << "Log something:" << someVariable;<br />
qCWarning(LOG_FOO) << "Something bad happened that users (end-users, or application developers using this library) should be aware of";<br />
qCCritical(LOG_FOO) << "Something happened so bad we had to terminate the application";<br />
</syntaxhighlight><br />
<br />
The syntax is much like <tt>cout</tt>, and most native C++ types, Qt-provided types and KF-provided types can be passed directly (like with <tt>someVariable</tt> in the example).<br />
<br />
With Qt 5.2, the <tt>qCDebug</tt> line will not produce any output; this is because logging categories are disabled by default. You need to include the line <syntaxhighlight lang="cpp-qt"><br />
QLoggingCategory::setFilterRules(QStringLiteral("foo.debug = true"));<br />
</syntaxhighlight><br />
somewhere in the application code, generally in the <tt>main()</tt> function. Of course, you would typically disable this call in release versions.<br />
<br />
== Improving Logging Output ==<br />
<br />
Qt provides a way of controlling the output of the logging methods via an environment variable. You can tell it to include the application name and PID, as well as the debugging category, and color-code the text. For example, running the following lines in your shell will produce something that looks quite like <tt>kDebug</tt>'s colored output: <syntaxhighlight lang="bash"><br />
c=`echo -e "\033"`<br />
export QT_MESSAGE_PATTERN="%{appname}(%{pid})/(%{category}) $c\[31m%{if-debug}$c\[34m%{endif}%{function}$c\[0m: %{message}"<br />
unset c<br />
</syntaxhighlight><br />
See [https://doc.qt.io/qt-5/qtglobal.html#qSetMessagePattern qSetMessagePattern documentation] for the full list of placeholders.<br />
<br />
== Managing Lots of Output ==<br />
<br />
If you have lots of debugging statements, they may appear too fast and leave the terminal window before you can read them. There are three main ways to deal with this:<br />
# [https://doc.qt.io/qt-5/qloggingcategory.html#setFilterRules Disable some logging categories] to limit the amount of output generated<br />
# Increase the amount of scrollback in the terminal so that output is not lost; in Konsole, you can go to <tt>Settings > Edit Current Profile...</tt> and click on the <tt>Scrollback</tt> tab to change this. Konsole also has a useful search feature: just press <tt>Ctrl+Shift+F</tt> or click <tt>Find...</tt> on the <tt>Edit</tt> menu.<br />
# Save the output to a file; <tt>tee</tt> is useful for this. For example, you can run <syntaxhighlight lang="bash">application 2>&1 | tee debug.log</syntaxhighlight> to save the output to the file <tt>debug.log</tt> while still viewing it in the terminal.<br />
<br />
= Qt 4 / kdelibs 4 =<br />
<br />
kdelibs provides a [http://api.kde.org/4.0-api/kdelibs-apidocs/kdecore/html/group__kdebug.html family of functions] that output information to <tt>stderr</tt>, meaning that if you run an application from the terminal, it will be displayed in that terminal window. If you run the application from the desktop (using KRunner or the application menu, for example), the output will normally appear in {{path|~/.xsession-errors}} or {{path|~/.X.err}} if you use a login manager like KDM, or on the console you ran <tt>startx</tt> from if you started X that way.<br />
<br />
To use these functions in your code, you need to include the correct header file <syntaxhighlight lang="cpp-qt"><br />
#include <KDebug><br />
</syntaxhighlight><br />
and then you can use the functions <syntaxhighlight lang="cpp-qt"><br />
kDebug() << "Something happened that only developers care about" << someVariable;<br />
kWarning() << "Something bad happened that users (end-users, or application developers using this library) should be aware of";<br />
kError() << "Something even worse happened";<br />
kFatal() << "Something happened so bad we had to terminate the application";<br />
</syntaxhighlight><br />
<br />
The syntax is much like <tt>cout</tt>, and most native C++ types, Qt-provided types and kdelibs-provided types can be passed directly (like with <tt>someVariable</tt> in the example).<br />
<br />
Note that the <tt>kDebug</tt> calls will only do anything if the code was compiled with debugging enabled (and so will generally not work in packages from a distribution). This means <tt>cmake</tt> should be run with the <tt>-DCMAKE_BUILD_TYPE=debugfull</tt> argument. The other functions, however, will produce output no matter how the code was compiled.<br />
<br />
== Debug Areas ==<br />
<br />
The debugging output can be controlled at runtime using debugging areas. This allows enabling debugging output for only certain libraries or plugins, for example. Debugging areas are numbers, so the <tt>KStatusNotifierItemPrivate::registerToDaemon</tt> method in the kdeui library, for example, has the call <syntaxhighlight lang="cpp-qt"><br />
kDebug(299) << "Registering a client interface to the KStatusNotifierWatcher";<br />
</syntaxhighlight><br />
The file <tt>kdebug.areas</tt> in the <tt>kdecore</tt> directory of kdelibs indicates that the number 299 is associated with "kdeui (KNotification)".<br />
<br />
This information is used by the <tt>kdebugdialog</tt> utility (which you can just run from the commandline or using KRunner) to turn these areas on and off, enabling or disabling those <tt>kDebug</tt> statements. It is also used by <tt>kDebug</tt>, <tt>kWarning</tt>, <tt>kError</tt> and <tt>kFatal</tt> to indicate which component output the line. For example, the line in the above example would produce the line <pre>kwalletmanager(642)/kdeui (KNotification) KStatusNotifierItemPrivate::registerToDaemon: Registering a client interface to the KStatusNotifierWatcher</pre><br />
when executed from within the application kwalletmanager, with PID 642.<br />
<br />
For applications, you can generally just omit the area number, and <tt>kDebug</tt> will use the default area. If you are developing a library or a plugin, though, you should get a number assigned (via the kde-core-devel mailing list) for your library or plugin, and use it in your code. Rather than using the number directly in every call to <tt>kDebug</tt> and friends, you can just add<syntaxhighlight lang="cmake"><br />
add_definition(-DKDE_DEFAULT_DEBUG_AREA=<number>)<br />
</syntaxhighlight><br />
to your <tt>CMakeLists.txt</tt> file.<br />
<br />
== Improving Log Output ==<br />
<br />
There are a couple of useful environment variables to control the output of <tt>kDebug</tt> and friends. <syntaxhighlight lang="bash"><br />
export KDE_COLOR_DEBUG=1<br />
</syntaxhighlight> will make them produce colored output, and <syntaxhighlight lang="bash"><br />
export KDE_DEBUG_TIMESTAMP=1<br />
</syntaxhighlight> will include timestamps in the output. <syntaxhighlight lang="bash"><br />
export KDE_DEBUG_TIMESTAMP=2<br />
</syntaxhighlight> can be used to include milliseconds in the timestamps.<br />
<br />
<br />
<br />
<syntaxhighlight lang="bash"><br />
export KDE_DEBUG_NOPROCESSINFO=1<br />
export KDE_DEBUG_NOAREANAME=1<br />
export KDE_DEBUG_NOMETHODNAME=1<br />
export KDE_DEBUG_FILELINE=1<br />
</syntaxhighlight>The above commands toggle various components of the debug messages.<br />
<br />
== Managing Lots of Output ==<br />
<br />
If you have lots of debugging statements, they may appear too fast and leave the terminal window before you can read them. There are three main ways to deal with this:<br />
# Use <tt>kdebugdialog</tt> to disable some logging areas to limit the amount of output generated<br />
# Increase the amount of scrollback in the terminal so that output is not lost; in Konsole, you can go to <tt>Settings > Edit Current Profile...</tt> and click on the <tt>Scrollback</tt> tab to change this. Konsole also has a useful search feature: just press <tt>Ctrl+Shift+F</tt> or click <tt>Find...</tt> on the <tt>Edit</tt> menu.<br />
# Save the output to a file; <tt>tee</tt> is useful for this. For example, you can run <pre>application 2&gt;&amp;1 | tee debug.log</pre> to save the output to the file <tt>debug.log</tt> while still viewing it in the terminal. This can also be used to capture output from <tt>startx</tt>.</div>Gjditchfieldhttps://community.kde.org/index.php?title=Guidelines_and_HOWTOs/Debugging/Using_Error_Messages&diff=92571Guidelines and HOWTOs/Debugging/Using Error Messages2021-07-19T18:45:31Z<p>Gjditchfield: /* Controlling Messages */</p>
<hr />
<div>= Qt 5 / KDE Frameworks 5 =<br />
<br />
<tt>kDebug()</tt> and friends have been deprecated in KDE Frameworks 5, and you should use Qt's built-in debugging instead. We recommend that you use [https://doc.qt.io/qt-5/qloggingcategory.html QLoggingCategory], particularly for libraries and plugins. Note that this is only available in Qt 5.2 and later.<br />
<br />
== Controlling Messages ==<br />
The source code for a library, plugin, or program named "Foo" may contain statements like<br />
<syntaxhighlight lang="cpp-qt"><br />
qCDebug(LOG_FOO) << "Log something:" << someVariable;<br />
</syntaxhighlight><br />
Here, <tt>LOG_FOO</tt> is a ''logging category''. If debug-level messages have been enabled for that logging category, then the statement will write a message to <tt>stderr</tt>.<br />
<br />
Some source file will define the logging category:<br />
<syntaxhighlight lang="cpp-qt"><br />
Q_LOGGING_CATEGORY(LOG_FOO, "some.namespace.foo")<br />
</syntaxhighlight><br />
Here, <tt>"some.namespace.foo"</tt> is the ''category name''. Once you know the category's name, you can<br />
set the <tt>QT_LOGGING_RULES</tt> environment variable to enable debug-level messages for the category:<br />
<syntaxhighlight lang="bash"><br />
QT_LOGGING_RULES="some.namespace.foo.debug=true"<br />
</syntaxhighlight><br />
<br />
Logging rules can be more complex than the example above. They can specify wildcards in the category name, enable or disable more than one message level, and control more than one logging category.<br />
They can also be stored in various configuration files. Please see the [https://doc.qt.io/qt-5/qloggingcategory.html#logging-rules QLoggingCategory documentation] for details.<br />
<br />
If you run the application from within a terminal application, like [http://www.kde.org/applications/system/konsole/ Konsole], you will see the logging output in that terminal window. If you use an Integrated Development Environment like [https://kde.org/applications/en/development/org.kde.kdevelop KDevelop] it will display the output in its windows. Otherwise, the messages will usually appear in {{path|~/.xsession-errors}} or {{path|~/.X.err}} if you use a login manager like KDM, or on the console you ran <tt>startx</tt> from if you started X that way. Also check the systemd log, since the application could be its direct child process.<br />
<br />
== Adding Logging to Your Code ==<br />
For a library or plugin called "Foo", you should have a common header that contains the following declaration (e.g. called "foo-debug.h")<br />
<syntaxhighlight lang="cpp-qt"><br />
#include <QLoggingCategory><br />
Q_DECLARE_LOGGING_CATEGORY(LOG_FOO)<br />
</syntaxhighlight> and exactly one source file containing <syntaxhighlight lang="cpp-qt"><br />
#include "foo-debug.h"<br />
Q_LOGGING_CATEGORY(LOG_FOO, "some.namespace.foo")<br />
</syntaxhighlight><br />
<br />
You should treat the category name (<tt>"some.namespace.foo"</tt> in the example) as something like reverse DNS; it cannot contain spaces, and dots indicate a hierarchy. For example, KDE PIM category names all start with <tt>"org.kde.pim."</tt>.<br />
<br />
To simplify the setup, you can use the ECM macro <tt>[https://api.kde.org/ecm/module/ECMQtDeclareLoggingCategory.html ecm_qt_declare_logging_category]</tt>, which generates the respective source files for you:<br />
<syntaxhighlight lang="cmake"><br />
include(ECMQtDeclareLoggingCategory)<br />
ecm_qt_declare_logging_category(FOO_SRCS<br />
HEADER foo-debug.h<br />
IDENTIFIER "LOG_FOO"<br />
CATEGORY_NAME "some.namespace.foo"<br />
)<br />
</syntaxhighlight><br />
<br />
Logging lines then look like <syntaxhighlight lang="cpp-qt"><br />
#include "foo-debug.hpp"<br />
qCDebug(LOG_FOO) << "Log something:" << someVariable;<br />
qCWarning(LOG_FOO) << "Something bad happened that users (end-users, or application developers using this library) should be aware of";<br />
qCCritical(LOG_FOO) << "Something happened so bad we had to terminate the application";<br />
</syntaxhighlight><br />
<br />
The syntax is much like <tt>cout</tt>, and most native C++ types, Qt-provided types and KF-provided types can be passed directly (like with <tt>someVariable</tt> in the example).<br />
<br />
With Qt 5.2, the <tt>qCDebug</tt> line will not produce any output; this is because logging categories are disabled by default. You need to include the line <syntaxhighlight lang="cpp-qt"><br />
QLoggingCategory::setFilterRules(QStringLiteral("foo.debug = true"));<br />
</syntaxhighlight><br />
somewhere in the application code, generally in the <tt>main()</tt> function. Of course, you would typically disable this call in release versions.<br />
<br />
== Improving Logging Output ==<br />
<br />
Qt provides a way of controlling the output of the logging methods via an environment variable. You can tell it to include the application name and PID, as well as the debugging category, and color-code the text. For example, running the following lines in your shell will produce something that looks quite like <tt>kDebug</tt>'s colored output: <syntaxhighlight lang="bash"><br />
c=`echo -e "\033"`<br />
export QT_MESSAGE_PATTERN="%{appname}(%{pid})/(%{category}) $c\[31m%{if-debug}$c\[34m%{endif}%{function}$c\[0m: %{message}"<br />
unset c<br />
</syntaxhighlight><br />
<br />
== Managing Lots of Output ==<br />
<br />
If you have lots of debugging statements, they may appear too fast and leave the terminal window before you can read them. There are three main ways to deal with this:<br />
# [https://doc.qt.io/qt-5/qloggingcategory.html#setFilterRules Disable some logging categories] to limit the amount of output generated<br />
# Increase the amount of scrollback in the terminal so that output is not lost; in Konsole, you can go to <tt>Settings > Edit Current Profile...</tt> and click on the <tt>Scrollback</tt> tab to change this. Konsole also has a useful search feature: just press <tt>Ctrl+Shift+F</tt> or click <tt>Find...</tt> on the <tt>Edit</tt> menu.<br />
# Save the output to a file; <tt>tee</tt> is useful for this. For example, you can run <syntaxhighlight lang="bash">application 2>&1 | tee debug.log</syntaxhighlight> to save the output to the file <tt>debug.log</tt> while still viewing it in the terminal.<br />
<br />
= Qt 4 / kdelibs 4 =<br />
<br />
kdelibs provides a [http://api.kde.org/4.0-api/kdelibs-apidocs/kdecore/html/group__kdebug.html family of functions] that output information to <tt>stderr</tt>, meaning that if you run an application from the terminal, it will be displayed in that terminal window. If you run the application from the desktop (using KRunner or the application menu, for example), the output will normally appear in {{path|~/.xsession-errors}} or {{path|~/.X.err}} if you use a login manager like KDM, or on the console you ran <tt>startx</tt> from if you started X that way.<br />
<br />
To use these functions in your code, you need to include the correct header file <syntaxhighlight lang="cpp-qt"><br />
#include <KDebug><br />
</syntaxhighlight><br />
and then you can use the functions <syntaxhighlight lang="cpp-qt"><br />
kDebug() << "Something happened that only developers care about" << someVariable;<br />
kWarning() << "Something bad happened that users (end-users, or application developers using this library) should be aware of";<br />
kError() << "Something even worse happened";<br />
kFatal() << "Something happened so bad we had to terminate the application";<br />
</syntaxhighlight><br />
<br />
The syntax is much like <tt>cout</tt>, and most native C++ types, Qt-provided types and kdelibs-provided types can be passed directly (like with <tt>someVariable</tt> in the example).<br />
<br />
Note that the <tt>kDebug</tt> calls will only do anything if the code was compiled with debugging enabled (and so will generally not work in packages from a distribution). This means <tt>cmake</tt> should be run with the <tt>-DCMAKE_BUILD_TYPE=debugfull</tt> argument. The other functions, however, will produce output no matter how the code was compiled.<br />
<br />
== Debug Areas ==<br />
<br />
The debugging output can be controlled at runtime using debugging areas. This allows enabling debugging output for only certain libraries or plugins, for example. Debugging areas are numbers, so the <tt>KStatusNotifierItemPrivate::registerToDaemon</tt> method in the kdeui library, for example, has the call <syntaxhighlight lang="cpp-qt"><br />
kDebug(299) << "Registering a client interface to the KStatusNotifierWatcher";<br />
</syntaxhighlight><br />
The file <tt>kdebug.areas</tt> in the <tt>kdecore</tt> directory of kdelibs indicates that the number 299 is associated with "kdeui (KNotification)".<br />
<br />
This information is used by the <tt>kdebugdialog</tt> utility (which you can just run from the commandline or using KRunner) to turn these areas on and off, enabling or disabling those <tt>kDebug</tt> statements. It is also used by <tt>kDebug</tt>, <tt>kWarning</tt>, <tt>kError</tt> and <tt>kFatal</tt> to indicate which component output the line. For example, the line in the above example would produce the line <pre>kwalletmanager(642)/kdeui (KNotification) KStatusNotifierItemPrivate::registerToDaemon: Registering a client interface to the KStatusNotifierWatcher</pre><br />
when executed from within the application kwalletmanager, with PID 642.<br />
<br />
For applications, you can generally just omit the area number, and <tt>kDebug</tt> will use the default area. If you are developing a library or a plugin, though, you should get a number assigned (via the kde-core-devel mailing list) for your library or plugin, and use it in your code. Rather than using the number directly in every call to <tt>kDebug</tt> and friends, you can just add<syntaxhighlight lang="cmake"><br />
add_definition(-DKDE_DEFAULT_DEBUG_AREA=<number>)<br />
</syntaxhighlight><br />
to your <tt>CMakeLists.txt</tt> file.<br />
<br />
== Improving Log Output ==<br />
<br />
There are a couple of useful environment variables to control the output of <tt>kDebug</tt> and friends. <syntaxhighlight lang="bash"><br />
export KDE_COLOR_DEBUG=1<br />
</syntaxhighlight> will make them produce colored output, and <syntaxhighlight lang="bash"><br />
export KDE_DEBUG_TIMESTAMP=1<br />
</syntaxhighlight> will include timestamps in the output. <syntaxhighlight lang="bash"><br />
export KDE_DEBUG_TIMESTAMP=2<br />
</syntaxhighlight> can be used to include milliseconds in the timestamps.<br />
<br />
<br />
<br />
<syntaxhighlight lang="bash"><br />
export KDE_DEBUG_NOPROCESSINFO=1<br />
export KDE_DEBUG_NOAREANAME=1<br />
export KDE_DEBUG_NOMETHODNAME=1<br />
export KDE_DEBUG_FILELINE=1<br />
</syntaxhighlight>The above commands toggle various components of the debug messages.<br />
<br />
== Managing Lots of Output ==<br />
<br />
If you have lots of debugging statements, they may appear too fast and leave the terminal window before you can read them. There are three main ways to deal with this:<br />
# Use <tt>kdebugdialog</tt> to disable some logging areas to limit the amount of output generated<br />
# Increase the amount of scrollback in the terminal so that output is not lost; in Konsole, you can go to <tt>Settings > Edit Current Profile...</tt> and click on the <tt>Scrollback</tt> tab to change this. Konsole also has a useful search feature: just press <tt>Ctrl+Shift+F</tt> or click <tt>Find...</tt> on the <tt>Edit</tt> menu.<br />
# Save the output to a file; <tt>tee</tt> is useful for this. For example, you can run <pre>application 2&gt;&amp;1 | tee debug.log</pre> to save the output to the file <tt>debug.log</tt> while still viewing it in the terminal. This can also be used to capture output from <tt>startx</tt>.</div>Gjditchfieldhttps://community.kde.org/index.php?title=Guidelines_and_HOWTOs/UnitTests&diff=92326Guidelines and HOWTOs/UnitTests2021-07-08T15:45:53Z<p>Gjditchfield: Mention ecm_add_tests, link to ECM documentation.</p>
<hr />
<div>: '''Author:''' Brad Hards, Sigma Bravo Pty Limited<br />
<br />
== Abstract ==<br />
<br />
This article provides guidance on writing unittests for software based on Qt<br />
and KDE frameworks. It uses the [https://doc.qt.io/qt-5/qttest-index.html QtTestLib framework] provided starting with Qt 4.1. It provides an introduction to the ideas behind unit testing, tutorial material on the [https://doc.qt.io/qt-5/qttest-index.html QtTestLib framework], and suggestions for getting the most value for your effort.<br />
<br />
== About Unit Testing ==<br />
<br />
A unit test is a test that checks the functionality, behaviour and correctness of a single software component. In Qt code unit tests are almost always used to test a single C++ class (although testing a macro or C function is also possible).<br />
<br />
Unit tests are a key part of Test Driven Development, however they are useful for all software development processes. It is not essential that all of the code is covered by unit tests (although that is obviously very desirable!). Even a single test is a useful step to improving code quality.<br />
<br />
Note that unit tests are dynamic tests (i.e. they run, using the compiled code) rather than static analysis tests (which operate on the source or some intermediate representation).<br />
<br />
Even if they don't call them "unit tests", most programmers have written some "throwaway" code that they use to check an implementation. If that code was cleaned up a little, and built into the development system, then it could be used over and over to check that the implementation is still OK. To make that work a little easier, we can use test frameworks.<br />
<br />
Note that it is sometimes tempting to treat the unit test as a pure verification tool. While it is true that unit tests do help to ensure correct functionality and behaviour, they also assist with other aspects of code quality. Writing a unit test requires a slightly different approach to coding up a class, and thinking about what inputs need to be tested can help to identify logic flaws in the code (even before the tests get run). In addition, the need to make the code testable is a very useful driver to ensure that classes do not suffer from close coupling.<br />
<br />
Anyway, enough of the conceptual stuff - lets talk about a specific tool that can reduce some of the effort and let us get on with the job.<br />
<br />
==About QtTestLib==<br />
<br />
QtTestlib is a lightweight testing library developed by the Qt Project and released under the LGPL (a commercial version is also available, for those who need alternative licensing). It is written in C++, and is cross-platform. It is provided as part of the tools included in Qt.<br />
<br />
In addition to normal unit testing capabilities, QtTestLib also offers basic GUI testing, based on sending QEvents. This allows you to test GUI widgets, but is not generally suitable for testing full applications.<br />
<br />
Each testcase is a standalone test application. Unlike CppUnit or JUnit, there is no Runner type class. Instead, each testcase is an executable which is simply run.<br />
<br />
== Tutorial 1: A simple test of a date class ==<br />
<br />
In this tutorial, we will build a simple test for a class that represents a date, using QtTestLib as the test framework. To avoid too much detail on how the date class works, we'll just use the QDate class that comes with Qt. In a normal unittest, you would more likely be testing code that you've written yourself.<br />
<br />
The code below is the entire testcase.<br />
<br />
<br />
'''Example 1. QDate test code'''<br />
<syntaxhighlight lang="cpp-qt" line="GESHI_NORMAL_LINE_NUMBERS"><br />
#include <QTest><br />
#include <QDate><br />
<br />
class testDate: public QObject<br />
{<br />
Q_OBJECT<br />
private slots:<br />
void testValidity();<br />
void testMonth();<br />
};<br />
<br />
void testDate::testValidity()<br />
{<br />
// 11 March 1967<br />
QDate date( 1967, 3, 11 );<br />
QVERIFY( date.isValid() );<br />
}<br />
<br />
void testDate::testMonth()<br />
{<br />
// 11 March 1967<br />
QDate date;<br />
date.setDate( 1967, 3, 11 );<br />
QCOMPARE( date.month(), 3 );<br />
QCOMPARE( QDate::longMonthName(date.month()),<br />
QString("March") );<br />
}<br />
<br />
<br />
QTEST_MAIN(testDate)<br />
#include "tutorial1.moc"<br />
</syntaxhighlight><br />
Save as autotests/tutorial1.cpp in your project's autotests directory following the example of [https://invent.kde.org/graphics/okular Okular]<br />
<br />
Stepping through the code, the first line imports the header files for the QtTest namespace. The second line imports the headers for the QDate class. Lines 4 to 10 give us the test class, testData. Note that testDate inherits from QObject and has the Q_OBJECT macro - QtTestLib requires specific Qt functionality that is present in QObject.<br />
<br />
Lines 12 to 17 provide our first test, which checks that a date is valid. Note the use of the QVERIFY macro, which checks that the condition is true. So if <tt>date.isValid()</tt> returns true, then the test will pass, otherwise the test will fail. QVERIFY is similar to ASSERT in other test suites.<br />
<br />
Similarly, lines 19 to 27 provide another test, which checks a setter, and a couple of accessor routines. In this case, we are using QCOMPARE, which checks that the conditions are equal. So if date.month() returns 3, then that part of that test will pass, otherwise the test will fail.<br />
<br />
{{Warning|As soon as a QVERIFY evaluates to false or a QCOMPARE does not have two equal values, the whole test is marked as failed and the next test will be started. So in the example above, if the check at line 24 fails, then the check at lines 25 and 26 will not be run.}}<br />
<br />
In a later tutorial we will see how to work around problems that this behaviour can cause.<br />
<br />
Line 30 uses the QTEST_MAIN which creates an entry point routine for us, with appropriate calls to invoke the testDate unit test class.<br />
<br />
Line 31 includes the Meta-Object compiler output, so we can make use of our QObject functionality.<br />
<br />
The qmake project file that corresponds to that code is shown below. You would then use qmake to turn this into a Makefile and then compile it with make.<br />
<br />
'''Example 2. QDate unit test project'''<br />
<pre><br />
CONFIG += qtestlib<br />
TEMPLATE = app<br />
TARGET +=<br />
DEPENDPATH += .<br />
INCLUDEPATH += .<br />
<br />
# Input<br />
SOURCES += tutorial1.cpp<br />
</pre><br />
Save as tutorial1.pro<br />
<br />
This is a fairly normal project file, except for the addition of the <tt>CONFIG += qtestlib</tt>. This adds the right header and library setup to the Makefile.<br />
<br />
Create an empty file called tutorial1.h and compile with <tt>qmake; make</tt> The output looks like the following:<br />
<br />
'''Example 3. QDate unit test output'''<br />
<pre><br />
$ ./tutorial1<br />
********* Start testing of testDate *********<br />
Config: Using QTest library 4.1.0, Qt 4.1.0-snapshot-20051003<br />
PASS : testDate::initTestCase()<br />
PASS : testDate::testValidity()<br />
PASS : testDate::testMonth()<br />
PASS : testDate::cleanupTestCase()<br />
Totals: 4 passed, 0 failed, 0 skipped<br />
********* Finished testing of testDate *********<br />
</pre><br />
<br />
Looking at the output above, you can see that the output includes the version of the test library and Qt itself, and then the status of each test that is run. In addition to the testValidity and testMonth tests that we defined, there is also a setup routine (initTestCase) and a teardown routine (cleanupTestCase) that can be used to do additional configuration if required.<br />
<br />
===Failing tests===<br />
<br />
If we had made an error in either the production code or the unit test code, then the results would show an error. An example is shown below:<br />
<br />
'''Example 4. QDate unit test output showing failure'''<br />
<pre><br />
$ ./tutorial1<br />
********* Start testing of testDate *********<br />
Config: Using QTest library 4.1.0, Qt 4.1.0-snapshot-20051003<br />
PASS : testDate::initTestCase()<br />
PASS : testDate::testValidity()<br />
FAIL! : testDate::testMonth() Compared values are not the same<br />
Actual (date.month()): 4<br />
Expected (3): 3<br />
Loc: [tutorial1.cpp(25)]<br />
PASS : testDate::cleanupTestCase()<br />
Totals: 3 passed, 1 failed, 0 skipped<br />
********* Finished testing of testDate *********<br />
</pre><br />
<br />
===Running selected tests===<br />
<br />
When the number of test functions increases, and some of the functions take a long time to run, it can be useful to only run a selected function. For example, if you only want to run the testMonth function, then you just specify that on the command line, as shown below:<br />
<br />
'''Example 5. QDate unit test output - selected function'''<br />
<br />
<pre><br />
$ ./tutorial1 testValidity<br />
********* Start testing of testDate *********<br />
Config: Using QTest library 4.1.0, Qt 4.1.0-snapshot-20051003<br />
PASS : testDate::initTestCase()<br />
PASS : testDate::testValidity()<br />
PASS : testDate::cleanupTestCase()<br />
Totals: 3 passed, 0 failed, 0 skipped<br />
********* Finished testing of testDate *********<br />
</pre><br />
<br />
Note that the initTestCase and cleanupTestCase routines are always run, so that any necessary setup and cleanup will still be done.<br />
<br />
You can get a list of the available functions by passing the -functions option, as shown below:<br />
<br />
'''Example 6. QDate unit test output - listing functions'''<br />
<pre><br />
$ ./tutorial1 -functions<br />
testValidity()<br />
testMonth()<br />
</pre><br />
<br />
===Verbose output options===<br />
<br />
You can get more verbose output by using the -v1, -v2 and -vs options. -v1 produces a message on entering each test function. I found this is useful when it looks like a test is hanging. This is shown below:<br />
<br />
'''Example 7. QDate unit test output - verbose output'''<br />
<br />
<pre><br />
$ ./tutorial1 -v1<br />
********* Start testing of testDate *********<br />
Config: Using QTest library 4.1.0, Qt 4.1.0-snapshot-20051003<br />
INFO : testDate::initTestCase() entering<br />
PASS : testDate::initTestCase()<br />
INFO : testDate::testValidity() entering<br />
PASS : testDate::testValidity()<br />
INFO : testDate::testMonth() entering<br />
PASS : testDate::testMonth()<br />
INFO : testDate::cleanupTestCase() entering<br />
PASS : testDate::cleanupTestCase()<br />
Totals: 4 passed, 0 failed, 0 skipped<br />
********* Finished testing of testDate *********<br />
</pre><br />
<br />
The -v2 option shows each QVERIFY, QCOMPARE and QTEST, as well as the message on entering each test function. I found this useful for verifying that a particular step is being run. This is shown below:<br />
<br />
''''Example 8. QDate unit test output - more verbose output'''<br />
<br />
<pre><br />
$ ./tutorial1 -v2<br />
********* Start testing of testDate *********<br />
Config: Using QTest library 4.1.0, Qt 4.1.0-snapshot-20051003<br />
INFO : testDate::initTestCase() entering<br />
PASS : testDate::initTestCase()<br />
INFO : testDate::testValidity() entering<br />
INFO : testDate::testValidity() QVERIFY(date.isValid())<br />
Loc: [tutorial1.cpp(17)]<br />
PASS : testDate::testValidity()<br />
INFO : testDate::testMonth() entering<br />
INFO : testDate::testMonth() COMPARE()<br />
Loc: [tutorial1.cpp(25)]<br />
INFO : testDate::testMonth() COMPARE()<br />
Loc: [tutorial1.cpp(27)]<br />
PASS : testDate::testMonth()<br />
INFO : testDate::cleanupTestCase() entering<br />
PASS : testDate::cleanupTestCase()<br />
Totals: 4 passed, 0 failed, 0 skipped<br />
********* Finished testing of testDate *********<br />
</pre><br />
<br />
The -vs option shows each signal that is emitted. In our example, there are no signals, so -vs has no effect. Getting a list of signals is useful for debugging failing tests, especially GUI tests which we will see in the third tutorial.<br />
<br />
===Output to a file===<br />
<br />
If you want to output the results of your testing to a file, you can use the -o filename, where you replace filename with the name of the file you want to save output to.<br />
<br />
==Tutorial 2: Data driven testing of a date class==<br />
<br />
In the previous example, we looked at how we can test a date class. If we decided that we really needed to test a lot more dates, then we'd be cutting and pasting a lot of code. If we subsequently changed the name of a function, then it has to be changed in a lot of places. As an alternative to introducing these types of maintenance problems into our tests, QtTestLib offers support for data driven testing.<br />
<br />
The easiest way to understand data driven testing is by an example, as shown below:<br />
<br />
'''Example 9. QDate test code, data driven version'''<br />
<syntaxhighlight lang="cpp-qt" line="GESHI_NORMAL_LINE_NUMBERS"><br />
#include <QtTest><br />
#include <QtCore><br />
<br />
<br />
class testDate: public QObject<br />
{<br />
Q_OBJECT<br />
private slots:<br />
void testValidity();<br />
void testMonth_data();<br />
void testMonth();<br />
};<br />
<br />
void testDate::testValidity()<br />
{<br />
// 12 March 1967<br />
QDate date( 1967, 3, 12 );<br />
QVERIFY( date.isValid() );<br />
}<br />
<br />
void testDate::testMonth_data()<br />
{<br />
QTest::addColumn<int>("year"); // the year we are testing<br />
QTest::addColumn<int>("month"); // the month we are testing<br />
QTest::addColumn<int>("day"); // the day we are testing<br />
QTest::addColumn<QString>("monthName"); // the name of the month<br />
<br />
QTest::newRow("1967/3/11") << 1967 << 3 << 11 << QString("March");<br />
QTest::newRow("1966/1/10") << 1966 << 1 << 10 << QString("January");<br />
QTest::newRow("1999/9/19") << 1999 << 9 << 19 << QString("September");<br />
// more rows of dates can go in here...<br />
}<br />
<br />
void testDate::testMonth()<br />
{<br />
QFETCH(int, year);<br />
QFETCH(int, month);<br />
QFETCH(int, day);<br />
QFETCH(QString, monthName);<br />
<br />
QDate date;<br />
date.setDate( year, month, day);<br />
QCOMPARE( date.month(), month );<br />
QCOMPARE( QDate::longMonthName(date.month()), monthName );<br />
}<br />
<br />
<br />
QTEST_MAIN(testDate)<br />
#include "tutorial2.moc"<br />
</syntaxhighlight><br />
<br />
As you can see, we've introduced a new method - testMonth_data, and moved the specific test date out of testMonth. We've had to add some more code (which will be explained soon), but the result is a separation of the data we are testing, and the code we are using to test it.<br />
The names of the functions are important - you must use the _data suffix for the data setup routine, and the first part of the data setup routine must match the name of the driver routine.<br />
<br />
It is useful to visualise the data as being a table, where the columns are the various data values required for a single run through the driver, and the rows are different runs. In our example, there are four columns (three integers, one for each part of the date; and one QString ), added in lines 23 through 30. The addColumn template obviously requires the type of variable to be added, and also requires a variable name argument. We then add as many rows as required using the newRow function, as shown in lines 23 through 26. The string argument to newRow is a label, which is handy for determining what is going on with failing tests, but doesn't have any effect on the test itself.<br />
<br />
To use the data, we simply use QFETCH to obtain the appropriate data from each row. The arguments to QFETCH are the type of the variable to fetch, and the name of the column (which is also the local name of the variable it gets fetched into). You can then use this data in a QCOMPARE or QVERIFY check. The code is run for each row, which you can see below:<br />
<br />
'''Example 10. Results of data driven testing, showing QFETCH'''<br />
<br />
<pre><br />
$ ./tutorial2 -v2<br />
********* Start testing of testDate *********<br />
Config: Using QTest library 4.1.0, Qt 4.1.0-snapshot-20051020<br />
INFO : testDate::initTestCase() entering<br />
PASS : testDate::initTestCase()<br />
INFO : testDate::testValidity() entering<br />
INFO : testDate::testValidity() QVERIFY(date.isValid())<br />
Loc: [tutorial2.cpp(19)]<br />
PASS : testDate::testValidity()<br />
INFO : testDate::testMonth() entering<br />
INFO : testDate::testMonth(1967/3/11) COMPARE()<br />
Loc: [tutorial2.cpp(44)]<br />
INFO : testDate::testMonth(1967/3/11) COMPARE()<br />
Loc: [tutorial2.cpp(45)]<br />
INFO : testDate::testMonth(1966/1/10) COMPARE()<br />
Loc: [tutorial2.cpp(44)]<br />
INFO : testDate::testMonth(1966/1/10) COMPARE()<br />
Loc: [tutorial2.cpp(45)]<br />
INFO : testDate::testMonth(1999/9/19) COMPARE()<br />
Loc: [tutorial2.cpp(44)]<br />
INFO : testDate::testMonth(1999/9/19) COMPARE()<br />
Loc: [tutorial2.cpp(45)]<br />
PASS : testDate::testMonth()<br />
INFO : testDate::cleanupTestCase() entering<br />
PASS : testDate::cleanupTestCase()<br />
Totals: 4 passed, 0 failed, 0 skipped<br />
********* Finished testing of testDate *********<br />
</pre><br />
<br />
=== The QTEST macro ===<br />
<br />
As an alternative to using QFETCH and QCOMPARE, you may be able to use the QTEST macro instead. QTEST takes two arguments, and if one is a string, it looks up that string as an argument in the current row. You can see how this can be used below, which is equivalent to the testMonth() code in the previous example.<br />
<br />
'''Example 11. QDate test code, data driven version using QTEST'''<br />
<br />
<syntaxhighlight lang="cpp-qt" line="GESHI_NORMAL_LINE_NUMBERS"><br />
void testDate::testMonth()<br />
{<br />
QFETCH(int, year);<br />
QFETCH(int, month);<br />
QFETCH(int, day);<br />
<br />
QDate date;<br />
date.setDate( year, month, day);<br />
QCOMPARE( date.month(), month );<br />
QTEST( QDate::longMonthName(date.month()), "monthName" );<br />
}<br />
</syntaxhighlight><br />
<br />
In the example above, note that monthName is enclosed in quotes, and we no longer have a QFETCH call for monthName.<br />
<br />
The other QCOMPARE could also have been converted to use QTEST, however this would be less efficient, because we already needed to use QFETCH to get month for the setDate in the line above.<br />
<br />
===Running selected tests with selected data===<br />
<br />
In the previous tutorial, we saw how to run a specific test by specifying the name of the test as a command line argument. In data driven testing, you can select which data you want the test run with, by adding a colon and the label for the data row. For example, if we just want to run the testMonth test for the first row, we would use <pre>./tutorial2 -v2 testMonth:1967/3/11</pre>. The result of this is shown below.<br />
<br />
'''Example 12. QDate unit test output - selected function and data'''<br />
<pre><br />
$ ./tutorial2 -v2 testMonth:1967/3/11<br />
********* Start testing of testDate *********<br />
Config: Using QTest library 4.1.0, Qt 4.1.0-snapshot-20051020<br />
INFO : testDate::initTestCase() entering<br />
PASS : testDate::initTestCase()<br />
INFO : testDate::testMonth() entering<br />
INFO : testDate::testMonth(1967/3/11) COMPARE()<br />
Loc: [tutorial2.cpp(44)]<br />
INFO : testDate::testMonth(1967/3/11) COMPARE()<br />
Loc: [tutorial2.cpp(45)]<br />
PASS : testDate::testMonth()<br />
INFO : testDate::cleanupTestCase() entering<br />
PASS : testDate::cleanupTestCase()<br />
Totals: 3 passed, 0 failed, 0 skipped<br />
********* Finished testing of testDate *********<br />
</pre><br />
<br />
==Tutorial 3: Testing Graphical User Interfaces==<br />
<br />
In the previous two tutorials, we've tested a date management class. This is an pretty typical use of unit testing. However Qt and KDE applications will make use graphical classes that take user input (typically from a keyboard and mouse). QtTestLib offers support for testing these classes, which we'll see in this tutorial.<br />
<br />
Again, we'll use an existing class as our test environment, and again it will be date related - the standard Qt {{qt|QDateEdit}} class. For those not familiar with this class, it is a simple date entry widget (although with some powerful back end capabilities). A picture of the widget is shown below.<br />
<br />
[[Image:Qdateedit_dlg.png|Thumb|'''Figure 1. QDateEdit widget screenshot''']]<br />
<br />
The way QtTestLib provides GUI testing is by injecting {{qt|QInputEvent}} events. To the application, these input events appear the same as normal key press/release and mouse clicks/drags. However the mouse and keyboard are unaffected, so that you can continue to use the machine normally while tests are being run.<br />
<br />
An example of how you can use the GUI functionality of QtTestLib is shown below.<br />
<br />
'''Example 13. QDateEdit test code'''<br />
<br />
<syntaxhighlight lang="cpp-qt" line="GESHI_NORMAL_LINE_NUMBERS"><br />
#include <QtTest><br />
#include <QtCore><br />
#include <QtGui><br />
Q_DECLARE_METATYPE(QDate)<br />
<br />
class testDateEdit: public QObject<br />
{<br />
Q_OBJECT<br />
private slots:<br />
void testChanges();<br />
void testValidator_data();<br />
void testValidator();<br />
};<br />
<br />
void testDateEdit::testChanges()<br />
{<br />
// 11 March 1967<br />
QDate date( 1967, 3, 11 );<br />
QDateEdit dateEdit( date );<br />
<br />
// up-arrow should increase day by one<br />
QTest::keyClick( &dateEdit, Qt::Key_Up );<br />
QCOMPARE( dateEdit.date(), date.addDays(1) );<br />
<br />
// we click twice on the "reduce" arrow at the bottom RH corner<br />
// first we need the widget size to know where to click<br />
QSize editWidgetSize = dateEdit.size();<br />
QPoint clickPoint(editWidgetSize.rwidth()-2, editWidgetSize.rheight()-2);<br />
// issue two clicks<br />
QTest::mouseClick( &dateEdit, Qt::LeftButton, 0, clickPoint);<br />
QTest::mouseClick( &dateEdit, Qt::LeftButton, 0, clickPoint);<br />
// and we should have decreased day by two (one less than original)<br />
QCOMPARE( dateEdit.date(), date.addDays(-1) );<br />
<br />
QTest::keyClicks( &dateEdit, "25122005" );<br />
QCOMPARE( dateEdit.date(), QDate( 2005, 12, 25 ) );<br />
<br />
QTest::keyClick( &dateEdit, Qt::Key_Tab, Qt::ShiftModifier );<br />
QTest::keyClicks( &dateEdit, "08" );<br />
QCOMPARE( dateEdit.date(), QDate( 2005, 8, 25 ) );<br />
}<br />
<br />
void testDateEdit::testValidator_data()<br />
{<br />
qRegisterMetaType<QDate>("QDate");<br />
<br />
QTest::addColumn<QDate>( "initialDate" );<br />
QTest::addColumn<QString>( "keyclicks" );<br />
QTest::addColumn<QDate>( "finalDate" );<br />
<br />
QTest::newRow( "1968/4/12" ) << QDate( 1967, 3, 11 )<br />
<< QString( "12041968" )<br />
<< QDate( 1968, 4, 12 );<br />
<br />
QTest::newRow( "1967/3/14" ) << QDate( 1967, 3, 11 )<br />
<< QString( "140abcdef[" )<br />
<< QDate( 1967, 3, 14 );<br />
// more rows can go in here<br />
}<br />
<br />
void testDateEdit::testValidator()<br />
{<br />
QFETCH( QDate, initialDate );<br />
QFETCH( QString, keyclicks );<br />
QFETCH( QDate, finalDate );<br />
<br />
QDateEdit dateEdit( initialDate );<br />
// this next line is just to start editing<br />
QTest::keyClick( &dateEdit, Qt::Key_Enter );<br />
QTest::keyClicks( &dateEdit, keyclicks );<br />
QCOMPARE( dateEdit.date(), finalDate );<br />
}<br />
<br />
QTEST_MAIN(testDateEdit)<br />
#include "tutorial3.moc"<br />
</syntaxhighlight><br />
<br />
Much of this code is common with previous examples, so I'll focus on the new elements and the more important changes as we work through the code line-by-line.<br />
<br />
Lines 1 to 3 import the various Qt declarations, as before.<br />
<br />
Line 4 is a macro that is required for the data-driven part of this test, which I'll come to soon.<br />
<br />
Lines 6 to 13 declare the test class - while the names have changed, it is pretty similar to the previous example. Note the testValidator and testValidator_data functions - we will be using data driven testing again in this example.<br />
<br />
Our first real test starts in line 15. Line 18 creates a QDate, and line 19 uses that date as the initial value for a QDateEdit widget.<br />
<br />
Lines 22 and 23 show how we can test what happens when we press the up-arrow key. The QTest::keyClick function takes a pointer to a widget, and a symbolic key name (a char or a Qt::Key). At line 23, we check that the effect of that event was to increment the date by a day. The QTest:keyClick function also takes an optional keyboard modifier (such as Qt::ShiftModifier for the shift key) and an optional delay value (in milliseconds). As an alternative to using QTest::keyClick, you can use QTest::keyPress and QTest::keyRelease to construct more complex keyboard sequences.<br />
<br />
Lines 27 to 33 show a similar test to the previous one, but in this case we are simulating a mouse click. We need to click in the lower right hand part of the widget (to hit the decrement arrow - see Figure 1), and that requires knowing how large the widget is. So lines 27 and 28 calculate the correct point based off the size of the widget. Line 30 (and the identical line 31) simulates clicking with the left-hand mouse button at the calculated point. The arguments to Qt::mouseClick are:<br />
<br />
*a pointer to the widget that the click event should be sent to.<br />
*the mouse button that is being clicked.<br />
*an optional keyboard modifier (such a Qt::ShiftModifier), or 0 for no modifiers.<br />
*an optional click point - this defaults to the middle of the widget if not specified.<br />
*an optional mouse delay.<br />
<br />
In addition to QTest::mouseClick, there is also QTest::mousePress, QTest::mouseRelease, QTest::mouseDClick (providing double-click) and QTest::mouseMove. The first three are used in the same way as QTest::mouseClick. The last takes a point to move the mouse to. You can use these functions in combination to simulate dragging with the mouse.<br />
<br />
Lines 35 and 36 show another approach to keyboard entry, using the QTest::keyClicks. Where QTest::keyClick sends a single key press, QTest::keyClicks takes a QString (or something equivalent, in line 35 a character array) that represents a sequence of key clicks to send. The other arguments are the same.<br />
<br />
Lines 38 to 40 show how you may need to use a combination of functions. After we've entered a new date in line 35, the cursor is at the end of the widget. At line 38, we use a Shift-Tab combination to move the cursor back to the month value. Then at line 39 we enter a new month value. Of course we could have used individual calls to QTest::keyClick, however that wouldn't have been as clear, and would also have required more code.<br />
<br />
=== Data-driven GUI testing ===<br />
<br />
Lines 61 to 72 show a data-driven test - in this case we are checking that the validator on QDateEdit is performing as expected. This is a case where data-driven testing can really help to ensure that things are working the way they should.<br />
<br />
At lines 63 to 65, we fetch in an initial value, a series of key-clicks, and an expected result. These are the columns that are set up in lines 47 to 49. However note that we are now pulling in a QDate, where in previous examples we used three integers and then build the QDate from those. However QDate isn't a registered type for {{qt|QMetaType}}, and so we need to register it before we can use it in our data-driven testing. This is done using the Q_DECLARE_METATYPE macro in line 4 and the qRegisterMetaType function in line 45.<br />
<br />
Lines 51 to 57 add in a couple of sample rows. Lines 51 to 53 represent a case where the input is valid, and lines 55 to 57 are a case where the input is only partly valid (the day part). A real test will obviously contain far more combinations than this.<br />
<br />
Those test rows are actually tested in lines 67 to 71. We construct the QDateEdit widget in line 67, using the initial value. We then send an Enter key click in line 69, which is required to get the widget into edit mode. At line 70 we simulate the data entry, and at line 71 we check whether the results are what was expected.<br />
<br />
Lines 74 and 75 are the same as we've seen in previous examples.<br />
<br />
=== Re-using test elements ===<br />
<br />
If you are re-using a set of events a number of times, then it may be an advantage to build a list of events, and then just replay them. This can improve maintainability and clarity of a set of tests, especially for mouse movements.<br />
<br />
The key class for building a list of test events is imaginatively known as QTestEventList. It is a QList of QTestEvents. The normal approach is to create the list, and then use various member functions to add key and mouse events. The normal functions that you'll need are addKeyClick and addMouseClick, which are very similar to the QTest::keyClick and QTest::mouseClick functions we used earlier in this tutorial. For finer grained operations, you can also use addKeyPress, addKeyRelease, addKeyEvent, addMousePress, addMouseRelease, addMouseDClick and addMouseMove to build up more complex event lists. You can also use addDelay to add a specified delay between events. When the list has been built up, you just call simulate on each widget.<br />
<br />
You can see how this works in the example below, which is the QDateEdit example (from above) converted to use QTestEventList.<br />
<br />
'''Example 14. QDateEdit test code, using QTestEventList'''<br />
<syntaxhighlight lang="cpp-qt" line="GESHI_NORMAL_LINE_NUMBERS"><br />
#include <QtTest><br />
#include <QtCore><br />
#include <QtGui><br />
Q_DECLARE_METATYPE(QDate)<br />
<br />
class testDateEdit: public QObject<br />
{<br />
Q_OBJECT<br />
private slots:<br />
void testChanges();<br />
void testValidator_data();<br />
void testValidator();<br />
};<br />
<br />
void testDateEdit::testChanges()<br />
{<br />
// 11 March 1967<br />
QDate date( 1967, 3, 11 );<br />
QDateEdit dateEdit( date );<br />
<br />
// up-arrow should increase day by one<br />
QTest::keyClick( &dateEdit, Qt::Key_Up );<br />
QCOMPARE( dateEdit.date(), date.addDays(1) );<br />
<br />
// we click twice on the "reduce" arrow at the bottom RH corner<br />
// first we need the widget size to know where to click<br />
QSize editWidgetSize = dateEdit.size();<br />
QPoint clickPoint(editWidgetSize.rwidth()-2, editWidgetSize.rheight()-2);<br />
// build a list that contains two clicks<br />
QTestEventList list1;<br />
list1.addMouseClick( Qt::LeftButton, 0, clickPoint);<br />
list1.addMouseClick( Qt::LeftButton, 0, clickPoint);<br />
// call that list on the widget<br />
list1.simulate( &dateEdit );<br />
// and we should have decreased day by two (one less than original)<br />
QCOMPARE( dateEdit.date(), date.addDays(-1) );<br />
<br />
QTest::keyClicks( &dateEdit, "25122005" );<br />
QCOMPARE( dateEdit.date(), QDate( 2005, 12, 25 ) );<br />
<br />
QTestEventList list2;<br />
list2.addKeyClick( Qt::Key_Tab, Qt::ShiftModifier );<br />
list2.addKeyClicks( "08" );<br />
list2.simulate( &dateEdit );<br />
QCOMPARE( dateEdit.date(), QDate( 2005, 8, 25 ) );<br />
}<br />
<br />
void testDateEdit::testValidator_data()<br />
{<br />
qRegisterMetaType<QDate>("QDate");<br />
<br />
QTest::addColumn<QDate>( "initialDate" );<br />
QTest::addColumn<QTestEventList>( "events" );<br />
QTest::addColumn<QDate>( "finalDate" );<br />
<br />
QTestEventList eventsList1;<br />
// this next line is just to start editing<br />
eventsList1.addKeyClick( Qt::Key_Enter );<br />
eventsList1.addKeyClicks( "12041968" );<br />
<br />
QTest::newRow( "1968/4/12" ) << QDate( 1967, 3, 11 )<br />
<< eventsList1<br />
<< QDate( 1968, 4, 12 );<br />
<br />
QTestEventList eventsList2;<br />
eventsList2.addKeyClick( Qt::Key_Enter );<br />
eventsList2.addKeyClicks( "140abcdef[" );<br />
<br />
QTest::newRow( "1967/3/14" ) << QDate( 1967, 3, 11 )<br />
<< eventsList2<br />
<< QDate( 1967, 3, 14 );<br />
// more rows can go in here<br />
}<br />
<br />
void testDateEdit::testValidator()<br />
{<br />
QFETCH( QDate, initialDate );<br />
QFETCH( QTestEventList, events );<br />
QFETCH( QDate, finalDate );<br />
<br />
QDateEdit dateEdit( initialDate );<br />
<br />
events.simulate( &dateEdit);<br />
<br />
QCOMPARE( dateEdit.date(), finalDate );<br />
}<br />
<br />
QTEST_MAIN(testDateEdit)<br />
#include "tutorial3a.moc"<br />
</syntaxhighlight><br />
<br />
This example is pretty much the same as the previous version, up to line 28. In line 30, we create a QTestEventList. We add events to the list in lines 31 and 32 - note that we don't specify the widget we are calling them on at this stage. In line 34, we simulate each event on the widget. If we had multiple widgets, we could call simulate using the same set of events.<br />
<br />
Lines 35 to 39 are as per the previous example.<br />
<br />
We create another list in lines 41 to 43, although this time we are using addKeyClick and addKeyClicks instead of adding mouse events. Note that an event list can contain combinations of mouse and keyboard events - it just didn't make sense in this test to have such a combination. We use the second list at line 44, and check the results in line 45.<br />
<br />
You can also build lists of events in data driven testing as well, as shown in lines 48 to 86. The key difference is that instead of fetching a QString in each row, we are fetching a QTestEventList. This requires that we add a column of QTestEventList, rather than QString (see line 53). At lines 56 to 59, we create a list of events. At line 62 we add those events to the applicable row. We create a second list at lines 65 to 67, and add that second list to the applicable row in line 70.<br />
<br />
We fetch the events in line 78, and use them in line 83. If we had multiple widgets, then we could use the same event list several times.<br />
<br />
==Tutorial 4 - Testing for failure and avoiding tests==<br />
<br />
Under some conditions, it is impossible to avoid tests failing. In this section, we'll see how to deal with these cases.<br />
<br />
===Skipping tests===<br />
<br />
Where a test doesn't make sense to run (for example, if the required test files aren't available, or the feature is architecture or operating system dependent), the cleanest solution is to skip the test.<br />
<br />
Tests are skipped using the QSKIP macro. QSKIP takes two arguments - a label string that should be used to describe why the test is being skipped, and a enumerated constant that controls how much of the test is skipped. If you pass SkipSingle, and the test is data driven, then only the current row is skipped. If you pass SkipAll and the test is data driven, then all following rows are skipped. If the test is not data driven, then it doesn't matter which one is used.<br />
<br />
You can see how QSKIP works in the example below:<br />
<br />
'''Example 15. Unit test showing skipped tests'''<br />
<syntaxhighlight lang="cpp-qt" line="GESHI_NORMAL_LINE_NUMBERS"><br />
void testDate::testSkip_data()<br />
{<br />
QTest::addColumn<int>("val1");<br />
QTest::addColumn<int>("val2");<br />
<br />
QTest::newRow("1") << 1 << 1;<br />
QTest::newRow("2") << 1 << 2;<br />
QTest::newRow("3") << 3 << 3;<br />
QTest::newRow("5") << 5 << 5;<br />
QTest::newRow("4") << 4 << 5;<br />
}<br />
<br />
void testDate::testSkip()<br />
{<br />
QFETCH(int, val1);<br />
QFETCH(int, val2);<br />
<br />
if ( val2 == 2 )<br />
QSKIP("Two isn't good, not doing it", SkipSingle);<br />
if ( val1 == 5 )<br />
QSKIP("Five! I've had enough, bailing here", SkipAll);<br />
QCOMPARE( val1, val2 );<br />
}<br />
</syntaxhighlight><br />
<br />
'''Example 16. Output of unit test showing skipped tests'''<br />
<pre><br />
$ ./tutorial4 testSkip -v2<br />
********* Start testing of testDate *********<br />
Config: Using QTest library 4.1.0, Qt 4.1.0-snapshot-20051107<br />
INFO : testDate::initTestCase() entering<br />
PASS : testDate::initTestCase()<br />
INFO : testDate::testSkip() entering<br />
INFO : testDate::testSkip(1) COMPARE()<br />
Loc: [tutorial4.cpp(82)]<br />
SKIP : testDate::testSkip(2) Two isn't good, not doing it<br />
Loc: [tutorial4.cpp(79)]<br />
INFO : testDate::testSkip(3) COMPARE()<br />
Loc: [tutorial4.cpp(82)]<br />
SKIP : testDate::testSkip(5) Five! I've had enough, bailing here<br />
Loc: [tutorial4.cpp(81)]<br />
PASS : testDate::testSkip()<br />
INFO : testDate::cleanupTestCase() entering<br />
PASS : testDate::cleanupTestCase()<br />
Totals: 3 passed, 0 failed, 2 skipped<br />
********* Finished testing of testDate *********<br />
</pre><br />
<br />
from the verbose output, you can see that the test was run on the first and third rows. The second row wasn't run because of the QSKIP call with a SkipSingle argument. Similarly, the fourth and fifth rows weren't run because the fourth row triggered a QSKIP call with a SkipAll argument.<br />
<br />
Also note that the test didn't fail, even though there were two calls to QSKIP. Conceptually, a skipped test is a test that didn't make sense to run for test validity reasons, rather than a test that is valid but will fail because of bugs or lack of features in the code being tested.<br />
<br />
===Handling expected failures===<br />
<br />
If you have valid tests, but the code that you are testing doesn't pass them, then ideally you fix the code you are testing. However sometimes that isn't possible in the time that you have available, or because of a need to avoid binary incompatible changes. In this case, it is undesirable to delete or modify the unit tests - it is better to flag the test as "expected to fail", using the QEXPECT_FAIL macro. An example of this is shown below:<br />
<br />
'''Example 17. Unit test showing expected failures'''<br />
<br />
<syntaxhighlight lang="cpp-qt" line="GESHI_NORMAL_LINE_NUMBERS"><br />
void testDate::testExpectedFail()<br />
{<br />
QEXPECT_FAIL("", "1 is not 2, even for very large 1", Continue);<br />
QCOMPARE( 1, 2 );<br />
QCOMPARE( 2, 2 );<br />
<br />
QEXPECT_FAIL("", "1 is not 2, even for very small 2", Abort);<br />
QCOMPARE( 1, 2 );<br />
// The next line will not be run, because we Abort on previous failure<br />
QCOMPARE( 3, 3 );<br />
}<br />
</syntaxhighlight><br />
<br />
'''Example 18. Output of unit test showing expected failures'''<br />
<br />
<pre><br />
$ ./tutorial4/tutorial4 testExpectedFail -v2<br />
********* Start testing of testDate *********<br />
Config: Using QTest library 4.1.0, Qt 4.1.0-snapshot-20051107<br />
INFO : testDate::initTestCase() entering<br />
PASS : testDate::initTestCase()<br />
INFO : testDate::testExpectedFail() entering<br />
INFO : testDate::testExpectedFail() Compared values are not the same<br />
Actual (1): 1<br />
Expected (2): 2<br />
Loc: [tutorial4.cpp(41)]<br />
XFAIL : testDate::testExpectedFail() 1 is not 2, even for very large 1<br />
Loc: [tutorial4.cpp(41)]<br />
INFO : testDate::testExpectedFail() COMPARE()<br />
Loc: [tutorial4.cpp(42)]<br />
INFO : testDate::testExpectedFail() Compared values are not the same<br />
Actual (1): 1<br />
Expected (2): 2<br />
Loc: [tutorial4.cpp(45)]<br />
XFAIL : testDate::testExpectedFail() 1 is not 2, even for very small 2<br />
Loc: [tutorial4.cpp(45)]<br />
PASS : testDate::testExpectedFail()<br />
INFO : testDate::cleanupTestCase() entering<br />
PASS : testDate::cleanupTestCase()<br />
Totals: 3 passed, 0 failed, 0 skipped<br />
********* Finished testing of testDate *********<br />
</pre><br />
<br />
As you can see from the verbose output, we expect a failure from each time we do a QCOMPARE( 1, 2);. In the first call to QEXPECT_FAIL, we use the Continue argument, so the rest of the tests will still be run. However in the second call to QEXPECT_FAILwe use the Abort and the test bails at this point. Generally it is better to use Continue unless you have a lot of closely related tests that would each need a QEXPECT_FAIL entry.<br />
<br />
Also note that tests that are marked as expected failures are not considered to be failures, so the test function above is considered to be a pass.<br />
<br />
If a test that is marked to be an expected failure, and it unexpectedly passes, then that is flagged as an error, as shown below:<br />
<br />
'''Example 19. Unit test showing unexpected pass'''<br />
<syntaxhighlight lang="cpp-qt" line="GESHI_NORMAL_LINE_NUMBERS"><br />
void testDate::testUnexpectedPass()<br />
{<br />
QEXPECT_FAIL("", "1 is not 2, even for very large 1", Continue);<br />
QCOMPARE( 1, 1 );<br />
QCOMPARE( 2, 2 );<br />
<br />
QEXPECT_FAIL("", "1 is not 2, even for very small 2", Abort);<br />
QCOMPARE( 1, 1 );<br />
QCOMPARE( 3, 3 );<br />
}<br />
</syntaxhighlight><br />
<br />
'''Example 20. Output of unit test showing unexpected pass'''<br />
<br />
<pre><br />
$ ./tutorial4/tutorial4 testUnexpectedPass -v2<br />
********* Start testing of testDate *********<br />
Config: Using QTest library 4.1.0, Qt 4.1.0-snapshot-20051107<br />
INFO : testDate::initTestCase() entering<br />
PASS : testDate::initTestCase()<br />
INFO : testDate::testUnexpectedPass() entering<br />
INFO : testDate::testUnexpectedPass() COMPARE()<br />
Loc: [tutorial4.cpp(53)]<br />
XPASS : testDate::testUnexpectedPass() COMPARE()<br />
Loc: [tutorial4.cpp(53)]<br />
INFO : testDate::testUnexpectedPass() COMPARE()<br />
Loc: [tutorial4.cpp(54)]<br />
INFO : testDate::testUnexpectedPass() COMPARE()<br />
Loc: [tutorial4.cpp(57)]<br />
XPASS : testDate::testUnexpectedPass() COMPARE()<br />
Loc: [tutorial4.cpp(57)]<br />
INFO : testDate::cleanupTestCase() entering<br />
PASS : testDate::cleanupTestCase()<br />
Totals: 2 passed, 2 failed, 0 skipped<br />
********* Finished testing of testDate *********<br />
</pre><br />
<br />
The effect of unexpected passes on the running of the test is controlled by the second argument to QEXPECT_FAIL. If the argument is Continue and the test unexpectedly passes, then the rest of the test function will be run. If the argument is Abort, then the test will stop.<br />
<br />
===Checking debug messages and warnings===<br />
<br />
If you are testing border cases, you will likely run across the case where some kind of message will be produced using the qDebug or qWarning functions. Where a test produces a debug or warning message, that message will be logged in the test output (although it will still be considered a pass unless some other check fails), as shown in the example below:<br />
<br />
Example 21. Unit test producing warning and debug messages<br />
<syntaxhighlight lang="cpp-qt" line="GESHI_NORMAL_LINE_NUMBERS"><br />
void testDate::testQdebug()<br />
{<br />
qWarning("warning");<br />
qDebug("debug");<br />
qCritical("critical");<br />
}<br />
</syntaxhighlight><br />
<br />
'''Example 22. Output of unit test producing warning and debug messages'''<br />
<pre><br />
$ ./tutorial4 testQdebug<br />
********* Start testing of testDate *********<br />
Config: Using QTest library 4.1.0, Qt 4.1.0-snapshot-20051107<br />
PASS : testDate::initTestCase()<br />
QWARN : testDate::testQdebug() warning<br />
QDEBUG : testDate::testQdebug() debug<br />
QSYSTEM: testDate::testQdebug() critical<br />
PASS : testDate::testQdebug()<br />
PASS : testDate::cleanupTestCase()<br />
Totals: 3 passed, 0 failed, 0 skipped<br />
********* Finished testing of testDate *********<br />
</pre><br />
<br />
Note that while this example produces the debug and warning messages within the test function (testQdebug), those messages would normally be propagated up from the code being tested. However the source of the messages does not make any difference to how they are handled.<br />
<br />
If your test needs include either a clean output, or verification that appropriate messages are generated, then you will probably need the QtTest::ignoreMessage function.<br />
<br />
{{Tip|'''Note:''' The ignoreMessage function can be used to ignore a message, however it might be clearer to think of this function as checking for the presence of an expected message. In particular, it is a test failure if you call ignoreMessage and the message is not generated.}}<br />
<br />
An example of how ignoreMessage works is shown below.<br />
<br />
'''Example 23. Example of using ignoreMessage'''<br />
<syntaxhighlight lang="cpp-qt"><br />
void testDate::testValidity()<br />
{<br />
QTest::ignoreMessage(QtWarningMsg, "validity warning");<br />
qWarning("validity warning");<br />
}<br />
<br />
void testDate::testValiditi()<br />
{<br />
QTest::ignoreMessage(QtWarningMsg, "validity warning");<br />
qWarning("validiti warning");<br />
}<br />
</syntaxhighlight><br />
<br />
<br />
'''Example 24. Output of ignoreMessage example'''<br />
<pre><br />
$ ./tutorial4 testValidity testValiditi<br />
********* Start testing of testDate *********<br />
Config: Using QTest library 4.1.0, Qt 4.1.0-snapshot-20051107<br />
PASS : testDate::initTestCase()<br />
PASS : testDate::testValidity()<br />
QWARN : testDate::testValiditi() validiti warning<br />
INFO : testDate::testValiditi() Did not receive message: "validity warning"<br />
FAIL! : testDate::testValiditi() Not all expected messages were received<br />
PASS : testDate::cleanupTestCase()<br />
Totals: 3 passed, 1 failed, 0 skipped<br />
********* Finished testing of testDate *********<br />
</pre><br />
<br />
Note that the warning message in testDate::testValidity has been "swallowed" by thecall to ignoreMessage.<br />
<br />
By contrast, the warning message in testDate::testValiditi still causes a warning to be logged, because the ignoreMessage call does not match the text in the warning message. In addition, because a we expected a particular warning message and it wasn't received, the testDate::testValiditi test function fails.<br />
<br />
==Tutorial 5: Testing Qt slots and signals==<br />
<br />
An important part of Qt programming is the use of signals and slots. This section covers the support for testing of these features.<br />
<br />
{{Tip|'''Note:''' If you are not familiar with Qt signals and slots, you probably should review the introduction to this feature provided in your Qt documentation. It is also available at http://doc.qt.io/qt-5/signalsandslots.html.}}<br />
<br />
===Testing slots===<br />
<br />
Testing slots is very easy, because a slot is just a specially annotated method. You can call slots just like any other method you'd like to test, as shown below:<br />
<br />
'''Example 25. QLabel test code, showing testing of a couple of slots'''<br />
<syntaxhighlight lang="cpp-qt" line="GESHI_NORMAL_LINE_NUMBERS"><br />
#include <QtTest><br />
#include <QtCore><br />
#include <QtGui><br />
<br />
class testLabel: public QObject<br />
{<br />
Q_OBJECT<br />
private slots:<br />
void testChanges();<br />
};<br />
<br />
void testLabel::testChanges()<br />
{<br />
QLabel label;<br />
<br />
// setNum() is a QLabel slot, but we can just call it like any<br />
// other method.<br />
label.setNum( 3 );<br />
QCOMPARE( label.text(), QString("3") );<br />
<br />
// clear() is also a slot.<br />
label.clear();<br />
QVERIFY( label.text().isEmpty() );<br />
}<br />
<br />
QTEST_MAIN(testLabel)<br />
#include "tutorial5.moc"<br />
</syntaxhighlight><br />
<br />
===Testing signals===<br />
Testing of signals is a little more difficult than testing of slots, however, Qt offers a very useful class called QSignalSpy that helps a lot.<br />
<br />
{{qt|QSignalSpy}} is a class provided with Qt that allows you to record the signals that have been emitted from a particular QObject subclass object. You can then check that the right number of signals have been emitted and that the right kind of signals was emitted. You can find more information on the QSignalSpy class in your Qt documentation.<br />
<br />
An example of how you can use QSignalSpy to test a class that has signals is shown below.<br />
<br />
'''Example 26. QCheckBox test code, showing testing of signals'''<br />
<br />
<syntaxhighlight lang="cpp-qt" line="GESHI_NORMAL_LINE_NUMBERS"><br />
#include <QtTest><br />
#include <QtCore><br />
#include <QtGui><br />
<br />
class testCheckBox: public QObject<br />
{<br />
Q_OBJECT<br />
private slots:<br />
void testSignals();<br />
};<br />
<br />
void testCheckBox::testSignals()<br />
{<br />
// You don't need to use an object created with "new" for<br />
// QSignalSpy, I just needed it in this case to test the emission<br />
// of a destroyed() signal.<br />
QCheckBox *xbox = new QCheckBox;<br />
<br />
// We are going to have two signal monitoring classes in use for<br />
// this test.<br />
// The first monitors the stateChanged() signal.<br />
// Also note that QSignalSpy takes a pointer to the object.<br />
QSignalSpy stateSpy( xbox, SIGNAL( stateChanged(int) ) );<br />
<br />
// Not strictly necessary, but I like to check that I have set up<br />
// my QSignalSpy correctly.<br />
QVERIFY( stateSpy.isValid() );<br />
<br />
// Now we check to make sure we don't have any signals already<br />
QCOMPARE( stateSpy.count(), 0 );<br />
<br />
// Here is a second monitoring class - this one for the<br />
// destroyed() signal.<br />
QSignalSpy destroyedSpy( xbox, SIGNAL( destroyed() ) );<br />
QVERIFY( destroyedSpy.isValid() );<br />
<br />
// A sanity check to verify the initial state<br />
// This also shows that you can mix normal method checks with<br />
// signal checks.<br />
QCOMPARE( xbox->checkState(), Qt::Unchecked );<br />
<br />
// Shouldn't already have any signals<br />
QCOMPARE( destroyedSpy.count(), 0 );<br />
<br />
// If we change the state, we should get a signal.<br />
xbox->setCheckState( Qt::Checked );<br />
QCOMPARE( stateSpy.count(), 1 );<br />
<br />
xbox->setCheckState( Qt::Unchecked );<br />
QCOMPARE( stateSpy.count(), 2 );<br />
<br />
xbox->setCheckState( Qt::PartiallyChecked );<br />
QCOMPARE( stateSpy.count(), 3 );<br />
<br />
// If we destroy the object, the signal should be emitted.<br />
delete xbox;<br />
<br />
// So the count of objects should increase.<br />
QCOMPARE( destroyedSpy.count(), 1 );<br />
<br />
// We can also review the signals that we collected<br />
// QSignalSpy is really a QList of QLists, so we take the first<br />
// list, which corresponds to the arguments for the first signal<br />
// we caught.<br />
QList<QVariant> firstSignalArgs = stateSpy.takeFirst();<br />
// stateChanged() only has one argument - an enumerated type (int)<br />
// So we take that argument from the list, and turn it into an integer.<br />
int firstSignalState = firstSignalArgs.at(0).toInt();<br />
// We can then check we got the right kind of signal.<br />
QCOMPARE( firstSignalState, static_cast<int>(Qt::Checked) );<br />
<br />
// check the next signal - note that takeFirst() removes from the list<br />
QList<QVariant> nextSignalArgs = stateSpy.takeFirst();<br />
// this shows another way of fudging the argument types<br />
Qt::CheckState nextSignalState = (Qt::CheckState)nextSignalArgs.at(0).toInt();<br />
QCOMPARE( nextSignalState, Qt::Unchecked );<br />
<br />
// and again for the third signal<br />
nextSignalArgs = stateSpy.takeFirst();<br />
nextSignalState = (Qt::CheckState)nextSignalArgs.at(0).toInt();<br />
QCOMPARE( nextSignalState, Qt::PartiallyChecked );<br />
}<br />
<br />
QTEST_MAIN(testCheckBox)<br />
#include "tutorial5a.moc"<br />
</syntaxhighlight><br />
<br />
The first 13 lines are essentially unchanged from previous examples that we've seen. Line 17 creates the object that will be tested - as noted in the comments in lines 14-16, the only reason that I'm creating it with new is because I need to delete it in line 59 to cause the destroyed() signal to be emitted.<br />
<br />
Line 23 sets up the first of our two QSignalSpy instances. The one in line 23 monitors the stateChanged(int) signal, and the one in line 34 monitors the destroyed() signal. If you get the name or signature of the signal wrong (for example, if you use stateChanged() instead of stateChanged(int)), then this will not be caught at compile time, but will result in a runtime failure. You can test if things were set up correctly using the isValid(), as shown in lines 27 and 35.<br />
<br />
As shown in line 40, there is no reason why you cannot test normal methods, signals and slots in the same test.<br />
<br />
Line 46 changes the state of the object under test, which is supposed to result in a stateChanged(int) signal being emitted. Line 47 checks that the number of signals increases from zero to one. The process is repeated in lines 49 and 50, and again in lines 52 and 53.<br />
<br />
Line 56 deletes the object under test, and line 59 tests that the destroyed() signal has been emitted.<br />
<br />
For signals that have arguments (such as our stateChanged(int) signal), you may also wish to check that the arguments were correct. You can do this by looking at the list of signal arguments. Exactly how you do this is fairly flexible, however for simple tests like the one in the example, you can manually work through the list using takeFirst() and check that each argument is correct. This is shown in line 65, 68 and 70 for the first signal. The same approach is shown in lines 73, 75 and 76 for the second signal, and the in lines 79 to 81 for the third signal. For a more complex set of tests, you may wish to apply some data driven techniques.<br />
<br />
{{Tip|'''Note:''' You should be aware that, for some class implementations, you may need to return control to the event loop to have signals emitted. If you need this, try using the {{qt|QTest}}::qWait() function.}}<br />
<br />
==Tutorial 6: Integrating with CMake==<br />
<br />
The KDE build tool is [http://www.cmake.org CMake], and I assume that you are familiar with the use of CMake. If not, you should review the [[Guidelines and HOWTOs/CMake|CMake Tutorial]] first.<br />
<br />
CMake offers quite good support for unit testing, and QTestLib tests can be easily integrated into any CMake build system.<br />
<br />
=== Configuring for Testing ===<br />
<br />
Tests are not built by default - you have to enable the test system, and build the tests.<br />
<br />
You enable tests by adding an '''ENABLE_TESTING()''' line to the top of your CMakeLists.txt file.<br />
<br />
In some configurations, there may be a build system option to turn on (or off) the compilation of tests. At this stage, you have to enable the '''BUILD_TESTING''' option in KDE4 modules, however this may go away in the near future, as later version of CMake can build the test applications on demand.<br />
<br />
If the tests are still not building, you might want to issue make buildtests in tests directory.<br />
<br />
=== Adding Tests ===<br />
By convention tests are put in the '''autotests''' directory. See [https://invent.kde.org/graphics/okular/-/tree/master/autotests Okular] for an example.<br />
<br />
You add a single test to the list of all tests that can be run by using<br />
'''ecm_add_test''', which looks like this in its simplest form:<br />
<br />
include(ECMAddTests)<br />
include_directories(AFTER "${CMAKE_CURRENT_SOURCE_DIR}/..")<br />
ecm_add_test(<br />
tutorial1.cpp<br />
LINK_LIBRARIES Qt5::Test<br />
)<br />
<br />
* The first argument is the filename.<br />
* There are named arguments for other options.<br />
<br />
If you have several tests with the same options, you can add them all by using the plural variant '''ecm_add_tests'''.<br />
<br />
ecm_add_tests(<br />
tutorial1.cpp tutorial2.cpp<br />
LINK_LIBRARIES Qt5::Test<br />
)<br />
<br />
See [https://api.kde.org/ecm/module/ECMAddTests.html ECM Documentation] for details.<br />
<br />
Note that these commands do nothing if '''ENABLE_TESTING()''' has not been run.<br />
<br />
=== Testing with Akonaki ===<br />
<br />
Some tests require an Akonadi server be up and running, so the test must launch a server process (isolated from any existing environment), load in test data, and shut the server down after the testing finishes. You can use the [https://techbase.kde.org/KDE_PIM/Akonadi/Testing Akonadi Testrunner] to do this; consult that document for information on configuring the test runner and test data.<br />
<br />
The KF5AkonadiMacros.cmake file in the Akonadi repository provides an '''add_akonadi_isolated_test''' function to add tests to the build.<br />
<br />
add_akonadi_isolated_test(<br />
SOURCE sometest.cpp<br />
LINK_LIBRARIES Qt5::Test<br />
)<br />
<br />
=== KDE4 CMake Recipe for QTestLib ===<br />
<br />
If you are working in a KDE4 environment, then it is pretty easy to get CMake set up to build and run a test on demand.<br />
<br />
<pre><br />
cmake_minimum_required(VERSION 2.8)<br />
<br />
FIND_PACKAGE ( KDE4 REQUIRED )<br />
FIND_PACKAGE ( Qt4 REQUIRED QT_USE_QT* )<br />
<br />
INCLUDE( ${QT_USE_FILE} )<br />
include(KDE4Defaults)<br />
<br />
set( kwhatevertest_SRCS kwhatevertest.cpp )<br />
<br />
kde4_add_unit_test( kwhatevertest<br />
TESTNAME ksubmodule-kwhatevertest<br />
${kwhatevertest_SRCS}<br />
)<br />
<br />
target_link_libraries( kwhatevertest<br />
${KDE4_KDECORE_LIBS}<br />
${QT_QTTEST_LIBRARY}<br />
${KDE4_KDEUI_LIBS}<br />
)<br />
<br />
</pre><br />
<br />
You are meant to replace "kwhatevertest" with the name of your test application. The target_link_libraries() line will need to contain whatever libraries are needed for the feature you are testing, so if it is a GUI feature, you'll likely need to use "${KDE4_KDEUI_LIBS}.<br />
<br />
=== Running the Tests ===<br />
To run all tests, you can just "make test". This will work through each of the tests that have been added (at any lower level) using '''kde4_add_unit_test''', provided that you have '''include(KDE4Defaults)''' in your CMakeLists.txt.<br />
<br />
This is equivalent to running the "ctest" executable with no arguments. If you want finer grained control over which tests are run or the output format, you can use additional arguments. These are explained in the ctest man page ("man ctest" on a *nix system, or run "ctest --help-full").<br />
<br />
To run a single test, use '''./tutorial1.shell''' rather than just '''./tutorial1''', this will make it use the locally-built version of the shared libraries you're testing, rather than the installed ones.<br />
<br />
Some tests are written so that the expected local is English and fail if it is not.<br />
If your local is not English you case use for instance `LANG=C ctest` or `LANG=C make` to force English when running the test.<br />
<br />
=== Further Reading ===<br />
<br />
Chapter 8 of the [http://www.kitware.com/products/cmakebook.html CMake Book] provides a detailed description of how to do testing with CMake. Also see Appendix B for more on CTest and the special commands you can use.<br />
<br />
Various sections of the CMake Wiki, especially [https://gitlab.kitware.com/cmake/community/wikis/doc/ctest/Testing-With-CTest CTest testing]<br />
<br />
==Tutorial 7: Integrating with qmake==<br />
<br />
TODO<br />
<br />
==Tutorial 8: XML output==<br />
TODO<br />
<br />
==Tutorial 9: KDE specifics ==<br />
For KDE specific enhancements see [http://api.kde.org/4.0-api/kdelibs-apidocs/kdecore/html/qtest__kde_8h.html API dox]<br />
<br />
TODO<br />
<br />
== Alternative tools for testing ==<br />
<br />
There are a range of alternative testing approaches that can<br />
be used either with unit tests, or as an addition to the unit tests.<br />
<br />
===Static tests===<br />
As noted in the introduction, unit tests are dynamic tests - they exercise the compiled code. Static tests are slightly different - they look for problems in the source code, rather than making sure that the object code runs correctly.<br />
<br />
Static test tools tend to identify completely different types of problems to unit tests, and you should seek to use them both.<br />
<br />
For more information on using static tests, see [[../Code Checking|the Code Checking tutorial]].<br />
<br />
===Coverage tools and CI===<br />
Add this option in the configuration of your project's CI build.<br />
# http://quickgit.kde.org/?p=websites%2Fbuild-kde-org.<br />
# git clone kde:websites/build-kde-org<br />
<br />
[DEFAULT]<br />
configureExtraArgs=-DBUILD_COVERAGE=ON<br />
<br />
=== GUI application testing - Squish and KDExecutor ===<br />
[https://www.froglogic.com/squish/ Squish] by [https://www.froglogic.com froglogic] and [http://www.kdab.net/?page=products&sub=kdexecutor KDExecutor for Qt3/KDE3] by [https://www.kdab.net Klarälvdalens Datakonsult (KDAB)] are commercial tools that facilitate GUI testing.</div>Gjditchfieldhttps://community.kde.org/index.php?title=KDE_PIM/Docker&diff=92133KDE PIM/Docker2021-06-16T22:13:47Z<p>Gjditchfield: /* Building and updating KDE PIM */</p>
<hr />
<div>To make developing KDE PIM as easy as possible we have prepared a Docker image based on the KDE Neon distribution. It contains all the dependencies necessary to compile KDE PIM, the environment for running self-compiled KDE PIM and pre-configured tool (kdesrc-build) used to build latest KDE PIM from sources.<br />
<br />
== Set up Docker ==<br />
If you use KDE Neon, Ubuntu or Debian, run the following commands to install Docker and add yourself to the <code>docker</code> group, so you can use it:<br />
<br />
sudo apt install docker.io xserver-xephyr<br />
sudo usermod -aG docker $(whoami)<br />
newgrp docker<br />
<br />
{{Warning|It's important to note that any user added to the docker group is root equivalent. More information [https://github.com/docker/docker/issues/9976 here] and [https://docs.docker.com/engine/security/security/ here].}}<br />
<br />
On other distributions, please follow your distro's guide on how to set up Docker there, since the steps can differ slightly on different distributions.<br />
<br />
== Preparations ==<br />
First, clone the git repository with the Dockerfile and support scripts.<br />
<br />
git clone https://invent.kde.org/dvratil/kdepim-docker.git<br />
cd kdepim-docker<br />
<br />
Next, create a directory where you want the sources code, build folders and everything else related to KDE PIM development to be stored. This is also where runtime data and configuration of Akonadi, Kontact and other apps you run inside the container will be stored. We will then expose this directory to the Docker container at runtime.<br />
<br />
mkdir ~/kdepim-dev<br />
<br />
== Making OpenGL work in the container ==<br />
Several parts of KDE PIM depend on OpenGL - this is due to our use of QtWebEngine, which is based on Blink and has a hard dependency on OpenGL for rendering web pages. There's no way around that and so we need to make OpenGL work in the container. Unfortunately, that is not a very straightforward process and it differs for each GPU vendor and drivers used.<br />
<br />
=== NVIDIA proprietary drivers ===<br />
The easiest way is to use NVIDIA's nvidia-docker from [https://github.com/NVIDIA/nvidia-docker nvidia-docker Github]. You can follow the instructions on the Github page regarding how to install it. The nvidia-docker will automatically find your installed NVIDIA drivers and will expose them into the Docker container at runtime, so you don't have to rebuild your container whenever you upgrade your NVIDIA drivers.<br />
<br />
Note that if you do this, you must pass <code>-n</code> switch to the <code>build.sh</code> and <code>run.sh</code> scripts from the kdepim-docker.git repository.<br />
<br />
=== Nouveau (NVIDIA opensource drivers) ===<br />
TODO<br />
<br />
=== Intel ===<br />
Works out of the box<br />
<br />
=== AMD/ATI ===<br />
TODO<br />
<br />
== Building Docker image ==<br />
<br />
In order to build the Docker image, run the <code>build.sh</code> script. If you are<br />
using proprietary NVIDIA drivers, run the script with the <code>-n</code> switch.<br />
<br />
The command will create a Docker image called kdepim:dev.<br />
<br />
== Running the Docker container ==<br />
<br />
To run the container, use the <code>run.sh</code> script:<br />
<br />
run.sh $HOME/kdepim-dev<br />
<br />
If you are using proprietary NVIDIA drivers, run the script with the <code>-n</code> switch:<br />
<br />
run.sh -n $HOME/kdepim-dev<br />
<br />
The content of the directory will be available in the container in the<br />
/home/neon/kdepim directory.<br />
<br />
If you want to get another terminal window opened inside the same container, just run run.sh again. It will automatically create a new terminal on the already running container.<br />
<br />
''Note:''<br />
<ul>In some systems, the container's /home/neon/kdepim directory may not be writable. If so, edit <code>run.sh</code> to remove the parameter <code>:rw,z</code> from the <code>-v</code> options. Then delete the container with the command <code>docker rm kdepim-dev</code>, and run the container again.<br />
</ul><br />
<br />
== Building and updating KDE PIM ==<br />
<br />
Once inside the container, you can use the following commands to compile the<br />
entire KDE PIM suite:<br />
<br />
sudo apt update<br />
sudo apt full-upgrade<br />
kdesrc-build kde-pim<br />
<br />
This will take a lot of time the first time, but all subsequent builds will be<br />
faster thanks to incremental builds and use of ccache. You can also use a specific repository name instead of the<br />
<code>kde-pim</code> group.<br />
<br />
Check the [https://kdesrc-build.kde.org kdesrc-build documentation] for more<br />
details about using kdesrc-build. For a start, you may want to customize the <code>-j</code> option in the <code>make-options</code> option group in /home/neon/.kdesrc-buildrc.<br />
<br />
kdesrc-build will clone all the repositories into /home/neon/kdepim/src/kde/pim.<br />
Build directories (where you can run <code>make</code> manually) are in /home/neon/kdepim/build/kde/pim.<br />
The binaries are installed to /home/neon/kdepim/install. The environment<br />
of the container is adjusted to work with the custom installation prefix.<br />
<br />
=== Fixing build errors ===<br />
If kdesrc-build cannot build from a repository, it will write a log file in /home/neon/kdepim/logs/latest/''repository''/error.log.<br />
<br />
It is often the case that some system has added a dependency on some package that is not installed.<br />
In that case, error.log will contain messages like<br />
<br />
Could not find a package configuration file provided by "Qt5Location" with<br />
any of the following names:<br />
<br />
Qt5LocationConfig.cmake<br />
qt5location-config.cmake<br />
<br />
To find the package, visit [https://packages.ubuntu.com/ Ubuntu Packages Search] and use the "Search the contents of packages" form to search for the cmake file, then use <code>sudo apt install</code> to install the package.<br />
<br />
== Development tools ==<br />
<br />
There's [https://www.kdevelop.org KDevelop] and [https://www.qt.io/ide/ QtCreator]<br />
preinstalled in the container and you can run them from there. You can also use<br />
them from outside of the container, but code completion might not work perfectly then.<br />
<br />
You can also use any other IDE of your choice either by installing it into the container<br />
with apt-get or use it from outside of the container.<br />
<br />
== Contributing ==<br />
<br />
You can find more details in the [[KDE_PIM/Development|Development]] section of our wiki. If you have any issues or questions, feel free to stop by on our IRC channel (#kontact) on freenode, or talk to us on the [[KDE_PIM/Development#Mailing_Lists|kde-pim mailinglist]].</div>Gjditchfieldhttps://community.kde.org/index.php?title=KDE_PIM/Docker&diff=92132KDE PIM/Docker2021-06-16T21:39:58Z<p>Gjditchfield: /* Building and updating KDE PIM */</p>
<hr />
<div>To make developing KDE PIM as easy as possible we have prepared a Docker image based on the KDE Neon distribution. It contains all the dependencies necessary to compile KDE PIM, the environment for running self-compiled KDE PIM and pre-configured tool (kdesrc-build) used to build latest KDE PIM from sources.<br />
<br />
== Set up Docker ==<br />
If you use KDE Neon, Ubuntu or Debian, run the following commands to install Docker and add yourself to the <code>docker</code> group, so you can use it:<br />
<br />
sudo apt install docker.io xserver-xephyr<br />
sudo usermod -aG docker $(whoami)<br />
newgrp docker<br />
<br />
{{Warning|It's important to note that any user added to the docker group is root equivalent. More information [https://github.com/docker/docker/issues/9976 here] and [https://docs.docker.com/engine/security/security/ here].}}<br />
<br />
On other distributions, please follow your distro's guide on how to set up Docker there, since the steps can differ slightly on different distributions.<br />
<br />
== Preparations ==<br />
First, clone the git repository with the Dockerfile and support scripts.<br />
<br />
git clone https://invent.kde.org/dvratil/kdepim-docker.git<br />
cd kdepim-docker<br />
<br />
Next, create a directory where you want the sources code, build folders and everything else related to KDE PIM development to be stored. This is also where runtime data and configuration of Akonadi, Kontact and other apps you run inside the container will be stored. We will then expose this directory to the Docker container at runtime.<br />
<br />
mkdir ~/kdepim-dev<br />
<br />
== Making OpenGL work in the container ==<br />
Several parts of KDE PIM depend on OpenGL - this is due to our use of QtWebEngine, which is based on Blink and has a hard dependency on OpenGL for rendering web pages. There's no way around that and so we need to make OpenGL work in the container. Unfortunately, that is not a very straightforward process and it differs for each GPU vendor and drivers used.<br />
<br />
=== NVIDIA proprietary drivers ===<br />
The easiest way is to use NVIDIA's nvidia-docker from [https://github.com/NVIDIA/nvidia-docker nvidia-docker Github]. You can follow the instructions on the Github page regarding how to install it. The nvidia-docker will automatically find your installed NVIDIA drivers and will expose them into the Docker container at runtime, so you don't have to rebuild your container whenever you upgrade your NVIDIA drivers.<br />
<br />
Note that if you do this, you must pass <code>-n</code> switch to the <code>build.sh</code> and <code>run.sh</code> scripts from the kdepim-docker.git repository.<br />
<br />
=== Nouveau (NVIDIA opensource drivers) ===<br />
TODO<br />
<br />
=== Intel ===<br />
Works out of the box<br />
<br />
=== AMD/ATI ===<br />
TODO<br />
<br />
== Building Docker image ==<br />
<br />
In order to build the Docker image, run the <code>build.sh</code> script. If you are<br />
using proprietary NVIDIA drivers, run the script with the <code>-n</code> switch.<br />
<br />
The command will create a Docker image called kdepim:dev.<br />
<br />
== Running the Docker container ==<br />
<br />
To run the container, use the <code>run.sh</code> script:<br />
<br />
run.sh $HOME/kdepim-dev<br />
<br />
If you are using proprietary NVIDIA drivers, run the script with the <code>-n</code> switch:<br />
<br />
run.sh -n $HOME/kdepim-dev<br />
<br />
The content of the directory will be available in the container in the<br />
/home/neon/kdepim directory.<br />
<br />
If you want to get another terminal window opened inside the same container, just run run.sh again. It will automatically create a new terminal on the already running container.<br />
<br />
''Note:''<br />
<ul>In some systems, the container's /home/neon/kdepim directory may not be writable. If so, edit <code>run.sh</code> to remove the parameter <code>:rw,z</code> from the <code>-v</code> options. Then delete the container with the command <code>docker rm kdepim-dev</code>, and run the container again.<br />
</ul><br />
<br />
== Building and updating KDE PIM ==<br />
<br />
Once inside the container, you can use the following commands to compile the<br />
entire KDE PIM suite:<br />
<br />
sudo apt update<br />
sudo apt full-upgrade<br />
kdesrc-build kde-pim<br />
<br />
This will take a lot of time the first time, but all subsequent builds will be<br />
faster thanks to incremental builds and use of ccache. You can also use a specific repository name instead of the<br />
<code>kde-pim</code> group.<br />
<br />
Check the [https://kdesrc-build.kde.org kdesrc-build documentation] for more<br />
details about using kdesrc-build. For a start, you may want to customize the <code>-j</code> option in the <code>make-options</code> option group in /home/neon/.kdesrc-buildrc.<br />
<br />
kdesrc-build will clone all the repositories into /home/neon/kdepim/src/kde/pim.<br />
Build directories (where you can run <code>make</code> manually) are in /home/neon/kdepim/build/kde/pim.<br />
The binaries are installed to /home/neon/kdepim/install. The environment<br />
of the container is adjusted to work with the custom installation prefix.<br />
<br />
== Development tools ==<br />
<br />
There's [https://www.kdevelop.org KDevelop] and [https://www.qt.io/ide/ QtCreator]<br />
preinstalled in the container and you can run them from there. You can also use<br />
them from outside of the container, but code completion might not work perfectly then.<br />
<br />
You can also use any other IDE of your choice either by installing it into the container<br />
with apt-get or use it from outside of the container.<br />
<br />
== Contributing ==<br />
<br />
You can find more details in the [[KDE_PIM/Development|Development]] section of our wiki. If you have any issues or questions, feel free to stop by on our IRC channel (#kontact) on freenode, or talk to us on the [[KDE_PIM/Development#Mailing_Lists|kde-pim mailinglist]].</div>Gjditchfieldhttps://community.kde.org/index.php?title=KDE_PIM/Akonadi/Architecture&diff=91259KDE PIM/Akonadi/Architecture2021-03-09T21:29:27Z<p>Gjditchfield: /* Notification API */</p>
<hr />
<div>= Akonadi Concepts and Architecture =<br />
This document describes and explains the core elements within Akonadi (like ''Items'', ''Collections'', etc.) as well as the architecture of the entire solution (clients, agents, server, etc.) and how they interact with each other. The reason this is all explained in a single document is so that it's easier to see how all the dots connect.<br />
<br />
Eventually, this should be moved or copied into Akonadi docs.<br />
<br />
Most of the following is meant to be a rough specification. '''Implementation details''' of a concept that their users should not rely upon are clearly marked.<br />
<br />
<br />
<br />
== Entities ==<br />
The term ''Entity'' is often used as a common term for all the elements described below.<br />
<br />
=== Attributes ===<br />
''Attributes'' are additional metadata that can be attached to other ''Entities'' (except for other ''Attributes''). An ''Attribute'' has a type and a value. ''Client applications'' and ''Agents'' can define their own ''Attributes'', but there are also some pre-defined ''Attributes''.<br />
<br />
'''Example:''' The pre-defined "EntityDisplay" ''Attribute'' allows customizing how an ''Entity'' is presented to the user in a client (by setting custom display name, icon, background color etc.).<br />
<br />
=== Items ===<br />
An ''Item'' is an abstract representation of data. ''Items'' have metadata (ID, size, MIME type, etc.), ''Payload'' parts (the actual data) and attributes. Each ''Item'' has exactly one parent ''Collection''.<br />
<br />
'''Example:''' An ''Item'' can represent an email. Such an email ''Item'' may have envelope, head and one or more body ''Payload'' parts. An ''Item'' can also represent a contact, a calendar event etc.<br />
<br />
=== Collections ===<br />
A ''Collection'', as the name suggests, is a collection of ''Items''. A ''Collection'' can also have child ''Collections'', thus creating a ''Collection tree''. ''Collections'' can also have attributes. Each ''Collection'' is owned by a ''Resource'' (see below). Finally, a list of MIME types is associated with each ''Collection''; every ''Item'' in a collection must be of one of the associated MIME types.<br />
<br />
'''Example:''' Email folders are collections of ''Items'' of "email" MIME type. Calendars are collections of ''Items'' of "Todo" or "Event" MIME type.<br />
<br />
=== Virtual Collections ===<br />
A ''Virtual Collection'' is a ''Collection'' that cannot own ''Items'' or have non-virtual subcollections. Instead of being a parent of ''Items'', ''Items'' are ''linked'' into ''Virtual Collections''. One ''Item'' can be linked into multiple ''Virtual Collections''.<br />
<br />
'''Example:''' ''Virtual Collections'' are typically used to hold search results, i.e., a ''Virtual Collection'' represents a search query and all ''Items'' linked to it are those that match the query.<br />
<br />
'''Implementation detail:''' ''Virtual Collections'' are represented as regular ''Collections''.<br />
<br />
=== Tags ===<br />
A "Tag" is a unary relation on "Items", and can thus be seen as an "Item" property. A single ''Item'' can have multiple ''Tags'' and a single ''Tag'' can be assigned to multiple ''Items''.<br />
<br />
'''Example:''' a "Work" tag can be assigned to many emails, tasks and events (or rather ''Items'' representing those) that are somehow related to the user's work.<br />
<br />
=== Relation ===<br />
A ''Relation'' is a binary relation on "Items", i.e., it describes a specific relation between '''exactly two''' ''Items''. A single ''Item'' can be in multiple ''Relations'', even in multiple ''Relations'' of the same type.<br />
<br />
'''Example:''' We can have an "INVITATION" ''Relation'' between an ''Item'' that represents an email with meeting invitation and an ''Item'' that represents a calendar event that was created from this invitation email. If the event has multiple participants and an invitation email was generated for each participant, those emails would all be in "INVITATION" '''Relation''' to the event.<br />
<br />
<br />
<br />
== Basic components ==<br />
<br />
=== Server ===<br />
''Server'' refers to the server process that other components talk to via the Akonadi ''Protocol''. It manages the cached ''Entities'' and persists them.<br />
<br />
'''Implementation detail:''' The ''Server'' uses a SQL database to persist the cached ''Entity''.<br />
<br />
=== Agents ===<br />
''Agents'' are single-purpose processes that get notified when an ''Entity'' is created, modified or removed from the ''Server''.<br />
<br />
'''Example:''' A MailFilterAgent which is notified whenever a new ''Item'' is created; if the ''Item'' holds an email, it will apply local mail filters to it and store the change back in Akonadi. <br />
<br />
=== Resources ===<br />
''Resources'' are special cases of ''Agents'' that synchronize data between the ''Server'' and a remote server.<br />
<br />
'''Example:''' An "IMAP resource" synchronizes data between the ''Server'' and some IMAP server.<br />
<br />
=== Agent Types and Agent Instances ===<br />
An ''Agent Type'' is a named ''Agent'' implementation. We cannot have two ''Agent Types'' with the same name. An ''Agent Instance'' is a running instance of some ''Agent Type''. Each ''Agent Type'' can have multiple ''Agent Instances''.<br />
<br />
The same terminology applies to ''Resources'': There are ''Resource Types'' with a unique name, and each can have multiple ''Resource Instances''.<br />
<br />
'''Example:''' To manage emails on multiple IMAP servers, we can create multiple ''Resource Instances'' of type ''IMAP resource''. The name of that type might be "IMAPResource", and there cannot be another ''Resource Type'' with the same name.<br />
<br />
=== Clients ===<br />
''Clients'' are user-facing applications that presents data from Akonadi to users and allows them to interact with the data.<br />
<br />
'''Example:''' ''KMail'' is a ''Client'' that presents email ''Items'' to the user, lets them create new email (as ''Items'') or folders (as ''Collections''), etc. ''KOrganizer'' works with calendar data instead.<br />
<br />
<br />
<br />
== Some more concepts ==<br />
<br />
=== ID ===<br />
''Item ID'', ''Collection ID'', and ''Tag ID'' are database primary keys, but are exposed to clients to uniquely identify each ''Entity''.<br />
<br />
=== Remote ID ===<br />
''RemoteID'' is a string-based identifier that is used by the backend (IMAP server, CalDAV server etc.) to identify the ''Entity''. This is only exposed to ''Resources'', since those are the only ones to actually understand what the ''Remote ID'' means.<br />
<br />
'''Example:''' The ''Remote ID'' can be the UID of an email ''Item'' or the mailbox name for a ''Collection'' on an IMAP ''Resource'', or the name of an email file on a Maildir ''Resource''.<br />
<br />
=== GID ===<br />
''GID'' is a string-based identifier extracted from the payload and is exposed to clients.<br />
<br />
'''Example:''' The Message-ID header of an email or the UID of an iCal event are typical ''GID''s of their corresponding ''Items''.<br />
<br />
=== Payload Type ===<br />
Each ''Payload'' part of an ''Item'' has one of three ''Payload'' types:<br />
<br />
'''Internal Payloads''' are stored directly inside Akonadi's database. These are used if the ''Payload'' is sufficiently small (4kB by default).<br />
<br />
'''External Payloads''' are used for larger payloads. The actual payload is stored in a separate cache file (inside file_db_data), and then only name of that file is stored in Akonadi's database.<br />
<br />
Finally, '''Foreign Payloads''' can be used for ''Resources'' where the backend is not remote, but presents local files to Akonadi instead. The database holds the absolute filepath of the local file that holds an ''Item's'' part. Note, however, that ''Foreign Payloads'' are not used by anyone as of now (2018-06).<br />
<br />
=== Cache ===<br />
Akonadi is a cache, not a storage. New ''Items'' are downloaded from the backend services (IMAP server, CalDAV server, maildir, ...) by ''Resources'' and uploaded to the ''Server'' reguarily. Any changes done to ''Entities '' by ''Clients'' (marking an email as read, creating a new event, deleting a contact etc.) are send to the respective ''Resource'' that owns the ''Item'' in question, and the ''Resource'' replays the change to the remote service. If the remote service is not available (let's say user is offline, but they mark a bunch of email as read or move them to some other folder) the changes are recorded by the ''Resource'' and are replayed once network is available.<br />
<br />
<br />
<br />
= Architecture Overview =<br />
<br />
'''TODO:''' This mix of architecture specification and implementation details might not be ideal.<br />
<br />
== Protocol ==<br />
All components communicate with each other via the ''Protocol''. The ''Protocol'' is a custom binary protocol with commands and responses. Each ''Client'' opens one or more connections (called ''Session'', ''Command Session'' or ''Command Bus'') to the ''Server'' and can send commands to the server requesting or modifying data.<br />
<br />
Each ''Client'' can create multiple ''Sessions''. This is useful because Sessions don't support command pipelining, meaning that the next command in the queue is not sent to the server until a response to the previous command has arrived, which can cause undesirable waits for the user.<br />
<br />
Note that the ''Protocol'' is an implementation detail. It is not exposed to the ''Clients'', who only interact with the ''Server'' via the Client API, which internally issues and handles the communication via the ''Protocol''.<br />
<br />
'''Example:''' In KMail, the message list and the message viewer each have their own ''Session''. This way, when the user opens a huge folder, they can click on the first email immediately and the message viewer can retrieve it through its own ''Session'' without having to wait for the message list to receive all emails from Akonadi first.<br />
<br />
<br />
== Akonadi Control ==<br />
''akonadi_control'' is a small, but very important part of Akonadi. When you type "akonadictl start", or when you start an Akonadi-enabled application like Kontact, they will start the ''akonadi_control'' process. Akonadi Control is responsible for starting the Akonadi server and all configured Resources. It also automatically restarts them when any of them crashes. The second important role of Akonadi Control is that it provides a DBus interface to communicate with the Akonadi Resources.<br />
<br />
== Akonadi Server ==<br />
Akonadi Server is the implementation of the ''Server'' concept described above. In principle, the Akonadi Server is very simple: it receives commands from clients, handles them by reading or writing to the database, sends back a response and generates a ''Change Notification'' if needed.<br />
<br />
Each connection is handled in a separate thread on the server in a Connection object. This allows the implementation of the command handlers to be blocking and also allows to keep some context for each connection. Whenever a new command is received on a connection, it inspects which type of command it is and creates a respective Handler (e.g. StoreHandler, AppendHandler, MoveHandler, etc.)<br />
<br />
For read ("Fetch") commands, the respective Handler will construct an SQL query to retrieve the requested Entities from the database. It will then serialize them into the Protocol and send them back to the client. In some cases, the Akonadi Server can request that the Item payloads are first retrieved from the owning Resource - this is because Collections can have an expiration policy, meaning that after some timeout, Akonadi Server will delete the payload of the Items in that Collection from the database. When a client requests a payload of an Item that is missing the payload, the Akonadi Server will request the Resource that owns the Item (via a DBus call) to retrieve the payload and upload it to Akonadi using the standard Job mechanism. Once done, the Resource notifies the Server via DBus call again that it has finished and the Akonadi server continues with retrieval as usual.<br />
<br />
For each write command, once the data are written to the database, the Server will generate a change notification describing what and how was changed.<br />
<br />
The Server also provides search functionality in part, which is described in detail below.<br />
<br />
== Search ==<br />
There is a special agent called Akonadi Indexing Agent which listens to changes in ''Items'' and indexes the ''Items'' into a Xapian database. There is a separate database for emails, contacts, event, notes, and contacts parsed from emails (like senders, etc.).<br />
<br />
When a client wants to perform a search, they can either query the Xapian database directly in-process through a search query, which will return a list of ''Item'' IDs matching the query. The client can then retrieve the respective Items from Akonadi.<br />
<br />
A second option is a so-called persistent search. Persistent search is represented as a ''Virtual Collection'' belonging to the virtual Search ''Resource''. The ''Virtual Collection'' holds a search query, which is re-executed whenever an Item changes and all Items matching the query are linked to the ''Virtual Collection''. This allows keeping a persistent filter for ''Items'' even across multiple different ''Collections''.<br />
<br />
Search infrastructure is currently undergoing major overhaul codenamed "Make Indexing Great Again". See https://phabricator.kde.org/T7014 for details.<br />
<br />
<br />
<br />
= Client API =<br />
From an application point of view, the ''Server'' and the ''Protocol'' are just implementation details that they are not aware of and don't interact with directly in any way. The only means for ''Clients'' to interact with Akonadi is through the Client API.<br />
<br />
''Clients'' communicate with the server either using the ''Jobs API'', the ''Notification API'', or a full-fledged ''Entity Tree Model''.<br />
<br />
== Jobs API ==<br />
''Jobs'' are the core elements of the Client API. A ''Job'' is an asynchronous task that can retrieve data from Akonadi or modify them. Once finished, the job emits the ''result()'' signal after which the ''Client'' can handle the result of the ''Job''.<br />
<br />
A ''Job'' is associated to a ''Session'' on creation. If the caller does not specify a ''Session'', the default ''Session'' is used. Each ''Session'' has a ''Job'' queue, where new ''Jobs'' are automatically enqueued. The ''Jobs'' of each ''Session'' are never processed in parallel, but sequentially: A ''Job'' is only processed when every earlier ''Job'' is finished.<br />
<br />
'''Example:''' An ''ItemFetchJob'' retrieves ''Items'' from Akonadi; an ''CollectionModifyJob'' modifies ''Collections''.<br />
<br />
== Notification API ==<br />
If a ''Client'' is interested in changes to ''Entities'' (create/modify/move/remove/link/...), it may subscribe to ''Change Notifications'' by creating a ''Monitor''. A ''Monitor'' has signals for each type of change that can occur, so ''Clients'' can connect to only those they are interested in. At any time, the ''Client'' may set the ''Monitor's'' scope, which specifies the kind of notification the ''Client'' is interested in, like the kind of affected ''Entities'', only a specific ''Entity'', the type of change, etc. For each ''Entity'' change that matches the scope, the ''Monitor'' issues a ''Change Notification'' that describes the change. The ''Change Notification'' contains a description of the change as well as the changed ''Item'', the latter being called ''Notification Payload''.<br />
<br />
A ''Change Recorder'' is a special ''Monitor'' that writes each reported change to a journal file. The user is responsible to call ''changeProcessed()'' whenever it handles a change upon which the ''Change Recorder'' will remove the notification from the journal and will dispatch the next notification in the queue. A ''Change Recorder'' is only very rarely needed by ''Clients''. Instead, they are used by some ''Resources'' to record local changes that have not yet been propagated to a remote server, e.g., if an ''Item'' of a ''Resource'' is modified while that ''Resource'' is offline. In this case, all notifications will be stored in the ''Change Recorder's'' journal and once the internet connection is available again, the Resource will request the ''Change Recorder'' to replay all notifications from the journal so that it can upload the changes to the backend.<br />
<br />
'''Implementation detail:''' ''Monitors'' establish a connection, called subscription, to the ''Server'' upon creation, and the ''Server'' keeps track of the currently existing subscriptions and the scopes of the corresponding ''Monitors''. Whenever an ''Entity'' is modified on the ''Server'' by a ''Client'' or ''Agent'', the ''Server'' generates a notification message that describes which ''Entities'' have changed, and how. It will then compare the notification message to the scopes of the subscribers to see if the subscriber is interested in this particular notification. If the notification message matches a subscriber's scope, the notification message as well as the changed ''Entity'' (''Notification Payload'') is sent over to the subscriber.<br />
<br />
On the client side, the notification message is received by the ''Monitor'' and put into a pipeline. The ''Monitor'' then issues a ''Change Notification'' containing a description of the change as well as the changed ''Entities'' themselves. This is done by emitting an appropriate signal that depends on the type of the notification.<br />
<br />
'''TODO:''' Does a Monitor establish two distinct connections, one Notification Bus and a Command Bus? If so, which is used for what? I guess that the Command Bus is used to subscribe/unsubscribe and to set the scope (communication from Monitor to Server), and the Notification Bus is used for the notification messages (communication from Server to Monitor)?<br />
<br />
== Entity Tree Model (ETM) ==<br />
An ''Entity Tree Model'' is a QAbstractItemModel which holds the entire tree of ''Collections'' and ''Items'' and keeps it up to date. It is possible to filter the content of an ETM in many ways to only include Entities of a certain type or MIME type. An ETM is usually used in applications in combination with various proxy models.<br />
<br />
It is not possible to modify ''Entities'' by changing them in the ETM. Instead, the ''Client'' has to use a ''Job'' to modify an ''Entity''.<br />
<br />
'''Example:''' In KMail, the folder list and message list are both sharing the same ETM under the hood, but use different proxy models to display only a specific part of the tree in each view.<br />
<br />
'''Implementation detail:''' An ETM automatically keeps itself up-to-date by using a ''Monitor''. Therefore, it will automatically (and asynchronously) reflect all changes made via the Jobs API by appropiately processing ''Change Notifications''.<br />
<br />
<br />
<br />
= Resources =<br />
As explained above, ''Resources'' take care of synchronizing changes between Akonadi and the ''Resource's'' backend (IMAP server, CalDAV server, local iCal file, etc.).<br />
<br />
'''TODO:''' This section is interesting for ''Resource'' authors, but then it lacks a ''Resource API'' section. Part of that is currently described in ''Change Replay'' (the fact that all ''Resources'' implement ''AgentBase::Observer'').<br />
<br />
== Tasks ==<br />
In general, there are two types of tasks: ChangeReplay and Sync. ChangeReplay tasks have the highest priority and they represent a change in the Akonadi data that needs to be written to the backend. This can be a new flag being added to an email, or a new calendar event being created or a contact being removed. Sync tasks (FetchItems, SyncCollectionTree, SyncCollection etc.) are tasks that are asking the Resource to download any new changes from the backend and put them into Akonadi. Sync tasks do not write anything to the backend.<br />
<br />
'''TODO:''' It should be possible to describe both ''Tasks'' without mentioning the ''Scheduler''. Also, make clear that Replay is Akonadi-to-backend propagation, and Sync is backend-to-Akonadi propagation.<br />
<br />
=== Change Replay ===<br />
''Change Replay'' tasks are created whenever the ''Change Recorder'' of a ''Resource'' is notified about a change. The ''Change Recorder'' will store the ''Change Notification'' in a journal and will pass it to the ''Scheduler''. In case the ''Resource'' cannot handle the change immediately, maybe because the ''Resource'' is offline and it needs network access to connect to the backend, the change remains stored in the ''Change Recorder's'' journal until the ''Resource'' goes online again. When that happens, the ''Change Recorder'' is asked to replay all the changes from the journal by passing them to the ''Scheduler''.<br />
<br />
Each ''Resource'' implements the ''AgentBase::Observer'' interface. This interface has methods like ''itemAdded(item, parentCol)'', ''itemsRemoved(items)'', ''itemsFlagsChanged(items, addedFlags, removedFlags)'' etc. that must be implemented by the Resource. In those methods, the Resource implementation takes the changed data and writes it to the backend using the backend protocol/format (IMAP, CalDAV, ICal etc.). As the ''Scheduler'' is replaying the tasks, depending on the type of the change it calls the respective method from the Observer interface, waits for the ''Resource'' to confirm that the change has been succesfully written to the backend, and then schedules the next task and so on until it runs out of tasks in the queue or until another task arrives.<br />
<br />
=== Synchronization ===<br />
Synchronization means retrieving data from the backend and storing them in Akonadi, so it's a one-way synchronization. Synchronization can be requested via a ''Resource's'' DBus interface - either via SynchronizeCollectionTree task, SynchronizeCollection task (which synchronizes all Items within a specified Collection) or SynchronizeItem, which retrieves a specified Item. There's also SyncAll tasks, which schedules SynchronizeCollectionTree task followed by SynchronizeCollection task for each Collection. There are more tasks of course to sync attributes, tags etc. but they all work on the same principle. <br />
<br />
The Resource does not have to synchronize the entire Item, for example for emails we often only synchronize the envelope, which is enough to display the email in the message list in KMail, and the actual body is retrieved on demand once the user opens the email in KMail and KMail requests the payload body from Akonadi.<br />
<br />
''Item'' Synchronization happens by the so-called merging process, when the ''Server'' tries to see if an ''Item'' with the same identification already exists in the Akonadi database. If so, it overwrites it with the newly received ''Item''. Otherwise a new ''Item'' entry is created in the database. The merging happens using RID (RemoteID) or optionally GID in cases where RIDs are unstable. It's also possible to combine both.<br />
<br />
== Scheduler ==<br />
Each ''Resource'' has a ''Scheduler'', which holds a queue of tasks that the ''Resource'' should perform. In fact, the ''Scheduler'' has several queues with different priorities. For example, a queue for tasks that write changes from Akonadi to the backend has the highest priority, while the queue to download new changes from the backend has a lower priority. Whenever the ''Resource'' receives a new task (more on tasks below) it puts it into the ''Scheduler'', which then dispatches it based on the priority by calling the respective method in the Resource implementation. Once the Resource is done with handling the task, it tells the ''Scheduler'' that it's done and that it can schedule the next task.<br />
<br />
== Online/Offline ==<br />
Resources can be in an online or offline state. This is not related to network connectivity status; even a local-only resource like the Maildir resource can be in offline state. The state indicates whether the ''Resource'' is able to store changes in its backend. For remote ''Resources'' (like IMAP) the online/offline status often matches the online/offline status of the network connectivity, but it's also possible to manually switch a ''Resource'' to offline.<br />
<br />
Resources which are in the offline state reject all Sync requests and store ''Change Notifications'' in their ''Change Recorder's'' journal. Once the ''Resource'' switches back to online, it will first replay all pending changes from its ''Change Recorder''; only then it will start processing Sync requests again.<br />
<br />
== Configuration ==<br />
Most ''Resources'' need a configuration. The configuration dialog can be invoked through a DBus call to the ''Resource'' and runs within the ''Resource's'' process.<br />
<br />
'''Example:''' Configuration options can be the server to which to connect to, credentials, sync frequency, etc.<br />
<br />
<br />
<br />
= DB Tables =<br />
<br />
This is a brief description of tables in the database that the ''Server'' stores all the data in and how they relate to the ''Entities'' and components described above.<br />
<br />
== SchemaVersion ==<br />
A standalone table that holds information about the current version of the schema.<br />
<br />
== ResourceTable == <br />
Holds a list of active ''Agent'' and ''Resources'' ''Instances''.<br />
<br />
'''TODO:''' What is an ''active Agent''? Is it an existing one, or one that is online? Can ''Agents'' be inactive?<br />
<br />
== PimItemTable ==<br />
Holds metadata about ''Items'' - ID, parent ''Collection'', size etc. This is a very big table - one row per every email, contact, event etc.<br />
<br />
== PartTable ==<br />
''PartTable'' holds the actual ''Payload'' parts and ''Attributes'' for ''Items''. This is the largest table in Akonadi as it contains on average 3 rows per each row in ''PimItemTable''.<br />
<br />
== PartTypeTable ==<br />
Contains names of parts and attributes from ''PartTable'' (like PLD:ENVELOPE, PLD:HEAD, ATR:noselect, etc.) - this is a very small table (around 10 rows normally) and its purpose is purely to de-duplicate the often-repeated strings from the already-big ''PartTable''.<br />
<br />
== MimeTypeTable ==<br />
''MimeTypeTable'' holds a list of MIME types. This is a very small table and like ''PartTypeTable'' is used simply to de-duplicate repeated strings from the ''PimItemTable'' and to allow a many-to-many relation between ''Collections'' and MIME types.<br />
<br />
== FlagTable ==<br />
''FlagTable'' holds ''Item'' flags, like "seen", "spam", "hasattachment" etc. The table only holds simple strings and is fairly small (we have around 20 flags).<br />
<br />
'''TODO:''' Flags are not described in the ''Basic Entities'' section above.<br />
<br />
== PimItemFlagRelation ==<br />
A single ''Item'' can have 0-N flags and this table describes the relation. This is a fairly big table as it usually has more than one flag per each ''PimItem'' row.<br />
<br />
== CollectionTable ==<br />
The ''CollectionTable'' holds metadata about ''Collections'' - their ID, parent ''Collection'', cache policy etc. This normally a small-ish table - one row per email folder, calendar, addressbook etc.<br />
<br />
== CollectionMimeTypeRelation ==<br />
This many-to-many table describes the relation between ''Collections'' in the ''CollectionTable'' and MIME types in the ''MimeTypeTable'', i.e., which ''Collection'' can hold ''Items'' of which MIME type.<br />
<br />
== CollectionAttributeTable ==<br />
This table holds additional ''Attributes'' for ''Collections''. One ''Collection'' can have multiple ''Attributes'', but an attribute belongs to exactly one ''Collection''.<br />
<br />
== CollectionPimItemRelation ==<br />
This table describes the relation between ''Items'' and ''Virtual Collections''. This does not describe parent-child relationship, that's in ''PimItemTable.collectionId''. The size of this table varies depending on how much you use the "Search" feature in KMail.<br />
<br />
== TagTable ==<br />
''TagTable'' holds ''Tags''. Usually a small table, one row per ''Tag'' and people generally don't have more then a few dozen ''Tags'' (most people don't use this feature at all).<br />
<br />
== TagTypeTable ==<br />
This table holds ''Tag'' types - this is purely to de-duplicate common strings from ''TabTable''.<br />
<br />
== TagAttributeTable ==<br />
A table equivalent to ''CollectionAttributeTable'', but for ''Tags'' instead of ''Collections''.<br />
<br />
== TagRemoteIdResourceRelation ==<br />
For each ''Tag'', this table holds the remote ID for each ''Resource'' that this ''Tag'' is used in. This allows for different representation of the same ''Tag'' in different ''Resources''.<br />
<br />
'''Example:''' The user wants to tag both emails and events with a "KDE" ''Tag'', but tags in IMAP are "$"-prefixed ("$KDE"), and CalDAV associates a tag's UUID (e.g. "{abcde-ef012-3456}") with events. In this case, the ''TagRemoteIdResourceRelation'' table has a ("KDE", "someIMAPResource", "$KDE") triple and a ("KDE", "someCalDAVResource", "{abcde-ef012-3456}") triple.<br />
<br />
'''TODO:''' This description has a strong specification taste to it. Most of it should probably be moved to the ''Basic Entities'' or ''Basic components'' section.<br />
<br />
== PimItemTagRelation ==<br />
A single ''Item'' can have multiple ''Tags'' and this table describes the relation.<br />
<br />
== RelationTable ==<br />
Holds ''Relations'' between two ''Items''.<br />
<br />
== RelationTypeTable ==<br />
An equivalent to ''TagTypeTable'', but for ''Relations''.</div>Gjditchfieldhttps://community.kde.org/index.php?title=KDE_PIM/Akonadi/Architecture&diff=91257KDE PIM/Akonadi/Architecture2021-03-09T21:05:13Z<p>Gjditchfield: /* Akonadi Server */</p>
<hr />
<div>= Akonadi Concepts and Architecture =<br />
This document describes and explains the core elements within Akonadi (like ''Items'', ''Collections'', etc.) as well as the architecture of the entire solution (clients, agents, server, etc.) and how they interact with each other. The reason this is all explained in a single document is so that it's easier to see how all the dots connect.<br />
<br />
Eventually, this should be moved or copied into Akonadi docs.<br />
<br />
Most of the following is meant to be a rough specification. '''Implementation details''' of a concept that their users should not rely upon are clearly marked.<br />
<br />
<br />
<br />
== Entities ==<br />
The term ''Entity'' is often used as a common term for all the elements described below.<br />
<br />
=== Attributes ===<br />
''Attributes'' are additional metadata that can be attached to other ''Entities'' (except for other ''Attributes''). An ''Attribute'' has a type and a value. ''Client applications'' and ''Agents'' can define their own ''Attributes'', but there are also some pre-defined ''Attributes''.<br />
<br />
'''Example:''' The pre-defined "EntityDisplay" ''Attribute'' allows customizing how an ''Entity'' is presented to the user in a client (by setting custom display name, icon, background color etc.).<br />
<br />
=== Items ===<br />
An ''Item'' is an abstract representation of data. ''Items'' have metadata (ID, size, MIME type, etc.), ''Payload'' parts (the actual data) and attributes. Each ''Item'' has exactly one parent ''Collection''.<br />
<br />
'''Example:''' An ''Item'' can represent an email. Such an email ''Item'' may have envelope, head and one or more body ''Payload'' parts. An ''Item'' can also represent a contact, a calendar event etc.<br />
<br />
=== Collections ===<br />
A ''Collection'', as the name suggests, is a collection of ''Items''. A ''Collection'' can also have child ''Collections'', thus creating a ''Collection tree''. ''Collections'' can also have attributes. Each ''Collection'' is owned by a ''Resource'' (see below). Finally, a list of MIME types is associated with each ''Collection''; every ''Item'' in a collection must be of one of the associated MIME types.<br />
<br />
'''Example:''' Email folders are collections of ''Items'' of "email" MIME type. Calendars are collections of ''Items'' of "Todo" or "Event" MIME type.<br />
<br />
=== Virtual Collections ===<br />
A ''Virtual Collection'' is a ''Collection'' that cannot own ''Items'' or have non-virtual subcollections. Instead of being a parent of ''Items'', ''Items'' are ''linked'' into ''Virtual Collections''. One ''Item'' can be linked into multiple ''Virtual Collections''.<br />
<br />
'''Example:''' ''Virtual Collections'' are typically used to hold search results, i.e., a ''Virtual Collection'' represents a search query and all ''Items'' linked to it are those that match the query.<br />
<br />
'''Implementation detail:''' ''Virtual Collections'' are represented as regular ''Collections''.<br />
<br />
=== Tags ===<br />
A "Tag" is a unary relation on "Items", and can thus be seen as an "Item" property. A single ''Item'' can have multiple ''Tags'' and a single ''Tag'' can be assigned to multiple ''Items''.<br />
<br />
'''Example:''' a "Work" tag can be assigned to many emails, tasks and events (or rather ''Items'' representing those) that are somehow related to the user's work.<br />
<br />
=== Relation ===<br />
A ''Relation'' is a binary relation on "Items", i.e., it describes a specific relation between '''exactly two''' ''Items''. A single ''Item'' can be in multiple ''Relations'', even in multiple ''Relations'' of the same type.<br />
<br />
'''Example:''' We can have an "INVITATION" ''Relation'' between an ''Item'' that represents an email with meeting invitation and an ''Item'' that represents a calendar event that was created from this invitation email. If the event has multiple participants and an invitation email was generated for each participant, those emails would all be in "INVITATION" '''Relation''' to the event.<br />
<br />
<br />
<br />
== Basic components ==<br />
<br />
=== Server ===<br />
''Server'' refers to the server process that other components talk to via the Akonadi ''Protocol''. It manages the cached ''Entities'' and persists them.<br />
<br />
'''Implementation detail:''' The ''Server'' uses a SQL database to persist the cached ''Entity''.<br />
<br />
=== Agents ===<br />
''Agents'' are single-purpose processes that get notified when an ''Entity'' is created, modified or removed from the ''Server''.<br />
<br />
'''Example:''' A MailFilterAgent which is notified whenever a new ''Item'' is created; if the ''Item'' holds an email, it will apply local mail filters to it and store the change back in Akonadi. <br />
<br />
=== Resources ===<br />
''Resources'' are special cases of ''Agents'' that synchronize data between the ''Server'' and a remote server.<br />
<br />
'''Example:''' An "IMAP resource" synchronizes data between the ''Server'' and some IMAP server.<br />
<br />
=== Agent Types and Agent Instances ===<br />
An ''Agent Type'' is a named ''Agent'' implementation. We cannot have two ''Agent Types'' with the same name. An ''Agent Instance'' is a running instance of some ''Agent Type''. Each ''Agent Type'' can have multiple ''Agent Instances''.<br />
<br />
The same terminology applies to ''Resources'': There are ''Resource Types'' with a unique name, and each can have multiple ''Resource Instances''.<br />
<br />
'''Example:''' To manage emails on multiple IMAP servers, we can create multiple ''Resource Instances'' of type ''IMAP resource''. The name of that type might be "IMAPResource", and there cannot be another ''Resource Type'' with the same name.<br />
<br />
=== Clients ===<br />
''Clients'' are user-facing applications that presents data from Akonadi to users and allows them to interact with the data.<br />
<br />
'''Example:''' ''KMail'' is a ''Client'' that presents email ''Items'' to the user, lets them create new email (as ''Items'') or folders (as ''Collections''), etc. ''KOrganizer'' works with calendar data instead.<br />
<br />
<br />
<br />
== Some more concepts ==<br />
<br />
=== ID ===<br />
''Item ID'', ''Collection ID'', and ''Tag ID'' are database primary keys, but are exposed to clients to uniquely identify each ''Entity''.<br />
<br />
=== Remote ID ===<br />
''RemoteID'' is a string-based identifier that is used by the backend (IMAP server, CalDAV server etc.) to identify the ''Entity''. This is only exposed to ''Resources'', since those are the only ones to actually understand what the ''Remote ID'' means.<br />
<br />
'''Example:''' The ''Remote ID'' can be the UID of an email ''Item'' or the mailbox name for a ''Collection'' on an IMAP ''Resource'', or the name of an email file on a Maildir ''Resource''.<br />
<br />
=== GID ===<br />
''GID'' is a string-based identifier extracted from the payload and is exposed to clients.<br />
<br />
'''Example:''' The Message-ID header of an email or the UID of an iCal event are typical ''GID''s of their corresponding ''Items''.<br />
<br />
=== Payload Type ===<br />
Each ''Payload'' part of an ''Item'' has one of three ''Payload'' types:<br />
<br />
'''Internal Payloads''' are stored directly inside Akonadi's database. These are used if the ''Payload'' is sufficiently small (4kB by default).<br />
<br />
'''External Payloads''' are used for larger payloads. The actual payload is stored in a separate cache file (inside file_db_data), and then only name of that file is stored in Akonadi's database.<br />
<br />
Finally, '''Foreign Payloads''' can be used for ''Resources'' where the backend is not remote, but presents local files to Akonadi instead. The database holds the absolute filepath of the local file that holds an ''Item's'' part. Note, however, that ''Foreign Payloads'' are not used by anyone as of now (2018-06).<br />
<br />
=== Cache ===<br />
Akonadi is a cache, not a storage. New ''Items'' are downloaded from the backend services (IMAP server, CalDAV server, maildir, ...) by ''Resources'' and uploaded to the ''Server'' reguarily. Any changes done to ''Entities '' by ''Clients'' (marking an email as read, creating a new event, deleting a contact etc.) are send to the respective ''Resource'' that owns the ''Item'' in question, and the ''Resource'' replays the change to the remote service. If the remote service is not available (let's say user is offline, but they mark a bunch of email as read or move them to some other folder) the changes are recorded by the ''Resource'' and are replayed once network is available.<br />
<br />
<br />
<br />
= Architecture Overview =<br />
<br />
'''TODO:''' This mix of architecture specification and implementation details might not be ideal.<br />
<br />
== Protocol ==<br />
All components communicate with each other via the ''Protocol''. The ''Protocol'' is a custom binary protocol with commands and responses. Each ''Client'' opens one or more connections (called ''Session'', ''Command Session'' or ''Command Bus'') to the ''Server'' and can send commands to the server requesting or modifying data.<br />
<br />
Each ''Client'' can create multiple ''Sessions''. This is useful because Sessions don't support command pipelining, meaning that the next command in the queue is not sent to the server until a response to the previous command has arrived, which can cause undesirable waits for the user.<br />
<br />
Note that the ''Protocol'' is an implementation detail. It is not exposed to the ''Clients'', who only interact with the ''Server'' via the Client API, which internally issues and handles the communication via the ''Protocol''.<br />
<br />
'''Example:''' In KMail, the message list and the message viewer each have their own ''Session''. This way, when the user opens a huge folder, they can click on the first email immediately and the message viewer can retrieve it through its own ''Session'' without having to wait for the message list to receive all emails from Akonadi first.<br />
<br />
<br />
== Akonadi Control ==<br />
''akonadi_control'' is a small, but very important part of Akonadi. When you type "akonadictl start", or when you start an Akonadi-enabled application like Kontact, they will start the ''akonadi_control'' process. Akonadi Control is responsible for starting the Akonadi server and all configured Resources. It also automatically restarts them when any of them crashes. The second important role of Akonadi Control is that it provides a DBus interface to communicate with the Akonadi Resources.<br />
<br />
== Akonadi Server ==<br />
Akonadi Server is the implementation of the ''Server'' concept described above. In principle, the Akonadi Server is very simple: it receives commands from clients, handles them by reading or writing to the database, sends back a response and generates a ''Change Notification'' if needed.<br />
<br />
Each connection is handled in a separate thread on the server in a Connection object. This allows the implementation of the command handlers to be blocking and also allows to keep some context for each connection. Whenever a new command is received on a connection, it inspects which type of command it is and creates a respective Handler (e.g. StoreHandler, AppendHandler, MoveHandler, etc.)<br />
<br />
For read ("Fetch") commands, the respective Handler will construct an SQL query to retrieve the requested Entities from the database. It will then serialize them into the Protocol and send them back to the client. In some cases, the Akonadi Server can request that the Item payloads are first retrieved from the owning Resource - this is because Collections can have an expiration policy, meaning that after some timeout, Akonadi Server will delete the payload of the Items in that Collection from the database. When a client requests a payload of an Item that is missing the payload, the Akonadi Server will request the Resource that owns the Item (via a DBus call) to retrieve the payload and upload it to Akonadi using the standard Job mechanism. Once done, the Resource notifies the Server via DBus call again that it has finished and the Akonadi server continues with retrieval as usual.<br />
<br />
For each write command, once the data are written to the database, the Server will generate a change notification describing what and how was changed.<br />
<br />
The Server also provides search functionality in part, which is described in detail below.<br />
<br />
== Search ==<br />
There is a special agent called Akonadi Indexing Agent which listens to changes in ''Items'' and indexes the ''Items'' into a Xapian database. There is a separate database for emails, contacts, event, notes, and contacts parsed from emails (like senders, etc.).<br />
<br />
When a client wants to perform a search, they can either query the Xapian database directly in-process through a search query, which will return a list of ''Item'' IDs matching the query. The client can then retrieve the respective Items from Akonadi.<br />
<br />
A second option is a so-called persistent search. Persistent search is represented as a ''Virtual Collection'' belonging to the virtual Search ''Resource''. The ''Virtual Collection'' holds a search query, which is re-executed whenever an Item changes and all Items matching the query are linked to the ''Virtual Collection''. This allows keeping a persistent filter for ''Items'' even across multiple different ''Collections''.<br />
<br />
Search infrastructure is currently undergoing major overhaul codenamed "Make Indexing Great Again". See https://phabricator.kde.org/T7014 for details.<br />
<br />
<br />
<br />
= Client API =<br />
From an application point of view, the ''Server'' and the ''Protocol'' are just implementation details that they are not aware of and don't interact with directly in any way. The only means for ''Clients'' to interact with Akonadi is through the Client API.<br />
<br />
''Clients'' communicate with the server either using the ''Jobs API'', the ''Notification API'', or a full-fledged ''Entity Tree Model''.<br />
<br />
== Jobs API ==<br />
''Jobs'' are the core elements of the Client API. A ''Job'' is an asynchronous task that can retrieve data from Akonadi or modify them. Once finished, the job emits the ''result()'' signal after which the ''Client'' can handle the result of the ''Job''.<br />
<br />
A ''Job'' is associated to a ''Session'' on creation. If the caller does not specify a ''Session'', the default ''Session'' is used. Each ''Session'' has a ''Job'' queue, where new ''Jobs'' are automatically enqueued. The ''Jobs'' of each ''Session'' are never processed in parallel, but sequentially: A ''Job'' is only processed when every earlier ''Job'' is finished.<br />
<br />
'''Example:''' An ''ItemFetchJob'' retrieves ''Items'' from Akonadi; an ''CollectionModifyJob'' modifies ''Collections''.<br />
<br />
== Notification API ==<br />
If a ''Client'' is interested in changes to ''Entities'' (create/modify/move/remove/link/...), it may subscribe to ''Change Notifications'' by creating a ''Monitor''. A ''Monitor'' has signals for each type of change that can occur, so ''Clients'' can connect only to those they are interested in. At any time, the ''Client'' may set the ''Monitor's'' scope, which specifies the kind of notification the ''Client'' is interested in, like the kind of affected ''Entities'', only a specific ''Entity'', the type of change, etc. For each ''Entity'' change that matches the scope, the ''Monitor'' issues a ''Change Notification'' that describes the change. The ''Change Notification'' contains a description of the change as well as the changed ''Item'', the latter being called ''Notification Payload''.<br />
<br />
A ''Change Recorder'' is a special ''Monitor'' that writes each reported change to a journal file. The user is responsible to call ''changeProcessed()'' whenever it handles a change upon which the ''Change Recorder'' will remove the notification from the journal and will dispatch the next notification in the queue. A ''Change Recorder'' is only very rarely needed by ''Clients''. Instead, they are used by some ''Resources'' to record local changes that have not yet been propagated to a remote server, e.g., if an ''Item'' of a ''Resource'' is modified while that ''Resource'' is offline. In this case, all notifications will be stored in the ''Change Recorder's'' journal and once the internet connection is available again, the Resource will request the ''Change Recorder'' to replay all notifications from the journal so that it can upload the changes to the backend.<br />
<br />
'''Implementation detail:''' ''Monitors'' establish a connection, called subscription, to the ''Server'' upon creation, and the ''Server'' keeps track of the currently existing subscriptions and the scopes of the corresponding ''Monitors''. Whenever an ''Entity'' is modified on the ''Server'' by a ''Client'' or ''Agent'', the ''Server'' generates a notification message that describes which ''Entities'' have changed, and how. It will then compare the notification message to the scopes of the subscribers to see if the subscriber is interested in this particular notification. If the notification message matches a subscriber's scope, the notification message as well as the changed ''Entity'' (''Notification Payload'') is sent over to the subscriber.<br />
<br />
On the client side, the notification message is received by the ''Monitor'' and put into a pipeline. The ''Monitor'' then issues a ''Change Notification'' containing a description of the change as well as the changed ''Entities'' themselves. This is done by emitting an appropriate signal that depends on the type of the notification.<br />
<br />
'''TODO:''' Does a Monitor establish two distinct connections, one Notification Bus and a Command Bus? If so, which is used for what? I guess that the Command Bus is used to subscribe/unsubscribe and to set the scope (communication from Monitor to Server), and the Notification Bus is used for the notification messages (communication from Server to Monitor)?<br />
<br />
== Entity Tree Model (ETM) ==<br />
An ''Entity Tree Model'' is a QAbstractItemModel which holds the entire tree of ''Collections'' and ''Items'' and keeps it up to date. It is possible to filter the content of an ETM in many ways to only include Entities of a certain type or MIME type. An ETM is usually used in applications in combination with various proxy models.<br />
<br />
It is not possible to modify ''Entities'' by changing them in the ETM. Instead, the ''Client'' has to use a ''Job'' to modify an ''Entity''.<br />
<br />
'''Example:''' In KMail, the folder list and message list are both sharing the same ETM under the hood, but use different proxy models to display only a specific part of the tree in each view.<br />
<br />
'''Implementation detail:''' An ETM automatically keeps itself up-to-date by using a ''Monitor''. Therefore, it will automatically (and asynchronously) reflect all changes made via the Jobs API by appropiately processing ''Change Notifications''.<br />
<br />
<br />
<br />
= Resources =<br />
As explained above, ''Resources'' take care of synchronizing changes between Akonadi and the ''Resource's'' backend (IMAP server, CalDAV server, local iCal file, etc.).<br />
<br />
'''TODO:''' This section is interesting for ''Resource'' authors, but then it lacks a ''Resource API'' section. Part of that is currently described in ''Change Replay'' (the fact that all ''Resources'' implement ''AgentBase::Observer'').<br />
<br />
== Tasks ==<br />
In general, there are two types of tasks: ChangeReplay and Sync. ChangeReplay tasks have the highest priority and they represent a change in the Akonadi data that needs to be written to the backend. This can be a new flag being added to an email, or a new calendar event being created or a contact being removed. Sync tasks (FetchItems, SyncCollectionTree, SyncCollection etc.) are tasks that are asking the Resource to download any new changes from the backend and put them into Akonadi. Sync tasks do not write anything to the backend.<br />
<br />
'''TODO:''' It should be possible to describe both ''Tasks'' without mentioning the ''Scheduler''. Also, make clear that Replay is Akonadi-to-backend propagation, and Sync is backend-to-Akonadi propagation.<br />
<br />
=== Change Replay ===<br />
''Change Replay'' tasks are created whenever the ''Change Recorder'' of a ''Resource'' is notified about a change. The ''Change Recorder'' will store the ''Change Notification'' in a journal and will pass it to the ''Scheduler''. In case the ''Resource'' cannot handle the change immediately, maybe because the ''Resource'' is offline and it needs network access to connect to the backend, the change remains stored in the ''Change Recorder's'' journal until the ''Resource'' goes online again. When that happens, the ''Change Recorder'' is asked to replay all the changes from the journal by passing them to the ''Scheduler''.<br />
<br />
Each ''Resource'' implements the ''AgentBase::Observer'' interface. This interface has methods like ''itemAdded(item, parentCol)'', ''itemsRemoved(items)'', ''itemsFlagsChanged(items, addedFlags, removedFlags)'' etc. that must be implemented by the Resource. In those methods, the Resource implementation takes the changed data and writes it to the backend using the backend protocol/format (IMAP, CalDAV, ICal etc.). As the ''Scheduler'' is replaying the tasks, depending on the type of the change it calls the respective method from the Observer interface, waits for the ''Resource'' to confirm that the change has been succesfully written to the backend, and then schedules the next task and so on until it runs out of tasks in the queue or until another task arrives.<br />
<br />
=== Synchronization ===<br />
Synchronization means retrieving data from the backend and storing them in Akonadi, so it's a one-way synchronization. Synchronization can be requested via a ''Resource's'' DBus interface - either via SynchronizeCollectionTree task, SynchronizeCollection task (which synchronizes all Items within a specified Collection) or SynchronizeItem, which retrieves a specified Item. There's also SyncAll tasks, which schedules SynchronizeCollectionTree task followed by SynchronizeCollection task for each Collection. There are more tasks of course to sync attributes, tags etc. but they all work on the same principle. <br />
<br />
The Resource does not have to synchronize the entire Item, for example for emails we often only synchronize the envelope, which is enough to display the email in the message list in KMail, and the actual body is retrieved on demand once the user opens the email in KMail and KMail requests the payload body from Akonadi.<br />
<br />
''Item'' Synchronization happens by the so-called merging process, when the ''Server'' tries to see if an ''Item'' with the same identification already exists in the Akonadi database. If so, it overwrites it with the newly received ''Item''. Otherwise a new ''Item'' entry is created in the database. The merging happens using RID (RemoteID) or optionally GID in cases where RIDs are unstable. It's also possible to combine both.<br />
<br />
== Scheduler ==<br />
Each ''Resource'' has a ''Scheduler'', which holds a queue of tasks that the ''Resource'' should perform. In fact, the ''Scheduler'' has several queues with different priorities. For example, a queue for tasks that write changes from Akonadi to the backend has the highest priority, while the queue to download new changes from the backend has a lower priority. Whenever the ''Resource'' receives a new task (more on tasks below) it puts it into the ''Scheduler'', which then dispatches it based on the priority by calling the respective method in the Resource implementation. Once the Resource is done with handling the task, it tells the ''Scheduler'' that it's done and that it can schedule the next task.<br />
<br />
== Online/Offline ==<br />
Resources can be in an online or offline state. This is not related to network connectivity status; even a local-only resource like the Maildir resource can be in offline state. The state indicates whether the ''Resource'' is able to store changes in its backend. For remote ''Resources'' (like IMAP) the online/offline status often matches the online/offline status of the network connectivity, but it's also possible to manually switch a ''Resource'' to offline.<br />
<br />
Resources which are in the offline state reject all Sync requests and store ''Change Notifications'' in their ''Change Recorder's'' journal. Once the ''Resource'' switches back to online, it will first replay all pending changes from its ''Change Recorder''; only then it will start processing Sync requests again.<br />
<br />
== Configuration ==<br />
Most ''Resources'' need a configuration. The configuration dialog can be invoked through a DBus call to the ''Resource'' and runs within the ''Resource's'' process.<br />
<br />
'''Example:''' Configuration options can be the server to which to connect to, credentials, sync frequency, etc.<br />
<br />
<br />
<br />
= DB Tables =<br />
<br />
This is a brief description of tables in the database that the ''Server'' stores all the data in and how they relate to the ''Entities'' and components described above.<br />
<br />
== SchemaVersion ==<br />
A standalone table that holds information about the current version of the schema.<br />
<br />
== ResourceTable == <br />
Holds a list of active ''Agent'' and ''Resources'' ''Instances''.<br />
<br />
'''TODO:''' What is an ''active Agent''? Is it an existing one, or one that is online? Can ''Agents'' be inactive?<br />
<br />
== PimItemTable ==<br />
Holds metadata about ''Items'' - ID, parent ''Collection'', size etc. This is a very big table - one row per every email, contact, event etc.<br />
<br />
== PartTable ==<br />
''PartTable'' holds the actual ''Payload'' parts and ''Attributes'' for ''Items''. This is the largest table in Akonadi as it contains on average 3 rows per each row in ''PimItemTable''.<br />
<br />
== PartTypeTable ==<br />
Contains names of parts and attributes from ''PartTable'' (like PLD:ENVELOPE, PLD:HEAD, ATR:noselect, etc.) - this is a very small table (around 10 rows normally) and its purpose is purely to de-duplicate the often-repeated strings from the already-big ''PartTable''.<br />
<br />
== MimeTypeTable ==<br />
''MimeTypeTable'' holds a list of MIME types. This is a very small table and like ''PartTypeTable'' is used simply to de-duplicate repeated strings from the ''PimItemTable'' and to allow a many-to-many relation between ''Collections'' and MIME types.<br />
<br />
== FlagTable ==<br />
''FlagTable'' holds ''Item'' flags, like "seen", "spam", "hasattachment" etc. The table only holds simple strings and is fairly small (we have around 20 flags).<br />
<br />
'''TODO:''' Flags are not described in the ''Basic Entities'' section above.<br />
<br />
== PimItemFlagRelation ==<br />
A single ''Item'' can have 0-N flags and this table describes the relation. This is a fairly big table as it usually has more than one flag per each ''PimItem'' row.<br />
<br />
== CollectionTable ==<br />
The ''CollectionTable'' holds metadata about ''Collections'' - their ID, parent ''Collection'', cache policy etc. This normally a small-ish table - one row per email folder, calendar, addressbook etc.<br />
<br />
== CollectionMimeTypeRelation ==<br />
This many-to-many table describes the relation between ''Collections'' in the ''CollectionTable'' and MIME types in the ''MimeTypeTable'', i.e., which ''Collection'' can hold ''Items'' of which MIME type.<br />
<br />
== CollectionAttributeTable ==<br />
This table holds additional ''Attributes'' for ''Collections''. One ''Collection'' can have multiple ''Attributes'', but an attribute belongs to exactly one ''Collection''.<br />
<br />
== CollectionPimItemRelation ==<br />
This table describes the relation between ''Items'' and ''Virtual Collections''. This does not describe parent-child relationship, that's in ''PimItemTable.collectionId''. The size of this table varies depending on how much you use the "Search" feature in KMail.<br />
<br />
== TagTable ==<br />
''TagTable'' holds ''Tags''. Usually a small table, one row per ''Tag'' and people generally don't have more then a few dozen ''Tags'' (most people don't use this feature at all).<br />
<br />
== TagTypeTable ==<br />
This table holds ''Tag'' types - this is purely to de-duplicate common strings from ''TabTable''.<br />
<br />
== TagAttributeTable ==<br />
A table equivalent to ''CollectionAttributeTable'', but for ''Tags'' instead of ''Collections''.<br />
<br />
== TagRemoteIdResourceRelation ==<br />
For each ''Tag'', this table holds the remote ID for each ''Resource'' that this ''Tag'' is used in. This allows for different representation of the same ''Tag'' in different ''Resources''.<br />
<br />
'''Example:''' The user wants to tag both emails and events with a "KDE" ''Tag'', but tags in IMAP are "$"-prefixed ("$KDE"), and CalDAV associates a tag's UUID (e.g. "{abcde-ef012-3456}") with events. In this case, the ''TagRemoteIdResourceRelation'' table has a ("KDE", "someIMAPResource", "$KDE") triple and a ("KDE", "someCalDAVResource", "{abcde-ef012-3456}") triple.<br />
<br />
'''TODO:''' This description has a strong specification taste to it. Most of it should probably be moved to the ''Basic Entities'' or ''Basic components'' section.<br />
<br />
== PimItemTagRelation ==<br />
A single ''Item'' can have multiple ''Tags'' and this table describes the relation.<br />
<br />
== RelationTable ==<br />
Holds ''Relations'' between two ''Items''.<br />
<br />
== RelationTypeTable ==<br />
An equivalent to ''TagTypeTable'', but for ''Relations''.</div>Gjditchfieldhttps://community.kde.org/index.php?title=KDE_PIM&diff=91196KDE PIM2021-02-28T18:27:19Z<p>Gjditchfield: Useful Techbase contents moved here.</p>
<hr />
<div>[[File:Mascot konqi-commu-mail.png|300px|right|Konqi and the mails]]<br />
Welcome to the home of the KDE PIM team. These pages act as a central point of information and collaboration for the developers and all other contributors or interested observers of KDE PIM. Here you will find information about KDE PIM, about the [[KDE_PIM/People|people]] behind KDE PIM, and how you can [[KDE_PIM/Contact|become part of the KDE PIM community]]. If you are looking for information about the usage of the KDE PIM applications then please have a look at [http://userbase.kde.org/Applications/Office#Kontact userbase.kde.org].<br />
<br />
KDE PIM is part of [http://www.kde.org KDE]. Its goal is to provide a suite of applications to [http://en.wikipedia.org/wiki/Personal_information_management manage personal information]. This includes mail, calendar, contacts and more. The main result is [http://userbase.kde.org/Kontact Kontact], our personal information manager.<br />
<br />
Kontact offers a thin graphical shell over the KDE PIM backend, the culmination of over 10 years of work to integrate the separate KDE PIM applications. The goal of this work was to ease development of new sources of data (like a google calendar or facebook contact importer) and to increase reliability and performance of KDE PIM. It will also make it far easier to find new ways to combine and view your personal information in new ways.<br />
<br />
== Vision ==<br />
<br />
Kontact is a personal information manager for people who care about privacy and have email-based workflows. It enables them to deal with large amounts of emails and coordinate teamwork in an effective way.<br />
<br />
We provide privacy by default and productivity in an elegant package.<br />
<br />
Unlike proprietary web applications it is Free Software and supports open standards. It gives you full control over your data and enables offline access.<br />
<br />
== Information ==<br />
<br />
[[KDE PIM/News|News]]<br />
<br />
[[KDE PIM/Contact|Contact]]<br />
<br />
[[KDE PIM/Privacy Policy|Privacy Policy]]<br />
<br />
== Community ==<br />
*[https://forum.kde.org/viewforum.php?f=215&sid=01b9e5d11189dbc7bb372ab16510decc KDE PIM Forums]<br />
*irc://freenode.net/kontact IRC channel.<br />
*[mailto:kdepim-users@kde.org kdepim-users@kde.org] mailing list (for users): [https://mail.kde.org/mailman/listinfo/kdepim-users subscribe], [http://lists.kde.org/?l=kdepim-users&r=1&w=2 archives]<br />
*[mailto:kde-pim@kde.org kde-pim@kde.org] mailing list (for developers): [https://mail.kde.org/mailman/listinfo/kde-pim subscribe], [http://lists.kde.org/?l=kde-pim&r=1&w=2 archives]<br />
<br />
[[KDE_PIM/Meetings|Meetings]]<br />
<br />
[[KDE_PIM/History|History]]<br />
<br />
[[KDE_PIM/People|People]]<br />
<br />
== Development ==<br />
<br />
[[KDE_PIM/Development|General]]<br />
<br />
[[KDE_PIM/Docker|Docker]]<br />
<br />
[[KDE_PIM/Flatpak|Flatpak]]<br />
<br />
[https://techbase.kde.org/Policies/Frameworks_Coding_Style Coding Style]<br />
<br />
[[Policies/Commit_Policy|Git Commit Policy]]<br />
<br />
[[KDE_PIM/Development/Bug_Reports|Bug Reports]]<br />
<br />
[[KDE_PIM/Development/Architecture|Architecture]]<br />
<br />
[[KDE_PIM/Akonadi|Akonadi]]<br />
<br />
[[KDE_PIM/Development/Tutorials|Developer Tutorials]]<br />
<br />
[[KDE_PIM/Components|Applications]]<br />
<br />
== Applications ==<br />
<br />
The following applications are developed by the KDE PIM team:<br />
<br />
=== Current ===<br />
<br />
{|style="width:75%" cellpadding="4"<br />
|[[Image:Icon48-Kontact.png|link=http://userbase.kde.org/Kontact|Kontact]]<br />
||'''<br />
[http://userbase.kde.org/Kontact Kontact]'''<br />
:<br />
KDE personal information management suite<br />
|-<br />
|[[Image:Icon48-Akregator.png|link=http://userbase.kde.org/Akregator|Akregator]]<br />
||'''<br />
[http://userbase.kde.org/Akregator Akregator]'''<br />
:<br />
KDE feed reader<br />
|-<br />
|[[Image:Icon48-Blogilo.png|link=http://userbase.kde.org/Blogilo|Blogilo]]<br />
||'''<br />
[http://userbase.kde.org/Blogilo Blogilo]'''<br />
:<br />
KDE blogging client<br />
|-<br />
|[[Image:Icon48-KAddressBook.png|link=http://userbase.kde.org/KAddressBook|KAddressBook]]<br />
||'''<br />
[http://userbase.kde.org/KAddressBook KAddressBook]'''<br />
:<br />
KDE address book<br />
|-<br />
|[[Image:Icon48-KAlarm.png|link=http://userbase.kde.org/KAlarm|KAlarm]]<br />
||'''<br />
[http://userbase.kde.org/KAlarm KAlarm]'''<br />
:<br />
KDE alarm clock<br />
|-<br />
|[[Image:Icon48-KMail.png|link=http://userbase.kde.org/KMail|KMail]]<br />
||'''<br />
[http://userbase.kde.org/KMail KMail]'''<br />
:<br />
KDE mail client<br />
|-<br />
|[[Image:Icon48-KNotes.png|link=http://userbase.kde.org/KNotes|KNotes]]<br />
||'''<br />
[http://userbase.kde.org/KNotes KNotes]'''<br />
:<br />
KDE sticky notes<br />
|-<br />
|[[Image:Icon48-KOrganizer.png|link=http://userbase.kde.org/KOrganizer|KOrganizer]]<br />
||'''<br />
[http://userbase.kde.org/KOrganizer KOrganizer]'''<br />
:<br />
KDE personal organizer<br />
|-<br />
|[[Image:Icon48-KonsoleKalendar.png|link=http://userbase.kde.org/KonsoleKalendar|KonsoleKalendar]]<br />
||'''<br />
[http://userbase.kde.org/KonsoleKalendar KonsoleKalendar]'''<br />
:<br />
KDE calendar command-line tool<br />
|-<br />
|[[Image:Icon48-KJots.png|link=http://userbase.kde.org/KJots|KJots]]<br />
||'''<br />
[http://userbase.kde.org/KJots KJots]'''<br />
:<br />
KDE note taking utility<br />
|}</div>Gjditchfieldhttps://community.kde.org/index.php?title=KDE_PIM&diff=91195KDE PIM2021-02-28T18:21:20Z<p>Gjditchfield: /* Development */</p>
<hr />
<div>''The content of [http://techbase.kde.org/Projects/PIM techbase.kde.org/Projects/PIM] is supposed to be moved to here. Help is welcome.''<br />
<br />
[[File:Mascot konqi-commu-mail.png|300px|right|Konqi and the mails]]<br />
Welcome to the home of the KDE PIM team. These pages act as a central point of information and collaboration for the developers and all other contributors or interested observers of KDE PIM. Here you will find information about KDE PIM, about the [[KDE_PIM/People|people]] behind KDE PIM, and how you can [[KDE_PIM/Contact|become part of the KDE PIM community]]. If you are looking for information about the usage of the KDE PIM applications then please have a look at [http://userbase.kde.org/Applications/Office#Kontact userbase.kde.org].<br />
<br />
KDE PIM is part of [http://www.kde.org KDE]. Its goal is to provide a suite of applications to [http://en.wikipedia.org/wiki/Personal_information_management manage personal information]. This includes mail, calendar, contacts and more. The main result is [http://userbase.kde.org/Kontact Kontact], our personal information manager.<br />
<br />
Kontact offers a thin graphical shell over the KDE PIM backend, the culmination of over 10 years of work to integrate the separate KDE PIM applications. The goal of this work was to ease development of new sources of data (like a google calendar or facebook contact importer) and to increase reliability and performance of KDE PIM. It will also make it far easier to find new ways to combine and view your personal information in new ways.<br />
<br />
== Vision ==<br />
<br />
Kontact is a personal information manager for people who care about privacy and have email-based workflows. It enables them to deal with large amounts of emails and coordinate teamwork in an effective way.<br />
<br />
We provide privacy by default and productivity in an elegant package.<br />
<br />
Unlike proprietary web applications it is Free Software and supports open standards. It gives you full control over your data and enables offline access.<br />
<br />
== Information ==<br />
<br />
[[KDE PIM/News|News]]<br />
<br />
[[KDE PIM/Contact|Contact]]<br />
<br />
[[KDE PIM/Privacy Policy|Privacy Policy]]<br />
<br />
== Community ==<br />
*[https://forum.kde.org/viewforum.php?f=215&sid=01b9e5d11189dbc7bb372ab16510decc KDE PIM Forums]<br />
*irc://freenode.net/kontact IRC channel.<br />
*[mailto:kdepim-users@kde.org kdepim-users@kde.org] mailing list (for users): [https://mail.kde.org/mailman/listinfo/kdepim-users subscribe], [http://lists.kde.org/?l=kdepim-users&r=1&w=2 archives]<br />
*[mailto:kde-pim@kde.org kde-pim@kde.org] mailing list (for developers): [https://mail.kde.org/mailman/listinfo/kde-pim subscribe], [http://lists.kde.org/?l=kde-pim&r=1&w=2 archives]<br />
<br />
[[KDE_PIM/Meetings|Meetings]]<br />
<br />
[[KDE_PIM/History|History]]<br />
<br />
[[KDE_PIM/People|People]]<br />
<br />
== Development ==<br />
<br />
[[KDE_PIM/Development|General]]<br />
<br />
[[KDE_PIM/Docker|Docker]]<br />
<br />
[[KDE_PIM/Flatpak|Flatpak]]<br />
<br />
[https://techbase.kde.org/Policies/Frameworks_Coding_Style Coding Style]<br />
<br />
[[Policies/Commit_Policy|Git Commit Policy]]<br />
<br />
[[KDE_PIM/Development/Bug_Reports|Bug Reports]]<br />
<br />
[[KDE_PIM/Development/Architecture|Architecture]]<br />
<br />
[[KDE_PIM/Akonadi|Akonadi]]<br />
<br />
[[KDE_PIM/Development/Tutorials|Developer Tutorials]]<br />
<br />
[[KDE_PIM/Components|Applications]]<br />
<br />
== Applications ==<br />
<br />
The following applications are developed by the KDE PIM team:<br />
<br />
=== Current ===<br />
<br />
{|style="width:75%" cellpadding="4"<br />
|[[Image:Icon48-Kontact.png|link=http://userbase.kde.org/Kontact|Kontact]]<br />
||'''<br />
[http://userbase.kde.org/Kontact Kontact]'''<br />
:<br />
KDE personal information management suite<br />
|-<br />
|[[Image:Icon48-Akregator.png|link=http://userbase.kde.org/Akregator|Akregator]]<br />
||'''<br />
[http://userbase.kde.org/Akregator Akregator]'''<br />
:<br />
KDE feed reader<br />
|-<br />
|[[Image:Icon48-Blogilo.png|link=http://userbase.kde.org/Blogilo|Blogilo]]<br />
||'''<br />
[http://userbase.kde.org/Blogilo Blogilo]'''<br />
:<br />
KDE blogging client<br />
|-<br />
|[[Image:Icon48-KAddressBook.png|link=http://userbase.kde.org/KAddressBook|KAddressBook]]<br />
||'''<br />
[http://userbase.kde.org/KAddressBook KAddressBook]'''<br />
:<br />
KDE address book<br />
|-<br />
|[[Image:Icon48-KAlarm.png|link=http://userbase.kde.org/KAlarm|KAlarm]]<br />
||'''<br />
[http://userbase.kde.org/KAlarm KAlarm]'''<br />
:<br />
KDE alarm clock<br />
|-<br />
|[[Image:Icon48-KMail.png|link=http://userbase.kde.org/KMail|KMail]]<br />
||'''<br />
[http://userbase.kde.org/KMail KMail]'''<br />
:<br />
KDE mail client<br />
|-<br />
|[[Image:Icon48-KNotes.png|link=http://userbase.kde.org/KNotes|KNotes]]<br />
||'''<br />
[http://userbase.kde.org/KNotes KNotes]'''<br />
:<br />
KDE sticky notes<br />
|-<br />
|[[Image:Icon48-KOrganizer.png|link=http://userbase.kde.org/KOrganizer|KOrganizer]]<br />
||'''<br />
[http://userbase.kde.org/KOrganizer KOrganizer]'''<br />
:<br />
KDE personal organizer<br />
|-<br />
|[[Image:Icon48-KonsoleKalendar.png|link=http://userbase.kde.org/KonsoleKalendar|KonsoleKalendar]]<br />
||'''<br />
[http://userbase.kde.org/KonsoleKalendar KonsoleKalendar]'''<br />
:<br />
KDE calendar command-line tool<br />
|-<br />
|[[Image:Icon48-KJots.png|link=http://userbase.kde.org/KJots|KJots]]<br />
||'''<br />
[http://userbase.kde.org/KJots KJots]'''<br />
:<br />
KDE note taking utility<br />
|}</div>Gjditchfieldhttps://community.kde.org/index.php?title=KDE_PIM&diff=91194KDE PIM2021-02-28T18:03:44Z<p>Gjditchfield: /* Community */</p>
<hr />
<div>''The content of [http://techbase.kde.org/Projects/PIM techbase.kde.org/Projects/PIM] is supposed to be moved to here. Help is welcome.''<br />
<br />
[[File:Mascot konqi-commu-mail.png|300px|right|Konqi and the mails]]<br />
Welcome to the home of the KDE PIM team. These pages act as a central point of information and collaboration for the developers and all other contributors or interested observers of KDE PIM. Here you will find information about KDE PIM, about the [[KDE_PIM/People|people]] behind KDE PIM, and how you can [[KDE_PIM/Contact|become part of the KDE PIM community]]. If you are looking for information about the usage of the KDE PIM applications then please have a look at [http://userbase.kde.org/Applications/Office#Kontact userbase.kde.org].<br />
<br />
KDE PIM is part of [http://www.kde.org KDE]. Its goal is to provide a suite of applications to [http://en.wikipedia.org/wiki/Personal_information_management manage personal information]. This includes mail, calendar, contacts and more. The main result is [http://userbase.kde.org/Kontact Kontact], our personal information manager.<br />
<br />
Kontact offers a thin graphical shell over the KDE PIM backend, the culmination of over 10 years of work to integrate the separate KDE PIM applications. The goal of this work was to ease development of new sources of data (like a google calendar or facebook contact importer) and to increase reliability and performance of KDE PIM. It will also make it far easier to find new ways to combine and view your personal information in new ways.<br />
<br />
== Vision ==<br />
<br />
Kontact is a personal information manager for people who care about privacy and have email-based workflows. It enables them to deal with large amounts of emails and coordinate teamwork in an effective way.<br />
<br />
We provide privacy by default and productivity in an elegant package.<br />
<br />
Unlike proprietary web applications it is Free Software and supports open standards. It gives you full control over your data and enables offline access.<br />
<br />
== Information ==<br />
<br />
[[KDE PIM/News|News]]<br />
<br />
[[KDE PIM/Contact|Contact]]<br />
<br />
[[KDE PIM/Privacy Policy|Privacy Policy]]<br />
<br />
== Community ==<br />
*[https://forum.kde.org/viewforum.php?f=215&sid=01b9e5d11189dbc7bb372ab16510decc KDE PIM Forums]<br />
*irc://freenode.net/kontact IRC channel.<br />
*[mailto:kdepim-users@kde.org kdepim-users@kde.org] mailing list (for users): [https://mail.kde.org/mailman/listinfo/kdepim-users subscribe], [http://lists.kde.org/?l=kdepim-users&r=1&w=2 archives]<br />
*[mailto:kde-pim@kde.org kde-pim@kde.org] mailing list (for developers): [https://mail.kde.org/mailman/listinfo/kde-pim subscribe], [http://lists.kde.org/?l=kde-pim&r=1&w=2 archives]<br />
<br />
[[KDE_PIM/Meetings|Meetings]]<br />
<br />
[[KDE_PIM/History|History]]<br />
<br />
[[KDE_PIM/People|People]]<br />
<br />
== Development ==<br />
<br />
[[KDE_PIM/Development|General]]<br />
<br />
[[KDE_PIM/Docker|Docker]]<br />
<br />
[[KDE_PIM/Flatpak|Flatpak]]<br />
<br />
[https://techbase.kde.org/Policies/Frameworks_Coding_Style Coding Style]<br />
<br />
[[Policies/Commit_Policy|Git Commit Policy]]<br />
<br />
[[KDE_PIM/Development/Bug_Reports|Bug Reports]]<br />
<br />
[[KDE_PIM/Development/Architecture|Architecture]]<br />
<br />
[[KDE_PIM/Akonadi|Akonadi]]<br />
<br />
[[KDE_PIM/Development/Tutorials|Tutorials]]<br />
<br />
[[KDE_PIM/Components|Applications]]<br />
<br />
== Applications ==<br />
<br />
The following applications are developed by the KDE PIM team:<br />
<br />
=== Current ===<br />
<br />
{|style="width:75%" cellpadding="4"<br />
|[[Image:Icon48-Kontact.png|link=http://userbase.kde.org/Kontact|Kontact]]<br />
||'''<br />
[http://userbase.kde.org/Kontact Kontact]'''<br />
:<br />
KDE personal information management suite<br />
|-<br />
|[[Image:Icon48-Akregator.png|link=http://userbase.kde.org/Akregator|Akregator]]<br />
||'''<br />
[http://userbase.kde.org/Akregator Akregator]'''<br />
:<br />
KDE feed reader<br />
|-<br />
|[[Image:Icon48-Blogilo.png|link=http://userbase.kde.org/Blogilo|Blogilo]]<br />
||'''<br />
[http://userbase.kde.org/Blogilo Blogilo]'''<br />
:<br />
KDE blogging client<br />
|-<br />
|[[Image:Icon48-KAddressBook.png|link=http://userbase.kde.org/KAddressBook|KAddressBook]]<br />
||'''<br />
[http://userbase.kde.org/KAddressBook KAddressBook]'''<br />
:<br />
KDE address book<br />
|-<br />
|[[Image:Icon48-KAlarm.png|link=http://userbase.kde.org/KAlarm|KAlarm]]<br />
||'''<br />
[http://userbase.kde.org/KAlarm KAlarm]'''<br />
:<br />
KDE alarm clock<br />
|-<br />
|[[Image:Icon48-KMail.png|link=http://userbase.kde.org/KMail|KMail]]<br />
||'''<br />
[http://userbase.kde.org/KMail KMail]'''<br />
:<br />
KDE mail client<br />
|-<br />
|[[Image:Icon48-KNotes.png|link=http://userbase.kde.org/KNotes|KNotes]]<br />
||'''<br />
[http://userbase.kde.org/KNotes KNotes]'''<br />
:<br />
KDE sticky notes<br />
|-<br />
|[[Image:Icon48-KOrganizer.png|link=http://userbase.kde.org/KOrganizer|KOrganizer]]<br />
||'''<br />
[http://userbase.kde.org/KOrganizer KOrganizer]'''<br />
:<br />
KDE personal organizer<br />
|-<br />
|[[Image:Icon48-KonsoleKalendar.png|link=http://userbase.kde.org/KonsoleKalendar|KonsoleKalendar]]<br />
||'''<br />
[http://userbase.kde.org/KonsoleKalendar KonsoleKalendar]'''<br />
:<br />
KDE calendar command-line tool<br />
|-<br />
|[[Image:Icon48-KJots.png|link=http://userbase.kde.org/KJots|KJots]]<br />
||'''<br />
[http://userbase.kde.org/KJots KJots]'''<br />
:<br />
KDE note taking utility<br />
|}</div>Gjditchfieldhttps://community.kde.org/index.php?title=KDE_PIM/Development/Tutorials&diff=91193KDE PIM/Development/Tutorials2021-02-28T17:49:19Z<p>Gjditchfield: </p>
<hr />
<div>This page lists the development tutorials for various bits and pieces of KDE PIM. It may be useful to read the [http://techbase.kde.org/Development/Git information on Git] and keep the [http://api.kde.org/ KDE API reference] handy.<br />
<br />
* [http://techbase.kde.org/Development/Tutorials/Akonadi/Application Creating an Akonadi application]<br />
* [http://techbase.kde.org/Development/Tutorials/Akonadi/Resources Creating an Akonadi PIM data Resource]<br />
* [http://techbase.kde.org/Development/Tutorials/Akonadi/SerializerPlugin Using your own data type with Akonadi]<br />
* [http://techbase.kde.org/Development/Tutorials/Writing_kontact_plugins Creating Kontact plugins]</div>Gjditchfieldhttps://community.kde.org/index.php?title=KDE_PIM&diff=91192KDE PIM2021-02-28T17:31:01Z<p>Gjditchfield: /* Community */</p>
<hr />
<div>''The content of [http://techbase.kde.org/Projects/PIM techbase.kde.org/Projects/PIM] is supposed to be moved to here. Help is welcome.''<br />
<br />
[[File:Mascot konqi-commu-mail.png|300px|right|Konqi and the mails]]<br />
Welcome to the home of the KDE PIM team. These pages act as a central point of information and collaboration for the developers and all other contributors or interested observers of KDE PIM. Here you will find information about KDE PIM, about the [[KDE_PIM/People|people]] behind KDE PIM, and how you can [[KDE_PIM/Contact|become part of the KDE PIM community]]. If you are looking for information about the usage of the KDE PIM applications then please have a look at [http://userbase.kde.org/Applications/Office#Kontact userbase.kde.org].<br />
<br />
KDE PIM is part of [http://www.kde.org KDE]. Its goal is to provide a suite of applications to [http://en.wikipedia.org/wiki/Personal_information_management manage personal information]. This includes mail, calendar, contacts and more. The main result is [http://userbase.kde.org/Kontact Kontact], our personal information manager.<br />
<br />
Kontact offers a thin graphical shell over the KDE PIM backend, the culmination of over 10 years of work to integrate the separate KDE PIM applications. The goal of this work was to ease development of new sources of data (like a google calendar or facebook contact importer) and to increase reliability and performance of KDE PIM. It will also make it far easier to find new ways to combine and view your personal information in new ways.<br />
<br />
== Vision ==<br />
<br />
Kontact is a personal information manager for people who care about privacy and have email-based workflows. It enables them to deal with large amounts of emails and coordinate teamwork in an effective way.<br />
<br />
We provide privacy by default and productivity in an elegant package.<br />
<br />
Unlike proprietary web applications it is Free Software and supports open standards. It gives you full control over your data and enables offline access.<br />
<br />
== Information ==<br />
<br />
[[KDE PIM/News|News]]<br />
<br />
[[KDE PIM/Contact|Contact]]<br />
<br />
[[KDE PIM/Privacy Policy|Privacy Policy]]<br />
<br />
== Community ==<br />
<br />
To join the team, simply email the [https://mail.kde.org/mailman/listinfo/kde-pim kde-pim] mailing list, visit the [https://forum.kde.org/viewforum.php?f=215&sid=01b9e5d11189dbc7bb372ab16510decc KDE PIM Forums], or join the irc://freenode.net/kontact IRC channel.<br />
<br />
[[KDE_PIM/Meetings|Meetings]]<br />
<br />
[[KDE_PIM/History|History]]<br />
<br />
[[KDE_PIM/People|People]]<br />
<br />
== Development ==<br />
<br />
[[KDE_PIM/Development|General]]<br />
<br />
[[KDE_PIM/Docker|Docker]]<br />
<br />
[[KDE_PIM/Flatpak|Flatpak]]<br />
<br />
[https://techbase.kde.org/Policies/Frameworks_Coding_Style Coding Style]<br />
<br />
[[Policies/Commit_Policy|Git Commit Policy]]<br />
<br />
[[KDE_PIM/Development/Bug_Reports|Bug Reports]]<br />
<br />
[[KDE_PIM/Development/Architecture|Architecture]]<br />
<br />
[[KDE_PIM/Akonadi|Akonadi]]<br />
<br />
[[KDE_PIM/Development/Tutorials|Tutorials]]<br />
<br />
[[KDE_PIM/Components|Applications]]<br />
<br />
== Applications ==<br />
<br />
The following applications are developed by the KDE PIM team:<br />
<br />
=== Current ===<br />
<br />
{|style="width:75%" cellpadding="4"<br />
|[[Image:Icon48-Kontact.png|link=http://userbase.kde.org/Kontact|Kontact]]<br />
||'''<br />
[http://userbase.kde.org/Kontact Kontact]'''<br />
:<br />
KDE personal information management suite<br />
|-<br />
|[[Image:Icon48-Akregator.png|link=http://userbase.kde.org/Akregator|Akregator]]<br />
||'''<br />
[http://userbase.kde.org/Akregator Akregator]'''<br />
:<br />
KDE feed reader<br />
|-<br />
|[[Image:Icon48-Blogilo.png|link=http://userbase.kde.org/Blogilo|Blogilo]]<br />
||'''<br />
[http://userbase.kde.org/Blogilo Blogilo]'''<br />
:<br />
KDE blogging client<br />
|-<br />
|[[Image:Icon48-KAddressBook.png|link=http://userbase.kde.org/KAddressBook|KAddressBook]]<br />
||'''<br />
[http://userbase.kde.org/KAddressBook KAddressBook]'''<br />
:<br />
KDE address book<br />
|-<br />
|[[Image:Icon48-KAlarm.png|link=http://userbase.kde.org/KAlarm|KAlarm]]<br />
||'''<br />
[http://userbase.kde.org/KAlarm KAlarm]'''<br />
:<br />
KDE alarm clock<br />
|-<br />
|[[Image:Icon48-KMail.png|link=http://userbase.kde.org/KMail|KMail]]<br />
||'''<br />
[http://userbase.kde.org/KMail KMail]'''<br />
:<br />
KDE mail client<br />
|-<br />
|[[Image:Icon48-KNotes.png|link=http://userbase.kde.org/KNotes|KNotes]]<br />
||'''<br />
[http://userbase.kde.org/KNotes KNotes]'''<br />
:<br />
KDE sticky notes<br />
|-<br />
|[[Image:Icon48-KOrganizer.png|link=http://userbase.kde.org/KOrganizer|KOrganizer]]<br />
||'''<br />
[http://userbase.kde.org/KOrganizer KOrganizer]'''<br />
:<br />
KDE personal organizer<br />
|-<br />
|[[Image:Icon48-KonsoleKalendar.png|link=http://userbase.kde.org/KonsoleKalendar|KonsoleKalendar]]<br />
||'''<br />
[http://userbase.kde.org/KonsoleKalendar KonsoleKalendar]'''<br />
:<br />
KDE calendar command-line tool<br />
|-<br />
|[[Image:Icon48-KJots.png|link=http://userbase.kde.org/KJots|KJots]]<br />
||'''<br />
[http://userbase.kde.org/KJots KJots]'''<br />
:<br />
KDE note taking utility<br />
|}</div>Gjditchfieldhttps://community.kde.org/index.php?title=KDE_PIM/KOrganizer&diff=91191KDE PIM/KOrganizer2021-02-28T17:07:16Z<p>Gjditchfield: /* Developer Information */</p>
<hr />
<div>= Developer Information =<br />
<br />
[[Image:Icon48-KOrganizer.png|left|KOrganizer]] KOrganizer is the calendar and scheduling program for KDE. KOrganizer provides management of events and tasks, alarm notification, network transparent handling of data, group scheduling, import and export of calendar files and more.<br />
<br clear=all><br />
* [[Get_Involved/development|Development Environment]] -- how to set up the tools you need.<br />
* [[Policies/Frameworks_Coding_Style|Coding Style]] -- please follow these style guidelines when writing code in KOrganizer, so that your code is easy to integrate with the existing codebase. <br />
* [[KDE_PIM/KOrganizer/Architecture|Architecture]] -- basic description of the architecture of KOrganizer.<br />
<br />
As always, the [https://api.kde.org/ API Dox] are a useful reference.<br />
<br />
== Calendar Formats ==<br />
<br />
KOrganizer is based on the iCalendar standard.<br />
* [https://tools.ietf.org/html/rfc5545 iCalendar format]<br />
* [https://tools.ietf.org/html/rfc5546 iTIP] iCalendar Transport-Independent Interoperability Protocol<br />
* [https://tools.ietf.org/html/rfc6047 iMIP] iCalendar Message-Based Interoperability Protocol<br />
* [https://github.com/libical/libical libical], an Open Source effort to implement the iCalendar and related standards.<br />
<br />
== KOrganizer Development Links ==<br />
<br />
There are many sources of information about KDE development on the internet. The following list shows only a small subset of the available web pages. Have a look for more by yourself.<br />
* [http://techbase.kde.org/ KDE Techbase] is the central site for KDE development information. Not all pages necessarily reflect the latest state of KDE development, because this is moving very fast, but there is a lot of useful information.<br />
* [[KDE_PIM/Contact|Mailing Lists]]. That's probably the ultimate source of development information. Follow discussions of KDE core and application developers and ask your questions. Unless you don't think about what you are saying, you will surely get an answer. The following mailing lists might be of interest for you:<br />
** [mailto:kde-pim@kde.org kde-pim@kde.org] for personal information manager issues, including KOrganizer development. That's the list for KOrganizer specific questions and discussions. Further information on the kdepim-mailinglist can be found [[KDE_PIM/Contact|here]].<br />
** [mailto:kde-devel@kde.org kde-devel@kde.org] for discussions about general KDE application development.<br />
** [mailto:qt-interest@trolltech.com qt-interest@trolltech.com] is the mailing list for [http://qt.nokia.com Qt] developers. As KDE is based on Qt, you will find this list valuable.<br />
* Finally there is the source code itself. Have a look at the [https://invent.kde.org/ KDE repositories]. There you have access to the complete code base of the central KDE packages and can follow the development process by looking at the history of individual files.<br />
<br />
== Architecture and framework ==<br />
<br />
* KOrganizer makes use of the KDE resource framework KResources which provides uniform management of calendar, addressbook and similar resources including generic ways to handle locking and change notification. This also provides a plugin interface for adding resources which makes it possible to easily extend Korganizer for example to access groupware servers or other ways to store calendar data.<br />
* KOrganizer configuration is based on KConfig XT. That means that there is an abstract XML based description of the configuration which is used for generating the needed code to access the configuration data and serves as base for external configuration tools.<br />
* Accessing the calendar from the command line is handled by a native command line application, konsolekalendar.</div>Gjditchfieldhttps://community.kde.org/index.php?title=KDE_PIM/KOrganizer&diff=91183KDE PIM/KOrganizer2021-02-27T21:18:48Z<p>Gjditchfield: /* Calendar Formats */</p>
<hr />
<div>= Developer Information =<br />
<br />
[[Image:Icon48-KOrganizer.png|left|KOrganizer]] KOrganizer is the calendar and scheduling program for KDE. KOrganizer provides management of events and tasks, alarm notification, web export, network transparent handling of data, group scheduling, import and export of calendar files and more.<br />
<br />
* [[KDE_PIM/Development/CodingStyle/Korganizer|Coding Style]] -- please follow these style guidelines when writing code in KOrganizer, so that your code is easy to integrate with the existing codebase. <br />
* [[KDE_PIM/KOrganizer/Architecture|Architecture]] -- basic description of the architecture of KOrganizer.<br />
<br />
As always, the [http://api.kde.org/4.x-api/kdepim-apidocs/ API Dox] are a useful reference. The [http://api.kde.org/4.x-api/kdepim-apidocs/korganizer/html/index.html KOrganizer APIDOX] and [http://api.kde.org/4.x-api/kdepimlibs-apidocs/kcal/html/index.html libkcal APIDOX] are quite extensive.<br />
<br />
== Calendar Formats ==<br />
<br />
KOrganizer is based on the iCalendar standard.<br />
* [https://tools.ietf.org/html/rfc5545 iCalendar format]<br />
* [https://tools.ietf.org/html/rfc5546 iTIP] iCalendar Transport-Independent Interoperability Protocol<br />
* [https://tools.ietf.org/html/rfc6047 iMIP] iCalendar Message-Based Interoperability Protocol<br />
* [https://github.com/libical/libical libical], an Open Source effort to implement the iCalendar and related standards.<br />
<br />
== KOrganizer Development Links ==<br />
<br />
There are many sources of information about KDE development on the internet. The following list shows only a small subset of the available web pages. Have a look for more by yourself.<br />
* [http://techbase.kde.org/ KDE Techbase] is the central site for KDE development information. Not all pages necessarily reflect the latest state of KDE development, because this is moving very fast, but there is a lot of useful information.<br />
* [[KDE_PIM/Contact|Mailing Lists]]. That's probably the ultimate source of development information. Follow discussions of KDE core and application developers and ask your questions. Unless you don't think about what you are saying, you will surely get an answer. The following mailing lists might be of interest for you:<br />
** [mailto:kde-pim@kde.org kde-pim@kde.org] for personal information manager issues, including KOrganizer development. That's the list for KOrganizer specific questions and discussions. Further information on the kdepim-mailinglist can be found [[KDE_PIM/Contact|here]].<br />
** [mailto:kde-devel@kde.org kde-devel@kde.org] for discussions about general KDE application development.<br />
** [mailto:qt-interest@trolltech.com qt-interest@trolltech.com] is the mailing list for [http://qt.nokia.com Qt] developers. As KDE is based on Qt, you will find this list valuable.<br />
* Finally there is the source code itself. Have a look at the [https://projects.kde.org/ web frontend to the KDE projects]. There you have access to the complete code base of the central KDE packages and can follow the development process by looking at the history of individual files.<br />
<br />
== Architecture and framework ==<br />
<br />
* KOrganizer makes use of the KDE resource framework KResources which provides uniform management of calendar, addressbook and similar resources including generic ways to handle locking and change notification. This also provides a plugin interface for adding resources which makes it possible to easily extend Korganizer for example to access groupware servers or other ways to store calendar data.<br />
* KOrganizer configuration is based on KConfig XT. That means that there is an abstract XML based description of the configuration which is used for generating the needed code to access the configuration data and serves as base for external configuration tools.<br />
* Accessing the calendar from the command line is handled by a native command line application, konsolekalendar.</div>Gjditchfieldhttps://community.kde.org/index.php?title=KDE_PIM/KOrganizer&diff=91182KDE PIM/KOrganizer2021-02-27T21:18:03Z<p>Gjditchfield: Dead links</p>
<hr />
<div>= Developer Information =<br />
<br />
[[Image:Icon48-KOrganizer.png|left|KOrganizer]] KOrganizer is the calendar and scheduling program for KDE. KOrganizer provides management of events and tasks, alarm notification, web export, network transparent handling of data, group scheduling, import and export of calendar files and more.<br />
<br />
* [[KDE_PIM/Development/CodingStyle/Korganizer|Coding Style]] -- please follow these style guidelines when writing code in KOrganizer, so that your code is easy to integrate with the existing codebase. <br />
* [[KDE_PIM/KOrganizer/Architecture|Architecture]] -- basic description of the architecture of KOrganizer.<br />
<br />
As always, the [http://api.kde.org/4.x-api/kdepim-apidocs/ API Dox] are a useful reference. The [http://api.kde.org/4.x-api/kdepim-apidocs/korganizer/html/index.html KOrganizer APIDOX] and [http://api.kde.org/4.x-api/kdepimlibs-apidocs/kcal/html/index.html libkcal APIDOX] are quite extensive.<br />
<br />
== Calendar Formats ==<br />
<br />
KOrganizer is based on the iCalendar standard.<br />
** [https://tools.ietf.org/html/rfc5545 iCalendar format]<br />
** [https://tools.ietf.org/html/rfc5546 iTIP] iCalendar Transport-Independent Interoperability Protocol<br />
** [https://tools.ietf.org/html/rfc6047 iMIP] iCalendar Message-Based Interoperability Protocol<br />
** [https://github.com/libical/libical libical], an Open Source effort to implement the iCalendar and related standards.<br />
<br />
== KOrganizer Development Links ==<br />
<br />
There are many sources of information about KDE development on the internet. The following list shows only a small subset of the available web pages. Have a look for more by yourself.<br />
* [http://techbase.kde.org/ KDE Techbase] is the central site for KDE development information. Not all pages necessarily reflect the latest state of KDE development, because this is moving very fast, but there is a lot of useful information.<br />
* [[KDE_PIM/Contact|Mailing Lists]]. That's probably the ultimate source of development information. Follow discussions of KDE core and application developers and ask your questions. Unless you don't think about what you are saying, you will surely get an answer. The following mailing lists might be of interest for you:<br />
** [mailto:kde-pim@kde.org kde-pim@kde.org] for personal information manager issues, including KOrganizer development. That's the list for KOrganizer specific questions and discussions. Further information on the kdepim-mailinglist can be found [[KDE_PIM/Contact|here]].<br />
** [mailto:kde-devel@kde.org kde-devel@kde.org] for discussions about general KDE application development.<br />
** [mailto:qt-interest@trolltech.com qt-interest@trolltech.com] is the mailing list for [http://qt.nokia.com Qt] developers. As KDE is based on Qt, you will find this list valuable.<br />
* Finally there is the source code itself. Have a look at the [https://projects.kde.org/ web frontend to the KDE projects]. There you have access to the complete code base of the central KDE packages and can follow the development process by looking at the history of individual files.<br />
<br />
== Architecture and framework ==<br />
<br />
* KOrganizer makes use of the KDE resource framework KResources which provides uniform management of calendar, addressbook and similar resources including generic ways to handle locking and change notification. This also provides a plugin interface for adding resources which makes it possible to easily extend Korganizer for example to access groupware servers or other ways to store calendar data.<br />
* KOrganizer configuration is based on KConfig XT. That means that there is an abstract XML based description of the configuration which is used for generating the needed code to access the configuration data and serves as base for external configuration tools.<br />
* Accessing the calendar from the command line is handled by a native command line application, konsolekalendar.</div>Gjditchfieldhttps://community.kde.org/index.php?title=Infrastructure/GitLab&diff=91176Infrastructure/GitLab2021-02-25T17:28:17Z<p>Gjditchfield: /* Switching the target branch of a Merge Request */</p>
<hr />
<div>KDE uses a GitLab instance at https://invent.kde.org for code review (as well as hosting and other important collaboration tasks). This page is intended to serve as a general-purpose introduction to the most important aspects: submitting and reviewing Merge Requests.<br />
<br />
{{Info|The KDE community does not generally use GitLab for bug reporting. Please continue to submit bug reports on https://bugs.kde.org. Task management is transitioning to Invent/GitLab but most projects still use https://phabricator.kde.org for now.}}<br />
<br />
<br />
= Workflow =<br />
The sanest and easiest way to submit code to KDE is by following a typical '''feature branch workflow''': keep your master branch synchronized with the origin repository, and make all changes on separate branches. '''Each Merge Request needs its own private, temporary branch.''' Once your Merge Request has been merged, delete the feature branch, and make another new branch for the next Merge Request. In this way, you can be working on multiple changes at once without them colliding with one another because each one lives on its own branch.<br />
<br />
<br />
= Logging in =<br />
Navigate to https://invent.kde.org/users/sign_in and log in using the username and password for your KDE Identity account. If you don't have one, you can [https://identity.kde.org/index.php?r=registration/index sign up for one here].<br />
<br />
== Setting up git ==<br />
<br />
You will need to set up git to use you account details to help identify your work: <br />
<br />
<syntaxhighlight lang="bash"><br />
git config user.name <Your Real Name><br />
git config user.email <Your identity.kde.org email><br />
</syntaxhighlight><br />
<br />
(You can set it up globally with --global)<br />
<br />
= Submitting a merge request =<br />
Contributing to KDE code using GitLab involves '''submitting a Merge Request'''. A Merge Request is a request to merge some of your code into the project's permanent source code repo. Here's how:<br />
<br />
== Build the project from source and make your change ==<br />
First you need to check out the project, compile it from source, and make some changes that you would like to submit to KDE! Instructions for doing this can be found at [[Get Involved/development]]. You will wind up with a checkout of the project at <tt>~/kde/src/[the project name]</tt> with some local changes applied to it.<br />
<br />
{{Info|If you prefer a different organizational structure for source code on your machine, you can of course check out the local copy of your KDE repos wherever you want. However for the purposes of this documentation, we will assume that they are located inside <tt>~/kde/src/</tt>}}<br />
<br />
== Fork the project ==<br />
{{Note|If you have commit access, you can prefix your branches with "<tt>work/</tt>" and push them directly to origin without having to fork, and safely ignore this section.}}<br />
<br />
Once you have made some local changes that you would like to submit to KDE, you need to create a personal fork of the project and push your changes to the forked copy.<br />
<br />
Navigate to https://invent.kde.org/kde and locate the project. If it is not visible in the list, you can use the search field. Once you find the project, click on it:<br />
<br />
[[File:Find_the_project.png|600px]]<br />
<br />
On the project's page, click on the "Fork" button in the top-right corner of the screen:<br />
<br />
[[File:Click_the_Fork_button.png|300px]]<br />
<br />
This will take you to a page asking you what namespace you want to create the project under. Click on yourself: <br />
<br />
[[File:Choose_the_fork_namespace.png|400px]]<br />
<br />
After a moment, the system will finish creating the fork and take you to the page for your fork. On that page, click on the blue "Clone" button in the top-right corner:<br />
<br />
[[File:Click_the_Clone_button.png|300px]]<br />
<br />
In the pop-up that appears, click on the "copy" button to the right of the upper text field. This will copy the URL for the fork onto your clipboard.<br />
<br />
[[File:Copy_the_URL.png|300px]]<br />
<br />
== Add the fork to your source checkout ==<br />
Next, open your terminal app and navigate to the location where the project's repo lives (i.e. <tt>~/kde/src/[the project name]</tt>).<br />
<br />
You need to add your fork as a '''remote''' to the existing repo:<br />
<br />
{{Input|1=<nowiki><br />
git remote add fork [the URL you copied to your clipboard]<br />
</nowiki>}}<br />
<br />
Run <tt>git remote -v</tt>. You should see something like this:<br />
<br />
<pre><br />
$ git remote -v<br />
fork git@invent.kde.org:ngraham/kid3.git (fetch)<br />
fork git@invent.kde.org:ngraham/kid3.git (push)<br />
origin https://invent.kde.org/kde/kid3.git (fetch)<br />
origin https://invent.kde.org/kde/kid3.git (push)<br />
</pre><br />
<br />
This means you have two remotes set up for your repo: "origin" points to the original repo, and "fork" points to your fork of it.<br />
<br />
== Make a branch and commit ==<br />
Now that you have your fork set up, it's time to create a branch to track your work and make a commit.<br />
<br />
{{Input|1=<nowiki><br />
git checkout -b my_awesome_feature<br />
git add [the files you changed]<br />
git commit<br />
</nowiki>}}<br />
<br />
{{Note|If you have commit access and are pushing a branch directly to origin, remember to prefix your branch with "<tt>work/</tt>". Branches with this prefix can be force-pushed to, and keywords like <tt>BUG:</tt> are not evaluated}}<br />
<br />
== Write a good commit message ==<br />
With Gitlab, once a merge request is merged, the first commit in the merge request will wind up in the Git history, so it is very important that the first commit in the merge request be formatted properly.<br />
<br />
Please follow [https://chris.beams.io/posts/git-commit/#seven-rules commit message best practices]: write a descriptive title in the form of an imperative sentence (e.g. "Fix button disappearing when view is changed") and on the next line, write at least one sentence describing your change and why it is necessary, adding more details if necessary.<br />
<br />
If your patch is intended to fix a Bugzilla ticket, include the following on its own line:<br />
<pre><br />
BUG: 385942<br />
</pre><br />
(The number should be just the Bugzilla ticket number, '''not the full URL''')<br />
<br />
For example a commit message might read:<br />
<br />
<pre><br />
Close memory leak in GC<br />
<br />
The GC was doing a bad job and leaking memory. Explicitly call delete where necessary.<br />
<br />
BUG: 385942<br />
FIXED-IN: 5.0.0<br />
</pre><br />
<br />
[https://community.kde.org/Policies/Commit_Policy#Special_keywords_in_GIT_and_SVN_log_messages Here is more information] about other special messages that interact with Bugzilla tickets.<br />
<br />
{{Note|These keywords will only work if the email address in your <tt>~/.config/git/config</tt> file matches the email address used for your https://bugs.kde.org account (See [https://techbase.kde.org/Development/Git/Configuration#Basic_Settings this page] for more information).}}<br />
<br />
{{Note|The Gitlab web interface can be used to rewrite the final commit message for merge requests with two or more commits which will be squash-merged. However for merge requests with only one comiit, or merge requests with a carefully curated commit history, the commits will be added to the immutable git history verbatim. So it is very important to format your commit messages well!<br />
<br />
This is different from Phabricator, in which the patch's title and summary were transformed into a commit message and squash-merging was always used.}}<br />
<br />
== Push to your fork ==<br />
At this point you have a branch in your local repo called "my_awesome_feature" (Hopefully in reality it is named something a bit more appropriate!) that has a commit on it with your work. Now push it to your fork:<br />
<br />
{{Input|1=<nowiki><br />
git push fork my_awesome_feature<br />
</nowiki>}}<br />
<br />
This will produce a message somewhat like this:<br />
<br />
<pre><br />
$ git push fork my_awesome_feature<br />
Enumerating objects: 5, done.<br />
Counting objects: 100% (5/5), done.<br />
Delta compression using up to 4 threads<br />
Compressing objects: 100% (3/3), done.<br />
Writing objects: 100% (3/3), 303 bytes | 303.00 KiB/s, done.<br />
Total 3 (delta 2), reused 0 (delta 0)<br />
remote: This commit is available for viewing at:<br />
remote: https://invent.kde.org/ngraham/kid3/commit/23a702439f494806cf3cfe14f212df58a0075bba<br />
remote: <br />
remote: To create a merge request for my_awesome_feature, visit:<br />
remote: https://invent.kde.org/ngraham/kid3/merge_requests/new?merge_request%5Bsource_branch%5D=my_awesome_feature<br />
remote: <br />
To invent.kde.org:ngraham/kid3.git<br />
* [new branch] my_awesome_feature -> my_awesome_feature<br />
</pre><br />
<br />
== Create the Merge Request ==<br />
Notice the "To create a merge request for my_awesome_feature..." message in the output of the push command (explained in the previous section). You can copy-and-paste the URL shown below it into a web browser. On some terminal apps, such as Konsole and Yakuake, you can ctrl+click on the link to go right there!<br />
<br />
You will be taken to a web page that looks like this:<br />
<br />
[[File:Merge_Request_info.png|800px]]<br />
<br />
In the Description section, write at least one sentence describing your change and why it is necessary, adding more details if needed. For Merge Requests that change something about the appearance or user interface, it's customary to include a screenshot of how the interface looks after your change has been applied. Bonus points for including a "Before" image too, so reviewers can easily compare them.<br />
<br />
If your patch is intended to fix a Bugzilla ticket, include the ticket number at the bottom of the description, just like how you did in the commit message (as explained in [[Infrastructure/GitLab#Write_a_good_commit_message]]):<br />
<pre><br />
BUG: 385942<br />
FIXED-IN: 5.21<br />
</pre><br />
<br />
{{Note|The <code>BUG:</code> keyword must be added both in the Merge Request description--which notifies the bug ticket of the Merge Request creation--and in the message of the first commit--which closes the bug ticket once the Merge Request is merged.<br><br />
In case you don't want to close the bug, but only add the information to it you can use <code>CCBUG:</code>. This can be the case if there are commits required before the bugfix can be done.}}<br />
<br />
Once you're done with that, click the "Submit Merge Request" button!<br />
<br />
[[File:Submit_Merge_Request.png|400px]]<br />
<br />
== What happens next? ==<br />
After you've submitted your Merge Request, KDE developers who work with the software in question will review it and provide feedback. This can often take a few days. However, if nobody has responded after a week, it's likely that the review was overlooked (sorry about that!) and it's appropriate to make a comment saying, "Ping!" or something to that effect.<br />
<br />
Once the Merge Request is accepted, KDE Developers will merge it for you!<br />
<br />
== Making changes to a Merge Request ==<br />
Oftentimes, reviewers will request changes before the Merge Request can be merged. To accomplish this, you make the requested changes locally, then create a new commit including your changes. First, stage all changed files:<br />
<br />
{{Input|1=<nowiki><br />
git add -u<br />
</nowiki>}}<br />
<br />
Now make a new commit with the staged files:<br />
<br />
{{Input|1=<nowiki><br />
git commit<br />
</nowiki>}}<br />
<br />
Then push the local branch with the new commit on it up to the remote branch:<br />
{{Input|1=<nowiki><br />
git push fork<br />
</nowiki>}}<br />
<br />
== Rebasing a Merge Request ==<br />
When other changes have been made to the project's source code repo since you submitted your merge request, you will need to ''rebase'' the Merge Request to incorporate those changes. Here's how you do so:<br />
<br />
{{Input|1=<nowiki><br />
# First, make sure you are on the branch for your merge request<br />
git fetch<br />
git pull --rebase origin master<br />
</nowiki>}}<br />
<br />
At this point, there may be merge conflicts. If there are, git will tell you which files have conflicts. Open each file and resolve the conflict by exiting the contents to keep only the appropriate change. Then run <tt>git add [file path]</tt> on each conflicted file once all the conflicts have been resolved.<br />
<br />
Now, you need to overwrite the version of the Merge Request on your remote branch with the version on your local branch. To do this, you have to force-push:<br />
<br />
{{Input|1=<nowiki><br />
git push --force fork<br />
</nowiki>}}<br />
<br />
<br />
= Testing someone else's Merge Request =<br />
First you'll need a development environment set up. If you haven't done that yet, it's time to do so. Follow the instructions on [[Get_Involved/development#Set_up_your_development_environment]]. It is also advisable to set up the <tt>git mr</tt> tool, which makes testing Merge Requests a breeze. Here's how to install it, depending on your operating system:<br />
<br />
''' Arch / Manjaro '''<br />
{{Input|1=<nowiki><br />
yay -S git-mr<br />
</nowiki>}}<br />
<br />
'''Debian/Ubuntu/KDE Neon'''<br />
{{Input|1=<nowiki><br />
sudo apt install git-extras<br />
</nowiki>}}<br />
<br />
''' Fedora '''<br />
{{Input|1=<nowiki><br />
sudo dnf install git-extras<br />
</nowiki>}}<br />
<br />
''' OpenSUSE '''<br />
{{Input|1=<nowiki><br />
sudo zypper install git-mr<br />
</nowiki>}}<br />
<br />
== Check out the Merge Request and compile the software ==<br />
First check out or enter the source repository for the software that's being patched. For example, let's say you want to test a Merge Request for Okular. If you've never built it before, check it out and build it once first:<br />
{{Input|1=<nowiki><br />
kdesrc-build okular<br />
</nowiki>}}<br />
<br />
Now go to its source directory:<br />
{{Input|1=<nowiki><br />
cd ~/kde/src/okular<br />
</nowiki>}}<br />
<br />
Find the Merge Request's ID. For example, for https://invent.kde.org/kde/okular/merge_requests/80, the ID is <tt>80</tt>.<br />
<br />
...and apply the Merge Request:<br />
{{Input|1=<nowiki><br />
git mr 80<br />
</nowiki>}}<br />
<br />
Now it's time to compile and run the software to make sure that the Merge Request does what it says it does and doesn't cause any regressions! Compile the patched source code:<br />
{{Input|1=<nowiki><br />
kdesrc-build okular --no-src --resume-from okular<br />
</nowiki>}}<br />
Those arguments will tell <tt>kdesrc-build</tt> to not update the source code and to not build any dependencies.<br />
<br />
If it didn't compile, that's reason alone to reject the Merge Request! Go to the web page for the Merge Request and report your findings.<br />
<br />
== Perform QA ==<br />
If it did compile, then it's time to perform QA, because it's important to thoroughly test Merge Requests to ensure that bad code and regressions don't slip in. '''This is the entire purpose of having a review infrastructure'''; it is very important.<br />
<br />
First make sure the unit tests all pass:<br />
{{Input|1=<nowiki><br />
cd ~kde/build/kde/applications/okular<br />
ctest<br />
</nowiki>}}<br />
<br />
If any tests fail, report this through a comment on the Merge Request's web page.<br />
<br />
Does everything all still work for you? If not, return to the web page and request changes, writing a detailed comment explaining what didn't work. It is permissible to do this even if you have not been specified as a reviewer! '''Anyone can reject a Merge Request on the grounds that it does not work, does not do what it says it does, or causes regressions.'''<br />
<br />
Next, try to break the Merge Request. Here are some ideas:<br />
# Remove the program's configuration file (<tt>~/.config/<program name>rc</tt> ) and re-open it<br />
# Try the program with a HiDPI scale factor (or without one) or with a different default font size<br />
# If it's a new feature, feed it unexpected input<br />
# Test related functionality<br />
<br />
A good Merge Request will handle corner cases and variations in configuration. The price of configurability is vigilant testing! We owe it to our users to test using many configurations, not just the defaults or our personal settings.<br />
<br />
== Perform code review ==<br />
{{Note|1= Need a section on code review here, preferably written by a core KDE developer or another very experienced developer}}<br />
<br />
== Engage with the author and other reviewers ==<br />
After you have run the program and evaluated the Merge Request, it's time to leave some review comments on the webpage. If you have been specified as a reviewer, or are a member of a group that has been specified as a reviewer, it is permissible to Accept the Merge Request. But keep in mind that reviewing involves responsibility: you are giving a thumbs-up to code that will be run potentially by millions of people. If you accept and land a Merge Request that causes regressions, you will be expected to help fix it if the original author cannot or has disappeared. It's important to take the reviewer role seriously.<br />
<br />
<br />
= Advanced topics =<br />
<br />
== Curating your merge request commit history ==<br />
<br />
For large or complex merge requests, it is strongly recommended to separate the pieces of your proposed change into individual commits--one for each component of the proposed change. For example, perhaps you are working on a feature that consists of multiple logically separable elements that nonetheless all live in the same source repo, or perhaps you are first doing some code refactoring, then you add a backend feature, then finally you add a front-end user interface for it.<br />
<br />
For this workflow, specifically mention in the merge request description that you would like reviewers to review individual commits separately, and not squash when merging.<br />
<br />
If you need to later make changes to your Merge Request, do not add new commits; instead, combine the new changes with one of the existing commits using git's interactive rebasing feature. To learn how to do this, see https://git-rebase.io/<br />
<br />
{{Note|Using Gitlab's "Apply suggestion" feature will create new commits in your Merge Request which must be manually squashed into one of the existing commits.}}<br />
<br />
== Using work branches instead of forks ==<br />
<br />
If you have a [[Infrastructure/Get a Developer Account|developer account]], you don't need to use forks to submit merge requests. Instead, you can make sure your branch names begin with "<tt>work/</tt>" and push them straight to the main repo. These branches can be force-pushed to as if they were forks. By convention, it is recommended to add your KDE username to the branch, so the final name would end up looking like "<tt>work/ngraham/my-awesome-feature</tt>."<br />
<br />
== Switching the target branch of a Merge Request ==<br />
Sometimes you will submit a merge request that is targeting the master branch, but will later be asked to target the stable branch instead because it is a bugfix. Or perhaps you have targeted the stable branch but the commit is considered too invasive and you are asked to target the master branch instead. In either of these circumstances, you will need to re-target your Merge Request. Here's how:<br />
<br />
{{Input|1=<nowiki><br />
git checkout [the local branch for your merge request]<br />
git fetch<br />
git rebase -i origin/[the name of the remote branch you want to target; for example release/19.12]<br />
</nowiki>}}<br />
<br />
This will open a text editor showing a list of commits, each on a separate line. Delete all of the lines of text except for the ones corresponding to new commits you have added as part of your merge request. Next, fix any merge conflicts if there are any.<br />
<br />
Then force-push the branch up to gitlab again:<br />
<br />
{{Input|1=<nowiki><br />
git push --force fork<br />
</nowiki>}}<br />
<br />
Finally, edit the merge request by clicking the "Edit" button in the top-right corner of the page and choose the desired different target branch:<br />
<br />
[[File:Editing_merge_request_target_branch.png]]<br />
<br />
Note that after merging a Merge Request to the stable branch, you are expected to merge that branch back to <tt>master</tt> afterwards:<br />
{{Input|1=<nowiki><br />
git checkout release/19.12<br />
git pull<br />
git checkout master<br />
git pull<br />
git merge -s recursive -Xours release/19.12<br />
git diff origin/master # Are the changes what you expected?<br />
git push<br />
</nowiki>}}<br />
<br />
{{warning|If at any time you feel nervous or run into trouble, ask your reviewers for help. This can be tricky, and undoing bad merges is a pain in the neck.}}<br />
<br />
== Pushing commits to somebody else's fork ==<br />
Sometimes someone will say "hey let's work on my branch together." So you will be pushing commits not to origin, and not to your fork, but to someone else's fork. Let's say you want to work on joliveira's "gsoc2019_numberFormat" branch.<br />
<br />
First you would need to add the URL for his fork as a remote:<br />
<br />
<pre><br />
$ cd ~/kde/src/okular<br />
$ git remote add joliveira_fork git@invent.kde.org:joliveira/okular.git<br />
$ git remote -v<br />
aacid_fork git@invent.kde.org:aacid/okular.git (fetch)<br />
aacid_fork git@invent.kde.org:aacid/okular.git (push)<br />
joliveira_fork git@invent.kde.org:joliveira/okular.git (fetch)<br />
joliveira_fork git@invent.kde.org:joliveira/okular.git (push)<br />
origin https://invent.kde.org/kde/okular.git (fetch)<br />
origin https://invent.kde.org/kde/okular.git (push)<br />
</pre><br />
<br />
Notice how there are now multiple forks set up as remotes.<br />
<br />
Next, you need to fetch all the repo metadata from the new remote:<br />
<br />
{{Input|1=<nowiki><br />
git fetch joliveira_fork<br />
</nowiki>}}<br />
<br />
This will download the list of branches. The next step is to switch to the one you want to collaborate on:<br />
<br />
{{Input|1=<nowiki><br />
git checkout --track joliveira_fork/gsoc2019_numberFormat<br />
</nowiki>}}<br />
<br />
This will create a local branch named "gsoc2019_numberFormat" from the contents of the remote branch joliveira_fork/gsoc2019_numberFormat and that also "tracks" it. This means that if someone else pushes changes to a remote version of that branch, you can run <tt>git pull --rebase</tt> while on your local "gsoc2019_numberFormat" branch to bring it up to date.<br />
<br />
Next, make your changes, add and commit. Then push the changes to the remote joliveira_fork remote:<br />
<br />
{{Input|1=<nowiki><br />
git push joliveira_fork gsoc2019_numberFormat<br />
</nowiki>}}<br />
<br />
== Generating "eternal" URLs to commits or objects in a repository ==<br />
<br />
History has taught that no system used by KDE around the code repositories stays forever.<br />
Quickgit, CGit, Phabricator & Co. came and at one point were replaced again. Sadly also taking with them the service-specific URLs (and host names).<br />
<br />
To give documentation, blog posts, commit messages and other long-living documents a way to reference commits or objects in the repository like directories or files, at given branches or at given tags, the service commits.kde.org exists. It maps and forwards URLs to the respective current service URLs.<br />
<br />
The pattern for URLs to commits is this:<br />
<pre><br />
https://commits.kde.org/<repo-id>/<commit-id><br />
</pre><br />
<br />
Example:<br />
<pre><br />
https://commits.kde.org/kcoreaddons/d2f4d353327b322ee6bfcc303169190ae44393f0<br />
</pre><br />
<br />
The pattern for URLs to objects is like this:<br />
<pre><br />
https://commits.kde.org/<repo-id>[?[path=<pathToFileOrDirectory]&[branch=<branch>|tag=<tag>]]<br />
</pre><br />
<path> should be without a leading /. It defaults to the top-level directory if not set. Either a branch or tag can be passed at which the objects should be shown. It defaults to the main branch (master usually).<br />
<br />
Examples:<br />
<pre><br />
https://commits.kde.org/kcoreaddons?path=src # points to src/ directory in master branch<br />
https://commits.kde.org/kcoreaddons?path=README.md&tag=v5.0.0 # points to README.md file at tag v5.0.0<br />
https://commits.kde.org/kdelibs?path=kdecore/tests&branch=KDE/3.5 # points to kdecore/tests directory in branch KDE/3.5<br />
</pre><br />
<br />
There currently is no service to generate commit.kde.org URLs from URLs for the actual system. This has to be done manually.<br />
<br />
== Submitting a merge request using the command line ==<br />
<br />
If you would prefer to avoid the web UI and submit a merge request entirely from the command line, you can! When you are pushing your branch, add <tt>-o merge_request.create -o merge_request.target=[target branch here]</tt> to the git push command.</div>Gjditchfieldhttps://community.kde.org/index.php?title=Get_Involved/development&diff=91058Get Involved/development2021-02-10T16:54:20Z<p>Gjditchfield: Fixed nested list bullets.</p>
<hr />
<div>[[File:Konqui dev close cropped.png|right|x200px|]]<br />
By joining the ranks of KDE developers, you will get to implement new features and defeat bugs both daunting and simple, all while collaborating to make coherent and stable releases. Developers collaborate in teams based on what area they are working in. These can be small teams working on a single application, up to large teams working on a group of related pieces of software. Many developers are in more than one team.<br />
<br />
KDE runs or participates in several mentoring programs to help new developers, including an informal list of people who are willing to help newcomers get started. See the [[Mentoring]] page for more details.<br />
<br />
{{Info|While any operating system can be used to patch or develop KDE software, it's easiest if you use a Linux distribution that provides relatively recent versions of Qt and KDE Frameworks, such as Arch/Manjaro, Fedora, KDE Neon, openSUSE Tumbleweed, or non-LTS versions of Kubuntu.<br /><br />Support for Windows, macOS, and Linux distros that ship older software (such as Debian, Ubuntu/Kubuntu 18.04, and openSUSE Leap 15) is still experimental, and you may have a better experience [[Get Involved/development/Developing in a virtual machine|doing your development in a virtual machine]] when using one of the distributions mentioned above.}}<br />
<br />
== New to C++/Qt software development? ==<br />
Most KDE software is written in C++ using the [https://www.qt.io Qt toolkit] and [[Frameworks | KDE Frameworks]]. Though prior experience with these technologies or other programming languages is helpful, you don't need to be a C++ programmer to get started! For example, no programming knowledge whatsoever is required to do things like improve text labels.<br />
<br />
If you'd like to dive deeper, the Qt wiki contains [https://wiki.qt.io/Books a list of online books] for learning Qt programming. Qt also provides [https://doc.qt.io/qt-5/qtexamplesandtutorials.html lots of examples] you can look at. Information about KDE Frameworks can be found on the [https://techbase.kde.org TechBase wiki], and a [[Books | book]] is available.<br />
<br />
== One-time setup: your development environment ==<br />
To build software, you need a '''development environment''': a set of tools that allows you to access and edit the source code, compile it into a form that the computer can run, and deploy it to a safe location. We will now go through the process of setting one up. To accomplish these tasks, you will need to enter commands using a terminal program, such as KDE's Konsole (but any terminal program will suffice).<br />
<br />
If you're not familiar with the command line interface, you can [https://lifehacker.com/5633909/who-needs-a-mouse-learn-to-use-the-command-line-for-almost-anything find a reasonable tutorial here]. However advanced command-line skills are not required, and you will learn what you need along the way!<br />
<br />
{{Note|'''Everything in this section only needs to be done once.''' Once you've done it, your development environment is set up and you can use it to submit patches and develop KDE Software!}}<br />
<br />
{{Note|As an alternative to the set-up process described here, you can install Docker and the [[KDE PIM/Docker|KDE PIM Docker image]]. It provides a development environment that is isolated from your day-to-day system. However, it is based on KDE Neon Unstable, which might not be to your taste.}}<br />
<br />
=== Install basic tools ===<br />
First you will need to use your operating system's package manager to install some basic tools:<br />
* Arch/Manjaro: <code>sudo pacman -S git cmake dialog</code><br />
* Fedora: <code>sudo dnf install git cmake dialog perl perl-IPC-Cmd</code><br />
* KDE Neon/Kubuntu/Ubuntu/Debian: <code>sudo apt install git cmake dialog</code><br />
* openSUSE Leap & Tumbleweed: <code>sudo zypper install git breezy cmake dialog</code><br />
<br /><br />
<br />
=== Configure Git ===<br />
We need to set your authorship information properly so that any changes you make can be properly attributed to you:<br />
{{Input|1=<nowiki><br />
git config --global user.name "Your Name"<br />
git config --global user.email "you@email.com"<br />
</nowiki>}}<br />
<br />
{{Note|The name you provide should be your actual name, not your KDE Identity username.<br />
<nowiki><br />
<br />
</nowiki><br />
The email address you specify above must be the same as the email address used for your https://bugs.kde.org account. If they don't match, then the <code>BUG: </code> and <code>FEATURE: </code> keywords won't work (see [https://techbase.kde.org/Development/Git/Configuration#Basic_Settings this page] for more information).}}<br />
<br /><br />
<br />
=== Set up kdesrc-build ===<br />
Next, we need a method of '''managing dependencies'''. Every software has dependencies: other pieces of software that provide the functionality they rely on. In order to compile any piece of software, its dependencies must be available.<br />
<br />
Most Linux-based operating systems do not provide development packages that are up-to-date enough for working on KDE software, so we will compile all the KDE dependencies ourselves. To do this, we use a command-line tool called <code>kdesrc-build</code> to download, manage, and build KDE source code repositories. Let's set it up now! First, we create a new directory for all the KDE source code we will be using. We then clone the source code repository that holds <code>kdesrc-build</code> in that directory, so we have a local copy of it on our computer.<br />
<br />
{{Input|1=<nowiki><br />
mkdir -p ~/kde/src<br />
cd ~/kde/src/<br />
git clone https://invent.kde.org/sdk/kdesrc-build.git && cd kdesrc-build<br />
</nowiki>}}<br />
<br />
Next, it's time to set up <code>kdesrc-build</code> and pick up the changes it made to your <code>~/.bashrc</code> for the current terminal session:<br />
<br />
{{Input|1=<nowiki><br />
./kdesrc-build --initial-setup<br />
source ~/.bashrc<br />
</nowiki>}}<br />
<br />
{{Warning|Do not quote or escape any file paths entered in the wizard! And do not run the command <code>kdesrc-build</code> by itself without any arguments because this will build everything, which is probably overkill right now.}}<br />
<br />
The initial setup tries to install the basic packages for compiling Qt and KDE software on your distro. It also creates a <code>~/.kdesrc-buildrc</code> configuration file.<br />
If you want a more guided setup process for <code>kdesrc-build</code>, run the command <code>kdesrc-build-setup</code> instead. However, unlike <code>--initial-setup</code>, it won't install packages from your distro for compiling programs so you will have to do that yourself.<br />
<br />
Consult the [https://docs.kde.org/trunk5/en/extragear-utils//kdesrc-build/ kdesrc-build manual] for more information and options.<br />
<br />
=== Set up Qt ===<br />
By default, <code>kdesrc-build</code> will build from source all the dependencies that a program or framework needs, including the Qt toolkit itself, because the <code>include-dependencies</code> option is set as default in the <code>~/.kdesrc-buildrc</code> file. <br />
<br />
If your Linux distribution provides a recent version of Qt (5.14 or newer), you can save some time and disk space and use that version instead of building your own. To configure <code>kdesrc-build</code> to skip building Qt, open the configuration file <code>~/.kdesrc-buildrc</code> and comment out the line with <code>qtdir</code> and any lines that begin with <code>include</code> and are related to qt5, but do not comment out the line that includes <code>kf5-qt5-build-include</code>. <br />
<br />
For example, comment/disable (put a <code>#</code> at the start of the line) or delete these lines if you want to use your distro's Qt packages (actual paths may vary):<br />
<br />
{{Input|1=<nowiki><br />
qtdir ~/kde/qt5 # Where to find Qt5<br />
include /path/to/kdesrc-build/qt5-build-include<br />
include /path/to/kdesrc-build/custom-qt5-libs-build-include<br />
</nowiki>}}<br />
<br />
=== Disable indexing for your development environment ===<br />
You'll want to disable indexing for your development-related git repos and the files they will build and install. Add <tt>~/kde</tt> to the exclusions list in System Settings > Search, like so:<br />
<br />
[[File:Disable indexing.jpeg|center|600px|]]<br />
<br />
=== Download non-KDE dependencies ===<br />
Even though <code>kdesrc-build</code> will take care of the KDE dependencies for you, it does not yet have the ability to install non-KDE dependencies (see https://invent.kde.org/sdk/kdesrc-build/-/issues/9), which are typically acquired using your package manager. To install the required non-KDE dependencies, [[Guidelines and HOWTOs/Build from source/Install the dependencies|read this page]] and follow the instructions laid out there.<br />
<br />
Once that's done, your development environment is set up and ready to build software! Let's take it for a spin.<br />
<br />
== Building software with kdesrc-build ==<br />
It can take an hour or more to compile a KDE Application, Framework, or Plasma itself for the first time. The reason for this is that <code>kdesrc-build</code> is compiling ''all'' of the KDE dependencies (See https://invent.kde.org/sdk/kdesrc-build/-/issues/17). The next time you want to compile that or any other piece of KDE software, it will be much faster since most of the dependencies will have already been compiled. If you don't want to build all dependencies (e.g., because you are using a rolling release distro that provides recent versions of software), edit the same configuration file and simply set <code>include-dependencies</code> to '''false''' or add the <code>--no-include-dependencies</code> option when running <code>kdesrc-build</code>.<br />
<br />
=== Applications ===<br />
'''[https://userbase.kde.org/Applications KDE Applications]''' like [https://userbase.kde.org/Dolphin Dolphin], [https://userbase.kde.org/Okular Okular], [https://userbase.kde.org/Konsole Konsole] and [https://userbase.kde.org/Gwenview Gwenview] are standalone apps that can be run on multiple platforms, such as Plasma, GNOME, even macOS and Windows! New versions of KDE Applications are [[Schedules/Applications/18.12 Release Schedule|released three times a year]]. Note that the Discover app store (git repo name: <code>plasma-discover</code>) and System Settings app (git repo name: <code>systemsettings</code>) are distributed alongside Plasma, but they build like apps using the below instructions. A list of all KDE applications can be found here: https://userbase.kde.org/Applications.<br />
<br />
The general steps required to build and run an application are described in the following using Dolphin as an example:<br />
<br />
{{Input|1=<nowiki><br />
kdesrc-build dolphin<br />
</nowiki>}}<br />
<br />
As a part of this process, Dolphin was installed to <code>~/kde/usr/bin/dolphin</code>. '''There is no need to manually install anything;''' <code>kdesrc-build</code> installed it for you! Source the project's auto-generated <code>prefix.sh</code> file every time you want to run your custom-compiled version of Dolphin: <br />
<br />
{{Input|1=<nowiki><br />
source ~/kde/build/dolphin/prefix.sh<br />
~/kde/usr/bin/dolphin<br />
</nowiki>}}<br />
<br />
Or using <code>kdesrc-run</code> wrapper:<br />
{{Input|1=<nowiki><br />
kdesrc-run dolphin<br />
</nowiki>}}<br />
<br />
Did it run? If so, then '''congratulations, you just compiled your own version of Dolphin from source code!'''<br />
<br />
=== Frameworks ===<br />
'''[[Frameworks|KDE Frameworks]]''' are libraries of tools and features that can be used by any application or Plasma itself. New versions of KDE Frameworks are [[Schedules/Frameworks|released once a month]]. A list of all frameworks can be found here: https://api.kde.org/frameworks.<br />
<br />
For example, here is how to build the KIO framework:<br />
<br />
{{Input|1=<nowiki><br />
kdesrc-build kio<br />
</nowiki>}}<br />
<br />
Now you can run an existing app using your custom-made version of the framework! For example, the following will run Dolphin, but it will be using the KIO library that you just built:<br />
<br />
{{Input|1=<nowiki><br />
source ~/kde/build/kio/prefix.sh<br />
~/kde/usr/bin/dolphin<br />
</nowiki>}}<br />
<br />
=== Plasma ===<br />
'''[[Plasma|KDE Plasma]]''' is the environment in which you can run apps. Plasma is responsible for providing a desktop with wallpaper, app launchers, and widgets; displaying notifications; managing wired and wireless networks; and similar operating-system level tasks. New versions of Plasma are [[Schedules/Plasma 5|released three times a year]]. Plasma has multiple ''shells'': [https://kde.org/plasma-desktop Plasma Desktop] for desktop, laptop, and 2-in-1 computers, [https://www.plasma-mobile.org/ Plasma Mobile] for mobile phones, Plasma Bigscreen for televisions, and so on. They all share certain common components, such as a window manager, networking stack, basic graphical components, and so on. Here is how to build them:<br />
{{Input|1=<nowiki><br />
kdesrc-build plasma-workspace plasma-framework plasma-nm plasma-pa plasma-thunderbolt plasma-vault plasma-disks plasma-workspace-wallpapers kdeplasma-addons krunner milou kwin kscreen sddm-kcm breeze discover print-manager plasma-sdk kaccounts-integration kaccounts-providers kdeconnect-kde plasma-browser-integration xdg-desktop-portal-kde khotkeys --include-dependencies<br />
</nowiki>}}<br />
<br />
==== Plasma Desktop ====<br />
To build the plasma Desktop environment and its related apps, also build the following:<br />
{{Input|1=<nowiki><br />
kdesrc-build plasma-desktop systemsettings ksysguard kinfocenter kmenuedit --include-dependencies<br />
</nowiki>}}<br />
<br />
To run your custom-built Plasma Desktop, first make it accessible from the SDDM login screen by running the following command:<br />
{{Input|1=<nowiki><br />
~/kde/build/plasma-workspace/login-sessions/install-sessions.sh<br />
</nowiki>}}<br />
<br />
Alternatively you can run the new version of plasma on top of your existing system for quick testing:<br />
{{Input|1=<nowiki><br />
source ~/kde/build/plasma-desktop/prefix.sh<br />
~/kde/usr/bin/plasmashell --replace<br />
</nowiki>}}<br />
<br />
You may need to set the LD_LIBRARY_PATH environment variable properly, to be able to run Plasma desktop. This can be done, for example, by editing <tt>~/kde/usr/lib64/libexec/plasma-dev-prefix.sh</tt> by uncommenting the <tt>export LD_LIBRARY_PATH</tt> line:<br />
<br />
<pre><br />
# LD_LIBRARY_PATH only needed if you are building without rpath<br />
export LD_LIBRARY_PATH=/home/plasmadev/kde/usr/lib64:$LD_LIBRARY_PATH<br />
</pre><br />
<br />
In order to make DBus and KAuth actions work properly, create a file named <code>/etc/dbus-1/session-local.conf</code> and add the following text to it:<br />
<br />
<busconfig><br />
<servicedir>/home/[your-user]/kde/usr/share/dbus-1/services</servicedir><br />
<servicedir>/home/[your-user]/kde/usr/share/dbus-1/system-services</servicedir><br />
<includedir>/home/[your-user]/kde/usr/share/dbus-1/system.d/</includedir><br />
</busconfig><br />
<br />
{{Note|<tt>/home/[your-user]/kde/usr</tt> is the default instalation for KDE applications if configured with the default settings for kdesrc-build. If you install kde software in any other place, you will need to edit the url accordingly}}<br />
<br />
After that, you can log out and select your new plasma session in SDDM's session chooser menu (which is located in the bottom-left corner of the screen if you're using the Breeze SDDM theme).<br />
<br />
{{Note |Some linux distros such as Arch Linux have a setting that forbids the usage of DBus scripts from the home folder. If you have problems with your login manager (such as sddm, gdm, lightdm) after creating the session-local.conf file, try to create a symlink for the dbus-1 folder inside of your installation to a place outside home (such as <tt>/opt/kde-dbus-scripts</tt>) and adapt the session-local file.<br />
<br />
<pre><br />
<busconfig><br />
<servicedir>/opt/kde-dbus-scripts/services</servicedir><br />
<servicedir>/opt/kde-dbus-scripts/system-services</servicedir><br />
<includedir>/opt/kde-dbus-scripts/system.d/</includedir><br />
</busconfig><br />
</pre><br />
}}<br />
<br />
==== Plasma Mobile ====<br />
To build the Plasma Mobile environment and its related apps, also build the following:<br />
{{Input|1=<nowiki><br />
kdesrc-build plasma-nano plasma-phone-components plasma-settings plasma-camera marble koko vvave okular plasma-angelfish plasma-samegame mtp-server kaidan peruse calindori index-fm maui-pix qrca keysmith --include-dependencies<br />
</nowiki>}}<br />
<br />
You can run your custom-built Plasma Mobile in a phone-sized window within your existing session like so:<br />
<br />
{{Input|1=<nowiki><br />
export XDG_RUNTIME_DIR=/tmp/<br />
export QT_QPA_PLATFORM=wayland<br />
export QT_QPA_PLATFORMTHEME=KDE<br />
export QT_WAYLAND_DISABLE_WINDOWDECORATION=1<br />
export XDG_CURRENT_DESKTOP=KDE<br />
export KSCREEN_BACKEND=QScreen<br />
export KDE_FULL_SESSION=1<br />
export KDE_SESSION_VERSION=5<br />
export QT_QUICK_CONTROLS_MOBILE=1<br />
export PLASMA_PLATFORM=phone:handheld<br />
export $(dbus-launch)<br />
dbus-run-session kwin_wayland --width 360 --height 720 --xwayland "plasmashell -p org.kde.plasma.phone"<br />
</nowiki>}}<br />
<br />
Plasma Mobile can also be run on a mobile device itself. For information on how to do that, see https://docs.plasma-mobile.org/DevGuide.html#plasma-mobile-device-environment.<br />
<br />
For more information, see https://docs.plasma-mobile.org/DevGuide.html.<br />
<br />
=== Iterating on a single project ===<br />
When you're working on a project and you want to rebuild it to test your changes, you can save a lot of time by only rebuilding that project, rather than the entire stack. For example if you are working on <code>plasma-desktop</code>, you can rebuild only that project rather than everything by running <code>kdesrc-build --no-src --no-include-dependencies plasma-desktop</code>.<br />
<br />
=== How to solve build problems ===<br />
Did one or more modules fail to build (displayed in red font) using <code>kdesrc-build</code>? Here's what to do:<br />
<br />
# Try building the failing module again from scratch using <code>kdesrc-build [failing module] --refresh-build</code><br />
# Make sure that you have all the dependencies for the failing module. Go back to the [[#Download non-KDE dependencies]] section and re-install the non-KDE dependencies. If that doesn't fix the problem, open the log file for the failing module, which <code>kdesrc-build</code> will print the path at the end of its output. Scroll to the bottom of the log file and read the output to see what missing dependency it is complaining about, then find and install the corresponding package using your distro's package manager your distro. If several looks relevant, install them all just to be safe. When you have the necessary dependencies, you can save time and resume from the failing module by adding <code>--resume-from [the name of the module that failed]</code> to your <code>kdesrc-build</code> command.<br />
# Check the [https://build.kde.org/view/Failing/ list of currently broken modules] on the KDE build server. If it's broken there, then it's not your fault. :)<br />
# Ask for help in the the [https://webchat.kde.org/#/room/#kde-devel:kde.org #kde-devel] channel on [[Matrix]] or freenode [[Internet Relay Chat | IRC]]. See [[Get Involved/development#Communicate with the team]]<br />
<br />
== Choose what to do ==<br />
Now that you can compile and deploy custom versions of KDE software, you can open your editor and start hacking on the source code! The code for the version of Dolphin that you built earlier is located at <code>~/kde/src/dolphin/</code>; other projects you build with <code>kdesrc-build</code> will live in similar locations.<br />
<br />
A good place to start is with a small bug or feature in an existing piece of software that affects you personally ("scratch your own itch"). Get in touch with the existing developers (see [[#Communicate with the team|Communicate with the team]], below) and they can help you out, by pointing you to the right place in the code and giving advice about how to tackle the problem.<br />
<br />
Try not to start by proposing or working on major features or significant design changes. These can be controversial, and the smoothest way to get going is by working on relatively non-controversial bugfixes. Start slowly and build trust!<br />
<br />
Here are some other ideas for starting points:<br />
<br />
* Improve awkwardly-worded messages and labels that are written in English. This is a great way for non-programmers to contribute! If you can compile software and have a good grasp of English, you can make a big difference here.<br />
* Work on [https://bugs.kde.org/buglist.cgi?bug_status=UNCONFIRMED&bug_status=CONFIRMED&bug_status=NEW&bug_status=ASSIGNED&bug_status=REOPENED&keywords=junior-jobs&list_id=1340815 Junior Jobs], which are small tasks that are suitable for beginners (both bugs and features).<br />
* Work on [https://bugs.kde.org/buglist.cgi?bug_status=UNCONFIRMED&bug_status=CONFIRMED&bug_status=ASSIGNED&bug_status=REOPENED&keywords=usability&keywords_type=allwords&list_id=1493316&order=product%2Cchangeddate%20DESC%2Cbug_status%20DESC%2Cresolution%2Cpriority%2Cassigned_to%2Cbug_id&query_format=advanced Bugs related to] KDE's [[Goals/Usability_%26_Productivity | Usability & Productivity initiative]], many of which are small and easy.<br />
* [http://www.englishbreakfastnetwork.org/ The English Breakfast Network] searches out simple, common issues in code that should be fixed, and going through the problems on there can provide a good overview of the code.<br />
<br />
== Test your changes ==<br />
Once you've made some changes, make sure the project still compiles and installs, and make sure the changes have the desired effect when you run the software. Then it's time to run the project's unit tests:<br />
<br />
{{Input|1=<nowiki><br />
cd ~/kde/build/dolphin/<br />
source ./prefix.sh<br />
ctest --output-on-failure<br />
</nowiki>}}<br />
<br />
If any test fails, that needs to be investigated and fixed before you can proceed. Once the tests pass, then run the software again to make sure it still behaves properly. If it doesn't, then go back and work on your patch some more, then re-compile and re-deploy, and test again, until the program does what you'd like it to do and all tests pass.<br />
<br />
== Submit a Merge Request ==<br />
Once you're happy with your patch and have verified that it does what you want, it's time to submit your changes for review!<br />
<br />
KDE uses [https://invent.kde.org GitLab] for merge request submission and review. [[Infrastructure/GitLab#Submitting_a_merge_request|Learn how to submit a Merge Request with GitLab]].<br />
<br />
== Communicate with the team ==<br />
There are several ways to get in touch with KDE developers, either generally or for a specific project. The most important communications channels are:<br />
* The [https://webchat.kde.org/#/room/#kde-devel:kde.org #kde-devel] channel on [[Matrix]] or the freenode [[Internet Relay Chat | IRC]], which is where KDE developers chat in real time about their work.<br />
* The [https://mail.kde.org/mailman/listinfo/kde-devel kde-devel mailing list] is the primary development mailing list. [http://kde.org/support/#mailinglists Learn more about mailing lists].<br />
<br />
These are general KDE development communication channels, and you may get directed to a more appropriate place for the project you are interested in. There is a [http://www.kde.org/mailinglists/ list of mailing lists] if you want to find a mailing list for a specific team directly. Many teams have their own real-time chat channels, too.<br />
<br />
You can also try looking for the team's section on the [[Main Page]] of this wiki. Many teams have information there for new contributors.<br />
<br />
== Source code cross referencing ==<br />
To search for a class, method, signal name... etc in all KDE repos, KDE uses a code referencing tool to index code in the various KDE repositories, you can search using the web interface available [https://lxr.kde.org/ here]. This is a very useful tool if you e.g. want to search for code usage examples in existing code... etc.<br />
<br />
Usage:<br />
* From the '''Branch group''' menu, you can select either '''kf5-qt5''', to search the code in the Git ''master'' branches or '''stable-kf5-qt5''' to search only the stable (released) branches<br />
* There are two search ''modes'':<br />
** On the '''Identifier search''' page, you can search for (note that this is case sensitive):<br />
*** class names, e.g. ''RenameDialog'', ''StatJob'', and of course any Qt class (used in KDE code, which is pretty much all of them), ''QLatin1String'', ''QListWidget''<br />
*** method names, e.g. ''addConfigSources()'' (from the KConfig framework) and signal names e.g. ''mimeTypeFound()''<br />
** on the '''General search''' page, you can search for strings, e.g. in Dolphin's context menu (accessed by right- clicking any empty space) there is '''Paste Clipboard Contents''', if you want to find in which source file this string is defined, search for '''Paste Clipboard Contents'''; this search includes classes/methods/signals names.<br />
<br />
== Next steps ==<br />
Sharpen your skills by going through the [https://techbase.kde.org/Development/Tutorials KDE development tutorials]. And try out [http://www.kdevelop.org KDevelop], the KDE IDE.<br />
<br />
After you have had several drama-free patches accepted, a KDE developer is likely to suggest you get a [[Infrastructure/Get a Developer Account|Developer account]], which will allow you to commit directly to KDE projects. With very few limits on where you can commit, you will be expected to act responsibly. At this point, congratulations! You are officially a KDE developer!<br />
<br />
You may also want to set up a more customized development environment. See [[Guidelines and HOWTOs/Build from source]].<br />
<br />
== Best practices & other useful information==<br />
* [[Get Involved/Design/Lessons Learned|Lessons learned over time regarding the development of user-facing software]]<br />
* [[Guidelines_and_HOWTOs/Debugging|Debugging]]<br />
* [[Guidelines and HOWTOs/UnitTests|Unit testing]]<br />
* [[Guidelines and HOWTOs/Code_Checking| Validating code]]<br />
* [[Guidelines and HOWTOs/API Documentation|Writing API documentation]] (related: https://api.kde.org).<br />
* [[Guidelines and HOWTOs/Licensing|Correctly state license information]]<br />
* [[Guidelines_and_HOWTOs/Wayland_Porting_Notes|Writing Wayland-friendly code]]<br />
* [[Frameworks/Porting_Notes|Porting from KDE 4 to Frameworks 5]]<br />
* [[Guidelines_and_HOWTOs/Making_apps_run_uninstalled|Running applications and their unit tests without first installing them]]<br />
* [https://community.kde.org/Infrastructure/GitLab#Testing_someone_else.27s_Merge_Request How to review merge requests]<br />
* [https://github.com/Wenzel/docker-kdesrc-build How to build with Docker]</div>Gjditchfieldhttps://community.kde.org/index.php?title=Policies/Binary_Compatibility_Issues_With_C%2B%2B&diff=91045Policies/Binary Compatibility Issues With C++2021-02-05T19:53:36Z<p>Gjditchfield: /* The Do's and Don'ts */</p>
<hr />
<div><languages /><br />
<translate><br />
<br />
== Definition ==<br />
<br />
A library is '''binary compatible''', if a program linked dynamically to a former version of the library continues running with newer versions of the library without the need to recompile.<br />
<br />
If a program needs to be recompiled to run with a new version of library but doesn't require any further modifications, the library is '''source compatible'''.<br />
<br />
Binary compatibility saves a lot of trouble. It makes it much easier to distribute software for a certain platform. Without ensuring binary compatibility between releases, people will be forced to provide statically linked binaries. Static binaries are bad because they<br />
* waste resources (especially memory)<br />
* don't allow the program to benefit from bugfixes or extensions in the libraries<br />
<br />
In the KDE project, we will provide binary compatibility within the life-span of a major release for the core libraries (kdelibs, kdepimlibs).<br />
<br />
== Note about ABI ==<br />
<br />
This text applies to most C++ ABIs used by compilers which KDE can be built with. It is mostly based on the [https://itanium-cxx-abi.github.io/cxx-abi/abi.html Itanium C++ ABI Draft], which is used by the GCC C++ compiler since version 3.4 in all platforms it supports. Information about Microsoft Visual C++ mangling scheme mostly comes from [http://www.agner.org/optimize/calling_conventions.pdf this article on calling conventions] (it's the most complete information found so far on MSVC ABI and name mangling).<br />
<br />
Some of the constraints specified here may not apply to a given compiler. The goal here is to list the most restrictive set of conditions when writing cross-platform C++ code, meant to be compiled with several different compilers.<br />
<br />
This page is updated when new binary incompatibility issues are found.<br />
<br />
== The Do's and Don'ts ==<br />
<br />
You can...<br />
<br />
* add new non-virtual functions, including signals and slots and constructors, that do not overload non-overloaded functions.<br />
* add a new enum to a class.<br />
* append new enumerators to an existing enum.<br />
** Exception: if that leads to the compiler choosing a larger underlying type for the enum, that makes the change binary-incompatible. Unfortunately, compilers have some leeway to choose the underlying type, so from an API-design perspective it's recommended to add a '''Max....''' enumerator with an explicit large value ('''=255''', '''=1<<15''', etc) to create an interval of numeric enumerator values that is guaranteed to fit into the chosen underlying type, whatever that may be.<br />
* reimplement virtual functions defined in the primary base class hierarchy (that is, virtuals defined in the first non-virtual base class, or in that class's first non-virtual base class, and so forth) '''if''' it is safe that programs linked with the prior version of the library call the implementation in the base class rather than the derived one. ''This is tricky and might be dangerous. Think twice before doing it. Alternatively see below for a workaround.''<br />
** Exception: if the overriding function has a [http://en.wikipedia.org/wiki/Covariant_return_type covariant return type], it's only a binary-compatible change if the more-derived type has always the same pointer address as the less-derived one. ''If in doubt, do not override with a covariant return type.''<br />
* change an inline function or make an inline function non-inline '''if''' it is safe that programs linked with the prior version of the library call the old implementation. ''This is tricky and might be dangerous. Think twice before doing it.''<br />
* remove private non-virtual functions '''if''' they are not called by any inline functions (and have never been).<br />
* remove private static members '''if''' they are not called by any inline functions (and have never been).<br />
* add new '''static''' data members.<br />
* change the default arguments of a method. It requires recompilation to use the actual new default argument values, though.<br />
* add new classes.<br />
* export a class that was not previously exported.<br />
* add or remove friend declarations to classes.<br />
* rename reserved member types<br />
* extend reserved bit fields, provided this doesn't cause the bit field to cross the boundary of its underlying type (8 bits for char & bool, 16 bits for short, 32 bits for int, etc.)<br />
* add the Q_OBJECT macro to a class if the class already inherits from QObject<br />
* add a Q_PROPERTY, Q_ENUMS or Q_FLAGS macro as that only modifies the meta-object generated by moc and not the class itself<br />
<br />
You cannot...<br />
* For existing classes:<br />
** [[Policies/Binary_Compatibility_Examples#Unexport_or_remove_a_class|unexport or remove]] an exported class.<br />
** [[Policies/Binary_Compatibility_Examples#Change_the_class_hierarchy|change the class hierachy]] in any way (add, remove, or reorder base classes).<br />
** [[Policies/Binary_Compatibility_Examples#Remove_class_finality|Remove]] <code>final</code>ity<br />
* For template classes:<br />
** [[Policies/Binary_Compatibility_Examples#Change_the_template_arguments_of_a_template_class|change the template arguments]] in any way (add, remove or reorder).<br />
* For existing functions of any type:<br />
** [[Policies/Binary_Compatibility_Examples#Unexport_a_function|unexport]] it.<br />
** remove it.<br />
*** remove the implementation of existing declared functions. The symbol comes from the implementation of the function, so this is effectively the function.<br />
** [[Policies/Binary_Compatibility_Examples#Inline_a_function|inline]] it (this includes moving a member function's body to the class definition, even without the inline keyword).<br />
** add an overload (binary compatible, but not source compatible: it makes <code>&func</code> ambiguous). Adding overloads to already overloaded functions is ok (since any use of <code>&func</code> already needed a cast).<br />
** change its signature. This includes:<br />
*** changing any of the types of the arguments in the [[Policies/Binary_Compatibility_Examples#Change_the_parameters_of_a_function|parameter list]], including changing the <code>const</code>/<code>volatile</code> qualifiers of the existing parameters (instead, add a new method)<br />
*** changing the <code>const</code>/<code>volatile</code> qualifiers of the function<br />
*** changing the [[Policies/Binary_Compatibility_Examples#Change_the_access_rights|access rights]] to some functions or data members, for example from <tt>private</tt> to <tt>public</tt>. With some compilers, this information may be part of the signature. If you need to make a private function protected or even public, you have to add a new function that calls the private one.<br />
*** changing the [[Policies/Binary_Compatibility_Examples#Change_the_CV-qualifiers_of_a_member_function|CV-qualifiers of a member function]]: the <code>const</code> and/or <code>volatile</code> that apply to the function itself.<br />
*** extending a function with another parameter, even if this parameter has a default argument. ''See below for a suggestion on how to avoid this issue''<br />
*** changing the [[Policies/Binary_Compatibility_Examples#Change_the_return_type|return type]] in any way<br />
*** Exception: non-member functions declared with <code>extern "C"</code> can change parameter types ('''be very careful''').<br />
* For virtual member functions:<br />
** [[Policies/Binary_Compatibility_Examples#Add_a_virtual_member_function_to_a_class_without_any|add a virtual function]] to a class that doesn't have any virtual functions or virtual bases.<br />
** [[Policies/Binary_Compatibility_Examples#Add_new_virtuals_to_a_non-leaf_class|add new virtual functions]] to non-leaf classes as this will break subclasses. Note that a class designed to be sub-classed by applications is '''always''' a non-leaf class. ''See below for some workarounds or ask on mailing lists.''<br />
** add new virtual functions for any reason, even to leaf classes, ''if the class is intended to remain binary compatible on Windows''. Doing so may [http://lists.kde.org/?l=kde-core-devel&m=139744177410091&w=2 reorder existing virtual functions] and break binary compatibility.<br />
** [[Policies/Binary_Compatibility_Examples#Change_the_order_of_the_declaration_of_virtual_functions|change the order]] of virtual functions in the class declaration.<br />
** [[Policies/Binary_Compatibility_Examples#Override_a_virtual_that_doesn.27t_come_from_a_primary_base|override an existing virtual function if that function is not in the primary base class]] (the first non-virtual base class, or the primary base class's primary base class and upwards).<br />
** [[Policies/Binary_Compatibility_Examples#Override_a_virtual_with_a_covariant_return_with_different_top_address|override an existing virtual function]] if the overriding function has a [http://en.wikipedia.org/wiki/Covariant_return_type covariant return type] for which the more-derived type has a pointer address different from the less-derived one (usually happens when, between the less-derived and the more-derived ones, there's multiple inheritance or virtual inheritance).<br />
** Remove a virtual function, even if it is a reimplementation of a virtual function from the base class<br />
* For static non-private members or for non-static non-member public data:<br />
** Remove or unexport it<br />
** Change its [[Policies/Binary_Compatibility_Examples#Change_the_type_of_global_data|type]]<br />
** Change its [[Policies/Binary_Compatibility_Examples#Change_the_CV-qualifiers_of_global_data|CV-qualifiers]]<br />
* For non-static members:<br />
** add new data members to an existing class.<br />
** change the order of non-static data members in a class.<br />
** change the type of the member, except for signedness (or more generally if the types are guaranteed to have the same size, and the member is not used by any inline method)<br />
** remove existing non-static data members from an existing class.<br />
<br />
If you need to add extend/modify the parameter list of an existing function, you need to add a new function instead with the new parameters. In that case, you may want to add a short note that the two functions shall be merged with a default argument in later versions of the library:<br />
<br />
<source lang="cpp-qt"><br />
void functionname( int a );<br />
void functionname( int a, int b ); //BCI: merge with int b = 0<br />
</source><br />
<br />
You should...<br />
<br />
In order to make a class to extend in the future you should follow these rules:<br />
* add d-pointer. ''See below''.<br />
* add non-inline virtual destructor even if the body is empty.<br />
* reimplement <code>event</code> in QObject-derived classes, even if the body for the function is just calling the base class' implementation. This is specifically to avoid problems caused by adding a reimplemented virtual function as discussed below.<br />
* make all constructors non-inline.<br />
* write non-inline implementations of the copy constructor and assignment operator unless the class cannot be copied by value. (E.g. classes inherited from QObject can't be.)<br />
<br />
== Techniques for Library Programmers ==<br />
<br />
The biggest problem when writing libraries is, that one cannot safely add data members since this would change the size and layout of every class, struct, or array containing objects of the type, including subclasses.<br />
<br />
=== Bitflags ===<br />
One exception are bitflags. If you use bitflags for enums or bools, you can safely round up to at least the next byte minus 1. A class with members<br />
<br />
<source lang="cpp-qt"><br />
uint m1 : 1;<br />
uint m2 : 3;<br />
uint m3 : 1;<br />
</source><br />
<source lang="cpp-qt"><br />
uint m1 : 1;<br />
uint m2 : 3;<br />
uint m3 : 1;<br />
uint m4 : 2; // new member<br />
</source><br />
without breaking binary compatibility. Please round up to a maxmimum of 7 bits (or 15 if the bitfield was already larger than 8). Using the very last bit may cause problems on some compilers.<br />
<br />
=== Using a d-Pointer===<br />
Bitflags and predefined reserved variables are nice, but far from being sufficient. This is where the d-pointer technique comes into play. The name "d-pointer" stems from Trolltech's Arnt Gulbrandsen, who first introduced the technique into Qt, making it one of the first C++ GUI libraries to maintain binary compatibility even between bigger release. The technique was quickly adapted as general programming pattern for the KDE libraries by everyone who saw it. It's a great trick to be able to add new private data members to a class without breaking binary compatibility.<br />
<br />
'''Remark:''' The d-pointer pattern has been described many<br />
times in computer science history under various names, e.g. as pimpl,<br />
as handle/body or as cheshire cat. Google helps finding online papers<br />
for any of these, just add C++ to the search terms.<br />
<br />
In your class definition for class Foo, define a forward declaration<br />
<source lang="cpp-qt"><br />
class FooPrivate;<br />
</source><br />
and the d-pointer in the private section:<br />
<source lang="cpp-qt"><br />
private:<br />
FooPrivate* d;<br />
</source><br />
The FooPrivate class itself is purely defined in the class implementation file (usually *.cpp ), for example:<br />
<source lang="cpp-qt"><br />
class FooPrivate {<br />
public:<br />
FooPrivate()<br />
: m1(0), m2(0)<br />
{}<br />
int m1;<br />
int m2;<br />
QString s;<br />
};<br />
</source><br />
<br />
All you have to do now is to create the private data in your constructors or your init function with<br />
<source lang="cpp-qt"><br />
d = new FooPrivate;<br />
</source><br />
and to delete it again in your destructor with<br />
<source lang="cpp-qt"><br />
delete d;<br />
</source><br />
<br />
In most circumstances you will want to make the dpointer constant to catch situations where it's accidentally getting modified or copied over so you'd lose ownership of the private object and create a memory leak:<br />
<source lang="cpp-qt"><br />
private:<br />
FooPrivate* const d;<br />
</source><br />
This allows you to modify the object pointed to by d but not the value of the pointer after it has been initialized.<br />
<br />
You may not want all member variables to live in the private data object, though. For very often used members, it's faster to put them directly in the class, since inline functions cannot access the d-pointer data. Also note that all data covered by the d-pointer is "private", despite being declared public in the d-pointer itself. For public or protected access, provide both a set and a get function. Example<br />
<source lang="cpp-qt"><br />
QString Foo::string() const<br />
{<br />
return d->s;<br />
}<br />
<br />
void Foo::setString( const QString& s )<br />
{<br />
d->s = s;<br />
}<br />
</source><br />
<br />
It is also possible (but not recommended) to declare the private class for the d-pointer as a nested private class (e.g. Foo::Private). If you use this technique, remember that the nested private class will inherit the public symbol visibility of the containing exported class. This will cause the functions of the private class to be named in the dynamic library's symbol table. You can use <code>Q_DECL_HIDDEN</code> in the implementation of the nested private class to manually re-hide the symbols. (For an existing class, this is technically an ABI change, but does not impact the public ABI supported by the KDE developers, so private symbols mistaken exposed may be re-hidden without further warning.). Other downsides of the nested private class include the lack of consistency with Qt and its Q_D/Q_Q macros, and the fact that it can't be forward-declared in unrelated headers anymore (which can be useful to declare it as a friend class). For all these reasons, prefer FooPrivate.<br />
<br />
<h2>Trouble shooting</h2><br />
<br />
=== Adding new data members to classes without d-pointer === <br />
<br />
If you don't have free bitflags, reserved variables and no d-pointer either, but you absolutely have to add a new private member variable, there are still some possibilities left. If your class inherits {{qt|QObject}}, you can for example place the additional data in a special child and find it by traversing over the list of children. You can access the list of children with QObject::children(). However, a fancier and usually faster approach is to use a hashtable to store a<br />
mapping between your object and the extra data. For this purpose, Qt provides a pointer-based dictionary called {{qt|QHash}} (or {{qt3|QPtrDict}} in Qt3).<br />
<br />
The basic trick in your class implementation of class Foo is:<br />
* Create a private data class FooPrivate.<br />
* Create a static QHash&lt;Foo *, FooPrivate *&gt;.<br />
*Note that some compilers/linkers (almost all, unfortunately) do not manage to create static objects in shared libraries. They simply forget to call the constructor. Therefore you should use the <tt>Q_GLOBAL_STATIC</tt> macro to create and access the object:<br />
<br />
<source lang="cpp-qt"><br />
// BCI: Add a real d-pointer<br />
typedef QHash<Foo *, FooPrivate *> FooPrivateHash;<br />
Q_GLOBAL_STATIC(FooPrivateHash, d_func)<br />
static FooPrivate *d(const Foo *foo)<br />
{<br />
FooPrivate *ret = d_func()->value(foo);<br />
if ( ! ret ) {<br />
ret = new FooPrivate;<br />
d_func()->insert(foo, ret);<br />
}<br />
return ret;<br />
}<br />
static void delete_d(const Foo *foo)<br />
{<br />
FooPrivate *ret = d_func()->value(foo);<br />
delete ret;<br />
d_func()->remove(foo);<br />
}<br />
</source><br />
<br />
* Now you can use the d-pointer in your class almost as simple as in the code before, just with a function call to d(this). For example:<br />
<br />
<source lang="cpp-qt"><br />
d(this)->m1 = 5;<br />
</source><br />
<br />
* Add a line to your destructor:<br />
<source lang="cpp-qt"><br />
delete_d(this);<br />
</source><br />
* Do not forget to add a BCI remark, so that the hack can be removed in the next version of the library.<br />
* Do not forget to add a d-pointer to your next class.<br />
<br />
=== Adding a reimplemented virtual function ===<br />
<br />
As already explained, you can safely reimplement a virtual function defined in one of the base classes only if it is safe that the programs linked with the prior version call the implementation in the base class rather than the derived one. This is because the compiler sometimes calls virtual functions directly if it can determine which one to call. For example, if you have <br />
<source lang="cpp-qt"><br />
void C::foo()<br />
{<br />
B::foo();<br />
}<br />
</source><br />
<br />
then B::foo() is called directly. If class B inherits from class A which implements foo() and B itself doesn't reimplement it, then C::foo() will in fact call A::foo(). If a newer version of the library adds B::foo(), C::foo() will call it only after a recompilation.<br />
<br />
Another more common example is:<br />
<source lang="cpp-qt"><br />
B b; // B derives from A<br />
b.foo();<br />
</source><br />
then the call to foo() will not use the virtual table. That means that<br />
if B::foo() didn't exist in the library but now does, code that was<br />
compiled with the earlier version will still call A::foo().<br />
<br />
If you can't guarantee things will continue to work without a recompilation, move functionality from A::foo() to a new protected function A::foo2() and use this code:<br />
<source lang="cpp-qt"><br />
void A::foo()<br />
{<br />
if( B* b = dynamic_cast< B* >( this ))<br />
b->B::foo(); // B:: is important<br />
else<br />
foo2();<br />
}<br />
void B::foo()<br />
{<br />
// added functionality<br />
A::foo2(); // call base function with real functionality<br />
}<br />
</source><br />
All calls to A::foo() for objects of type B (or inherited) will result in calling B::foo(). The only case that will not work as expected are calls to A::foo() that explicitly specify A::foo(), but B::foo() calls A::foo2() instead and there should not be other places doing so.<br />
<br />
=== Using a new class ===<br />
<br />
A relatively simple method of "extending" a class can be writing a replacement class that will include also the new functionality (and that may inherit from the old class to reuse the code). This of course requires adapting and recompiling applications using the library, so it is not possible this way to fix or extend functionality of classes that are used by applications compiled against an older version of the library. However, especially with small and/or performance-critical classes it may be simpler to write them without making sure they'll be simple to extend in the future and if the need arises later write a new replacement class that will provide new features or better performance.<br />
<br />
=== Adding new virtual functions to leaf classes ===<br />
This technique is one of cases of using a new class that can help if there's a need to add new virtual functions to a class that should stay binary compatible and there is no class inheriting from it that should also stay binary compatible (i.e. all classes inheriting from it are in applications). In such case it's possible to add a new class inheriting from the original one that will add them. Applications using the new functionality will of course have to be modified to use the new class.<br />
<source lang="cpp-qt"><br />
class A {<br />
public:<br />
virtual void foo();<br />
};<br />
class B : public A { // newly added class<br />
public:<br />
virtual void bar(); // newly added virtual function<br />
};<br />
void A::foo()<br />
{<br />
// here it's needed to call a new virtual function<br />
if( B* this2 = dynamic_cast< B* >( this ))<br />
this2->bar();<br />
}<br />
</source><br />
It is not possible to use this technique when there are other inherited classes that should also stay binary compatible because they'd have to inherit from the new class.<br />
<br />
=== Using signals instead of virtual functions ===<br />
Qt's signals and slots are invoked using a special virtual method created by the Q_OBJECT macro and it exists in every class inherited from {{qt|QObject}}. Therefore adding new signals and slots doesn't affect binary compatibility and the signals/slots mechanism can be used to emulate virtual functions.<br />
<br />
<source lang="cpp-qt"><br />
class A : public QObject {<br />
Q_OBJECT<br />
public:<br />
A();<br />
virtual void foo();<br />
signals:<br />
void bar( int* ); // added new "virtual" function<br />
protected slots:<br />
// implementation of the virtual function in A<br />
void barslot( int* );<br />
};<br />
<br />
A::A()<br />
{<br />
connect(this, SIGNAL( bar(int*)), this, SLOT( barslot(int*)));<br />
}<br />
<br />
void A::foo()<br />
{<br />
int ret;<br />
emit bar( &ret );<br />
}<br />
<br />
void A::barslot( int* ret )<br />
{<br />
*ret = 10;<br />
}<br />
</source><br />
<br />
Function bar() will act like a virtual function, barslot() implements the actual functionality of it. Since signals have void return value, data must be returned using arguments. As there will be only one slot connected to the signal returning data from the slot this way will work without problems. Note that with Qt4 for this to work the connection type will have to be Qt::DirectConnection.<br />
<br />
If an inherited class will want to re-implement the functionality of bar() it will have to provide its own slot:<br />
<source lang="cpp-qt"><br />
class B : public A {<br />
Q_OBJECT<br />
public:<br />
B();<br />
protected slots: // necessary to specify as a slot again<br />
void barslot( int* ); // reimplemented functionality of bar()<br />
};<br />
<br />
B::B()<br />
{<br />
disconnect(this, SIGNAL(bar(int*)), this, SLOT(barslot(int*)));<br />
connect(this, SIGNAL(bar(int*)), this, SLOT(barslot(int*)));<br />
}<br />
<br />
void B::barslot( int* ret )<br />
{<br />
*ret = 20;<br />
}<br />
</source><br />
<br />
Now B::barslot() will act like virtual reimplementation of A::bar(). Note that it is necessary to specify barslot() again as a slot in B and that in the constructor it is necessary to first disconnect and then connect again, that will disconnect A::barslot() and connect B::barslot() instead.<br />
<br />
Note: the same can be accomplished by implementing a virtual slot.<br />
<br />
[[Category:Policies]] [[Category:C++]]<br />
</translate></div>Gjditchfieldhttps://community.kde.org/index.php?title=Get_Involved/development&diff=91044Get Involved/development2021-02-05T19:36:40Z<p>Gjditchfield: Link to PIM Docker image page</p>
<hr />
<div>[[File:Konqui dev close cropped.png|right|x200px|]]<br />
By joining the ranks of KDE developers, you will get to implement new features and defeat bugs both daunting and simple, all while collaborating to make coherent and stable releases. Developers collaborate in teams based on what area they are working in. These can be small teams working on a single application, up to large teams working on a group of related pieces of software. Many developers are in more than one team.<br />
<br />
KDE runs or participates in several mentoring programs to help new developers, including an informal list of people who are willing to help newcomers get started. See the [[Mentoring]] page for more details.<br />
<br />
{{Info|While any operating system can be used to patch or develop KDE software, it's easiest if you use a Linux distribution that provides relatively recent versions of Qt and KDE Frameworks, such as Arch/Manjaro, Fedora, KDE Neon, openSUSE Tumbleweed, or non-LTS versions of Kubuntu.<br /><br />Support for Windows, macOS, and Linux distros that ship older software (such as Debian, Ubuntu/Kubuntu 18.04, and openSUSE Leap 15) is still experimental, and you may have a better experience [[Get Involved/development/Developing in a virtual machine|doing your development in a virtual machine]] when using one of the distributions mentioned above.}}<br />
<br />
== New to C++/Qt software development? ==<br />
Most KDE software is written in C++ using the [https://www.qt.io Qt toolkit] and [[Frameworks | KDE Frameworks]]. Though prior experience with these technologies or other programming languages is helpful, you don't need to be a C++ programmer to get started! For example, no programming knowledge whatsoever is required to do things like improve text labels.<br />
<br />
If you'd like to dive deeper, the Qt wiki contains [https://wiki.qt.io/Books a list of online books] for learning Qt programming. Qt also provides [https://doc.qt.io/qt-5/qtexamplesandtutorials.html lots of examples] you can look at. Information about KDE Frameworks can be found on the [https://techbase.kde.org TechBase wiki], and a [[Books | book]] is available.<br />
<br />
== One-time setup: your development environment ==<br />
To build software, you need a '''development environment''': a set of tools that allows you to access and edit the source code, compile it into a form that the computer can run, and deploy it to a safe location. We will now go through the process of setting one up. To accomplish these tasks, you will need to enter commands using a terminal program, such as KDE's Konsole (but any terminal program will suffice).<br />
<br />
If you're not familiar with the command line interface, you can [https://lifehacker.com/5633909/who-needs-a-mouse-learn-to-use-the-command-line-for-almost-anything find a reasonable tutorial here]. However advanced command-line skills are not required, and you will learn what you need along the way!<br />
<br />
{{Note|'''Everything in this section only needs to be done once.''' Once you've done it, your development environment is set up and you can use it to submit patches and develop KDE Software!}}<br />
<br />
{{Note|As an alternative to the set-up process described here, you can install Docker and the [[KDE PIM/Docker|KDE PIM Docker image]]. It provides a development environment that is isolated from your day-to-day system. However, it is based on KDE Neon Unstable, which might not be to your taste.}}<br />
<br />
=== Install basic tools ===<br />
First you will need to use your operating system's package manager to install some basic tools:<br />
* Arch/Manjaro: <code>sudo pacman -S git cmake dialog</code><br />
* Fedora: <code>sudo dnf install git cmake dialog perl</code><br />
* KDE Neon/Kubuntu/Ubuntu/Debian: <code>sudo apt install git cmake dialog</code><br />
* openSUSE Leap & Tumbleweed: <code>sudo zypper install git breezy cmake dialog</code><br />
<br /><br />
<br />
=== Configure Git ===<br />
We need to set your authorship information properly so that any changes you make can be properly attributed to you:<br />
{{Input|1=<nowiki><br />
git config --global user.name "Your Name"<br />
git config --global user.email "you@email.com"<br />
</nowiki>}}<br />
<br />
{{Note|The name you provide should be your actual name, not your KDE Identity username.<br />
<nowiki><br />
<br />
</nowiki><br />
The email address you specify above must be the same as the email address used for your https://bugs.kde.org account. If they don't match, then the <code>BUG: </code> and <code>FEATURE: </code> keywords won't work (see [https://techbase.kde.org/Development/Git/Configuration#Basic_Settings this page] for more information).}}<br />
<br /><br />
<br />
=== Set up kdesrc-build ===<br />
Next, we need a method of '''managing dependencies'''. Every software has dependencies: other pieces of software that provide the functionality they rely on. In order to compile any piece of software, its dependencies must be available.<br />
<br />
Most Linux-based operating systems do not provide development packages that are up-to-date enough for working on KDE software, so we will compile all the KDE dependencies ourselves. To do this, we use a command-line tool called <code>kdesrc-build</code> to download, manage, and build KDE source code repositories. Let's set it up now! First, we create a new directory for all the KDE source code we will be using. We then clone the source code repository that holds <code>kdesrc-build</code> in that directory, so we have a local copy of it on our computer.<br />
<br />
{{Input|1=<nowiki><br />
mkdir -p ~/kde/src<br />
cd ~/kde/src/<br />
git clone https://invent.kde.org/sdk/kdesrc-build.git && cd kdesrc-build<br />
</nowiki>}}<br />
<br />
Next, it's time to set up <code>kdesrc-build</code> and pick up the changes it made to your <code>~/.bashrc</code> for the current terminal session:<br />
<br />
{{Input|1=<nowiki><br />
./kdesrc-build --initial-setup<br />
source ~/.bashrc<br />
</nowiki>}}<br />
<br />
{{Warning|Do not quote or escape any file paths entered in the wizard! And do not run the command <code>kdesrc-build</code> by itself without any arguments because this will build everything, which is probably overkill right now.}}<br />
<br />
The initial setup tries to install the basic packages for compiling Qt and KDE software on your distro. It also creates a <code>~/.kdesrc-buildrc</code> configuration file.<br />
If you want a more guided setup process for <code>kdesrc-build</code>, run the command <code>kdesrc-build-setup</code> instead. However, unlike <code>--initial-setup</code>, it won't install packages from your distro for compiling programs so you will have to do that yourself.<br />
<br />
Consult the [https://docs.kde.org/trunk5/en/extragear-utils//kdesrc-build/ kdesrc-build manual] for more information and options.<br />
<br />
=== Set up Qt ===<br />
By default, <code>kdesrc-build</code> will build from source all the dependencies that a program or framework needs, including the Qt toolkit itself, because the <code>include-dependencies</code> option is set as default in the <code>~/.kdesrc-buildrc</code> file. <br />
<br />
If your Linux distribution provides a recent version of Qt (5.14 or newer), you can save some time and disk space and use that version instead of building your own. To configure <code>kdesrc-build</code> to skip building Qt, open the configuration file <code>~/.kdesrc-buildrc</code> and comment out the line with <code>qtdir</code> and any lines that begin with <code>include</code> and are related to qt5, but do not comment out the line that includes <code>kf5-qt5-build-include</code>. <br />
<br />
For example, comment/disable (put a <code>#</code> at the start of the line) or delete these lines if you want to use your distro's Qt packages (actual paths may vary):<br />
<br />
{{Input|1=<nowiki><br />
qtdir ~/kde/qt5 # Where to find Qt5<br />
include /path/to/kdesrc-build/qt5-build-include<br />
include /path/to/kdesrc-build/custom-qt5-libs-build-include<br />
</nowiki>}}<br />
<br />
=== Disable indexing for your development environment ===<br />
You'll want to disable indexing for your development-related git repos and the files they will build and install. Add <tt>~/kde</tt> to the exclusions list in System Settings > Search, like so:<br />
<br />
[[File:Disable indexing.jpeg|center|600px|]]<br />
<br />
=== Download non-KDE dependencies ===<br />
Even though <code>kdesrc-build</code> will take care of the KDE dependencies for you, it does not yet have the ability to install non-KDE dependencies (see https://invent.kde.org/sdk/kdesrc-build/-/issues/9), which are typically acquired using your package manager. To install the required non-KDE dependencies, [[Guidelines and HOWTOs/Build from source/Install the dependencies|read this page]] and follow the instructions laid out there.<br />
<br />
Once that's done, your development environment is set up and ready to build software! Let's take it for a spin.<br />
<br />
== Building software with kdesrc-build ==<br />
It can take an hour or more to compile a KDE Application, Framework, or Plasma itself for the first time. The reason for this is that <code>kdesrc-build</code> is compiling ''all'' of the KDE dependencies (See https://invent.kde.org/sdk/kdesrc-build/-/issues/17). The next time you want to compile that or any other piece of KDE software, it will be much faster since most of the dependencies will have already been compiled. If you don't want to build all dependencies (e.g., because you are using a rolling release distro that provides recent versions of software), edit the same configuration file and simply set <code>include-dependencies</code> to '''false''' or add the <code>--no-include-dependencies</code> option when running <code>kdesrc-build</code>.<br />
<br />
=== Applications ===<br />
'''[https://userbase.kde.org/Applications KDE Applications]''' like [https://userbase.kde.org/Dolphin Dolphin], [https://userbase.kde.org/Okular Okular], [https://userbase.kde.org/Konsole Konsole] and [https://userbase.kde.org/Gwenview Gwenview] are standalone apps that can be run on multiple platforms, such as Plasma, GNOME, even macOS and Windows! New versions of KDE Applications are [[Schedules/Applications/18.12 Release Schedule|released three times a year]]. Note that the Discover app store (git repo name: <code>plasma-discover</code>) and System Settings app (git repo name: <code>systemsettings</code>) are distributed alongside Plasma, but they build like apps using the below instructions. A list of all KDE applications can be found here: https://userbase.kde.org/Applications.<br />
<br />
The general steps required to build and run an application are described in the following using Dolphin as an example:<br />
<br />
{{Input|1=<nowiki><br />
kdesrc-build dolphin<br />
</nowiki>}}<br />
<br />
As a part of this process, Dolphin was installed to <code>~/kde/usr/bin/dolphin</code>. '''There is no need to manually install anything;''' <code>kdesrc-build</code> installed it for you! Source the project's auto-generated <code>prefix.sh</code> file every time you want to run your custom-compiled version of Dolphin: <br />
<br />
{{Input|1=<nowiki><br />
source ~/kde/build/dolphin/prefix.sh<br />
~/kde/usr/bin/dolphin<br />
</nowiki>}}<br />
<br />
Or using <code>kdesrc-run</code> wrapper:<br />
{{Input|1=<nowiki><br />
kdesrc-run dolphin<br />
</nowiki>}}<br />
<br />
Did it run? If so, then '''congratulations, you just compiled your own version of Dolphin from source code!'''<br />
<br />
=== Frameworks ===<br />
'''[[Frameworks|KDE Frameworks]]''' are libraries of tools and features that can be used by any application or Plasma itself. New versions of KDE Frameworks are [[Schedules/Frameworks|released once a month]]. A list of all frameworks can be found here: https://api.kde.org/frameworks.<br />
<br />
For example, here is how to build the KIO framework:<br />
<br />
{{Input|1=<nowiki><br />
kdesrc-build kio<br />
</nowiki>}}<br />
<br />
Now you can run an existing app using your custom-made version of the framework! For example, the following will run Dolphin, but it will be using the KIO library that you just built:<br />
<br />
{{Input|1=<nowiki><br />
source ~/kde/build/kio/prefix.sh<br />
~/kde/usr/bin/dolphin<br />
</nowiki>}}<br />
<br />
=== Plasma ===<br />
'''[[Plasma|KDE Plasma]]''' is the environment in which you can run apps. Plasma is responsible for providing a desktop with wallpaper, app launchers, and widgets; displaying notifications; managing wired and wireless networks; and similar operating-system level tasks. New versions of Plasma are [[Schedules/Plasma 5|released three times a year]]. Plasma has multiple ''shells'': [https://kde.org/plasma-desktop Plasma Desktop] for desktop, laptop, and 2-in-1 computers, [https://www.plasma-mobile.org/ Plasma Mobile] for mobile phones, Plasma Bigscreen for televisions, and so on. They all share certain common components, such as a window manager, networking stack, basic graphical components, and so on. Here is how to build them:<br />
{{Input|1=<nowiki><br />
kdesrc-build plasma-workspace plasma-framework plasma-nm plasma-pa plasma-thunderbolt plasma-vault plasma-disks plasma-workspace-wallpapers kdeplasma-addons krunner milou kwin kscreen sddm-kcm breeze discover print-manager plasma-sdk kaccounts-integration kaccounts-providers kdeconnect-kde plasma-browser-integration xdg-desktop-portal-kde khotkeys --include-dependencies<br />
</nowiki>}}<br />
<br />
==== Plasma Desktop ====<br />
To build the plasma Desktop environment and its related apps, also build the following:<br />
{{Input|1=<nowiki><br />
kdesrc-build plasma-desktop systemsettings ksysguard kinfocenter kmenuedit --include-dependencies<br />
</nowiki>}}<br />
<br />
To run your custom-built Plasma Desktop, first make it accessible from the SDDM login screen by running the following command:<br />
{{Input|1=<nowiki><br />
~/kde/build/plasma-workspace/login-sessions/install-sessions.sh<br />
</nowiki>}}<br />
<br />
Alternatively you can run the new version of plasma on top of your existing system for quick testing:<br />
{{Input|1=<nowiki><br />
source ~/kde/build/plasma-desktop/prefix.sh<br />
~/kde/usr/bin/plasmashell --replace<br />
</nowiki>}}<br />
<br />
You may need to set the LD_LIBRARY_PATH environment variable properly, to be able to run Plasma desktop. This can be done, for example, by editing <tt>~/kde/usr/lib64/libexec/plasma-dev-prefix.sh</tt> by uncommenting the <tt>export LD_LIBRARY_PATH</tt> line:<br />
<br />
<pre><br />
# LD_LIBRARY_PATH only needed if you are building without rpath<br />
export LD_LIBRARY_PATH=/home/plasmadev/kde/usr/lib64:$LD_LIBRARY_PATH<br />
</pre><br />
<br />
In order to make DBus and KAuth actions work properly, create a file named <code>/etc/dbus-1/session-local.conf</code> and add the following text to it:<br />
<br />
<busconfig><br />
<servicedir>/home/[your-user]/kde/usr/share/dbus-1/services</servicedir><br />
<servicedir>/home/[your-user]/kde/usr/share/dbus-1/system-services</servicedir><br />
<includedir>/home/[your-user]/kde/usr/share/dbus-1/system.d/</includedir><br />
</busconfig><br />
<br />
{{Note|<tt>/home/[your-user]/kde/usr</tt> is the default instalation for KDE applications if configured with the default settings for kdesrc-build. If you install kde software in any other place, you will need to edit the url accordingly}}<br />
<br />
After that, you can log out and select your new plasma session in SDDM's session chooser menu (which is located in the bottom-left corner of the screen if you're using the Breeze SDDM theme).<br />
<br />
{{Note |Some linux distros such as Arch Linux have a setting that forbids the usage of DBus scripts from the home folder. If you have problems with your login manager (such as sddm, gdm, lightdm) after creating the session-local.conf file, try to create a symlink for the dbus-1 folder inside of your installation to a place outside home (such as <tt>/opt/kde-dbus-scripts</tt>) and adapt the session-local file.<br />
<br />
<pre><br />
<busconfig><br />
<servicedir>/opt/kde-dbus-scripts/services</servicedir><br />
<servicedir>/opt/kde-dbus-scripts/system-services</servicedir><br />
<includedir>/opt/kde-dbus-scripts/system.d/</includedir><br />
</busconfig><br />
</pre><br />
}}<br />
<br />
==== Plasma Mobile ====<br />
To build the Plasma Mobile environment and its related apps, also build the following:<br />
{{Input|1=<nowiki><br />
kdesrc-build plasma-nano plasma-phone-components plasma-settings plasma-camera marble koko vvave okular plasma-angelfish plasma-samegame mtp-server kaidan peruse calindori index-fm maui-pix qrca keysmith --include-dependencies<br />
</nowiki>}}<br />
<br />
You can run your custom-built Plasma Mobile in a phone-sized window within your existing session like so:<br />
<br />
{{Input|1=<nowiki><br />
export XDG_RUNTIME_DIR=/tmp/<br />
export QT_QPA_PLATFORM=wayland<br />
export QT_QPA_PLATFORMTHEME=KDE<br />
export QT_WAYLAND_DISABLE_WINDOWDECORATION=1<br />
export XDG_CURRENT_DESKTOP=KDE<br />
export KSCREEN_BACKEND=QScreen<br />
export KDE_FULL_SESSION=1<br />
export KDE_SESSION_VERSION=5<br />
export QT_QUICK_CONTROLS_MOBILE=1<br />
export PLASMA_PLATFORM=phone:handheld<br />
export $(dbus-launch)<br />
dbus-run-session kwin_wayland --width 360 --height 720 --xwayland "plasmashell -p org.kde.plasma.phone"<br />
</nowiki>}}<br />
<br />
Plasma Mobile can also be run on a mobile device itself. For information on how to do that, see https://docs.plasma-mobile.org/DevGuide.html#plasma-mobile-device-environment.<br />
<br />
For more information, see https://docs.plasma-mobile.org/DevGuide.html.<br />
<br />
=== Iterating on a single project ===<br />
When you're working on a project and you want to rebuild it to test your changes, you can save a lot of time by only rebuilding that project, rather than the entire stack. For example if you are working on <code>plasma-desktop</code>, you can rebuild only that project rather than everything by running <code>kdesrc-build --no-src --no-include-dependencies plasma-desktop</code>.<br />
<br />
=== How to solve build problems ===<br />
Did one or more modules fail to build (displayed in red font) using <code>kdesrc-build</code>? Here's what to do:<br />
<br />
# Try building the failing module again from scratch using <code>kdesrc-build [failing module] --refresh-build</code><br />
# Make sure that you have all the dependencies for the failing module. Go back to the [[#Download non-KDE dependencies]] section and re-install the non-KDE dependencies. If that doesn't fix the problem, open the log file for the failing module, which <code>kdesrc-build</code> will print the path at the end of its output. Scroll to the bottom of the log file and read the output to see what missing dependency it is complaining about, then find and install the corresponding package using your distro's package manager your distro. If several looks relevant, install them all just to be safe. When you have the necessary dependencies, you can save time and resume from the failing module by adding <code>--resume-from [the name of the module that failed]</code> to your <code>kdesrc-build</code> command.<br />
# Check the [https://build.kde.org/view/Failing/ list of currently broken modules] on the KDE build server. If it's broken there, then it's not your fault. :)<br />
# Ask for help in the the [https://webchat.kde.org/#/room/#kde-devel:kde.org #kde-devel] channel on [[Matrix]] or freenode [[Internet Relay Chat | IRC]]. See [[Get Involved/development#Communicate with the team]]<br />
<br />
== Choose what to do ==<br />
Now that you can compile and deploy custom versions of KDE software, you can open your editor and start hacking on the source code! The code for the version of Dolphin that you built earlier is located at <code>~/kde/src/dolphin/</code>; other projects you build with <code>kdesrc-build</code> will live in similar locations.<br />
<br />
A good place to start is with a small bug or feature in an existing piece of software that affects you personally ("scratch your own itch"). Get in touch with the existing developers (see [[#Communicate with the team|Communicate with the team]], below) and they can help you out, by pointing you to the right place in the code and giving advice about how to tackle the problem.<br />
<br />
Try not to start by proposing or working on major features or significant design changes. These can be controversial, and the smoothest way to get going is by working on relatively non-controversial bugfixes. Start slowly and build trust!<br />
<br />
Here are some other ideas for starting points:<br />
<br />
* Improve awkwardly-worded messages and labels that are written in English. This is a great way for non-programmers to contribute! If you can compile software and have a good grasp of English, you can make a big difference here.<br />
* Work on [https://bugs.kde.org/buglist.cgi?bug_status=UNCONFIRMED&bug_status=CONFIRMED&bug_status=NEW&bug_status=ASSIGNED&bug_status=REOPENED&keywords=junior-jobs&list_id=1340815 Junior Jobs], which are small tasks that are suitable for beginners (both bugs and features).<br />
* Work on [https://bugs.kde.org/buglist.cgi?bug_status=UNCONFIRMED&bug_status=CONFIRMED&bug_status=ASSIGNED&bug_status=REOPENED&keywords=usability&keywords_type=allwords&list_id=1493316&order=product%2Cchangeddate%20DESC%2Cbug_status%20DESC%2Cresolution%2Cpriority%2Cassigned_to%2Cbug_id&query_format=advanced Bugs related to] KDE's [[Goals/Usability_%26_Productivity | Usability & Productivity initiative]], many of which are small and easy.<br />
* [http://www.englishbreakfastnetwork.org/ The English Breakfast Network] searches out simple, common issues in code that should be fixed, and going through the problems on there can provide a good overview of the code.<br />
<br />
== Test your changes ==<br />
Once you've made some changes, make sure the project still compiles and installs, and make sure the changes have the desired effect when you run the software. Then it's time to run the project's unit tests:<br />
<br />
{{Input|1=<nowiki><br />
cd ~/kde/build/dolphin/<br />
source ./prefix.sh<br />
ctest --output-on-failure<br />
</nowiki>}}<br />
<br />
If any test fails, that needs to be investigated and fixed before you can proceed. Once the tests pass, then run the software again to make sure it still behaves properly. If it doesn't, then go back and work on your patch some more, then re-compile and re-deploy, and test again, until the program does what you'd like it to do and all tests pass.<br />
<br />
== Submit a Merge Request ==<br />
Once you're happy with your patch and have verified that it does what you want, it's time to submit your changes for review!<br />
<br />
KDE uses [https://invent.kde.org GitLab] for merge request submission and review. [[Infrastructure/GitLab#Submitting_a_merge_request|Learn how to submit a Merge Request with GitLab]].<br />
<br />
== Communicate with the team ==<br />
There are several ways to get in touch with KDE developers, either generally or for a specific project. The most important communications channels are:<br />
* The [https://webchat.kde.org/#/room/#kde-devel:kde.org #kde-devel] channel on [[Matrix]] or the freenode [[Internet Relay Chat | IRC]], which is where KDE developers chat in real time about their work.<br />
* The [https://mail.kde.org/mailman/listinfo/kde-devel kde-devel mailing list] is the primary development mailing list. [http://kde.org/support/#mailinglists Learn more about mailing lists].<br />
<br />
These are general KDE development communication channels, and you may get directed to a more appropriate place for the project you are interested in. There is a [http://www.kde.org/mailinglists/ list of mailing lists] if you want to find a mailing list for a specific team directly. Many teams have their own real-time chat channels, too.<br />
<br />
You can also try looking for the team's section on the [[Main Page]] of this wiki. Many teams have information there for new contributors.<br />
<br />
== Next steps ==<br />
Sharpen your skills by going through the [https://techbase.kde.org/Development/Tutorials KDE development tutorials]. And try out [http://www.kdevelop.org KDevelop], the KDE IDE.<br />
<br />
After you have had several drama-free patches accepted, a KDE developer is likely to suggest you get a [[Infrastructure/Get a Developer Account|Developer account]], which will allow you to commit directly to KDE projects. With very few limits on where you can commit, you will be expected to act responsibly. At this point, congratulations! You are officially a KDE developer!<br />
<br />
You may also want to set up a more customized development environment. See [[Guidelines and HOWTOs/Build from source]].<br />
<br />
== Best practices & other useful information==<br />
* [[Get Involved/Design/Lessons Learned|Lessons learned over time regarding the development of user-facing software]]<br />
* [[Guidelines_and_HOWTOs/Debugging|Debugging]]<br />
* [[Guidelines and HOWTOs/UnitTests|Unit testing]]<br />
* [[Guidelines and HOWTOs/Code_Checking| Validating code]]<br />
* [[Guidelines and HOWTOs/API Documentation|Writing API documentation]] (related: https://api.kde.org).<br />
* [[Guidelines and HOWTOs/Licensing|Correctly state license information]]<br />
* [[Guidelines_and_HOWTOs/Wayland_Porting_Notes|Writing Wayland-friendly code]]<br />
* [[Frameworks/Porting_Notes|Porting from KDE 4 to Frameworks 5]]<br />
* [[Guidelines_and_HOWTOs/Making_apps_run_uninstalled|Running applications and their unit tests without first installing them]]<br />
* [https://community.kde.org/Infrastructure/GitLab#Testing_someone_else.27s_Merge_Request How to review merge requests]<br />
* [https://github.com/Wenzel/docker-kdesrc-build How to build with Docker]</div>Gjditchfieldhttps://community.kde.org/index.php?title=Guidelines_and_HOWTOs/Bug_triaging&diff=91010Guidelines and HOWTOs/Bug triaging2021-01-28T20:21:20Z<p>Gjditchfield: /* Bug editing permissions */</p>
<hr />
<div>[[File:Mascot konqi-support-bughunt.png|thumbnail|right|Help [[Konqi]] catch some bugs!]]<br />
The '''KDE Bugsquad''' keeps track of incoming bugs in KDE software and goes through old bugs. We verify that a bug exists, whether it is reproducible, and that the reporter has given enough information. '''Our goal is to save developers from doing this, which helps them fix bugs more quickly and do more work on KDE software.'''<br />
<br />
Getting involved in the Bugsquad is a good place to start. An existing member will help you out and mentor you. One of the great things about the Bugsquad and bug triaging is that '''you do not need any programming knowledge!''' That said, experience has shown that members of this team learn a lot about KDE and Qt programming during the course of dealing with bug reports, and many move on to developing the software itself. If you are just starting to learn programming, bug triaging is a great way to gain familiarity with the components and give practical support to the KDE community.<br />
<br />
For live chat, we have the [https://webchat.kde.org/#/room/#kde-bugs:kde.org Bugsquad Matrix group] which is bridged to the [irc://chat.freenode.net/kde-bugs #kde-bugs IRC channel]. Please feel free to stop by!<br />
<br />
To join the Bugsquad, [https://phabricator.kde.org/project/update/290/join/ become a member] of our [https://phabricator.kde.org/project/profile/290/ project on Phabricator]. We organize Bug Days, where we focus on a specific product, with the goal of reviewing all open bugs by the end of the day. These meetings occur primarily on #kde-bugs and are disclosed over the [https://mail.kde.org/cgi-bin/mailman/listinfo/bugsquad Bugsquad mailing list].<br />
<br />
We have a [https://phabricator.kde.org/calendar/query/Tp0Fc0J7sB6v/ Bugsquad calendar] you can view or export as ICS to your calendar of choice. The calendar lists all of the upcoming Bug Days and what product we will be focusing on. These days are great times to get involved and ask questions!<br />
<br />
= General considerations =<br />
* '''Be polite.''' Bug triagers are the face of KDE. Be nice to bug reporters, even if they aren't nice to you! You have an opportunity to showcase the best of the KDE community, and turn skeptics into fans. Being nice is more impactful than being right. <br />
* '''Explain your decisions.''' It can be very frustrating for a bug reporter if his/her report is suddenly closed without any reason. Write why you think that your action is the correct one, and don't establish your opinion as the all-ruling fact. <br />
* '''Don't be afraid to make mistakes.''' Everybody makes mistakes, especially when starting out with bug triaging.<br />
* KDE uses the Bugzilla bug tracker system. If you are not familiar with it, you may find this [[Bugsquad/Quick Introduction to Bugzilla|quick Introduction to Bugzilla]] useful.<br />
* The [https://addons.mozilla.org/en-US/firefox/addon/bugzillajs/ BugzillaJS add-on for Firefox] is highly recommended, as it greatly improves the user experience.<br />
<br />
= Decide what to work on =<br />
=== All bugs filed recently ===<br />
* All the bugs ('''any type''') reported [https://bugs.kde.org/buglist.cgi?bug_status=NEW&bug_status=ASSIGNED&bug_status=REOPENED&bug_status=UNCONFIRMED&bugidtype=include&chfield=%5BBug+creation%5D&chfieldfrom=1d&chfieldto=Now&bug_file_loc=&cmdtype=doit today] or the [https://bugs.kde.org/buglist.cgi?bug_status=NEW&bug_status=ASSIGNED&bug_status=REOPENED&bug_status=UNCONFIRMED&bugidtype=include&chfield=%5BBug+creation%5D&chfieldfrom=7d&chfieldto=Now&bug_file_loc=&cmdtype=doit last week]<br />
* All the '''crashes''' reported [https://bugs.kde.org/buglist.cgi?query_format=advanced&short_desc_type=allwordssubstr&short_desc=&long_desc_type=allwordssubstr&long_desc=&bug_file_loc_type=allwordssubstr&bug_file_loc=&keywords_type=allwords&keywords=&bug_status=UNCONFIRMED&bug_status=NEW&bug_status=ASSIGNED&bug_status=REOPENED&bug_severity=crash&emailtype1=substring&email1=&emailtype2=substring&email2=&bugidtype=include&bug_id=&votes=&chfieldfrom=1d&chfieldto=Now&chfield=%5BBug+creation%5D&chfieldvalue=&cmdtype=doit&order=Reuse+same+sort+as+last+time&field0-0-0=noop&type0-0-0=noop&value0-0-0= today] or the [https://bugs.kde.org/buglist.cgi?query_format=advanced&short_desc_type=allwordssubstr&short_desc=&long_desc_type=allwordssubstr&long_desc=&bug_file_loc_type=allwordssubstr&bug_file_loc=&keywords_type=allwords&keywords=&bug_status=UNCONFIRMED&bug_status=NEW&bug_status=ASSIGNED&bug_status=REOPENED&bug_severity=crash&emailtype1=substring&email1=&emailtype2=substring&email2=&bugidtype=include&bug_id=&votes=&chfieldfrom=7d&chfieldto=Now&chfield=%5BBug+creation%5D&chfieldvalue=&cmdtype=doit&order=Reuse+same+sort+as+last+time&field0-0-0=noop&type0-0-0=noop&value0-0-0= last week]<br />
* All the '''normal bugs''' reported [https://bugs.kde.org/buglist.cgi?query_format=advanced&short_desc_type=allwordssubstr&short_desc=&long_desc_type=allwordssubstr&long_desc=&bug_file_loc_type=allwordssubstr&bug_file_loc=&keywords_type=allwords&keywords=&bug_status=UNCONFIRMED&bug_status=NEW&bug_status=ASSIGNED&bug_status=REOPENED&bug_severity=critical&bug_severity=grave&bug_severity=major&bug_severity=normal&bug_severity=minor&emailtype1=substring&email1=&emailtype2=substring&email2=&bugidtype=include&bug_id=&votes=&chfieldfrom=1d&chfieldto=Now&chfield=%5BBug+creation%5D&chfieldvalue=&cmdtype=doit&order=Reuse+same+sort+as+last+time&field0-0-0=noop&type0-0-0=noop&value0-0-0= today] or the [https://bugs.kde.org/buglist.cgi?query_format=advanced&short_desc_type=allwordssubstr&short_desc=&long_desc_type=allwordssubstr&long_desc=&bug_file_loc_type=allwordssubstr&bug_file_loc=&keywords_type=allwords&keywords=&bug_status=UNCONFIRMED&bug_status=NEW&bug_status=ASSIGNED&bug_status=REOPENED&bug_severity=critical&bug_severity=grave&bug_severity=major&bug_severity=normal&bug_severity=minor&emailtype1=substring&email1=&emailtype2=substring&email2=&bugidtype=include&bug_id=&votes=&chfieldfrom=7d&chfieldto=Now&chfield=%5BBug+creation%5D&chfieldvalue=&cmdtype=doit&order=Reuse+same+sort+as+last+time&field0-0-0=noop&type0-0-0=noop&value0-0-0= last week]<br />
* All the '''feature requests''' reported [https://bugs.kde.org/buglist.cgi?query_format=advanced&short_desc_type=allwordssubstr&short_desc=&long_desc_type=allwordssubstr&long_desc=&bug_file_loc_type=allwordssubstr&bug_file_loc=&keywords_type=allwords&keywords=&bug_status=UNCONFIRMED&bug_status=NEW&bug_status=ASSIGNED&bug_status=REOPENED&bug_severity=wishlist&emailtype1=substring&email1=&emailtype2=substring&email2=&bugidtype=include&bug_id=&votes=&chfieldfrom=1d&chfieldto=Now&chfield=%5BBug+creation%5D&chfieldvalue=&cmdtype=doit&order=Reuse+same+sort+as+last+time&field0-0-0=noop&type0-0-0=noop&value0-0-0= today] or the [https://bugs.kde.org/buglist.cgi?query_format=advanced&short_desc_type=allwordssubstr&short_desc=&long_desc_type=allwordssubstr&long_desc=&bug_file_loc_type=allwordssubstr&bug_file_loc=&keywords_type=allwords&keywords=&bug_status=UNCONFIRMED&bug_status=NEW&bug_status=ASSIGNED&bug_status=REOPENED&bug_severity=wishlist&emailtype1=substring&email1=&emailtype2=substring&email2=&bugidtype=include&bug_id=&votes=&chfieldfrom=7d&chfieldto=Now&chfield=%5BBug+creation%5D&chfieldvalue=&cmdtype=doit&order=Reuse+same+sort+as+last+time&field0-0-0=noop&type0-0-0=noop&value0-0-0= last week]<br />
<br />
=== All bug reports of a single product ===<br />
You'll want to use the Advanced search for this: https://bugs.kde.org/query.cgi<br />
<br />
You can see which products are most in need of bug triaging here, with links to their bug lists: https://bugs.kde.org/weekly-bug-summary.cgi?tops=50&days=7<br />
<br />
Here are some common and popular KDE products in need of bug triaging:<br />
* Plasma [https://bugs.kde.org/buglist.cgi?bug_severity=critical&bug_severity=grave&bug_severity=major&bug_severity=crash&bug_severity=normal&bug_severity=minor&bug_status=UNCONFIRMED&bug_status=CONFIRMED&bug_status=ASSIGNED&bug_status=REOPENED&list_id=1506503&product=plasmashell&query_format=advanced bugs] / [https://bugs.kde.org/buglist.cgi?bug_severity=wishlist&bug_status=UNCONFIRMED&bug_status=CONFIRMED&bug_status=ASSIGNED&bug_status=REOPENED&list_id=1506504&product=plasmashell&query_format=advanced feature requests]<br />
* System Settings [https://bugs.kde.org/buglist.cgi?bug_severity=critical&bug_severity=grave&bug_severity=major&bug_severity=crash&bug_severity=normal&bug_severity=minor&bug_status=UNCONFIRMED&bug_status=CONFIRMED&bug_status=ASSIGNED&bug_status=REOPENED&list_id=1506503&product=systemsettings&query_format=advanced bugs] / [https://bugs.kde.org/buglist.cgi?bug_severity=wishlist&bug_status=UNCONFIRMED&bug_status=CONFIRMED&bug_status=ASSIGNED&bug_status=REOPENED&list_id=1506504&product=systemsettings&query_format=advanced feature requests]<br />
* Dolphin [https://bugs.kde.org/buglist.cgi?bug_severity=critical&bug_severity=grave&bug_severity=major&bug_severity=crash&bug_severity=normal&bug_severity=minor&bug_status=UNCONFIRMED&bug_status=CONFIRMED&bug_status=ASSIGNED&bug_status=REOPENED&list_id=1506503&product=dolphin&query_format=advanced bugs] / [https://bugs.kde.org/buglist.cgi?bug_severity=wishlist&bug_status=UNCONFIRMED&bug_status=CONFIRMED&bug_status=ASSIGNED&bug_status=REOPENED&list_id=1506504&product=dolphin&query_format=advanced feature requests]<br />
* Okular [https://bugs.kde.org/buglist.cgi?bug_severity=critical&bug_severity=grave&bug_severity=major&bug_severity=crash&bug_severity=normal&bug_severity=minor&bug_status=UNCONFIRMED&bug_status=CONFIRMED&bug_status=ASSIGNED&bug_status=REOPENED&list_id=1506503&product=okular&query_format=advanced bugs] / [https://bugs.kde.org/buglist.cgi?bug_severity=wishlist&bug_status=UNCONFIRMED&bug_status=CONFIRMED&bug_status=ASSIGNED&bug_status=REOPENED&list_id=1506504&product=okular&query_format=advanced feature requests]<br />
* Gwenview [https://bugs.kde.org/buglist.cgi?bug_severity=critical&bug_severity=grave&bug_severity=major&bug_severity=crash&bug_severity=normal&bug_severity=minor&bug_status=UNCONFIRMED&bug_status=CONFIRMED&bug_status=ASSIGNED&bug_status=REOPENED&list_id=1506503&product=gwenview&query_format=advanced bugs] / [https://bugs.kde.org/buglist.cgi?bug_severity=wishlist&bug_status=UNCONFIRMED&bug_status=CONFIRMED&bug_status=ASSIGNED&bug_status=REOPENED&list_id=1506504&product=gwenview&query_format=advanced feature requests]<br />
* Konsole [https://bugs.kde.org/buglist.cgi?bug_severity=critical&bug_severity=grave&bug_severity=major&bug_severity=crash&bug_severity=normal&bug_severity=minor&bug_status=UNCONFIRMED&bug_status=CONFIRMED&bug_status=ASSIGNED&bug_status=REOPENED&list_id=1506503&product=konsole&query_format=advanced bugs] / [https://bugs.kde.org/buglist.cgi?bug_severity=wishlist&bug_status=UNCONFIRMED&bug_status=CONFIRMED&bug_status=ASSIGNED&bug_status=REOPENED&list_id=1506504&product=konsole&query_format=advanced feature requests]<br />
* Kate & KTextEditor framework [https://bugs.kde.org/buglist.cgi?bug_severity=critical&bug_severity=grave&bug_severity=major&bug_severity=crash&bug_severity=normal&bug_severity=minor&bug_status=UNCONFIRMED&bug_status=CONFIRMED&bug_status=ASSIGNED&bug_status=REOPENED&list_id=1506507&product=frameworks-ktexteditor&product=kate&query_format=advanced bugs] / [https://bugs.kde.org/buglist.cgi?bug_severity=wishlist&bug_status=UNCONFIRMED&bug_status=CONFIRMED&bug_status=ASSIGNED&bug_status=REOPENED&list_id=1506506&product=frameworks-ktexteditor&product=kate&query_format=advanced feature requests]<br />
* KIO framework [https://bugs.kde.org/buglist.cgi?bug_severity=critical&bug_severity=grave&bug_severity=major&bug_severity=crash&bug_severity=normal&bug_severity=minor&bug_status=UNCONFIRMED&bug_status=CONFIRMED&bug_status=ASSIGNED&bug_status=REOPENED&list_id=1506508&product=frameworks-kio&product=kio&product=kio-extras&query_format=advanced bugs] / [https://bugs.kde.org/buglist.cgi?bug_severity=wishlist&bug_status=UNCONFIRMED&bug_status=CONFIRMED&bug_status=ASSIGNED&bug_status=REOPENED&list_id=1506510&product=frameworks-kio&product=kio&product=kio-extras&query_format=advanced feature requests]<br />
<br />
= Bug editing permissions =<br />
All regular Bugzilla users can perform standard editing functions. You can change a number of fields, including the product, component, version, platform, status, and more. You are restricted from only a few abilities, such as bulk editing, changing the Priority and Severity fields (Importance), or re-opening CLOSED bugs. After getting comfortable with the KDE Bugzilla process, you can request "contributor" privileges from [[Sysadmin]], which will allow you to perform those actions.<br />
<br />
= What to do with bug reports =<br />
Now that you have a list of bug reports, pick one and start working! Here is the optimal bug triaging workflow:<br />
<br />
[[File:DarioAndres_GuideToBugTriaging_Workflow.png]]<br />
<br />
{{Note|If at any point you aren't sure how to proceed, move onto the next bug or ask a KDE developer or another more experienced contributor.}}<br />
<br />
== Identify duplicates ==<br />
As KDE has so many users, we get a lot of reports about bugs which have already been reported (duplicates). Before putting any effort in the current report, check for an existing report. If you find a pre-existing bug report describing the same issue, mark this one as a duplicate of it.<br />
<br />
Please see [[Guidelines_and_HOWTOs/Bug_triaging/Identifying_duplicates|the article on identifying duplicates]].<br />
<br />
== Identify bugs caused by external issues (UPSTREAM/DOWNSTREAM) ==<br />
Not all real bugs affecting KDE software are actually caused by a fault in KDE software. The issue may be "upstream" or "downstream" of KDE.<br />
<br />
"Upstream" means that the problem lies in an underlying technology that KDE software uses (e.g. Qt, X11, the Linux kernel). Others who make use of KDE software (e.g Linux distributions, 3rd party Plasma plugins) are '''downstream''' of KDE.<br />
<br />
Examples of Upstream issues:<br />
* KDE app experiences a new behavioral issue after upgrading to a newer version of Qt<br />
* KDE app crashes in QtQuick<br />
* KDE app experiences an issue, but only when using the proprietary NVIDIA driver<br />
<br />
<br />
Examples of Downstream issues:<br />
<br />
* A KDE app crashes because a dependent package is not automatically installed by the user's distro<br />
* Fonts are really hard to read, but only in certain distros<br />
* Graphical corruption with a Plasma theme downloaded from store.kde.org<br />
<br />
== Ask for any missing information ==<br />
Now that you know that the bug report is unique and that it is not an external issue, you need to check that all the needed information is there.<br />
<br />
* If the reporter has not entered the Platform correctly (i.e., it is "unspecified/Linux"), then ask which platform/distro they are using. This can help determine if there are distro/downstream bugs. Mark the bug as NEEDSINFO/WAITINGFORINFO.<br />
* If the description is not written in English and you cannot understand it, you may want to ask other KDE contributors to translate it for you. Alternatively, you can ask the reporter to use some online translation system.<br />
* If the reporter has not indicated what versions of KDE Plasma, Qt, and KDE Frameworks they are using, ask them to provide that information. ([[Get Involved/Bug Reporting#Software_versions]])<br />
* If the explanation is not clear enough, ask for clarification and concrete steps to reproduce. ([[Get Involved/Bug Reporting#Steps to Reproduce]])<br />
* If the issue is hard to picture without a visual aid and there are no screenshots or a screen recording attached, ask for one. ([[Get Involved/Bug Reporting#Screenshots and screen recordings]])<br />
* If the bug report is about a crash and the backtrace information is incomplete, ask the user to install the missing debug symbols package, reproduce the bug, and generate a new backtrace. ([[Guidelines and HOWTOs/Debugging/How to create useful crash reports]])<br />
<br />
<br />
{{Note| After asking for further information, mark the report as "NEEDSINFO" with resolution "WAITINGFORINFO" (or resolution "BACKTRACE" if you are waiting for a complete backtrace).}}<br />
<br />
== Reproduce the bug ==<br />
At this point, the bug report enough is complete enough that you should use the information provided by the reporter and try to make the bug appear on your own system.<br />
<br />
{{Note|It is important to have up-to-date KDE components installed to test bugs. Only try to reproduce bugs using the latest versions of KDE software, or even development versions.}}<br />
<br />
{{Warning|Testing bug reports may modify/alter your own desktop configuration; also, to try to reproduce some bugs you may need a clean pristine (or slightly modified) environment. We recommend you to perform tests on a separate KDE installation or a clean user. There is also a way to start KDE applications with a clean configuration, even under your current configuration (setting the KDEDIR environment variable at run-time to an empty directory).}}<br />
<br />
You may want to use this reference text to setup your testing environment: [http://forum.kde.org/viewtopic.php?f=9&t=84475 Preparing a testing environment]<br />
<br />
If you can't reproduce with any scenario mentioned in the comments, you may want to try other related situations. Write down any newly discovered steps to reproduce.<br />
<br />
=== If you cannot reproduce the bug under any circumstances ===<br />
Write down the steps you performed. Ask the reporter if your steps missed something, or if anyone notices any other strange or non-default situation or configuration which may be related. Also ask the reporter for more information and mark it NEEDSINFO/WAITINGFORINFO.<br />
<br />
Hopefully, you will get feedback from the reporters and you could gather more information to try to reproduce the bug or close the report as RESOLVED/WORKSFORME (or FIXED) after a short wait.<br />
<br />
=== If you can reproduce the bug ===<br />
If you had to combine several steps to make your own "recipe" to reproduce, write them down. This kind of information is useful for the developers. If you had to use custom input data (text, or a file), you may want to attach it to the bug report. Don't forget to mention the environment and circumstances under which you can reproduce the bug. <br />
<br />
== Set Bugzilla fields ==<br />
Often a bug report isn't properly categorized, or lacks some information in the Bugzilla fields (which are useful for sorting and filtering). If a field isn't mentioned below, you don't need to change it. All users have permission to change most Bugzilla fields.<br />
<br />
=== Product ===<br />
Many bug reports are reported against the wrong product. This may happen because the original reporter didn't know which application/library the bug belongs to. For example, many bugs filed to Dolphin are actually about KIO (the I/O framework) or Baloo (the search frameworks). Remember to check the [[Guidelines_and_HOWTOs/Bug_triaging/Identifying_duplicates#List_of_related_KDE_technologies|KDE related technologies list]].<br />
<br />
{{Warning|Only reassign if you are sure the bug is in the wrong product.}}<br />
=== Component ===<br />
As with the Product field, many bug reports are reported against the wrong component or none at all. Set the Component if you are able to determine a more appropriate one than the bug's current assignment.<br />
<br />
=== Version===<br />
If the report has an application version, you need to set the version in the Bugzilla field. Ideally the version field should reflect the latest version the bug is reproducible with. If the version is missing from the list, please contact the software maintainer or the KDE Bugzilla admins to add the version.<br />
<br />
=== Platform ===<br />
This field is only important if the bug is related to one distribution or a specific system (the majority of bug reports are common to most platforms).<br />
<br />
=== Severity ===<br />
Mark the bug's severity. Some hints about the various severity options:<br />
* '''Critical''': A widespread, easily reproducible issue that causes data loss.<br />
* '''Grave''': The software is basically unusable, with no workaround.<br />
* '''Major''': A major feature is broken, and the workaround (if any) is painful and difficult.<br />
* '''Crash''': The software crashes or hangs.<br />
* '''Normal''': It's a bug that should be fixed, possibly with a reasonable workaround.<br />
* '''Minor''': Minor cosmetic issue or loss of function with an easy workaround.<br />
* '''Wishlist''': Request for a new feature or enhancement.<br />
<br />
=== Keywords ===<br />
If the bug is a regression in features or functionality, add the "regression keyword" and change the importance to HI to alert developers.<br />
<br />
If the bug is Wayland-related, add the "wayland" keyword.<br />
<br />
If the bug involves a poor user interface or demands a tedious workflow, tag it with the "usability" keyword.<br />
<br />
If the bug seems like it would be really easy to fix, add the "junior-jobs" keyword. Examples of issues that would be easy to fix:<br />
* Inaccurate description or text label<br />
* Visual layout inconsistencies<br />
* Pixellated icons when an app is run in HiDPI mode<br />
* User interface element is the wrong color when using Breeze Dark<br />
<br />
New code contributors often start with "junior-jobs" bugs, so ensuring that there is a steady supply of bugs tagged with this keyword helps them get started with small, manageable tasks.<br />
<br />
<br />
=== Status ===<br />
If you can reproduce the issue, ''and you are confident that you have set all fields correctly'', set the bug report's status to CONFIRMED. This only applies to bugs; feature requests ("wishlist" items) don't need to be confirmed, since they're not bugs.<br />
<br />
== Rename the bug report ==<br />
The bug report's summary might not accurately represent the bug, especially after you have triaged the bug and found the root cause or determined it to be another issue. You may want to update the summary to contain enough information to identify the issue properly. A good summary:<br />
<br />
* Is readable as a grammatically correct English sentence<br />
* Contains a brief explanation of the root cause (if it was found)<br />
* Includes some of the symptoms people are experiencing<br />
* Includes <tt>ClassName::FunctionName</tt> pairs if the bug describes a crash<br />
* Isn't too long<br />
<br />
= Special circumstances =<br />
=== Bug describes multiple issues ===<br />
You must close the bug report as RESOLVED/NOT A BUG with a '''humane''' explanation that bug reports can only contain a single issue. ([[Get Involved/Bug Reporting#One issue per bug report]])<br />
<br />
=== Bug includes a patch ===<br />
If the bug reporter or someone else included a patch, ask them to submit it using GitLab, and remind them about [[Get_Involved/Issue_Reporting#Submit_patches_using_GitLab.2C_not_the_issue_tracker]].<br />
<br />
=== Reporter seems very technically knowledgeable ===<br />
Sometimes a very technically knowledgeable bug reporter will correctly identify the source of the issue, and maybe even the exact line of code that's causing the problem. Encourage them to submit a patch, and point them to https://community.kde.org/Get_Involved/development.<br />
<br />
=== Same person reports a lot of bugs ===<br />
Anybody who reports a lot of KDE bugs--especially if they are high quality bugs--is quite likely a committed KDE user who is a good candidate for becoming a more involved contributor over time. '''Stumbling on someone like this in the bug tracker is like striking gold.''' Take every opportunity to develop a relationship with this person and try to guide them through the KDE "[[Get Involved|get involved]]" pipeline. Oftentimes people who submit a lot of bugs move on to bug triaging and then later development.<br />
<br />
= Policies =<br />
* Bugs placed into NEEDSINFO status will receive a reminder if the ticket:<br />
** Is at least 15 days old<br />
** Has not received any comment within 15 days<br />
* If a bug remains in NEEDSINFO for another 15 days with no comment, it will be closed as RESOLVED > WORKSFORME.<br />
* If a bug remains in NEEDSINFO with a comment provided within less than 15 days, no action will be taken (as it does not meet the above criteria).</div>Gjditchfieldhttps://community.kde.org/index.php?title=Guidelines_and_HOWTOs/CMake&diff=90547Guidelines and HOWTOs/CMake2020-11-10T21:08:56Z<p>Gjditchfield: The wiki moved.</p>
<hr />
<div>Most KDE software uses [http://www.cmake.org CMake] as a buildsystem. This page is the starting point for CMake-related documentation for KDE software.<br />
<br />
You should be aware that there are two main development platforms that KDE software is based on: the older kdelibs4, and its replacement, the [http://api.kde.org/frameworks-api/frameworks5-apidocs/ KDE Frameworks]. Quite a bit of KDE software is in the process of transitioning from kdelibs4 to KDE Frameworks, and the way CMake is used is slightly different between the two.<br />
<br />
<br />
This page contains some tutorials to help you get started on building a CMake-based buildsystem. This is the recommended way of building your software if you use KDE technologies, such as KDE Frameworks.<br />
<br />
==Beginners==<br />
<br />
* [[/FirstProject|A first CMake project]]: if you've never even looked at CMake code before, start here.<br />
* [[/Frameworks|Using a framework]]: introduces you to using a KDE Framework - finding the package and linking your program against it.<br />
<br />
==Intermediate==<br />
<br />
* [[/Library|Creating a library]]: demonstrates best practices when creating a library with a CMake-based buildsystem (such as a KDE Framework).<br />
<br />
== Building with CMake in short ==<br />
<br />
If you just want to build a CMake-based project on a UNIX system, the following recipe will do that:<br />
<br />
<syntaxhighlight lang="bash"><br />
cd /path/to/project/source<br />
mkdir build<br />
cd build<br />
cmake -DCMAKE_INSTALL_PREFIX=/where/to/install/to ..<br />
make<br />
make install<br />
</syntaxhighlight><br />
<br />
Don't forget to replace both paths. If you want an explanation of what this command does and how to make CMake behave differently, or you want to build on Windows or OS/X, see [[/Building | the building with CMake]] chapter.<br />
<br />
==Useful resources==<br />
<br />
[https://cmake.org/resources/ CMake Resources]<br />
<br />
You may want to check out the [https://gitlab.kitware.com/cmake/community/-/wikis/home CMake wiki], although beware that it contains quite a bit of out-of-date information.<br />
<br />
===Reference documentation===<br />
* [http://www.cmake.org/HTML/Documentation.html CMake's own documentation]<br />
* [http://api.kde.org/ecm Extra CMake Modules documentation]<br />
<br />
===Upgrading from KDELibs4===<br />
See https://techbase.kde.org/ECM5/IncompatibleChangesKDELibs4ToECM.<br />
<br />
===FAQs===<br />
<br />
* [[/FAQs|KDE's CMake FAQs]]<br />
* [https://gitlab.kitware.com/cmake/community/-/wikis/FAQ CMake FAQ on the CMake wiki]<br />
<br />
===Mailing lists===<br />
<br />
;[http://mail.kde.org/pipermail/kde-buildsystem/ The kde-buildsystem mailing list]<br />
:Discussion of CMake in the KDE community, as well as development of Extra CMake Modules.</div>Gjditchfieldhttps://community.kde.org/index.php?title=Guidelines_and_HOWTOs/CMake&diff=90546Guidelines and HOWTOs/CMake2020-11-10T20:56:28Z<p>Gjditchfield: The CMake mailing lists have been shut down.</p>
<hr />
<div>Most KDE software uses [http://www.cmake.org CMake] as a buildsystem. This page is the starting point for CMake-related documentation for KDE software.<br />
<br />
You should be aware that there are two main development platforms that KDE software is based on: the older kdelibs4, and its replacement, the [http://api.kde.org/frameworks-api/frameworks5-apidocs/ KDE Frameworks]. Quite a bit of KDE software is in the process of transitioning from kdelibs4 to KDE Frameworks, and the way CMake is used is slightly different between the two.<br />
<br />
<br />
This page contains some tutorials to help you get started on building a CMake-based buildsystem. This is the recommended way of building your software if you use KDE technologies, such as KDE Frameworks.<br />
<br />
==Beginners==<br />
<br />
* [[/FirstProject|A first CMake project]]: if you've never even looked at CMake code before, start here.<br />
* [[/Frameworks|Using a framework]]: introduces you to using a KDE Framework - finding the package and linking your program against it.<br />
<br />
==Intermediate==<br />
<br />
* [[/Library|Creating a library]]: demonstrates best practices when creating a library with a CMake-based buildsystem (such as a KDE Framework).<br />
<br />
== Building with CMake in short ==<br />
<br />
If you just want to build a CMake-based project on a UNIX system, the following recipe will do that:<br />
<br />
<syntaxhighlight lang="bash"><br />
cd /path/to/project/source<br />
mkdir build<br />
cd build<br />
cmake -DCMAKE_INSTALL_PREFIX=/where/to/install/to ..<br />
make<br />
make install<br />
</syntaxhighlight><br />
<br />
Don't forget to replace both paths. If you want an explanation of what this command does and how to make CMake behave differently, or you want to build on Windows or OS/X, see [[/Building | the building with CMake]] chapter.<br />
<br />
==Useful resources==<br />
<br />
You may want to check out the [http://www.cmake.org/Wiki/CMake CMake wiki], although beware that it contains quite a bit of out-of-date information.<br />
<br />
===Reference documentation===<br />
* [http://www.cmake.org/HTML/Documentation.html CMake's own documentation]<br />
* [http://api.kde.org/ecm Extra CMake Modules documentation]<br />
<br />
===Upgrading from KDELibs4===<br />
See https://techbase.kde.org/ECM5/IncompatibleChangesKDELibs4ToECM.<br />
<br />
===FAQs===<br />
<br />
* [[/FAQs|KDE's CMake FAQs]]<br />
* [http://www.cmake.org/Wiki/CMake_FAQ CMake FAQ on the CMake wiki]<br />
<br />
===Mailing lists===<br />
<br />
;[http://mail.kde.org/pipermail/kde-buildsystem/ The kde-buildsystem mailing list]<br />
:Discussion of CMake in the KDE community, as well as development of Extra CMake Modules.</div>Gjditchfieldhttps://community.kde.org/index.php?title=Guidelines_and_HOWTOs/UnitTests&diff=90545Guidelines and HOWTOs/UnitTests2020-11-10T20:29:28Z<p>Gjditchfield: Quick notes on autotests that need Akonadi</p>
<hr />
<div>: '''Author:''' Brad Hards, Sigma Bravo Pty Limited<br />
<br />
== Abstract ==<br />
<br />
This article provides guidance on writing unittests for software based on Qt<br />
and KDE frameworks. It uses the [https://doc.qt.io/qt-5/qttest-index.html QtTestLib framework] provided starting with Qt 4.1. It provides an introduction to the ideas behind unit testing, tutorial material on the [https://doc.qt.io/qt-5/qttest-index.html QtTestLib framework], and suggestions for getting the most value for your effort.<br />
<br />
== About Unit Testing ==<br />
<br />
A unit test is a test that checks the functionality, behaviour and correctness of a single software component. In Qt code unit tests are almost always used to test a single C++ class (although testing a macro or C function is also possible).<br />
<br />
Unit tests are a key part of Test Driven Development, however they are useful for all software development processes. It is not essential that all of the code is covered by unit tests (although that is obviously very desirable!). Even a single test is a useful step to improving code quality.<br />
<br />
Note that unit tests are dynamic tests (i.e. they run, using the compiled code) rather than static analysis tests (which operate on the source or some intermediate representation).<br />
<br />
Even if they don't call them "unit tests", most programmers have written some "throwaway" code that they use to check an implementation. If that code was cleaned up a little, and built into the development system, then it could be used over and over to check that the implementation is still OK. To make that work a little easier, we can use test frameworks.<br />
<br />
Note that it is sometimes tempting to treat the unit test as a pure verification tool. While it is true that unit tests do help to ensure correct functionality and behaviour, they also assist with other aspects of code quality. Writing a unit test requires a slightly different approach to coding up a class, and thinking about what inputs need to be tested can help to identify logic flaws in the code (even before the tests get run). In addition, the need to make the code testable is a very useful driver to ensure that classes do not suffer from close coupling.<br />
<br />
Anyway, enough of the conceptual stuff - lets talk about a specific tool that can reduce some of the effort and let us get on with the job.<br />
<br />
==About QtTestLib==<br />
<br />
QtTestlib is a lightweight testing library developed by the Qt Project and released under the LGPL (a commercial version is also available, for those who need alternative licensing). It is written in C++, and is cross-platform. It is provided as part of the tools included in Qt.<br />
<br />
In addition to normal unit testing capabilities, QtTestLib also offers basic GUI testing, based on sending QEvents. This allows you to test GUI widgets, but is not generally suitable for testing full applications.<br />
<br />
Each testcase is a standalone test application. Unlike CppUnit or JUnit, there is no Runner type class. Instead, each testcase is an executable which is simply run.<br />
<br />
== Tutorial 1: A simple test of a date class ==<br />
<br />
In this tutorial, we will build a simple test for a class that represents a date, using QtTestLib as the test framework. To avoid too much detail on how the date class works, we'll just use the QDate class that comes with Qt. In a normal unittest, you would more likely be testing code that you've written yourself.<br />
<br />
The code below is the entire testcase.<br />
<br />
<br />
'''Example 1. QDate test code'''<br />
<syntaxhighlight lang="cpp-qt" line="GESHI_NORMAL_LINE_NUMBERS"><br />
#include <QTest><br />
#include <QDate><br />
<br />
class testDate: public QObject<br />
{<br />
Q_OBJECT<br />
private slots:<br />
void testValidity();<br />
void testMonth();<br />
};<br />
<br />
void testDate::testValidity()<br />
{<br />
// 11 March 1967<br />
QDate date( 1967, 3, 11 );<br />
QVERIFY( date.isValid() );<br />
}<br />
<br />
void testDate::testMonth()<br />
{<br />
// 11 March 1967<br />
QDate date;<br />
date.setDate( 1967, 3, 11 );<br />
QCOMPARE( date.month(), 3 );<br />
QCOMPARE( QDate::longMonthName(date.month()),<br />
QString("March") );<br />
}<br />
<br />
<br />
QTEST_MAIN(testDate)<br />
#include "tutorial1.moc"<br />
</syntaxhighlight><br />
Save as autotests/tutorial1.cpp in your project's autotests directory following the example of [https://invent.kde.org/graphics/okular Okular]<br />
<br />
Stepping through the code, the first line imports the header files for the QtTest namespace. The second line imports the headers for the QDate class. Lines 4 to 10 give us the test class, testData. Note that testDate inherits from QObject and has the Q_OBJECT macro - QtTestLib requires specific Qt functionality that is present in QObject.<br />
<br />
Lines 12 to 17 provide our first test, which checks that a date is valid. Note the use of the QVERIFY macro, which checks that the condition is true. So if <tt>date.isValid()</tt> returns true, then the test will pass, otherwise the test will fail. QVERIFY is similar to ASSERT in other test suites.<br />
<br />
Similarly, lines 19 to 27 provide another test, which checks a setter, and a couple of accessor routines. In this case, we are using QCOMPARE, which checks that the conditions are equal. So if date.month() returns 3, then that part of that test will pass, otherwise the test will fail.<br />
<br />
{{Warning|As soon as a QVERIFY evaluates to false or a QCOMPARE does not have two equal values, the whole test is marked as failed and the next test will be started. So in the example above, if the check at line 24 fails, then the check at lines 25 and 26 will not be run.}}<br />
<br />
In a later tutorial we will see how to work around problems that this behaviour can cause.<br />
<br />
Line 30 uses the QTEST_MAIN which creates an entry point routine for us, with appropriate calls to invoke the testDate unit test class.<br />
<br />
Line 31 includes the Meta-Object compiler output, so we can make use of our QObject functionality.<br />
<br />
The qmake project file that corresponds to that code is shown below. You would then use qmake to turn this into a Makefile and then compile it with make.<br />
<br />
'''Example 2. QDate unit test project'''<br />
<pre><br />
CONFIG += qtestlib<br />
TEMPLATE = app<br />
TARGET +=<br />
DEPENDPATH += .<br />
INCLUDEPATH += .<br />
<br />
# Input<br />
SOURCES += tutorial1.cpp<br />
</pre><br />
Save as tutorial1.pro<br />
<br />
This is a fairly normal project file, except for the addition of the <tt>CONFIG += qtestlib</tt>. This adds the right header and library setup to the Makefile.<br />
<br />
Create an empty file called tutorial1.h and compile with <tt>qmake; make</tt> The output looks like the following:<br />
<br />
'''Example 3. QDate unit test output'''<br />
<pre><br />
$ ./tutorial1<br />
********* Start testing of testDate *********<br />
Config: Using QTest library 4.1.0, Qt 4.1.0-snapshot-20051003<br />
PASS : testDate::initTestCase()<br />
PASS : testDate::testValidity()<br />
PASS : testDate::testMonth()<br />
PASS : testDate::cleanupTestCase()<br />
Totals: 4 passed, 0 failed, 0 skipped<br />
********* Finished testing of testDate *********<br />
</pre><br />
<br />
Looking at the output above, you can see that the output includes the version of the test library and Qt itself, and then the status of each test that is run. In addition to the testValidity and testMonth tests that we defined, there is also a setup routine (initTestCase) and a teardown routine (cleanupTestCase) that can be used to do additional configuration if required.<br />
<br />
===Failing tests===<br />
<br />
If we had made an error in either the production code or the unit test code, then the results would show an error. An example is shown below:<br />
<br />
'''Example 4. QDate unit test output showing failure'''<br />
<pre><br />
$ ./tutorial1<br />
********* Start testing of testDate *********<br />
Config: Using QTest library 4.1.0, Qt 4.1.0-snapshot-20051003<br />
PASS : testDate::initTestCase()<br />
PASS : testDate::testValidity()<br />
FAIL! : testDate::testMonth() Compared values are not the same<br />
Actual (date.month()): 4<br />
Expected (3): 3<br />
Loc: [tutorial1.cpp(25)]<br />
PASS : testDate::cleanupTestCase()<br />
Totals: 3 passed, 1 failed, 0 skipped<br />
********* Finished testing of testDate *********<br />
</pre><br />
<br />
===Running selected tests===<br />
<br />
When the number of test functions increases, and some of the functions take a long time to run, it can be useful to only run a selected function. For example, if you only want to run the testMonth function, then you just specify that on the command line, as shown below:<br />
<br />
'''Example 5. QDate unit test output - selected function'''<br />
<br />
<pre><br />
$ ./tutorial1 testValidity<br />
********* Start testing of testDate *********<br />
Config: Using QTest library 4.1.0, Qt 4.1.0-snapshot-20051003<br />
PASS : testDate::initTestCase()<br />
PASS : testDate::testValidity()<br />
PASS : testDate::cleanupTestCase()<br />
Totals: 3 passed, 0 failed, 0 skipped<br />
********* Finished testing of testDate *********<br />
</pre><br />
<br />
Note that the initTestCase and cleanupTestCase routines are always run, so that any necessary setup and cleanup will still be done.<br />
<br />
You can get a list of the available functions by passing the -functions option, as shown below:<br />
<br />
'''Example 6. QDate unit test output - listing functions'''<br />
<pre><br />
$ ./tutorial1 -functions<br />
testValidity()<br />
testMonth()<br />
</pre><br />
<br />
===Verbose output options===<br />
<br />
You can get more verbose output by using the -v1, -v2 and -vs options. -v1 produces a message on entering each test function. I found this is useful when it looks like a test is hanging. This is shown below:<br />
<br />
'''Example 7. QDate unit test output - verbose output'''<br />
<br />
<pre><br />
$ ./tutorial1 -v1<br />
********* Start testing of testDate *********<br />
Config: Using QTest library 4.1.0, Qt 4.1.0-snapshot-20051003<br />
INFO : testDate::initTestCase() entering<br />
PASS : testDate::initTestCase()<br />
INFO : testDate::testValidity() entering<br />
PASS : testDate::testValidity()<br />
INFO : testDate::testMonth() entering<br />
PASS : testDate::testMonth()<br />
INFO : testDate::cleanupTestCase() entering<br />
PASS : testDate::cleanupTestCase()<br />
Totals: 4 passed, 0 failed, 0 skipped<br />
********* Finished testing of testDate *********<br />
</pre><br />
<br />
The -v2 option shows each QVERIFY, QCOMPARE and QTEST, as well as the message on entering each test function. I found this useful for verifying that a particular step is being run. This is shown below:<br />
<br />
''''Example 8. QDate unit test output - more verbose output'''<br />
<br />
<pre><br />
$ ./tutorial1 -v2<br />
********* Start testing of testDate *********<br />
Config: Using QTest library 4.1.0, Qt 4.1.0-snapshot-20051003<br />
INFO : testDate::initTestCase() entering<br />
PASS : testDate::initTestCase()<br />
INFO : testDate::testValidity() entering<br />
INFO : testDate::testValidity() QVERIFY(date.isValid())<br />
Loc: [tutorial1.cpp(17)]<br />
PASS : testDate::testValidity()<br />
INFO : testDate::testMonth() entering<br />
INFO : testDate::testMonth() COMPARE()<br />
Loc: [tutorial1.cpp(25)]<br />
INFO : testDate::testMonth() COMPARE()<br />
Loc: [tutorial1.cpp(27)]<br />
PASS : testDate::testMonth()<br />
INFO : testDate::cleanupTestCase() entering<br />
PASS : testDate::cleanupTestCase()<br />
Totals: 4 passed, 0 failed, 0 skipped<br />
********* Finished testing of testDate *********<br />
</pre><br />
<br />
The -vs option shows each signal that is emitted. In our example, there are no signals, so -vs has no effect. Getting a list of signals is useful for debugging failing tests, especially GUI tests which we will see in the third tutorial.<br />
<br />
===Output to a file===<br />
<br />
If you want to output the results of your testing to a file, you can use the -o filename, where you replace filename with the name of the file you want to save output to.<br />
<br />
==Tutorial 2: Data driven testing of a date class==<br />
<br />
In the previous example, we looked at how we can test a date class. If we decided that we really needed to test a lot more dates, then we'd be cutting and pasting a lot of code. If we subsequently changed the name of a function, then it has to be changed in a lot of places. As an alternative to introducing these types of maintenance problems into our tests, QtTestLib offers support for data driven testing.<br />
<br />
The easiest way to understand data driven testing is by an example, as shown below:<br />
<br />
'''Example 9. QDate test code, data driven version'''<br />
<syntaxhighlight lang="cpp-qt" line="GESHI_NORMAL_LINE_NUMBERS"><br />
#include <QtTest><br />
#include <QtCore><br />
<br />
<br />
class testDate: public QObject<br />
{<br />
Q_OBJECT<br />
private slots:<br />
void testValidity();<br />
void testMonth_data();<br />
void testMonth();<br />
};<br />
<br />
void testDate::testValidity()<br />
{<br />
// 12 March 1967<br />
QDate date( 1967, 3, 12 );<br />
QVERIFY( date.isValid() );<br />
}<br />
<br />
void testDate::testMonth_data()<br />
{<br />
QTest::addColumn<int>("year"); // the year we are testing<br />
QTest::addColumn<int>("month"); // the month we are testing<br />
QTest::addColumn<int>("day"); // the day we are testing<br />
QTest::addColumn<QString>("monthName"); // the name of the month<br />
<br />
QTest::newRow("1967/3/11") << 1967 << 3 << 11 << QString("March");<br />
QTest::newRow("1966/1/10") << 1966 << 1 << 10 << QString("January");<br />
QTest::newRow("1999/9/19") << 1999 << 9 << 19 << QString("September");<br />
// more rows of dates can go in here...<br />
}<br />
<br />
void testDate::testMonth()<br />
{<br />
QFETCH(int, year);<br />
QFETCH(int, month);<br />
QFETCH(int, day);<br />
QFETCH(QString, monthName);<br />
<br />
QDate date;<br />
date.setDate( year, month, day);<br />
QCOMPARE( date.month(), month );<br />
QCOMPARE( QDate::longMonthName(date.month()), monthName );<br />
}<br />
<br />
<br />
QTEST_MAIN(testDate)<br />
#include "tutorial2.moc"<br />
</syntaxhighlight><br />
<br />
As you can see, we've introduced a new method - testMonth_data, and moved the specific test date out of testMonth. We've had to add some more code (which will be explained soon), but the result is a separation of the data we are testing, and the code we are using to test it.<br />
The names of the functions are important - you must use the _data suffix for the data setup routine, and the first part of the data setup routine must match the name of the driver routine.<br />
<br />
It is useful to visualise the data as being a table, where the columns are the various data values required for a single run through the driver, and the rows are different runs. In our example, there are four columns (three integers, one for each part of the date; and one QString ), added in lines 23 through 30. The addColumn template obviously requires the type of variable to be added, and also requires a variable name argument. We then add as many rows as required using the newRow function, as shown in lines 23 through 26. The string argument to newRow is a label, which is handy for determining what is going on with failing tests, but doesn't have any effect on the test itself.<br />
<br />
To use the data, we simply use QFETCH to obtain the appropriate data from each row. The arguments to QFETCH are the type of the variable to fetch, and the name of the column (which is also the local name of the variable it gets fetched into). You can then use this data in a QCOMPARE or QVERIFY check. The code is run for each row, which you can see below:<br />
<br />
'''Example 10. Results of data driven testing, showing QFETCH'''<br />
<br />
<pre><br />
$ ./tutorial2 -v2<br />
********* Start testing of testDate *********<br />
Config: Using QTest library 4.1.0, Qt 4.1.0-snapshot-20051020<br />
INFO : testDate::initTestCase() entering<br />
PASS : testDate::initTestCase()<br />
INFO : testDate::testValidity() entering<br />
INFO : testDate::testValidity() QVERIFY(date.isValid())<br />
Loc: [tutorial2.cpp(19)]<br />
PASS : testDate::testValidity()<br />
INFO : testDate::testMonth() entering<br />
INFO : testDate::testMonth(1967/3/11) COMPARE()<br />
Loc: [tutorial2.cpp(44)]<br />
INFO : testDate::testMonth(1967/3/11) COMPARE()<br />
Loc: [tutorial2.cpp(45)]<br />
INFO : testDate::testMonth(1966/1/10) COMPARE()<br />
Loc: [tutorial2.cpp(44)]<br />
INFO : testDate::testMonth(1966/1/10) COMPARE()<br />
Loc: [tutorial2.cpp(45)]<br />
INFO : testDate::testMonth(1999/9/19) COMPARE()<br />
Loc: [tutorial2.cpp(44)]<br />
INFO : testDate::testMonth(1999/9/19) COMPARE()<br />
Loc: [tutorial2.cpp(45)]<br />
PASS : testDate::testMonth()<br />
INFO : testDate::cleanupTestCase() entering<br />
PASS : testDate::cleanupTestCase()<br />
Totals: 4 passed, 0 failed, 0 skipped<br />
********* Finished testing of testDate *********<br />
</pre><br />
<br />
=== The QTEST macro ===<br />
<br />
As an alternative to using QFETCH and QCOMPARE, you may be able to use the QTEST macro instead. QTEST takes two arguments, and if one is a string, it looks up that string as an argument in the current row. You can see how this can be used below, which is equivalent to the testMonth() code in the previous example.<br />
<br />
'''Example 11. QDate test code, data driven version using QTEST'''<br />
<br />
<syntaxhighlight lang="cpp-qt" line="GESHI_NORMAL_LINE_NUMBERS"><br />
void testDate::testMonth()<br />
{<br />
QFETCH(int, year);<br />
QFETCH(int, month);<br />
QFETCH(int, day);<br />
<br />
QDate date;<br />
date.setDate( year, month, day);<br />
QCOMPARE( date.month(), month );<br />
QTEST( QDate::longMonthName(date.month()), "monthName" );<br />
}<br />
</syntaxhighlight><br />
<br />
In the example above, note that monthName is enclosed in quotes, and we no longer have a QFETCH call for monthName.<br />
<br />
The other QCOMPARE could also have been converted to use QTEST, however this would be less efficient, because we already needed to use QFETCH to get month for the setDate in the line above.<br />
<br />
===Running selected tests with selected data===<br />
<br />
In the previous tutorial, we saw how to run a specific test by specifying the name of the test as a command line argument. In data driven testing, you can select which data you want the test run with, by adding a colon and the label for the data row. For example, if we just want to run the testMonth test for the first row, we would use <pre>./tutorial2 -v2 testMonth:1967/3/11</pre>. The result of this is shown below.<br />
<br />
'''Example 12. QDate unit test output - selected function and data'''<br />
<pre><br />
$ ./tutorial2 -v2 testMonth:1967/3/11<br />
********* Start testing of testDate *********<br />
Config: Using QTest library 4.1.0, Qt 4.1.0-snapshot-20051020<br />
INFO : testDate::initTestCase() entering<br />
PASS : testDate::initTestCase()<br />
INFO : testDate::testMonth() entering<br />
INFO : testDate::testMonth(1967/3/11) COMPARE()<br />
Loc: [tutorial2.cpp(44)]<br />
INFO : testDate::testMonth(1967/3/11) COMPARE()<br />
Loc: [tutorial2.cpp(45)]<br />
PASS : testDate::testMonth()<br />
INFO : testDate::cleanupTestCase() entering<br />
PASS : testDate::cleanupTestCase()<br />
Totals: 3 passed, 0 failed, 0 skipped<br />
********* Finished testing of testDate *********<br />
</pre><br />
<br />
==Tutorial 3: Testing Graphical User Interfaces==<br />
<br />
In the previous two tutorials, we've tested a date management class. This is an pretty typical use of unit testing. However Qt and KDE applications will make use graphical classes that take user input (typically from a keyboard and mouse). QtTestLib offers support for testing these classes, which we'll see in this tutorial.<br />
<br />
Again, we'll use an existing class as our test environment, and again it will be date related - the standard Qt {{qt|QDateEdit}} class. For those not familiar with this class, it is a simple date entry widget (although with some powerful back end capabilities). A picture of the widget is shown below.<br />
<br />
[[Image:Qdateedit_dlg.png|Thumb|'''Figure 1. QDateEdit widget screenshot''']]<br />
<br />
The way QtTestLib provides GUI testing is by injecting {{qt|QInputEvent}} events. To the application, these input events appear the same as normal key press/release and mouse clicks/drags. However the mouse and keyboard are unaffected, so that you can continue to use the machine normally while tests are being run.<br />
<br />
An example of how you can use the GUI functionality of QtTestLib is shown below.<br />
<br />
'''Example 13. QDateEdit test code'''<br />
<br />
<syntaxhighlight lang="cpp-qt" line="GESHI_NORMAL_LINE_NUMBERS"><br />
#include <QtTest><br />
#include <QtCore><br />
#include <QtGui><br />
Q_DECLARE_METATYPE(QDate)<br />
<br />
class testDateEdit: public QObject<br />
{<br />
Q_OBJECT<br />
private slots:<br />
void testChanges();<br />
void testValidator_data();<br />
void testValidator();<br />
};<br />
<br />
void testDateEdit::testChanges()<br />
{<br />
// 11 March 1967<br />
QDate date( 1967, 3, 11 );<br />
QDateEdit dateEdit( date );<br />
<br />
// up-arrow should increase day by one<br />
QTest::keyClick( &dateEdit, Qt::Key_Up );<br />
QCOMPARE( dateEdit.date(), date.addDays(1) );<br />
<br />
// we click twice on the "reduce" arrow at the bottom RH corner<br />
// first we need the widget size to know where to click<br />
QSize editWidgetSize = dateEdit.size();<br />
QPoint clickPoint(editWidgetSize.rwidth()-2, editWidgetSize.rheight()-2);<br />
// issue two clicks<br />
QTest::mouseClick( &dateEdit, Qt::LeftButton, 0, clickPoint);<br />
QTest::mouseClick( &dateEdit, Qt::LeftButton, 0, clickPoint);<br />
// and we should have decreased day by two (one less than original)<br />
QCOMPARE( dateEdit.date(), date.addDays(-1) );<br />
<br />
QTest::keyClicks( &dateEdit, "25122005" );<br />
QCOMPARE( dateEdit.date(), QDate( 2005, 12, 25 ) );<br />
<br />
QTest::keyClick( &dateEdit, Qt::Key_Tab, Qt::ShiftModifier );<br />
QTest::keyClicks( &dateEdit, "08" );<br />
QCOMPARE( dateEdit.date(), QDate( 2005, 8, 25 ) );<br />
}<br />
<br />
void testDateEdit::testValidator_data()<br />
{<br />
qRegisterMetaType<QDate>("QDate");<br />
<br />
QTest::addColumn<QDate>( "initialDate" );<br />
QTest::addColumn<QString>( "keyclicks" );<br />
QTest::addColumn<QDate>( "finalDate" );<br />
<br />
QTest::newRow( "1968/4/12" ) << QDate( 1967, 3, 11 )<br />
<< QString( "12041968" )<br />
<< QDate( 1968, 4, 12 );<br />
<br />
QTest::newRow( "1967/3/14" ) << QDate( 1967, 3, 11 )<br />
<< QString( "140abcdef[" )<br />
<< QDate( 1967, 3, 14 );<br />
// more rows can go in here<br />
}<br />
<br />
void testDateEdit::testValidator()<br />
{<br />
QFETCH( QDate, initialDate );<br />
QFETCH( QString, keyclicks );<br />
QFETCH( QDate, finalDate );<br />
<br />
QDateEdit dateEdit( initialDate );<br />
// this next line is just to start editing<br />
QTest::keyClick( &dateEdit, Qt::Key_Enter );<br />
QTest::keyClicks( &dateEdit, keyclicks );<br />
QCOMPARE( dateEdit.date(), finalDate );<br />
}<br />
<br />
QTEST_MAIN(testDateEdit)<br />
#include "tutorial3.moc"<br />
</syntaxhighlight><br />
<br />
Much of this code is common with previous examples, so I'll focus on the new elements and the more important changes as we work through the code line-by-line.<br />
<br />
Lines 1 to 3 import the various Qt declarations, as before.<br />
<br />
Line 4 is a macro that is required for the data-driven part of this test, which I'll come to soon.<br />
<br />
Lines 5 to 12 declare the test class - while the names have changed, it is pretty similar to the previous example. Note the testValidator and testValidator_data functions - we will be using data driven testing again in this example.<br />
<br />
Our first real test starts in line 13. Line 16 creates a QDate, and line 17 uses that date as the initial value for a QDateEdit widget.<br />
<br />
Lines 19 and 20 show how we can test what happens when we press the up-arrow key. The QTest::keyClick function takes a pointer to a widget, and a symbolic key name (a char or a Qt::Key). At line 20, we check that the effect of that event was to increment the date by a day. The QTest:keyClick function also takes an optional keyboard modifier (such as Qt::ShiftModifier for the shift key) and an optional delay value (in milliseconds). As an alternative to using QTest::keyClick, you can use QTest::keyPress and QTest::keyRelease to construct more complex keyboard sequences.<br />
<br />
Lines 23 to 29 show a similar test to the previous one, but in this case we are simulating a mouse click. We need to click in the lower right hand part of the widget (to hit the decrement arrow - see Figure 1), and that requires knowing how large the widget is. So lines 23 and 24 calculate the correct point based off the size of the widget. Line 26 (and the identical line 27) simulates clicking with the left-hand mouse button at the calculated point. The arguments to Qt::mouseClick are:<br />
<br />
*a pointer to the widget that the click event should be sent to.<br />
*the mouse button that is being clicked.<br />
*an optional keyboard modifier (such a Qt::ShiftModifier), or 0 for no modifiers.<br />
*an optional click point - this defaults to the middle of the widget if not specified.<br />
*an optional mouse delay.<br />
<br />
In addition to QTest::mouseClick, there is also QTest::mousePress, QTest::mouseRelease, QTest::mouseDClick (providing double-click) and QTest::mouseMove. The first three are used in the same way as QTest::mouseClick. The last takes a point to move the mouse to. You can use these functions in combination to simulate dragging with the mouse.<br />
<br />
Lines 30 and 31 show another approach to keyboard entry, using the QTest::keyClicks. Where QTest::keyClick sends a single key press, QTest::keyClicks takes a QString (or something equivalent, in line 30 a character array) that represents a sequence of key clicks to send. The other arguments are the same.<br />
<br />
Lines 32 to 34 show how you may need to use a combination of functions. After we've entered a new date in line 30, the cursor is at the end of the widget. At line 32, we use a Shift-Tab combination to move the cursor back to the month value. Then at line 33 we enter a new month value. Of course we could have used individual calls to QTest::keyClick, however that wouldn't have been as clear, and would also have required more code.<br />
<br />
=== Data-driven GUI testing ===<br />
<br />
Lines 50 to 60 show a data-driven test - in this case we are checking that the validator on QDateEdit is performing as expected. This is a case where data-driven testing can really help to ensure that things are working the way they should.<br />
<br />
At lines 52 to 54, we fetch in an initial value, a series of key-clicks, and an expected result. These are the columns that are set up in lines 39 to 41. However note that we are now pulling in a QDate, where in previous examples we used three integers and then build the QDate from those. However QDate isn't a registered type for {{qt|QMetaType}}, and so we need to register it before we can use it in our data-driven testing. This is done using the Q_DECLARE_METATYPE macro in line 4 and the qRegisterMetaType function in line 38.<br />
<br />
Lines 42 to 47 add in a couple of sample rows. Lines 42 to 44 represent a case where the input is valid, and lines 45 to 47 are a case where the input is only partly valid (the day part). A real test will obviously contain far more combinations than this.<br />
<br />
Those test rows are actually tested in lines 55 to 59. We construct the QDateEdit widget in line 55, using the initial value. We then send an Enter key click in line 57, which is required to get the widget into edit mode. At line 58 we simulate the data entry, and at line 59 we check whether the results are what was expected.<br />
<br />
Lines 61 and 62 are the same as we've seen in previous examples.<br />
<br />
=== Re-using test elements ===<br />
<br />
If you are re-using a set of events a number of times, then it may be an advantage to build a list of events, and then just replay them. This can improve maintainability and clarity of a set of tests, especially for mouse movements.<br />
<br />
The key class for building a list of test events is imaginatively known as QTestEventList. It is a QList of QTestEvents. The normal approach is to create the list, and then use various member functions to add key and mouse events. The normal functions that you'll need are addKeyClick and addMouseClick, which are very similar to the QTest::keyClick and QTest::mouseClick functions we used earlier in this tutorial. For finer grained operations, you can also use addKeyPress, addKeyRelease, addKeyEvent, addMousePress, addMouseRelease, addMouseDClick and addMouseMove to build up more complex event lists. You can also use addDelay to add a specified delay between events. When the list has been built up, you just call simulate on each widget.<br />
<br />
You can see how this works in the example below, which is the QDateEdit example (from above) converted to use QTestEventList.<br />
<br />
'''Example 14. QDateEdit test code, using QTestEventList'''<br />
<syntaxhighlight lang="cpp-qt" line="GESHI_NORMAL_LINE_NUMBERS"><br />
#include <QtTest><br />
#include <QtCore><br />
#include <QtGui><br />
Q_DECLARE_METATYPE(QDate)<br />
<br />
class testDateEdit: public QObject<br />
{<br />
Q_OBJECT<br />
private slots:<br />
void testChanges();<br />
void testValidator_data();<br />
void testValidator();<br />
};<br />
<br />
void testDateEdit::testChanges()<br />
{<br />
// 11 March 1967<br />
QDate date( 1967, 3, 11 );<br />
QDateEdit dateEdit( date );<br />
<br />
// up-arrow should increase day by one<br />
QTest::keyClick( &dateEdit, Qt::Key_Up );<br />
QCOMPARE( dateEdit.date(), date.addDays(1) );<br />
<br />
// we click twice on the "reduce" arrow at the bottom RH corner<br />
// first we need the widget size to know where to click<br />
QSize editWidgetSize = dateEdit.size();<br />
QPoint clickPoint(editWidgetSize.rwidth()-2, editWidgetSize.rheight()-2);<br />
// build a list that contains two clicks<br />
QTestEventList list1;<br />
list1.addMouseClick( Qt::LeftButton, 0, clickPoint);<br />
list1.addMouseClick( Qt::LeftButton, 0, clickPoint);<br />
// call that list on the widget<br />
list1.simulate( &dateEdit );<br />
// and we should have decreased day by two (one less than original)<br />
QCOMPARE( dateEdit.date(), date.addDays(-1) );<br />
<br />
QTest::keyClicks( &dateEdit, "25122005" );<br />
QCOMPARE( dateEdit.date(), QDate( 2005, 12, 25 ) );<br />
<br />
QTestEventList list2;<br />
list2.addKeyClick( Qt::Key_Tab, Qt::ShiftModifier );<br />
list2.addKeyClicks( "08" );<br />
list2.simulate( &dateEdit );<br />
QCOMPARE( dateEdit.date(), QDate( 2005, 8, 25 ) );<br />
}<br />
<br />
void testDateEdit::testValidator_data()<br />
{<br />
qRegisterMetaType<QDate>("QDate");<br />
<br />
QTest::addColumn<QDate>( "initialDate" );<br />
QTest::addColumn<QTestEventList>( "events" );<br />
QTest::addColumn<QDate>( "finalDate" );<br />
<br />
QTestEventList eventsList1;<br />
// this next line is just to start editing<br />
eventsList1.addKeyClick( Qt::Key_Enter );<br />
eventsList1.addKeyClicks( "12041968" );<br />
<br />
QTest::newRow( "1968/4/12" ) << QDate( 1967, 3, 11 )<br />
<< eventsList1<br />
<< QDate( 1968, 4, 12 );<br />
<br />
QTestEventList eventsList2;<br />
eventsList2.addKeyClick( Qt::Key_Enter );<br />
eventsList2.addKeyClicks( "140abcdef[" );<br />
<br />
QTest::newRow( "1967/3/14" ) << QDate( 1967, 3, 11 )<br />
<< eventsList2<br />
<< QDate( 1967, 3, 14 );<br />
// more rows can go in here<br />
}<br />
<br />
void testDateEdit::testValidator()<br />
{<br />
QFETCH( QDate, initialDate );<br />
QFETCH( QTestEventList, events );<br />
QFETCH( QDate, finalDate );<br />
<br />
QDateEdit dateEdit( initialDate );<br />
<br />
events.simulate( &dateEdit);<br />
<br />
QCOMPARE( dateEdit.date(), finalDate );<br />
}<br />
<br />
QTEST_MAIN(testDateEdit)<br />
#include "tutorial3a.moc"<br />
</syntaxhighlight><br />
<br />
This example is pretty much the same as the previous version, up to line 25. In line 26, we create a QTestEventList. We add events to the list in lines 27 and 28 - note that we don't specify the widget we are calling them on at this stage. In line 30, we simulate each event on the widget. If we had multiple widgets, we could call simulate using the same set of events.<br />
<br />
Lines 31 to 34 are as per the previous example.<br />
<br />
We create another list in lines 35 to 37, although this time we are using addKeyClick and addKeyClicks instead of adding mouse events. Note that an event list can contain combinations of mouse and keyboard events - it just didn't make sense in this test to have such a combination. We use the second list at line 38, and check the results in line 39.<br />
<br />
You can also build lists of events in data driven testing as well, as shown in lines 41 to 70. The key difference is that instead of fetching a QString in each row, we are fetching a QTestEventList. This requires that we add a column of QTestEventList, rather than QString (see line 45). At lines 47 to 50, we create a list of events. At line 52 we add those events to the applicable row. We create a second list at lines 54 to 56, and add that second list to the applicable row in line 58.<br />
<br />
We fetch the events in line 65, and use them in line 68. If we had multiple widgets, then we could use the same event list several times.<br />
<br />
==Tutorial 4 - Testing for failure and avoiding tests==<br />
<br />
Under some conditions, it is impossible to avoid tests failing. In this section, we'll see how to deal with these cases.<br />
<br />
===Skipping tests===<br />
<br />
Where a test doesn't make sense to run (for example, if the required test files aren't available, or the feature is architecture or operating system dependent), the cleanest solution is to skip the test.<br />
<br />
Tests are skipped using the QSKIP macro. QSKIP takes two arguments - a label string that should be used to describe why the test is being skipped, and a enumerated constant that controls how much of the test is skipped. If you pass SkipSingle, and the test is data driven, then only the current row is skipped. If you pass SkipAll and the test is data driven, then all following rows are skipped. If the test is not data driven, then it doesn't matter which one is used.<br />
<br />
You can see how QSKIP works in the example below:<br />
<br />
'''Example 15. Unit test showing skipped tests'''<br />
<syntaxhighlight lang="cpp-qt" line="GESHI_NORMAL_LINE_NUMBERS"><br />
void testDate::testSkip_data()<br />
{<br />
QTest::addColumn<int>("val1");<br />
QTest::addColumn<int>("val2");<br />
<br />
QTest::newRow("1") << 1 << 1;<br />
QTest::newRow("2") << 1 << 2;<br />
QTest::newRow("3") << 3 << 3;<br />
QTest::newRow("5") << 5 << 5;<br />
QTest::newRow("4") << 4 << 5;<br />
}<br />
<br />
void testDate::testSkip()<br />
{<br />
QFETCH(int, val1);<br />
QFETCH(int, val2);<br />
<br />
if ( val2 == 2 )<br />
QSKIP("Two isn't good, not doing it", SkipSingle);<br />
if ( val1 == 5 )<br />
QSKIP("Five! I've had enough, bailing here", SkipAll);<br />
QCOMPARE( val1, val2 );<br />
}<br />
</syntaxhighlight><br />
<br />
'''Example 16. Output of unit test showing skipped tests'''<br />
<pre><br />
$ ./tutorial4 testSkip -v2<br />
********* Start testing of testDate *********<br />
Config: Using QTest library 4.1.0, Qt 4.1.0-snapshot-20051107<br />
INFO : testDate::initTestCase() entering<br />
PASS : testDate::initTestCase()<br />
INFO : testDate::testSkip() entering<br />
INFO : testDate::testSkip(1) COMPARE()<br />
Loc: [tutorial4.cpp(82)]<br />
SKIP : testDate::testSkip(2) Two isn't good, not doing it<br />
Loc: [tutorial4.cpp(79)]<br />
INFO : testDate::testSkip(3) COMPARE()<br />
Loc: [tutorial4.cpp(82)]<br />
SKIP : testDate::testSkip(5) Five! I've had enough, bailing here<br />
Loc: [tutorial4.cpp(81)]<br />
PASS : testDate::testSkip()<br />
INFO : testDate::cleanupTestCase() entering<br />
PASS : testDate::cleanupTestCase()<br />
Totals: 3 passed, 0 failed, 2 skipped<br />
********* Finished testing of testDate *********<br />
</pre><br />
<br />
from the verbose output, you can see that the test was run on the first and third rows. The second row wasn't run because of the QSKIP call with a SkipSingle argument. Similarly, the fourth and fifth rows weren't run because the fourth row triggered a QSKIP call with a SkipAll argument.<br />
<br />
Also note that the test didn't fail, even though there were two calls to QSKIP. Conceptually, a skipped test is a test that didn't make sense to run for test validity reasons, rather than a test that is valid but will fail because of bugs or lack of features in the code being tested.<br />
<br />
===Handling expected failures===<br />
<br />
If you have valid tests, but the code that you are testing doesn't pass them, then ideally you fix the code you are testing. However sometimes that isn't possible in the time that you have available, or because of a need to avoid binary incompatible changes. In this case, it is undesirable to delete or modify the unit tests - it is better to flag the test as "expected to fail", using the QEXPECT_FAIL macro. An example of this is shown below:<br />
<br />
'''Example 17. Unit test showing expected failures'''<br />
<br />
<syntaxhighlight lang="cpp-qt" line="GESHI_NORMAL_LINE_NUMBERS"><br />
void testDate::testExpectedFail()<br />
{<br />
QEXPECT_FAIL("", "1 is not 2, even for very large 1", Continue);<br />
QCOMPARE( 1, 2 );<br />
QCOMPARE( 2, 2 );<br />
<br />
QEXPECT_FAIL("", "1 is not 2, even for very small 2", Abort);<br />
QCOMPARE( 1, 2 );<br />
// The next line will not be run, because we Abort on previous failure<br />
QCOMPARE( 3, 3 );<br />
}<br />
</syntaxhighlight><br />
<br />
'''Example 18. Output of unit test showing expected failures'''<br />
<br />
<pre><br />
$ ./tutorial4/tutorial4 testExpectedFail -v2<br />
********* Start testing of testDate *********<br />
Config: Using QTest library 4.1.0, Qt 4.1.0-snapshot-20051107<br />
INFO : testDate::initTestCase() entering<br />
PASS : testDate::initTestCase()<br />
INFO : testDate::testExpectedFail() entering<br />
INFO : testDate::testExpectedFail() Compared values are not the same<br />
Actual (1): 1<br />
Expected (2): 2<br />
Loc: [tutorial4.cpp(41)]<br />
XFAIL : testDate::testExpectedFail() 1 is not 2, even for very large 1<br />
Loc: [tutorial4.cpp(41)]<br />
INFO : testDate::testExpectedFail() COMPARE()<br />
Loc: [tutorial4.cpp(42)]<br />
INFO : testDate::testExpectedFail() Compared values are not the same<br />
Actual (1): 1<br />
Expected (2): 2<br />
Loc: [tutorial4.cpp(45)]<br />
XFAIL : testDate::testExpectedFail() 1 is not 2, even for very small 2<br />
Loc: [tutorial4.cpp(45)]<br />
PASS : testDate::testExpectedFail()<br />
INFO : testDate::cleanupTestCase() entering<br />
PASS : testDate::cleanupTestCase()<br />
Totals: 3 passed, 0 failed, 0 skipped<br />
********* Finished testing of testDate *********<br />
</pre><br />
<br />
As you can see from the verbose output, we expect a failure from each time we do a QCOMPARE( 1, 2);. In the first call to QEXPECT_FAIL, we use the Continue argument, so the rest of the tests will still be run. However in the second call to QEXPECT_FAILwe use the Abort and the test bails at this point. Generally it is better to use Continue unless you have a lot of closely related tests that would each need a QEXPECT_FAIL entry.<br />
<br />
Also note that tests that are marked as expected failures are not considered to be failures, so the test function above is considered to be a pass.<br />
<br />
If a test that is marked to be an expected failure, and it unexpectedly passes, then that is flagged as an error, as shown below:<br />
<br />
'''Example 19. Unit test showing unexpected pass'''<br />
<syntaxhighlight lang="cpp-qt" line="GESHI_NORMAL_LINE_NUMBERS"><br />
void testDate::testUnexpectedPass()<br />
{<br />
QEXPECT_FAIL("", "1 is not 2, even for very large 1", Continue);<br />
QCOMPARE( 1, 1 );<br />
QCOMPARE( 2, 2 );<br />
<br />
QEXPECT_FAIL("", "1 is not 2, even for very small 2", Abort);<br />
QCOMPARE( 1, 1 );<br />
QCOMPARE( 3, 3 );<br />
}<br />
</syntaxhighlight><br />
<br />
'''Example 20. Output of unit test showing unexpected pass'''<br />
<br />
<pre><br />
$ ./tutorial4/tutorial4 testUnexpectedPass -v2<br />
********* Start testing of testDate *********<br />
Config: Using QTest library 4.1.0, Qt 4.1.0-snapshot-20051107<br />
INFO : testDate::initTestCase() entering<br />
PASS : testDate::initTestCase()<br />
INFO : testDate::testUnexpectedPass() entering<br />
INFO : testDate::testUnexpectedPass() COMPARE()<br />
Loc: [tutorial4.cpp(53)]<br />
XPASS : testDate::testUnexpectedPass() COMPARE()<br />
Loc: [tutorial4.cpp(53)]<br />
INFO : testDate::testUnexpectedPass() COMPARE()<br />
Loc: [tutorial4.cpp(54)]<br />
INFO : testDate::testUnexpectedPass() COMPARE()<br />
Loc: [tutorial4.cpp(57)]<br />
XPASS : testDate::testUnexpectedPass() COMPARE()<br />
Loc: [tutorial4.cpp(57)]<br />
INFO : testDate::cleanupTestCase() entering<br />
PASS : testDate::cleanupTestCase()<br />
Totals: 2 passed, 2 failed, 0 skipped<br />
********* Finished testing of testDate *********<br />
</pre><br />
<br />
The effect of unexpected passes on the running of the test is controlled by the second argument to QEXPECT_FAIL. If the argument is Continue and the test unexpectedly passes, then the rest of the test function will be run. If the argument is Abort, then the test will stop.<br />
<br />
===Checking debug messages and warnings===<br />
<br />
If you are testing border cases, you will likely run across the case where some kind of message will be produced using the qDebug or qWarning functions. Where a test produces a debug or warning message, that message will be logged in the test output (although it will still be considered a pass unless some other check fails), as shown in the example below:<br />
<br />
Example 21. Unit test producing warning and debug messages<br />
<syntaxhighlight lang="cpp-qt" line="GESHI_NORMAL_LINE_NUMBERS"><br />
void testDate::testQdebug()<br />
{<br />
qWarning("warning");<br />
qDebug("debug");<br />
qCritical("critical");<br />
}<br />
</syntaxhighlight><br />
<br />
'''Example 22. Output of unit test producing warning and debug messages'''<br />
<pre><br />
$ ./tutorial4 testQdebug<br />
********* Start testing of testDate *********<br />
Config: Using QTest library 4.1.0, Qt 4.1.0-snapshot-20051107<br />
PASS : testDate::initTestCase()<br />
QWARN : testDate::testQdebug() warning<br />
QDEBUG : testDate::testQdebug() debug<br />
QSYSTEM: testDate::testQdebug() critical<br />
PASS : testDate::testQdebug()<br />
PASS : testDate::cleanupTestCase()<br />
Totals: 3 passed, 0 failed, 0 skipped<br />
********* Finished testing of testDate *********<br />
</pre><br />
<br />
Note that while this example produces the debug and warning messages within the test function (testQdebug), those messages would normally be propagated up from the code being tested. However the source of the messages does not make any difference to how they are handled.<br />
<br />
If your test needs include either a clean output, or verification that appropriate messages are generated, then you will probably need the QtTest::ignoreMessage function.<br />
<br />
{{Tip|'''Note:''' The ignoreMessage function can be used to ignore a message, however it might be clearer to think of this function as checking for the presence of an expected message. In particular, it is a test failure if you call ignoreMessage and the message is not generated.}}<br />
<br />
An example of how ignoreMessage works is shown below.<br />
<br />
'''Example 23. Example of using ignoreMessage'''<br />
<syntaxhighlight lang="cpp-qt"><br />
void testDate::testValidity()<br />
{<br />
QTest::ignoreMessage(QtWarningMsg, "validity warning");<br />
qWarning("validity warning");<br />
}<br />
<br />
void testDate::testValiditi()<br />
{<br />
QTest::ignoreMessage(QtWarningMsg, "validity warning");<br />
qWarning("validiti warning");<br />
}<br />
</syntaxhighlight><br />
<br />
<br />
'''Example 24. Output of ignoreMessage example'''<br />
<pre><br />
$ ./tutorial4 testValidity testValiditi<br />
********* Start testing of testDate *********<br />
Config: Using QTest library 4.1.0, Qt 4.1.0-snapshot-20051107<br />
PASS : testDate::initTestCase()<br />
PASS : testDate::testValidity()<br />
QWARN : testDate::testValiditi() validiti warning<br />
INFO : testDate::testValiditi() Did not receive message: "validity warning"<br />
FAIL! : testDate::testValiditi() Not all expected messages were received<br />
PASS : testDate::cleanupTestCase()<br />
Totals: 3 passed, 1 failed, 0 skipped<br />
********* Finished testing of testDate *********<br />
</pre><br />
<br />
Note that the warning message in testDate::testValidity has been "swallowed" by thecall to ignoreMessage.<br />
<br />
By contrast, the warning message in testDate::testValiditi still causes a warning to be logged, because the ignoreMessage call does not match the text in the warning message. In addition, because a we expected a particular warning message and it wasn't received, the testDate::testValiditi test function fails.<br />
<br />
==Tutorial 5: Testing Qt slots and signals==<br />
<br />
An important part of Qt programming is the use of signals and slots. This section covers the support for testing of these features.<br />
<br />
{{Tip|'''Note:''' If you are not familiar with Qt signals and slots, you probably should review the introduction to this feature provided in your Qt documentation. It is also available at http://doc.qt.io/qt-5/signalsandslots.html.}}<br />
<br />
===Testing slots===<br />
<br />
Testing slots is very easy, because a slot is just a specially annotated method. You can call slots just like any other method you'd like to test, as shown below:<br />
<br />
'''Example 25. QLabel test code, showing testing of a couple of slots'''<br />
<syntaxhighlight lang="cpp-qt" line="GESHI_NORMAL_LINE_NUMBERS"><br />
#include <QtTest><br />
#include <QtCore><br />
#include <QtGui><br />
<br />
class testLabel: public QObject<br />
{<br />
Q_OBJECT<br />
private slots:<br />
void testChanges();<br />
};<br />
<br />
void testLabel::testChanges()<br />
{<br />
QLabel label;<br />
<br />
// setNum() is a QLabel slot, but we can just call it like any<br />
// other method.<br />
label.setNum( 3 );<br />
QCOMPARE( label.text(), QString("3") );<br />
<br />
// clear() is also a slot.<br />
label.clear();<br />
QVERIFY( label.text().isEmpty() );<br />
}<br />
<br />
QTEST_MAIN(testLabel)<br />
#include "tutorial5.moc"<br />
</syntaxhighlight><br />
<br />
===Testing signals===<br />
Testing of signals is a little more difficult than testing of slots, however, Qt offers a very useful class called QSignalSpy that helps a lot.<br />
<br />
{{qt|QSignalSpy}} is a class provided with Qt that allows you to record the signals that have been emitted from a particular QObject subclass object. You can then check that the right number of signals have been emitted and that the right kind of signals was emitted. You can find more information on the QSignalSpy class in your Qt documentation.<br />
<br />
An example of how you can use QSignalSpy to test a class that has signals is shown below.<br />
<br />
'''Example 26. QCheckBox test code, showing testing of signals'''<br />
<br />
<syntaxhighlight lang="cpp-qt" line="GESHI_NORMAL_LINE_NUMBERS"><br />
#include <QtTest><br />
#include <QtCore><br />
#include <QtGui><br />
<br />
class testCheckBox: public QObject<br />
{<br />
Q_OBJECT<br />
private slots:<br />
void testSignals();<br />
};<br />
<br />
void testCheckBox::testSignals()<br />
{<br />
// You don't need to use an object created with "new" for<br />
// QSignalSpy, I just needed it in this case to test the emission<br />
// of a destroyed() signal.<br />
QCheckBox *xbox = new QCheckBox;<br />
<br />
// We are going to have two signal monitoring classes in use for<br />
// this test.<br />
// The first monitors the stateChanged() signal.<br />
// Also note that QSignalSpy takes a pointer to the object.<br />
QSignalSpy stateSpy( xbox, SIGNAL( stateChanged(int) ) );<br />
<br />
// Not strictly necessary, but I like to check that I have set up<br />
// my QSignalSpy correctly.<br />
QVERIFY( stateSpy.isValid() );<br />
<br />
// Now we check to make sure we don't have any signals already<br />
QCOMPARE( stateSpy.count(), 0 );<br />
<br />
// Here is a second monitoring class - this one for the<br />
// destroyed() signal.<br />
QSignalSpy destroyedSpy( xbox, SIGNAL( destroyed() ) );<br />
QVERIFY( destroyedSpy.isValid() );<br />
<br />
// A sanity check to verify the initial state<br />
// This also shows that you can mix normal method checks with<br />
// signal checks.<br />
QCOMPARE( xbox->checkState(), Qt::Unchecked );<br />
<br />
// Shouldn't already have any signals<br />
QCOMPARE( destroyedSpy.count(), 0 );<br />
<br />
// If we change the state, we should get a signal.<br />
xbox->setCheckState( Qt::Checked );<br />
QCOMPARE( stateSpy.count(), 1 );<br />
<br />
xbox->setCheckState( Qt::Unchecked );<br />
QCOMPARE( stateSpy.count(), 2 );<br />
<br />
xbox->setCheckState( Qt::PartiallyChecked );<br />
QCOMPARE( stateSpy.count(), 3 );<br />
<br />
// If we destroy the object, the signal should be emitted.<br />
delete xbox;<br />
<br />
// So the count of objects should increase.<br />
QCOMPARE( destroyedSpy.count(), 1 );<br />
<br />
// We can also review the signals that we collected<br />
// QSignalSpy is really a QList of QLists, so we take the first<br />
// list, which corresponds to the arguments for the first signal<br />
// we caught.<br />
QList<QVariant> firstSignalArgs = stateSpy.takeFirst();<br />
// stateChanged() only has one argument - an enumerated type (int)<br />
// So we take that argument from the list, and turn it into an integer.<br />
int firstSignalState = firstSignalArgs.at(0).toInt();<br />
// We can then check we got the right kind of signal.<br />
QCOMPARE( firstSignalState, static_cast<int>(Qt::Checked) );<br />
<br />
// check the next signal - note that takeFirst() removes from the list<br />
QList<QVariant> nextSignalArgs = stateSpy.takeFirst();<br />
// this shows another way of fudging the argument types<br />
Qt::CheckState nextSignalState = (Qt::CheckState)nextSignalArgs.at(0).toInt();<br />
QCOMPARE( nextSignalState, Qt::Unchecked );<br />
<br />
// and again for the third signal<br />
nextSignalArgs = stateSpy.takeFirst();<br />
nextSignalState = (Qt::CheckState)nextSignalArgs.at(0).toInt();<br />
QCOMPARE( nextSignalState, Qt::PartiallyChecked );<br />
}<br />
<br />
QTEST_MAIN(testCheckBox)<br />
#include "tutorial5a.moc"<br />
</syntaxhighlight><br />
<br />
The first 11 lines are essentially unchanged from previous examples that we've seen. Line 15 creates the object that will be tested - as noted in the comments in lines 12-14, the only reason that I'm creating it with new is because I need to delete it in line 45 to cause the destroyed() signal to be emitted.<br />
<br />
Line 20 sets up the first of our two QSignalSpy instances. The one in line 20 monitors the stateChanged(int) signal, and the one in line 29 monitors the destroyed() signal. If you get the name or signature of the signal wrong (for example, if you use stateChanged() instead of stateChanged(int)), then this will not be caught at compile time, but will result in a runtime failure. You can test if things were set up correctly using the isValid(), as shown in lines 24 and 30.<br />
<br />
As shown in line 34, there is no reason why you cannot test normal methods, signals and slots in the same test.<br />
<br />
Line 38 changes the state of the object under test, which is supposed to result in a stateChanged(int) signal being emitted. Line 39 checks that the number of signals increases from zero to one. Lines 40 and 41 repeat the process, and again in lines 42 and 43.<br />
<br />
Line 45 deletes the object under test, and line 47 tests that the destroyed() signal has been emitted.<br />
<br />
For signals that have arguments (such as our stateChanged(int) signal), you may also wish to check that the arguments were correct. You can do this by looking at the list of signal arguments. Exactly how you do this is fairly flexible, however for simple tests like the one in the example, you can manually work through the list using takeFirst() and check that each argument is correct. This is shown in line 52, 55 and 57 for the first signal. The same approach is shown in lines 59, 61 and 62 for the second signal, and the in lines 64 to 66 for the third signal. For a more complex set of tests, you may wish to apply some data driven techniques.<br />
<br />
{{Tip|'''Note:''' You should be aware that, for some class implementations, you may need to return control to the event loop to have signals emitted. If you need this, try using the {{qt|QTest}}::qWait() function.}}<br />
<br />
==Tutorial 6: Integrating with CMake==<br />
<br />
The KDE build tool is [http://www.cmake.org CMake], and I assume that you are familiar with the use of CMake. If not, you should review the [[Guidelines and HOWTOs/CMake|CMake Tutorial]] first.<br />
<br />
CMake offers quite good support for unit testing, and QTestLib tests can be easily integrated into any CMake build system.<br />
<br />
=== Configuring for Testing ===<br />
<br />
Tests are not built by default - you have to enable the test system, and build the tests.<br />
<br />
You enable tests by adding an '''ENABLE_TESTING()''' line to the top of your CMakeLists.txt file.<br />
<br />
In some configurations, there may be a build system option to turn on (or off) the compilation of tests. At this stage, you have to enable the '''BUILD_TESTING''' option in KDE4 modules, however this may go away in the near future, as later version of CMake can build the test applications on demand.<br />
<br />
If the tests are still not building, you might want to issue make buildtests in tests directory.<br />
<br />
=== Adding Tests ===<br />
<br />
You add a single test to the list of all tests that can be run by using<br />
'''ecm_add_test''', which looks like this<br />
<br />
include(ECMAddTests)<br />
include_directories(AFTER "${CMAKE_CURRENT_SOURCE_DIR}/..")<br />
ecm_add_test(<br />
tutorial1.cpp<br />
TEST_NAME Tut1<br />
LINK_LIBRARIES Qt5::Test<br />
NAME_PREFIX "tut1-"<br />
)<br />
<br />
* The first argument is the filename.<br />
* There are named arguments for other options.<br />
<br />
Note that '''ecm_add_test''' does nothing if '''ENABLE_TESTING()''' has not been run.<br />
<br />
By convention tests are put in the '''autotests''' directory. See [https://cgit.kde.org/okular.git okular] for an example.<br />
<br />
=== Testing with Akonaki ===<br />
<br />
Some tests require an Akonadi server be up and running, so the test must launch a server process (isolated from any existing environment), load in test data, and shut the server down after the testing finishes. You can use the [https://techbase.kde.org/KDE_PIM/Akonadi/Testing Akonadi Testrunner] to do this; consult that document for information on configuring the test runner and test data.<br />
<br />
The KF5AkonadiMacros.cmake file in the Akonadi repository provides an '''add_akonadi_isolated_test''' function to add tests to the build.<br />
<br />
add_akonadi_isolated_test(<br />
SOURCE sometest.cpp<br />
LINK_LIBRARIES Qt5::Test<br />
)<br />
<br />
=== KDE4 CMake Recipe for QTestLib ===<br />
<br />
If you are working in a KDE4 environment, then it is pretty easy to get CMake set up to build and run a test on demand.<br />
<br />
<pre><br />
cmake_minimum_required(VERSION 2.8)<br />
<br />
FIND_PACKAGE ( KDE4 REQUIRED )<br />
FIND_PACKAGE ( Qt4 REQUIRED QT_USE_QT* )<br />
<br />
INCLUDE( ${QT_USE_FILE} )<br />
include(KDE4Defaults)<br />
<br />
set( kwhatevertest_SRCS kwhatevertest.cpp )<br />
<br />
kde4_add_unit_test( kwhatevertest<br />
TESTNAME ksubmodule-kwhatevertest<br />
${kwhatevertest_SRCS}<br />
)<br />
<br />
target_link_libraries( kwhatevertest<br />
${KDE4_KDECORE_LIBS}<br />
${QT_QTTEST_LIBRARY}<br />
${KDE4_KDEUI_LIBS}<br />
)<br />
<br />
</pre><br />
<br />
You are meant to replace "kwhatevertest" with the name of your test application. The target_link_libraries() line will need to contain whatever libraries are needed for the feature you are testing, so if it is a GUI feature, you'll likely need to use "${KDE4_KDEUI_LIBS}.<br />
<br />
=== Running the Tests ===<br />
To run all tests, you can just "make test". This will work through each of the tests that have been added (at any lower level) using '''kde4_add_unit_test''', provided that you have '''include(KDE4Defaults)''' in your CMakeLists.txt.<br />
<br />
This is equivalent to running the "ctest" executable with no arguments. If you want finer grained control over which tests are run or the output format, you can use additional arguments. These are explained in the ctest man page ("man ctest" on a *nix system, or run "ctest --help-full").<br />
<br />
To run a single test, use '''./tutorial1.shell''' rather than just '''./tutorial1''', this will make it use the locally-built version of the shared libraries you're testing, rather than the installed ones.<br />
<br />
Some tests are written so that the expected local is English and fail if it is not.<br />
If your local is not English you case use for instance `LANG=C ctest` or `LANG=C make` to force English when running the test.<br />
<br />
=== Further Reading ===<br />
<br />
Chapter 8 of the [http://www.kitware.com/products/cmakebook.html CMake Book] provides a detailed description of how to do testing with CMake. Also see Appendix B for more on CTest and the special commands you can use.<br />
<br />
Various sections of the CMake Wiki, especially [https://gitlab.kitware.com/cmake/community/wikis/doc/ctest/Testing-With-CTest CTest testing]<br />
<br />
==Tutorial 7: Integrating with qmake==<br />
<br />
TODO<br />
<br />
==Tutorial 8: XML output==<br />
TODO<br />
<br />
==Tutorial 9: KDE specifics ==<br />
For KDE specific enhancements see [http://api.kde.org/4.0-api/kdelibs-apidocs/kdecore/html/qtest__kde_8h.html API dox]<br />
<br />
TODO<br />
<br />
== Alternative tools for testing ==<br />
<br />
There are a range of alternative testing approaches that can<br />
be used either with unit tests, or as an addition to the unit tests.<br />
<br />
===Static tests===<br />
As noted in the introduction, unit tests are dynamic tests - they exercise the compiled code. Static tests are slightly different - they look for problems in the source code, rather than making sure that the object code runs correctly.<br />
<br />
Static test tools tend to identify completely different types of problems to unit tests, and you should seek to use them both.<br />
<br />
For more information on using static tests, see [[../Code Checking|the Code Checking tutorial]].<br />
<br />
===Coverage tools and CI===<br />
Add this option in the configuration of your project's CI build.<br />
# http://quickgit.kde.org/?p=websites%2Fbuild-kde-org.<br />
# git clone kde:websites/build-kde-org<br />
<br />
[DEFAULT]<br />
configureExtraArgs=-DBUILD_COVERAGE=ON<br />
<br />
=== GUI application testing - Squish and KDExecutor ===<br />
[https://www.froglogic.com/squish/ Squish] by [https://www.froglogic.com froglogic] and [http://www.kdab.net/?page=products&sub=kdexecutor KDExecutor for Qt3/KDE3] by [https://www.kdab.net Klarälvdalens Datakonsult (KDAB)] are commercial tools that facilitate GUI testing.</div>Gjditchfieldhttps://community.kde.org/index.php?title=Guidelines_and_HOWTOs/Debugging/Using_Error_Messages&diff=90087Guidelines and HOWTOs/Debugging/Using Error Messages2020-09-07T19:47:36Z<p>Gjditchfield: Separate "how to turn on logging in someone's code" from "how to add logging to my code".</p>
<hr />
<div>= Qt 5 / KDE Frameworks 5 =<br />
<br />
<tt>kDebug()</tt> and friends have been deprecated in KDE Frameworks 5, and you should use Qt's built-in debugging instead. We recommend that you use [https://doc.qt.io/qt-5/qloggingcategory.html QLoggingCategory], particularly for libraries and plugins. Note that this is only available in Qt 5.2 and later.<br />
<br />
== Controlling Messages ==<br />
The source code for a library, plugin, or program named "Foo" may contain statements like<br />
<syntaxhighlight lang="cpp-qt"><br />
qCDebug(LOG_FOO) << "Log something:" << someVariable;<br />
</syntaxhighlight><br />
Here, <tt>LOG_FOO</tt> is a ''logging category''. If debug-level messages have been enabled for that logging category, then the statement will write a message to <tt>stderr</tt>.<br />
<br />
Some source file will define the logging category:<br />
<syntaxhighlight lang="cpp-qt"><br />
Q_LOGGING_CATEGORY(LOG_FOO, "some.namespace.foo")<br />
</syntaxhighlight><br />
Here, <tt>"some.namespace.foo"</tt> is the ''category name''. Once you know the category's name, you can<br />
set the <tt>QT_LOGGING_RULES</tt> environment variable to enable debug-level messages for the category:<br />
<syntaxhighlight lang="bash"><br />
QT_LOGGING_RULES="some.namespace.foo.debug=true"<br />
</syntaxhighlight><br />
<br />
Logging rules can be more complex than the example above. They can specify wildcards in the category name, enable or disable more than one message level, and control more than one logging category.<br />
They can also be stored in various configuration files. Please see the QLoggingCategory documentation for details.<br />
<br />
If you run the application from within a terminal application, like [http://www.kde.org/applications/system/konsole/ Konsole], you will see the logging output in that terminal window. If you use an Integrated Development Environment like [https://kde.org/applications/en/development/org.kde.kdevelop KDevelop] it will display the output in its windows. Otherwise, the messages will usually appear in {{path|~/.xsession-errors}} or {{path|~/.X.err}} if you use a login manager like KDM, or on the console you ran <tt>startx</tt> from if you started X that way. Also check the systemd log, since the application could be its direct child process.<br />
<br />
== Adding Logging to Your Code ==<br />
For a library or plugin called "Foo", you should have a common header that contains the following declaration (e.g. called "foo-debug.h")<br />
<syntaxhighlight lang="cpp-qt"><br />
#include <QLoggingCategory><br />
Q_DECLARE_LOGGING_CATEGORY(LOG_FOO)<br />
</syntaxhighlight> and exactly one source file containing <syntaxhighlight lang="cpp-qt"><br />
#include "foo-debug.h"<br />
Q_LOGGING_CATEGORY(LOG_FOO, "some.namespace.foo")<br />
</syntaxhighlight><br />
<br />
You should treat the category name (<tt>"some.namespace.foo"</tt> in the example) as something like reverse DNS; it cannot contain spaces, and dots indicate a hierarchy. For example, KDE PIM category names all start with <tt>"org.kde.pim."</tt>.<br />
<br />
To simplify the setup, you can use the ECM macro <tt>[https://api.kde.org/ecm/module/ECMQtDeclareLoggingCategory.html ecm_qt_declare_logging_category]</tt>, which generates the respective source files for you:<br />
<syntaxhighlight lang="cmake"><br />
include(ECMQtDeclareLoggingCategory)<br />
ecm_qt_declare_logging_category(FOO_SRCS<br />
HEADER foo-debug.h<br />
IDENTIFIER "LOG_FOO"<br />
CATEGORY_NAME "some.namespace.foo"<br />
)<br />
</syntaxhighlight><br />
<br />
Logging lines then look like <syntaxhighlight lang="cpp-qt"><br />
#include "foo-debug.hpp"<br />
qCDebug(LOG_FOO) << "Log something:" << someVariable;<br />
qCWarning(LOG_FOO) << "Something bad happened that users (end-users, or application developers using this library) should be aware of";<br />
qCCritical(LOG_FOO) << "Something happened so bad we had to terminate the application";<br />
</syntaxhighlight><br />
<br />
The syntax is much like <tt>cout</tt>, and most native C++ types, Qt-provided types and KF-provided types can be passed directly (like with <tt>someVariable</tt> in the example).<br />
<br />
With Qt 5.2, the <tt>qCDebug</tt> line will not produce any output; this is because logging categories are disabled by default. You need to include the line <syntaxhighlight lang="cpp-qt"><br />
QLoggingCategory::setFilterRules(QStringLiteral("foo.debug = true"));<br />
</syntaxhighlight><br />
somewhere in the application code, generally in the <tt>main()</tt> function. Of course, you would typically disable this call in release versions.<br />
<br />
== Improving Logging Output ==<br />
<br />
Qt provides a way of controlling the output of the logging methods via an environment variable. You can tell it to include the application name and PID, as well as the debugging category, and color-code the text. For example, running the following lines in your shell will produce something that looks quite like <tt>kDebug</tt>'s colored output: <syntaxhighlight lang="bash"><br />
c=`echo -e "\033"`<br />
export QT_MESSAGE_PATTERN="%{appname}(%{pid})/(%{category}) $c\[31m%{if-debug}$c\[34m%{endif}%{function}$c\[0m: %{message}"<br />
unset c<br />
</syntaxhighlight><br />
<br />
== Managing Lots of Output ==<br />
<br />
If you have lots of debugging statements, they may appear too fast and leave the terminal window before you can read them. There are three main ways to deal with this:<br />
# [https://doc.qt.io/qt-5/qloggingcategory.html#setFilterRules Disable some logging categories] to limit the amount of output generated<br />
# Increase the amount of scrollback in the terminal so that output is not lost; in Konsole, you can go to <tt>Settings > Edit Current Profile...</tt> and click on the <tt>Scrollback</tt> tab to change this. Konsole also has a useful search feature: just press <tt>Ctrl+Shift+F</tt> or click <tt>Find...</tt> on the <tt>Edit</tt> menu.<br />
# Save the output to a file; <tt>tee</tt> is useful for this. For example, you can run <syntaxhighlight lang="bash">application 2>&1 | tee debug.log</syntaxhighlight> to save the output to the file <tt>debug.log</tt> while still viewing it in the terminal.<br />
<br />
= Qt 4 / kdelibs 4 =<br />
<br />
kdelibs provides a [http://api.kde.org/4.0-api/kdelibs-apidocs/kdecore/html/group__kdebug.html family of functions] that output information to <tt>stderr</tt>, meaning that if you run an application from the terminal, it will be displayed in that terminal window. If you run the application from the desktop (using KRunner or the application menu, for example), the output will normally appear in {{path|~/.xsession-errors}} or {{path|~/.X.err}} if you use a login manager like KDM, or on the console you ran <tt>startx</tt> from if you started X that way.<br />
<br />
To use these functions in your code, you need to include the correct header file <syntaxhighlight lang="cpp-qt"><br />
#include <KDebug><br />
</syntaxhighlight><br />
and then you can use the functions <syntaxhighlight lang="cpp-qt"><br />
kDebug() << "Something happened that only developers care about" << someVariable;<br />
kWarning() << "Something bad happened that users (end-users, or application developers using this library) should be aware of";<br />
kError() << "Something even worse happened";<br />
kFatal() << "Something happened so bad we had to terminate the application";<br />
</syntaxhighlight><br />
<br />
The syntax is much like <tt>cout</tt>, and most native C++ types, Qt-provided types and kdelibs-provided types can be passed directly (like with <tt>someVariable</tt> in the example).<br />
<br />
Note that the <tt>kDebug</tt> calls will only do anything if the code was compiled with debugging enabled (and so will generally not work in packages from a distribution). This means <tt>cmake</tt> should be run with the <tt>-DCMAKE_BUILD_TYPE=debugfull</tt> argument. The other functions, however, will produce output no matter how the code was compiled.<br />
<br />
== Debug Areas ==<br />
<br />
The debugging output can be controlled at runtime using debugging areas. This allows enabling debugging output for only certain libraries or plugins, for example. Debugging areas are numbers, so the <tt>KStatusNotifierItemPrivate::registerToDaemon</tt> method in the kdeui library, for example, has the call <syntaxhighlight lang="cpp-qt"><br />
kDebug(299) << "Registering a client interface to the KStatusNotifierWatcher";<br />
</syntaxhighlight><br />
The file <tt>kdebug.areas</tt> in the <tt>kdecore</tt> directory of kdelibs indicates that the number 299 is associated with "kdeui (KNotification)".<br />
<br />
This information is used by the <tt>kdebugdialog</tt> utility (which you can just run from the commandline or using KRunner) to turn these areas on and off, enabling or disabling those <tt>kDebug</tt> statements. It is also used by <tt>kDebug</tt>, <tt>kWarning</tt>, <tt>kError</tt> and <tt>kFatal</tt> to indicate which component output the line. For example, the line in the above example would produce the line <pre>kwalletmanager(642)/kdeui (KNotification) KStatusNotifierItemPrivate::registerToDaemon: Registering a client interface to the KStatusNotifierWatcher</pre><br />
when executed from within the application kwalletmanager, with PID 642.<br />
<br />
For applications, you can generally just omit the area number, and <tt>kDebug</tt> will use the default area. If you are developing a library or a plugin, though, you should get a number assigned (via the kde-core-devel mailing list) for your library or plugin, and use it in your code. Rather than using the number directly in every call to <tt>kDebug</tt> and friends, you can just add<syntaxhighlight lang="cmake"><br />
add_definition(-DKDE_DEFAULT_DEBUG_AREA=<number>)<br />
</syntaxhighlight><br />
to your <tt>CMakeLists.txt</tt> file.<br />
<br />
== Improving Log Output ==<br />
<br />
There are a couple of useful environment variables to control the output of <tt>kDebug</tt> and friends. <syntaxhighlight lang="bash"><br />
export KDE_COLOR_DEBUG=1<br />
</syntaxhighlight> will make them produce colored output, and <syntaxhighlight lang="bash"><br />
export KDE_DEBUG_TIMESTAMP=1<br />
</syntaxhighlight> will include timestamps in the output. <syntaxhighlight lang="bash"><br />
export KDE_DEBUG_TIMESTAMP=2<br />
</syntaxhighlight> can be used to include milliseconds in the timestamps.<br />
<br />
<br />
<br />
<syntaxhighlight lang="bash"><br />
export KDE_DEBUG_NOPROCESSINFO=1<br />
export KDE_DEBUG_NOAREANAME=1<br />
export KDE_DEBUG_NOMETHODNAME=1<br />
export KDE_DEBUG_FILELINE=1<br />
</syntaxhighlight>The above commands toggle various components of the debug messages.<br />
<br />
== Managing Lots of Output ==<br />
<br />
If you have lots of debugging statements, they may appear too fast and leave the terminal window before you can read them. There are three main ways to deal with this:<br />
# Use <tt>kdebugdialog</tt> to disable some logging areas to limit the amount of output generated<br />
# Increase the amount of scrollback in the terminal so that output is not lost; in Konsole, you can go to <tt>Settings > Edit Current Profile...</tt> and click on the <tt>Scrollback</tt> tab to change this. Konsole also has a useful search feature: just press <tt>Ctrl+Shift+F</tt> or click <tt>Find...</tt> on the <tt>Edit</tt> menu.<br />
# Save the output to a file; <tt>tee</tt> is useful for this. For example, you can run <pre>application 2&gt;&amp;1 | tee debug.log</pre> to save the output to the file <tt>debug.log</tt> while still viewing it in the terminal. This can also be used to capture output from <tt>startx</tt>.</div>Gjditchfieldhttps://community.kde.org/index.php?title=Guidelines_and_HOWTOs/Debugging/Using_Error_Messages&diff=90084Guidelines and HOWTOs/Debugging/Using Error Messages2020-09-07T17:00:13Z<p>Gjditchfield: Move long-deprecated material to the bottom.</p>
<hr />
<div>= Qt 5 / KDE Frameworks 5 =<br />
<br />
<tt>kDebug()</tt> and friends have been deprecated in KDE Frameworks 5, and you should use Qt's built-in debugging instead. We recommend that you use [https://doc.qt.io/qt-5/qloggingcategory.html QLoggingCategory], particularly for libraries and plugins. Note that this is only available in Qt 5.2 and later.<br />
<br />
In particular, for a library or plugin called "Foo", you should have a common header that contains the following declaration (e.g. called "foo-debug,hpp")<br />
<syntaxhighlight lang="cpp-qt"><br />
#include <QLoggingCategory><br />
Q_DECLARE_LOGGING_CATEGORY(LOG_FOO)<br />
</syntaxhighlight> and exactly one source file containing <syntaxhighlight lang="cpp-qt"><br />
#include "foo-debug.hpp"<br />
Q_LOGGING_CATEGORY(LOG_FOO, "some.namespace.foo")<br />
</syntaxhighlight><br />
<br />
You should treat the identifier string ("some.namespace.foo" in the example) as something like reverse DNS; it cannot contain spaces, and dots indicate a hierarchy. For example, a Plasma dataengine called "Foo" might use the category <tt>"plasma.engine.foo"</tt>.<br />
<br />
To simplify the setup, you can use the ECM macro <tt>[https://api.kde.org/ecm/module/ECMQtDeclareLoggingCategory.html ecm_qt_declare_logging_category]</tt>, which generates the respective source files for you:<br />
<syntaxhighlight lang="cmake"><br />
include(ECMQtDeclareLoggingCategory)<br />
ecm_qt_declare_logging_category(FOO_SRCS<br />
HEADER foo-debug.hpp<br />
IDENTIFIER "LOG_FOO"<br />
CATEGORY_NAME "some.namespace.foo"<br />
)<br />
</syntaxhighlight><br />
<br />
Logging lines then look like <syntaxhighlight lang="cpp-qt"><br />
#include "foo-debug.hpp"<br />
qCDebug(LOG_FOO) << "Log something:" << someVariable;<br />
qCWarning(LOG_FOO) << "Something bad happened that users (end-users, or application developers using this library) should be aware of";<br />
qCCritical(LOG_FOO) << "Something happened so bad we had to terminate the application";<br />
</syntaxhighlight><br />
<br />
The syntax is much like <tt>cout</tt>, and most native C++ types, Qt-provided types and KF-provided types can be passed directly (like with <tt>someVariable</tt> in the example).<br />
<br />
With Qt 5.2, the <tt>qCDebug</tt> line will not produce any output; this is because logging categories are disabled by default. You need to include the line <syntaxhighlight lang="cpp-qt"><br />
QLoggingCategory::setFilterRules(QStringLiteral("foo.debug = true"));<br />
</syntaxhighlight><br />
somewhere in the application code, generally in the <tt>main()</tt> function. Of course, you would typically disable this call in release versions.<br />
Since Qt 5.3, logging can be controlled at run-time through the <tt>QT_LOGGING_RULES</tt> environment variable or the configuration file <tt>qtlogging.ini</tt> (location is platform-dependent or supplied in environment variable <tt>QT_LOGGING_CONF</tt>).<br />
<br />
If you run your application from within a terminal application, like [http://www.kde.org/applications/system/konsole/ Konsole], you will see the logging output in that terminal window. Otherwise, it will usually appear in {{path|~/.xsession-errors}} or {{path|~/.X.err}} if you use a login manager like KDM, or on the console you ran <tt>startx</tt> from if you started X that way. Also check the systemd log, since the application could be its direct childprocess.<br />
<br />
== Improving Logging Output ==<br />
<br />
Qt provides a way of controlling the output of the logging methods via an environment variable. You can tell it to include the application name and PID, as well as the debugging category, and color-code the text. For example, running the following lines in your shell will produce something that looks quite like <tt>kDebug</tt>'s colored output: <syntaxhighlight lang="bash"><br />
c=`echo -e "\033"`<br />
export QT_MESSAGE_PATTERN="%{appname}(%{pid})/(%{category}) $c\[31m%{if-debug}$c\[34m%{endif}%{function}$c\[0m: %{message}"<br />
unset c<br />
</syntaxhighlight><br />
<br />
== Managing Lots of Output ==<br />
<br />
If you have lots of debugging statements, they may appear too fast and leave the terminal window before you can read them. There are three main ways to deal with this:<br />
# [https://doc.qt.io/qt-5/qloggingcategory.html#setFilterRules Disable some logging categories] to limit the amount of output generated<br />
# Increase the amount of scrollback in the terminal so that output is not lost; in Konsole, you can go to <tt>Settings > Edit Current Profile...</tt> and click on the <tt>Scrollback</tt> tab to change this. Konsole also has a useful search feature: just press <tt>Ctrl+Shift+F</tt> or click <tt>Find...</tt> on the <tt>Edit</tt> menu.<br />
# Save the output to a file; <tt>tee</tt> is useful for this. For example, you can run <syntaxhighlight lang="bash">application 2>&1 | tee debug.log</syntaxhighlight> to save the output to the file <tt>debug.log</tt> while still viewing it in the terminal.<br />
<br />
= Qt 4 / kdelibs 4 =<br />
<br />
kdelibs provides a [http://api.kde.org/4.0-api/kdelibs-apidocs/kdecore/html/group__kdebug.html family of functions] that output information to <tt>stderr</tt>, meaning that if you run an application from the terminal, it will be displayed in that terminal window. If you run the application from the desktop (using KRunner or the application menu, for example), the output will normally appear in {{path|~/.xsession-errors}} or {{path|~/.X.err}} if you use a login manager like KDM, or on the console you ran <tt>startx</tt> from if you started X that way.<br />
<br />
To use these functions in your code, you need to include the correct header file <syntaxhighlight lang="cpp-qt"><br />
#include <KDebug><br />
</syntaxhighlight><br />
and then you can use the functions <syntaxhighlight lang="cpp-qt"><br />
kDebug() << "Something happened that only developers care about" << someVariable;<br />
kWarning() << "Something bad happened that users (end-users, or application developers using this library) should be aware of";<br />
kError() << "Something even worse happened";<br />
kFatal() << "Something happened so bad we had to terminate the application";<br />
</syntaxhighlight><br />
<br />
The syntax is much like <tt>cout</tt>, and most native C++ types, Qt-provided types and kdelibs-provided types can be passed directly (like with <tt>someVariable</tt> in the example).<br />
<br />
Note that the <tt>kDebug</tt> calls will only do anything if the code was compiled with debugging enabled (and so will generally not work in packages from a distribution). This means <tt>cmake</tt> should be run with the <tt>-DCMAKE_BUILD_TYPE=debugfull</tt> argument. The other functions, however, will produce output no matter how the code was compiled.<br />
<br />
== Debug Areas ==<br />
<br />
The debugging output can be controlled at runtime using debugging areas. This allows enabling debugging output for only certain libraries or plugins, for example. Debugging areas are numbers, so the <tt>KStatusNotifierItemPrivate::registerToDaemon</tt> method in the kdeui library, for example, has the call <syntaxhighlight lang="cpp-qt"><br />
kDebug(299) << "Registering a client interface to the KStatusNotifierWatcher";<br />
</syntaxhighlight><br />
The file <tt>kdebug.areas</tt> in the <tt>kdecore</tt> directory of kdelibs indicates that the number 299 is associated with "kdeui (KNotification)".<br />
<br />
This information is used by the <tt>kdebugdialog</tt> utility (which you can just run from the commandline or using KRunner) to turn these areas on and off, enabling or disabling those <tt>kDebug</tt> statements. It is also used by <tt>kDebug</tt>, <tt>kWarning</tt>, <tt>kError</tt> and <tt>kFatal</tt> to indicate which component output the line. For example, the line in the above example would produce the line <pre>kwalletmanager(642)/kdeui (KNotification) KStatusNotifierItemPrivate::registerToDaemon: Registering a client interface to the KStatusNotifierWatcher</pre><br />
when executed from within the application kwalletmanager, with PID 642.<br />
<br />
For applications, you can generally just omit the area number, and <tt>kDebug</tt> will use the default area. If you are developing a library or a plugin, though, you should get a number assigned (via the kde-core-devel mailing list) for your library or plugin, and use it in your code. Rather than using the number directly in every call to <tt>kDebug</tt> and friends, you can just add<syntaxhighlight lang="cmake"><br />
add_definition(-DKDE_DEFAULT_DEBUG_AREA=<number>)<br />
</syntaxhighlight><br />
to your <tt>CMakeLists.txt</tt> file.<br />
<br />
== Improving Log Output ==<br />
<br />
There are a couple of useful environment variables to control the output of <tt>kDebug</tt> and friends. <syntaxhighlight lang="bash"><br />
export KDE_COLOR_DEBUG=1<br />
</syntaxhighlight> will make them produce colored output, and <syntaxhighlight lang="bash"><br />
export KDE_DEBUG_TIMESTAMP=1<br />
</syntaxhighlight> will include timestamps in the output. <syntaxhighlight lang="bash"><br />
export KDE_DEBUG_TIMESTAMP=2<br />
</syntaxhighlight> can be used to include milliseconds in the timestamps.<br />
<br />
<br />
<br />
<syntaxhighlight lang="bash"><br />
export KDE_DEBUG_NOPROCESSINFO=1<br />
export KDE_DEBUG_NOAREANAME=1<br />
export KDE_DEBUG_NOMETHODNAME=1<br />
export KDE_DEBUG_FILELINE=1<br />
</syntaxhighlight>The above commands toggle various components of the debug messages.<br />
<br />
== Managing Lots of Output ==<br />
<br />
If you have lots of debugging statements, they may appear too fast and leave the terminal window before you can read them. There are three main ways to deal with this:<br />
# Use <tt>kdebugdialog</tt> to disable some logging areas to limit the amount of output generated<br />
# Increase the amount of scrollback in the terminal so that output is not lost; in Konsole, you can go to <tt>Settings > Edit Current Profile...</tt> and click on the <tt>Scrollback</tt> tab to change this. Konsole also has a useful search feature: just press <tt>Ctrl+Shift+F</tt> or click <tt>Find...</tt> on the <tt>Edit</tt> menu.<br />
# Save the output to a file; <tt>tee</tt> is useful for this. For example, you can run <pre>application 2&gt;&amp;1 | tee debug.log</pre> to save the output to the file <tt>debug.log</tt> while still viewing it in the terminal. This can also be used to capture output from <tt>startx</tt>.</div>Gjditchfieldhttps://community.kde.org/index.php?title=Guidelines_and_HOWTOs/Debugging&diff=90083Guidelines and HOWTOs/Debugging2020-09-07T16:15:37Z<p>Gjditchfield: /* Related Pages (Misc) */</p>
<hr />
<div>[[File:Mascot konqi-support-bughunt.png|thumbnail|right|Help [[Konqi]] to catch some bugs!]]<br />
Debugging KDE applications can be done on different levels. Most applications "talk" invisibly through debug statements while they are running. Looking at this information mostly gives you enough info to find out what went wrong. For further details, read the dedicated article about [[/Using Error Messages|error messages]].<br />
<br />
On a different level we have post-mortem debugging. This is used ''after'' an application died, probably because of a programming error. The drkonqi dialog allows you to create a '''backtrace''', and possibly find out where it went wrong.<br />
<br />
There are debuggers like gdb which can do a lot more than just find out where it went wrong. You should read the man page of gdb to find out more, and possibly download 'kdbg', 'ddd', or 'inspire' which make gdb a lot simpler to use. Read [[/Debugging with GDB|Debugging with GDB]] for a detailed tutorial.<br />
<br />
== Tools ==<br />
; Valgrind<br />
: [[/Valgrind|Valgrind]] helps to find memory leaks and uninitialized memory blocks. Additional features are a profiler and more. Valgrind is one of the most important development tools!<br />
<br />
; The GNU Project Debugger (GDB)<br />
: [http://sources.redhat.com/gdb/ GDB] helps in debugging source code. As GDB evolved over time it is recommended to use version 6.x. Graphical frontends are available (see below). See also the debugging tutorial [[/Debugging with GDB|Debugging with GDB]].<br />
<br />
; KDbg and DDD<br />
: [http://www.kdbg.org/ KDbg] and [http://www.gnu.org/software/ddd/ DDD] are graphical user interfaces to GDB, which are able to set breakpoints, step through the code etc.<br />
<br />
; MS Windows tools (Process Explorer, Console, WinDbg, DebugView...)<br />
: More info on the [[Windows|KDE on Windows page]].<br />
<br />
== Related Pages (Misc) ==<br />
*[[Plasma/Debugging | Debugging Plasmashell and widgets]]<br />
*[[KWin/Debugging | Debugging KWin]]<br />
*[[/Shared Memory Usage in KDE|Shared Memory Usage in KDE]]<br />
*[[/Using Error Messages|Controlling logging and error messages]]<br />
*[[/Debugging symbols|Debugging symbols]]<br />
*[[/Debugging with GDB|Debugging with GDB]]<br />
*[[/Valgrind|Debugging with Valgrind]]<br />
*[[/Debugging IOSlaves|Debugging IOSlaves]]<br />
*[[/MS Windows|Debugging on MS Windows]]<br />
*[[KDE/FAQs/Debugging_FAQ|Debugging FAQ]]<br />
*[[/How to create useful crash reports|How to create useful crash reports]]<br />
*[[/Phonon|Debugging Phonon]]<br />
*[[PIM/Akonadi/Debug_IMAP | Debugging IMAP for Akonadi/KMail]]<br />
*[https://fedoraproject.org/wiki/KDE/Debugging Fedora wiki on debugging KDE]<br />
<br />
[[Category:Programming]]</div>Gjditchfieldhttps://community.kde.org/index.php?title=Guidelines_and_HOWTOs/Submit_an_icon&diff=88911Guidelines and HOWTOs/Submit an icon2020-07-07T16:52:32Z<p>Gjditchfield: /* Start working */</p>
<hr />
<div>= Get the Breeze Icons git repository =<br />
First you will need to download the breeze-icons source code repository, build it, create a personal fork of it, and set up your personal fork as a new "remote" in your local download of the git repository. Instructions for doing that can be found here: https://community.kde.org/Infrastructure/GitLab<br />
<br />
<br />
= Start working =<br />
As part of the previous step, you should have a git repository named "breeze-icons" at <tt>[your home folder]/kde/src/breeze-icons/</tt>. You can now get on with editing an existing icon or submitting a new one. Explore the folder structure inside <tt>[your home folder]/kde/src/breeze-icons/</tt> a bit to get a feel for the structure of the repository--where things live, what kinds of icons go where, and so on. The [https://hig.kde.org/style/icons/index.html Breeze Icon design guidelines] offer more information, and helpful guidelines regarding the style you should match.<br />
<br />
KDE icons are SVG files. We recommend the use of Inkscape or another free, open-source vector graphics editor for maximum compatibility.<br />
<br />
= How to edit an existing icon =<br />
Many KDE icons come in multiple versions that are optimized for various sizes, so once you're done editing one version, make sure to edit the others accordingly. For example, if you edit <tt>icons/actions/16/document-open-recent.svg</tt>, you will also need to edit the versions of that file present in <tt>icons/actions/22</tt>, <tt>icons/actions/16</tt>, <tt>icons/actions/24</tt>, and <tt>icons/actions/32</tt>.<br />
<br />
<br />
= How to create a new icon =<br />
To submit a new icon, first create it in your vector graphics editor, and then once you're done, save it to the appropriate place in the <tt>breeze-icons</tt> repository. For example if you are submitting a new app icon, you would save it inside <tt>[your home folder]/kde/src/breeze-icons/icons/apps/48/</tt>. If you are submitting an icon of a type that comes in multiple sizes (for example, an action icon), be sure to save each version to the correct location.<br />
<br />
<br />
= Post-processing steps =<br />
After using Inkscape to edit an icon or create a new icon, you will want to do two things before you submit your work for review<br />
# Run an SVG optimizer on it. To learn how to do this, see https://community.kde.org/Guidelines_and_HOWTOs/Icon_Workflow_Tips#SVG_optimization<br />
# If it is a monochrome icon, fix the internal stylesheets, which inkscape has mangled. TO learn how to do this, see https://community.kde.org/Guidelines_and_HOWTOs/Icon_Workflow_Tips#Embedding_stylesheets_in_SVGs<br />
<br />
<br />
= Submit your proposal for review =<br />
Once you have edited some icons and/or saved your proposed new icons to the appropriate places, it's time to submit a merge request so that the KDE VDG team can review it. Type the following commands in your terminal window, pressing the enter or return key after each line, as before:<br />
{{Input|1=<nowiki><br />
cd ~/kde/src/breeze-icons/<br />
git checkout -b some-name-of-your-choosing-for-this-git-branch<br />
git add .<br />
git commit<br />
[now enter a good commit message]<br />
git push fork<br />
</nowiki>}}<br />
<br />
After you push the new commit to your fork using the last command, the terminal window will display a link you can visit to create a Merge Request. You can control-click on that link to open it directly in your web browser.<br />
<br />
To finalize submitting your merge request, see https://community.kde.org/Infrastructure/GitLab#Create_the_Merge_Request<br />
<br />
<br />
= What happens next =<br />
Members of the KDE VDG team will review your patch and propose improvements. This is a conversation that you can and should participate in! If changes are requested, you should make those changes in your local breeze-icons git repo and push them to your fork. To learn how to do this, see https://community.kde.org/Infrastructure/GitLab#What_happens_next.3F.<br />
<br />
Keep going until your reviewers are happy. At that point the merge request will be merged and you'll officially be a KDE contributor!<br />
<br />
<br />
= Tips and tricks =<br />
Also, it's helpful if you go through the full development environment setup (https://community.kde.org/Get_Involved/development#One-time_setup:_your_development_environment) This will allow you to compile the tests in the Breeze Icons repo and then run them:<br />
{{Input|1=<nowiki><br />
kdesrc-build breeze-icons<br />
cd ~/kde/build/breeze-icons/<br />
ctest<br />
</nowiki>}}<br />
<br />
See [[Guidelines and HOWTOs/Icon Workflow Tips]] for more information about size optimization, embedding stylesheets into monochrome icons, and making compilation images for putting into your review request.</div>Gjditchfieldhttps://community.kde.org/index.php?title=Infrastructure/GitLab&diff=88909Infrastructure/GitLab2020-07-07T13:55:02Z<p>Gjditchfield: Use a URL for a page that documents the special messages.</p>
<hr />
<div>KDE uses a GitLab instance at https://invent.kde.org for code review (as well as hosting and other important collaboration tasks). This page is intended to serve as a general-purpose introduction to the most important aspects: submitting and reviewing Merge Requests.<br />
<br />
{{Info|The KDE community does not generally use GitLab for bug reporting. Please continue to submit bug reports on https://bugs.kde.org. Task management is transitioning to Invent/GitLab but most projects still use https://phabricator.kde.org for now.}}<br />
<br />
<br />
= Workflow =<br />
The sanest and easiest way to submit code to KDE is by following a typical '''feature branch workflow''': keep your master branch synchronized with the origin repository, and make all changes on separate branches. '''Each Merge Request needs its own private, temporary branch.''' Once your Merge Request has been merged, delete the feature branch, and make another new branch for the next Merge Request. In this way, you can be working on multiple changes at once without them colliding with one another because each one lives on its own branch.<br />
<br />
<br />
= Logging in =<br />
Navigate to https://invent.kde.org/users/sign_in and log in using the username and password for your KDE Identity account. If you don't have one, you can [https://identity.kde.org/index.php?r=registration/index sign up for one here].<br />
<br />
== Setting up git ==<br />
<br />
You will need to set up git to use you account details to help identify your work: <br />
<br />
<syntaxhighlight><br />
git config user.name <Your Real Name><br />
git config user.email <Your identity.kde.org email><br />
</syntaxhighlight><br />
<br />
(You can set it up globally with --global)<br />
<br />
= Submitting a merge request =<br />
Contributing to KDE code using GitLab involves '''submitting a Merge Request'''. A Merge Request is a request to merge some of your code into the project's permanent source code repo. Here's how:<br />
<br />
== Build the project from source and make your change ==<br />
First you need to check out the project, compile it from source, and make some changes that you would like to submit to KDE! Instructions for doing this can be found at [[Get Involved/development]]. You will wind up with a checkout of the project at <tt>~/kde/src/[the project name]</tt> with some local changes applied to it.<br />
<br />
{{Info|If you prefer a different organizational structure for source code on your machine, you can of course check out the local copy of your KDE repos wherever you want. However for the purposes of this documentation, we will assume that they are located inside <tt>~/kde/src/</tt>}}<br />
<br />
== Fork the project ==<br />
Once you have made some local changes that you would like to submit to KDE, you need to create a personal fork of the project and push your changes to the forked copy.<br />
<br />
Navigate to https://invent.kde.org/kde and locate the project. If it is not visible in the list, you can use the search field. Once you find the project, click on it:<br />
<br />
[[File:Find_the_project.png|600px]]<br />
<br />
On the project's page, click on the "Fork" button in the top-right corner of the screen:<br />
<br />
[[File:Click_the_Fork_button.png|300px]]<br />
<br />
This will take you to a page asking you what namespace you want to create the project under. Click on yourself: <br />
<br />
[[File:Choose_the_fork_namespace.png|400px]]<br />
<br />
After a moment, the system will finish creating the fork and take you to the page for your fork. On that page, click on the blue "Clone" button in the top-right corner:<br />
<br />
[[File:Click_the_Clone_button.png|300px]]<br />
<br />
In the pop-up that appears, click on the "copy" button to the right of the upper text field. This will copy the URL for the fork onto your clipboard.<br />
<br />
[[File:Copy_the_URL.png|300px]]<br />
<br />
== Add the fork to your source checkout ==<br />
Next, open your terminal app and navigate to the location where the project's repo lives (i.e. <tt>~/kde/src/[the project name]</tt>).<br />
<br />
You need to add your fork as a '''remote''' to the existing repo:<br />
<br />
{{Input|1=<nowiki><br />
git remote add fork [the URL you copied to your clipboard]<br />
</nowiki>}}<br />
<br />
Run <tt>git remote -v</tt>. You should see something like this:<br />
<br />
<pre><br />
$ git remote -v<br />
fork git@invent.kde.org:ngraham/kid3.git (fetch)<br />
fork git@invent.kde.org:ngraham/kid3.git (push)<br />
origin https://invent.kde.org/kde/kid3.git (fetch)<br />
origin https://invent.kde.org/kde/kid3.git (push)<br />
</pre><br />
<br />
This means you have two remotes set up for your repo: "origin" points to the original repo, and "fork" points to your fork of it.<br />
<br />
== Make a branch and commit ==<br />
Now that you have your fork set up, it's time to create a branch to track your work and make a commit.<br />
<br />
{{Input|1=<nowiki><br />
git checkout -b my_awesome_feature<br />
git add [the files you changed]<br />
git commit<br />
</nowiki>}}<br />
<br />
== Write a good commit message ==<br />
With Gitlab, once a merge request is merged, the first commit in the merge request will wind up in the Git history, so it is very important that the first commit in the merge request be formatted properly.<br />
<br />
Please follow [https://chris.beams.io/posts/git-commit/#seven-rules commit message best practices]: write a descriptive title in the form of an imperative sentence (e.g. "Fix button disappearing when view is changed") and on the next line, write at least one sentence describing your change and why it is necessary, adding more details if necessary.<br />
<br />
If your patch is intended to fix a Bugzilla ticket, include the following on its own line:<br />
<pre><br />
BUG: 385942<br />
</pre><br />
(The number should be just the Bugzilla ticket number, '''not the full URL''')<br />
<br />
[https://community.kde.org/Policies/Commit_Policy#Special_keywords_in_GIT_and_SVN_log_messages Here is more information] about other special messages that interact with Bugzilla tickets.<br />
<br />
{{Note|These keywords will only work if the email address in your <tt>~/.config/git/config</tt> file matches the email address used for your https://bugs.kde.org account (See [https://techbase.kde.org/Development/Git/Configuration#Basic_Settings this page] for more information).}}<br />
<br />
{{Note|If a merge request has only a single commit in it, then this commit will be added to the immutable git history with no opportunity to change it from the web interface before merging. This is different from Phabricator, in which the patch's title and summary are transformed into a commit message, overwriting the underlying commit message itself. So it is very important to format your commit message well. The web interface can only be used to rewrite a commit message when a merge request has at least two commits, and they are squashed when merging, and the person doing the merging writes a new commit message in the process of squashing.}}<br />
<br />
== Push to your fork ==<br />
At this point you have a branch in your local repo called "my_awesome_feature" (Hopefully in reality it is named something a bit more appropriate!) that has a commit on it with your work. Now push it to your fork:<br />
<br />
{{Input|1=<nowiki><br />
git push fork my_awesome_feature<br />
</nowiki>}}<br />
<br />
This will produce a message somewhat like this:<br />
<br />
<pre><br />
$ git push fork my_awesome_feature<br />
Enumerating objects: 5, done.<br />
Counting objects: 100% (5/5), done.<br />
Delta compression using up to 4 threads<br />
Compressing objects: 100% (3/3), done.<br />
Writing objects: 100% (3/3), 303 bytes | 303.00 KiB/s, done.<br />
Total 3 (delta 2), reused 0 (delta 0)<br />
remote: This commit is available for viewing at:<br />
remote: https://invent.kde.org/ngraham/kid3/commit/23a702439f494806cf3cfe14f212df58a0075bba<br />
remote: <br />
remote: To create a merge request for my_awesome_feature, visit:<br />
remote: https://invent.kde.org/ngraham/kid3/merge_requests/new?merge_request%5Bsource_branch%5D=my_awesome_feature<br />
remote: <br />
To invent.kde.org:ngraham/kid3.git<br />
* [new branch] my_awesome_feature -> my_awesome_feature<br />
</pre><br />
<br />
== Create the Merge Request ==<br />
Notice the "To create a merge request for my_awesome_feature..." message above. You can copy-and-paste the URL shown below it into a web browser. On some terminal apps, such as Konsole and Yakuake, you can ctrl+click on the link to go right there!<br />
<br />
You will be taken to a web page that looks like this:<br />
<br />
[[File:Merge_Request_info.png|800px]]<br />
<br />
In the Description section, write at least one sentence describing your change and why it is necessary, adding more details if needed. For Merge Requests that change something about the appearance or user interface, it's customary to include a screenshot of how the interface looks after your change has been applied. Bonus points for including a "Before" image too, so reviewers can easily compare them.<br />
<br />
In the section below, it is very important that you make sure all three checkboxes are checked:<br />
<br />
[[File:Check_all_checkboxes!.png|500px]]<br />
<br />
Once you're done with that, click the "Submit Merge Request" button!<br />
<br />
[[File:Submit_Merge_Request.png|400px]]<br />
<br />
<br />
== What happens next? ==<br />
After you've submitted your Merge Request, KDE developers who work with the software in question will review it and provide feedback. This can often take a few days. However, if nobody has responded after a week, it's likely that the review was overlooked (sorry about that!) and it's appropriate to make a comment saying, "Ping!" or something to that effect.<br />
<br />
Once the Merge Request is accepted, KDE Developers will merge it for you!<br />
<br />
== Making changes to a Merge Request ==<br />
Oftentimes, reviewers will request changes before the Merge Request can be merged. To accomplish this, you make the requested changes locally, then create a new commit including your changes. First, stage all changed files:<br />
<br />
{{Input|1=<nowiki><br />
git add -u<br />
</nowiki>}}<br />
<br />
Now make a new commit with the staged files:<br />
<br />
{{Input|1=<nowiki><br />
git commit<br />
</nowiki>}}<br />
<br />
Then push the local branch with the new commit on it up to the remote branch:<br />
{{Input|1=<nowiki><br />
git push fork<br />
</nowiki>}}<br />
<br />
== Rebasing a Merge Request ==<br />
When other changes have been made to the project's source code repo since you submitted your merge request, you will need to ''rebase'' the Merge Request to incorporate those changes. Here's how you do so:<br />
<br />
{{Input|1=<nowiki><br />
# First, make sure you are on the branch for your merge request<br />
git fetch<br />
git pull --rebase origin master<br />
</nowiki>}}<br />
<br />
At this point, there may be merge conflicts. If there are, git will tell you which files have conflicts. Open each file and resolve the conflict by exiting the contents to keep only the appropriate change. Then run <tt>git add [file path]</tt> on each conflicted file once all the conflicts have been resolved.<br />
<br />
Now, you need to overwrite the version of the Merge Request on your remote branch with the version on your local branch. To do this, you have to force-push:<br />
<br />
{{Input|1=<nowiki><br />
git push --force fork<br />
</nowiki>}}<br />
<br />
<br />
= Testing someone else's Merge Request =<br />
First you'll need a development environment set up. If you haven't done that yet, it's time to do so. Follow the instructions on [[Get_Involved/development#Set_up_your_development_environment]]. It is also advisable to set up the <tt>git mr</tt> tool, which makes testing Merge Requests a breeze. Here's how to install it, depending on your operating system:<br />
<br />
''' Arch / Manjaro '''<br />
{{Input|1=<nowiki><br />
yay -S git-mr<br />
</nowiki>}}<br />
<br />
'''Debian/Ubuntu/KDE Neon'''<br />
{{Input|1=<nowiki><br />
sudo apt install git-extras<br />
</nowiki>}}<br />
<br />
''' Fedora '''<br />
{{Input|1=<nowiki><br />
sudo dnf install git-extras<br />
</nowiki>}}<br />
<br />
''' OpenSUSE '''<br />
{{Input|1=<nowiki><br />
sudo zypper install git-mr<br />
</nowiki>}}<br />
<br />
== Check out the Merge Request and compile the software ==<br />
First check out or enter the source repository for the software that's being patched. For example, let's say you want to test a Merge Request for Okular. If you've never built it before, check it out and build it once first:<br />
{{Input|1=<nowiki><br />
kdesrc-build okular<br />
</nowiki>}}<br />
<br />
Now go to its source directory:<br />
{{Input|1=<nowiki><br />
cd ~/kde/src/okular<br />
</nowiki>}}<br />
<br />
Find the Merge Request's ID. For example, for https://invent.kde.org/kde/okular/merge_requests/80, the ID is <tt>80</tt>.<br />
<br />
...and apply the Merge Request:<br />
{{Input|1=<nowiki><br />
git mr 80<br />
</nowiki>}}<br />
<br />
Now it's time to compile and run the software to make sure that the Merge Request does what it says it does and doesn't cause any regressions! Compile the patched source code:<br />
{{Input|1=<nowiki><br />
kdesrc-build okular --no-src --resume-from okular<br />
</nowiki>}}<br />
Those arguments will tell <tt>kdesrc-build</tt> to not update the source code and to not build any dependencies.<br />
<br />
If it didn't compile, that's reason alone to reject the Merge Request! Go to the web page for the Merge Request and report your findings.<br />
<br />
== Perform QA ==<br />
If it did compile, then it's time to perform QA, because it's important to thoroughly test Merge Requests to ensure that bad code and regressions don't slip in. '''This is the entire purpose of having a review infrastructure'''; it is very important.<br />
<br />
First make sure the unit tests all pass:<br />
{{Input|1=<nowiki><br />
cd ~kde/build/kde/applications/okular<br />
ctest<br />
</nowiki>}}<br />
<br />
If any tests fail, report this through a comment on the Merge Request's web page.<br />
<br />
Does everything all still work for you? If not, return to the web page and request changes, writing a detailed comment explaining what didn't work. It is permissible to do this even if you have not been specified as a reviewer! '''Anyone can reject a Merge Request on the grounds that it does not work, does not do what it says it does, or causes regressions.'''<br />
<br />
Next, try to break the Merge Request. Here are some ideas:<br />
# Remove the program's configuration file (<tt>~/.config/<program name>rc</tt> ) and re-open it<br />
# Try the program with a HiDPI scale factor (or without one) or with a different default font size<br />
# If it's a new feature, feed it unexpected input<br />
# Test related functionality<br />
<br />
A good Merge Request will handle corner cases and variations in configuration. The price of configurability is vigilant testing! We owe it to our users to test using many configurations, not just the defaults or our personal settings.<br />
<br />
== Perform code review ==<br />
{{Note|1= Need a section on code review here, preferably written by a core KDE developer or another very experienced developer}}<br />
<br />
== Engage with the author and other reviewers ==<br />
After you have run the program and evaluated the Merge Request, it's time to leave some review comments on the webpage. If you have been specified as a reviewer, or are a member of a group that has been specified as a reviewer, it is permissible to Accept the Merge Request. But keep in mind that reviewing involves responsibility: you are giving a thumbs-up to code that will be run potentially by millions of people. If you accept and land a Merge Request that causes regressions, you will be expected to help fix it if the original author cannot or has disappeared. It's important to take the reviewer role seriously.<br />
<br />
== Make sure the target branch is set correctly ==<br />
When the "Merge" button is clicked, the Merge Request will be landed on the target branch. If the target branch is not correct, or reviewers have decided that it should be landed on a different branch (for example, perhaps the change is a low-risk bugfix suitable for landing on the stable branch), then the target branch must be changed before merging. To do this, edit the merge request by clicking the "Edit" button in the top-right corner of the page and choose a different target branch:<br />
<br />
[[File:Editing_merge_request_target_branch.png]]<br />
<br />
If this results in conflicts, then pull down the remote branch, rebase it locally, and push to the remote branch.<br />
<br />
Note that after committing to the stable branch you are expected to merge that branch to <tt>master</tt> afterwards:<br />
{{Input|1=<nowiki><br />
git checkout release/19.12<br />
git pull<br />
git checkout master<br />
git pull<br />
git merge -s recursive -Xours release/19.12<br />
git push<br />
</nowiki>}}<br />
<br />
{{warning|If at any time you feel nervous or run into trouble, ask your reviewers for help. This can be tricky, and undoing bad merges is a pain in the neck.}}<br />
<br />
= Pushing commits to somebody else's fork =<br />
Sometimes someone will say "hey let's work on my branch together." So you will be pushing commits not to origin, and not to your fork, but to someone else's fork. Let's say you want to work on joliveira's "gsoc2019_numberFormat" branch.<br />
<br />
First you would need to add the URL for his fork as a remote:<br />
<br />
<pre><br />
$ cd ~/kde/src/okular<br />
$ git remote add joliveira_fork git@invent.kde.org:joliveira/okular.git<br />
$ git remote -v<br />
aacid_fork git@invent.kde.org:aacid/okular.git (fetch)<br />
aacid_fork git@invent.kde.org:aacid/okular.git (push)<br />
joliveira_fork git@invent.kde.org:joliveira/okular.git (fetch)<br />
joliveira_fork git@invent.kde.org:joliveira/okular.git (push)<br />
origin https://invent.kde.org/kde/okular.git (fetch)<br />
origin https://invent.kde.org/kde/okular.git (push)<br />
</pre><br />
<br />
Notice how there are now multiple forks set up as remotes.<br />
<br />
Next, you need to fetch all the repo metadata from the new remote:<br />
<br />
{{Input|1=<nowiki><br />
git fetch joliveira_fork<br />
</nowiki>}}<br />
<br />
This will download the list of branches. The next step is to switch to the one you want to collaborate on:<br />
<br />
{{Input|1=<nowiki><br />
git checkout --track joliveira_fork/gsoc2019_numberFormat<br />
</nowiki>}}<br />
<br />
This will create a local branch named "gsoc2019_numberFormat" from the contents of the remote branch joliveira_fork/gsoc2019_numberFormat and that also "tracks" it. This means that if someone else pushes changes to a remote version of that branch, you can run <tt>git pull --rebase</tt> while on your local "gsoc2019_numberFormat" branch to bring it up to date.<br />
<br />
Next, make your changes, add and commit. Then push the changes to the remote joliveira_fork remote:<br />
<br />
{{Input|1=<nowiki><br />
git push joliveira_fork gsoc2019_numberFormat<br />
</nowiki>}}<br />
<br />
= Generating "eternal" URLs to commits or objects in a repository =<br />
<br />
History has taught that no system used by KDE around the code repositories stays forever.<br />
Quickgit, CGit, Phabricator & Co. came and at one point were replaced again. Sadly also taking with them the service-specific URLs (and host names).<br />
<br />
To give documentation, blog posts, commit messages and other long-living documents a way to reference commits or objects in the repository like directories or files, at given branches or at given tags, the service commits.kde.org exists. It maps and forwards URLs to the respective current service URLs.<br />
<br />
The pattern for URLs to commits is this:<br />
<pre><br />
https://commits.kde.org/<repo-id>/<commit-id><br />
</pre><br />
<br />
Example:<br />
<pre><br />
https://commits.kde.org/kcoreaddons/d2f4d353327b322ee6bfcc303169190ae44393f0<br />
</pre><br />
<br />
The pattern for URLs to objects is like this:<br />
<pre><br />
https://commits.kde.org/<repo-id>[?[path=<pathToFileOrDirectory]&[branch=<branch>|tag=<tag>]]<br />
</pre><br />
<path> should be without a leading /. It defaults to the top-level directory if not set. Either a branch or tag can be passed at which the objects should be shown. It defaults to the main branch (master usually).<br />
<br />
Examples:<br />
<pre><br />
https://commits.kde.org/kcoreaddons?path=src # points to src/ directory in master branch<br />
https://commits.kde.org/kcoreaddons?path=README.md&tag=v5.0.0 # points to README.md file at tag v5.0.0<br />
https://commits.kde.org/kdelibs?path=kdecore/tests&branch=KDE/3.5 # points to kdecore/tests directory in branch KDE/3.5<br />
</pre><br />
<br />
There currently is no service to generate commit.kde.org URLs from URLs for the actual system. This has to be done manually.</div>Gjditchfieldhttps://community.kde.org/index.php?title=Infrastructure/GitLab&diff=88902Infrastructure/GitLab2020-07-07T03:18:46Z<p>Gjditchfield: /* Write a good commit message */</p>
<hr />
<div>KDE uses a GitLab instance at https://invent.kde.org for code review (as well as hosting and other important collaboration tasks). This page is intended to serve as a general-purpose introduction to the most important aspects: submitting and reviewing Merge Requests.<br />
<br />
{{Info|The KDE community does not generally use GitLab for bug reporting. Please continue to submit bug reports on https://bugs.kde.org. Task management is transitioning to Invent/GitLab but most projects still use https://phabricator.kde.org for now.}}<br />
<br />
<br />
= Workflow =<br />
The sanest and easiest way to submit code to KDE is by following a typical '''feature branch workflow''': keep your master branch synchronized with the origin repository, and make all changes on separate branches. '''Each Merge Request needs its own private, temporary branch.''' Once your Merge Request has been merged, delete the feature branch, and make another new branch for the next Merge Request. In this way, you can be working on multiple changes at once without them colliding with one another because each one lives on its own branch.<br />
<br />
<br />
= Logging in =<br />
Navigate to https://invent.kde.org/users/sign_in and log in using the username and password for your KDE Identity account. If you don't have one, you can [https://identity.kde.org/index.php?r=registration/index sign up for one here].<br />
<br />
== Setting up git ==<br />
<br />
You will need to set up git to use you account details to help identify your work: <br />
<br />
<syntaxhighlight><br />
git config user.name <Your Real Name><br />
git config user.email <Your identity.kde.org email><br />
</syntaxhighlight><br />
<br />
(You can set it up globally with --global)<br />
<br />
= Submitting a merge request =<br />
Contributing to KDE code using GitLab involves '''submitting a Merge Request'''. A Merge Request is a request to merge some of your code into the project's permanent source code repo. Here's how:<br />
<br />
== Build the project from source and make your change ==<br />
First you need to check out the project, compile it from source, and make some changes that you would like to submit to KDE! Instructions for doing this can be found at [[Get Involved/development]]. You will wind up with a checkout of the project at <tt>~/kde/src/[the project name]</tt> with some local changes applied to it.<br />
<br />
{{Info|If you prefer a different organizational structure for source code on your machine, you can of course check out the local copy of your KDE repos wherever you want. However for the purposes of this documentation, we will assume that they are located inside <tt>~/kde/src/</tt>}}<br />
<br />
== Fork the project ==<br />
Once you have made some local changes that you would like to submit to KDE, you need to create a personal fork of the project and push your changes to the forked copy.<br />
<br />
Navigate to https://invent.kde.org/kde and locate the project. If it is not visible in the list, you can use the search field. Once you find the project, click on it:<br />
<br />
[[File:Find_the_project.png|600px]]<br />
<br />
On the project's page, click on the "Fork" button in the top-right corner of the screen:<br />
<br />
[[File:Click_the_Fork_button.png|300px]]<br />
<br />
This will take you to a page asking you what namespace you want to create the project under. Click on yourself: <br />
<br />
[[File:Choose_the_fork_namespace.png|400px]]<br />
<br />
After a moment, the system will finish creating the fork and take you to the page for your fork. On that page, click on the blue "Clone" button in the top-right corner:<br />
<br />
[[File:Click_the_Clone_button.png|300px]]<br />
<br />
In the pop-up that appears, click on the "copy" button to the right of the upper text field. This will copy the URL for the fork onto your clipboard.<br />
<br />
[[File:Copy_the_URL.png|300px]]<br />
<br />
== Add the fork to your source checkout ==<br />
Next, open your terminal app and navigate to the location where the project's repo lives (i.e. <tt>~/kde/src/[the project name]</tt>).<br />
<br />
You need to add your fork as a '''remote''' to the existing repo:<br />
<br />
{{Input|1=<nowiki><br />
git remote add fork [the URL you copied to your clipboard]<br />
</nowiki>}}<br />
<br />
Run <tt>git remote -v</tt>. You should see something like this:<br />
<br />
<pre><br />
$ git remote -v<br />
fork git@invent.kde.org:ngraham/kid3.git (fetch)<br />
fork git@invent.kde.org:ngraham/kid3.git (push)<br />
origin https://invent.kde.org/kde/kid3.git (fetch)<br />
origin https://invent.kde.org/kde/kid3.git (push)<br />
</pre><br />
<br />
This means you have two remotes set up for your repo: "origin" points to the original repo, and "fork" points to your fork of it.<br />
<br />
== Make a branch and commit ==<br />
Now that you have your fork set up, it's time to create a branch to track your work and make a commit.<br />
<br />
{{Input|1=<nowiki><br />
git checkout -b my_awesome_feature<br />
git add [the files you changed]<br />
git commit<br />
</nowiki>}}<br />
<br />
== Write a good commit message ==<br />
With Gitlab, once a merge request is merged, the first commit in the merge request will wind up in the Git history, so it is very important that the first commit in the merge request be formatted properly.<br />
<br />
Please follow [https://chris.beams.io/posts/git-commit/#seven-rules commit message best practices]: write a descriptive title in the form of an imperative sentence (e.g. "Fix button disappearing when view is changed") and on the next line, write at least one sentence describing your change and why it is necessary, adding more details if necessary.<br />
<br />
If your patch is intended to fix a Bugzilla ticket, include the following on its own line:<br />
<pre><br />
BUG: 385942<br />
</pre><br />
(The number should be just the Bugzilla ticket number, '''not the full URL''')<br />
<br />
[https://techbase.kde.org/Development/Git/Configuration#Commit_Template Here is more information] about other special messages that interact with Bugzilla tickets.<br />
<br />
{{Note|These keywords will only work if the email address in your <tt>~/.config/git/config</tt> file matches the email address used for your https://bugs.kde.org account (See [https://techbase.kde.org/Development/Git/Configuration#Basic_Settings this page] for more information).}}<br />
<br />
{{Note|If a merge request has only a single commit in it, then this commit will be added to the immutable git history with no opportunity to change it from the web interface before merging. This is different from Phabricator, in which the patch's title and summary are transformed into a commit message, overwriting the underlying commit message itself. So it is very important to format your commit message well. The web interface can only be used to rewrite a commit message when a merge request has at least two commits, and they are squashed when merging, and the person doing the merging writes a new commit message in the process of squashing.}}<br />
<br />
== Push to your fork ==<br />
At this point you have a branch in your local repo called "my_awesome_feature" (Hopefully in reality it is named something a bit more appropriate!) that has a commit on it with your work. Now push it to your fork:<br />
<br />
{{Input|1=<nowiki><br />
git push fork my_awesome_feature<br />
</nowiki>}}<br />
<br />
This will produce a message somewhat like this:<br />
<br />
<pre><br />
$ git push fork my_awesome_feature<br />
Enumerating objects: 5, done.<br />
Counting objects: 100% (5/5), done.<br />
Delta compression using up to 4 threads<br />
Compressing objects: 100% (3/3), done.<br />
Writing objects: 100% (3/3), 303 bytes | 303.00 KiB/s, done.<br />
Total 3 (delta 2), reused 0 (delta 0)<br />
remote: This commit is available for viewing at:<br />
remote: https://invent.kde.org/ngraham/kid3/commit/23a702439f494806cf3cfe14f212df58a0075bba<br />
remote: <br />
remote: To create a merge request for my_awesome_feature, visit:<br />
remote: https://invent.kde.org/ngraham/kid3/merge_requests/new?merge_request%5Bsource_branch%5D=my_awesome_feature<br />
remote: <br />
To invent.kde.org:ngraham/kid3.git<br />
* [new branch] my_awesome_feature -> my_awesome_feature<br />
</pre><br />
<br />
== Create the Merge Request ==<br />
Notice the "To create a merge request for my_awesome_feature..." message above. You can copy-and-paste the URL shown below it into a web browser. On some terminal apps, such as Konsole and Yakuake, you can ctrl+click on the link to go right there!<br />
<br />
You will be taken to a web page that looks like this:<br />
<br />
[[File:Merge_Request_info.png|800px]]<br />
<br />
In the Description section, write at least one sentence describing your change and why it is necessary, adding more details if needed. For Merge Requests that change something about the appearance or user interface, it's customary to include a screenshot of how the interface looks after your change has been applied. Bonus points for including a "Before" image too, so reviewers can easily compare them.<br />
<br />
In the section below, it is very important that you make sure all three checkboxes are checked:<br />
<br />
[[File:Check_all_checkboxes!.png|500px]]<br />
<br />
Once you're done with that, click the "Submit Merge Request" button!<br />
<br />
[[File:Submit_Merge_Request.png|400px]]<br />
<br />
<br />
== What happens next? ==<br />
After you've submitted your Merge Request, KDE developers who work with the software in question will review it and provide feedback. This can often take a few days. However, if nobody has responded after a week, it's likely that the review was overlooked (sorry about that!) and it's appropriate to make a comment saying, "Ping!" or something to that effect.<br />
<br />
Once the Merge Request is accepted, KDE Developers will merge it for you!<br />
<br />
== Making changes to a Merge Request ==<br />
Oftentimes, reviewers will request changes before the Merge Request can be merged. To accomplish this, you make the requested changes locally, then create a new commit including your changes. First, stage all changed files:<br />
<br />
{{Input|1=<nowiki><br />
git add -u<br />
</nowiki>}}<br />
<br />
Now make a new commit with the staged files:<br />
<br />
{{Input|1=<nowiki><br />
git commit<br />
</nowiki>}}<br />
<br />
Then push the local branch with the new commit on it up to the remote branch:<br />
{{Input|1=<nowiki><br />
git push fork<br />
</nowiki>}}<br />
<br />
== Rebasing a Merge Request ==<br />
When other changes have been made to the project's source code repo since you submitted your merge request, you will need to ''rebase'' the Merge Request to incorporate those changes. Here's how you do so:<br />
<br />
{{Input|1=<nowiki><br />
# First, make sure you are on the branch for your merge request<br />
git fetch<br />
git pull --rebase origin master<br />
</nowiki>}}<br />
<br />
At this point, there may be merge conflicts. If there are, git will tell you which files have conflicts. Open each file and resolve the conflict by exiting the contents to keep only the appropriate change. Then run <tt>git add [file path]</tt> on each conflicted file once all the conflicts have been resolved.<br />
<br />
Now, you need to overwrite the version of the Merge Request on your remote branch with the version on your local branch. To do this, you have to force-push:<br />
<br />
{{Input|1=<nowiki><br />
git push --force fork<br />
</nowiki>}}<br />
<br />
<br />
= Testing someone else's Merge Request =<br />
First you'll need a development environment set up. If you haven't done that yet, it's time to do so. Follow the instructions on [[Get_Involved/development#Set_up_your_development_environment]]. It is also advisable to set up the <tt>git mr</tt> tool, which makes testing Merge Requests a breeze. Here's how to install it, depending on your operating system:<br />
<br />
''' Arch / Manjaro '''<br />
{{Input|1=<nowiki><br />
yay -S git-mr<br />
</nowiki>}}<br />
<br />
'''Debian/Ubuntu/KDE Neon'''<br />
{{Input|1=<nowiki><br />
sudo apt install git-extras<br />
</nowiki>}}<br />
<br />
''' Fedora '''<br />
{{Input|1=<nowiki><br />
sudo dnf install git-extras<br />
</nowiki>}}<br />
<br />
''' OpenSUSE '''<br />
{{Input|1=<nowiki><br />
sudo zypper install git-mr<br />
</nowiki>}}<br />
<br />
== Check out the Merge Request and compile the software ==<br />
First check out or enter the source repository for the software that's being patched. For example, let's say you want to test a Merge Request for Okular. If you've never built it before, check it out and build it once first:<br />
{{Input|1=<nowiki><br />
kdesrc-build okular<br />
</nowiki>}}<br />
<br />
Now go to its source directory:<br />
{{Input|1=<nowiki><br />
cd ~/kde/src/okular<br />
</nowiki>}}<br />
<br />
Find the Merge Request's ID. For example, for https://invent.kde.org/kde/okular/merge_requests/80, the ID is <tt>80</tt>.<br />
<br />
...and apply the Merge Request:<br />
{{Input|1=<nowiki><br />
git mr 80<br />
</nowiki>}}<br />
<br />
Now it's time to compile and run the software to make sure that the Merge Request does what it says it does and doesn't cause any regressions! Compile the patched source code:<br />
{{Input|1=<nowiki><br />
kdesrc-build okular --no-src --resume-from okular<br />
</nowiki>}}<br />
Those arguments will tell <tt>kdesrc-build</tt> to not update the source code and to not build any dependencies.<br />
<br />
If it didn't compile, that's reason alone to reject the Merge Request! Go to the web page for the Merge Request and report your findings.<br />
<br />
== Perform QA ==<br />
If it did compile, then it's time to perform QA, because it's important to thoroughly test Merge Requests to ensure that bad code and regressions don't slip in. '''This is the entire purpose of having a review infrastructure'''; it is very important.<br />
<br />
First make sure the unit tests all pass:<br />
{{Input|1=<nowiki><br />
cd ~kde/build/kde/applications/okular<br />
ctest<br />
</nowiki>}}<br />
<br />
If any tests fail, report this through a comment on the Merge Request's web page.<br />
<br />
Does everything all still work for you? If not, return to the web page and request changes, writing a detailed comment explaining what didn't work. It is permissible to do this even if you have not been specified as a reviewer! '''Anyone can reject a Merge Request on the grounds that it does not work, does not do what it says it does, or causes regressions.'''<br />
<br />
Next, try to break the Merge Request. Here are some ideas:<br />
# Remove the program's configuration file (<tt>~/.config/<program name>rc</tt> ) and re-open it<br />
# Try the program with a HiDPI scale factor (or without one) or with a different default font size<br />
# If it's a new feature, feed it unexpected input<br />
# Test related functionality<br />
<br />
A good Merge Request will handle corner cases and variations in configuration. The price of configurability is vigilant testing! We owe it to our users to test using many configurations, not just the defaults or our personal settings.<br />
<br />
== Perform code review ==<br />
{{Note|1= Need a section on code review here, preferably written by a core KDE developer or another very experienced developer}}<br />
<br />
== Engage with the author and other reviewers ==<br />
After you have run the program and evaluated the Merge Request, it's time to leave some review comments on the webpage. If you have been specified as a reviewer, or are a member of a group that has been specified as a reviewer, it is permissible to Accept the Merge Request. But keep in mind that reviewing involves responsibility: you are giving a thumbs-up to code that will be run potentially by millions of people. If you accept and land a Merge Request that causes regressions, you will be expected to help fix it if the original author cannot or has disappeared. It's important to take the reviewer role seriously.<br />
<br />
== Make sure the target branch is set correctly ==<br />
When the "Merge" button is clicked, the Merge Request will be landed on the target branch. If the target branch is not correct, or reviewers have decided that it should be landed on a different branch (for example, perhaps the change is a low-risk bugfix suitable for landing on the stable branch), then the target branch must be changed before merging. To do this, edit the merge request by clicking the "Edit" button in the top-right corner of the page and choose a different target branch:<br />
<br />
[[File:Editing_merge_request_target_branch.png]]<br />
<br />
If this results in conflicts, then pull down the remote branch, rebase it locally, and push to the remote branch.<br />
<br />
Note that after committing to the stable branch you are expected to merge that branch to <tt>master</tt> afterwards:<br />
{{Input|1=<nowiki><br />
git checkout release/19.12<br />
git pull<br />
git checkout master<br />
git pull<br />
git merge -s recursive -Xours release/19.12<br />
git push<br />
</nowiki>}}<br />
<br />
{{warning|If at any time you feel nervous or run into trouble, ask your reviewers for help. This can be tricky, and undoing bad merges is a pain in the neck.}}<br />
<br />
= Pushing commits to somebody else's fork =<br />
Sometimes someone will say "hey let's work on my branch together." So you will be pushing commits not to origin, and not to your fork, but to someone else's fork. Let's say you want to work on joliveira's "gsoc2019_numberFormat" branch.<br />
<br />
First you would need to add the URL for his fork as a remote:<br />
<br />
<pre><br />
$ cd ~/kde/src/okular<br />
$ git remote add joliveira_fork git@invent.kde.org:joliveira/okular.git<br />
$ git remote -v<br />
aacid_fork git@invent.kde.org:aacid/okular.git (fetch)<br />
aacid_fork git@invent.kde.org:aacid/okular.git (push)<br />
joliveira_fork git@invent.kde.org:joliveira/okular.git (fetch)<br />
joliveira_fork git@invent.kde.org:joliveira/okular.git (push)<br />
origin https://invent.kde.org/kde/okular.git (fetch)<br />
origin https://invent.kde.org/kde/okular.git (push)<br />
</pre><br />
<br />
Notice how there are now multiple forks set up as remotes.<br />
<br />
Next, you need to fetch all the repo metadata from the new remote:<br />
<br />
{{Input|1=<nowiki><br />
git fetch joliveira_fork<br />
</nowiki>}}<br />
<br />
This will download the list of branches. The next step is to switch to the one you want to collaborate on:<br />
<br />
{{Input|1=<nowiki><br />
git checkout --track joliveira_fork/gsoc2019_numberFormat<br />
</nowiki>}}<br />
<br />
This will create a local branch named "gsoc2019_numberFormat" from the contents of the remote branch joliveira_fork/gsoc2019_numberFormat and that also "tracks" it. This means that if someone else pushes changes to a remote version of that branch, you can run <tt>git pull --rebase</tt> while on your local "gsoc2019_numberFormat" branch to bring it up to date.<br />
<br />
Next, make your changes, add and commit. Then push the changes to the remote joliveira_fork remote:<br />
<br />
{{Input|1=<nowiki><br />
git push joliveira_fork gsoc2019_numberFormat<br />
</nowiki>}}<br />
<br />
= Generating "eternal" URLs to commits or objects in a repository =<br />
<br />
History has taught that no system used by KDE around the code repositories stays forever.<br />
Quickgit, CGit, Phabricator & Co. came and at one point were replaced again. Sadly also taking with them the service-specific URLs (and host names).<br />
<br />
To give documentation, blog posts, commit messages and other long-living documents a way to reference commits or objects in the repository like directories or files, at given branches or at given tags, the service commits.kde.org exists. It maps and forwards URLs to the respective current service URLs.<br />
<br />
The pattern for URLs to commits is this:<br />
<pre><br />
https://commits.kde.org/<repo-id>/<commit-id><br />
</pre><br />
<br />
Example:<br />
<pre><br />
https://commits.kde.org/kcoreaddons/d2f4d353327b322ee6bfcc303169190ae44393f0<br />
</pre><br />
<br />
The pattern for URLs to objects is like this:<br />
<pre><br />
https://commits.kde.org/<repo-id>[?[path=<pathToFileOrDirectory]&[branch=<branch>|tag=<tag>]]<br />
</pre><br />
<path> should be without a leading /. It defaults to the top-level directory if not set. Either a branch or tag can be passed at which the objects should be shown. It defaults to the main branch (master usually).<br />
<br />
Examples:<br />
<pre><br />
https://commits.kde.org/kcoreaddons?path=src # points to src/ directory in master branch<br />
https://commits.kde.org/kcoreaddons?path=README.md&tag=v5.0.0 # points to README.md file at tag v5.0.0<br />
https://commits.kde.org/kdelibs?path=kdecore/tests&branch=KDE/3.5 # points to kdecore/tests directory in branch KDE/3.5<br />
</pre><br />
<br />
There currently is no service to generate commit.kde.org URLs from URLs for the actual system. This has to be done manually.</div>Gjditchfieldhttps://community.kde.org/index.php?title=Infrastructure/GitLab&diff=88568Infrastructure/GitLab2020-06-18T17:38:05Z<p>Gjditchfield: /* Make sure the changes to the stable branch are present in the files you are merging to master */</p>
<hr />
<div>KDE uses a GitLab instance at https://invent.kde.org for code review (as well as hosting and other important collaboration tasks). This page is intended to serve as a general-purpose introduction to the most important aspects: submitting and reviewing Merge Requests.<br />
<br />
{{Info|The KDE community does not generally use GitLab for bug reporting. Please continue to submit bug reports on https://bugs.kde.org. Task management is transitioning to Invent/GitLab but most projects still use https://phabricator.kde.org for now.}}<br />
<br />
<br />
= Workflow =<br />
The sanest and easiest way to submit code to KDE is by following a typical '''feature branch workflow''': keep your master branch synchronized with the origin repository, and make all changes on separate branches. '''Each Merge Request needs its own private, temporary branch.''' Once your Merge Request has been merged, delete the feature branch, and make another new branch for the next Merge Request. In this way, you can be working on multiple changes at once without them colliding with one another because each one lives on its own branch.<br />
<br />
<br />
= Logging in =<br />
Navigate to https://invent.kde.org/users/sign_in and log in using the username and password for your KDE Identity account. If you don't have one, you can [https://identity.kde.org/index.php?r=registration/index sign up for one here].<br />
<br />
== Setting up git ==<br />
<br />
You will need to set up git to use you account details to help identify your work: <br />
<br />
<syntaxhighlight><br />
git config user.name <Your Real Name><br />
git config user.email <Your identity.kde.org email><br />
</syntaxhighlight><br />
<br />
(You can set it up globally with --global)<br />
<br />
= Submitting a merge request =<br />
Contributing to KDE code using GitLab involves '''submitting a Merge Request'''. A Merge Request is a request to merge some of your code into the project's permanent source code repo. Here's how:<br />
<br />
== Build the project from source and make your change ==<br />
First you need to check out the project, compile it from source, and make some changes that you would like to submit to KDE! Instructions for doing this can be found at [[Get Involved/development]]. You will wind up with a checkout of the project at <tt>~/kde/src/[the project name]</tt> with some local changes applied to it.<br />
<br />
{{Info|If you prefer a different organizational structure for source code on your machine, you can of course check out the local copy of your KDE repos wherever you want. However for the purposes of this documentation, we will assume that they are located inside <tt>~/kde/src/</tt>}}<br />
<br />
== Fork the project ==<br />
Once you have made some local changes that you would like to submit to KDE, you need to create a personal fork of the project and push your changes to the forked copy.<br />
<br />
Navigate to https://invent.kde.org/kde and locate the project. If it is not visible in the list, you can use the search field. Once you find the project, click on it:<br />
<br />
[[File:Find_the_project.png|600px]]<br />
<br />
On the project's page, click on the "Fork" button in the top-right corner of the screen:<br />
<br />
[[File:Click_the_Fork_button.png|300px]]<br />
<br />
This will take you to a page asking you what namespace you want to create the project under. Click on yourself: <br />
<br />
[[File:Choose_the_fork_namespace.png|400px]]<br />
<br />
After a moment, the system will finish creating the fork and take you to the page for your fork. On that page, click on the blue "Clone" button in the top-right corner:<br />
<br />
[[File:Click_the_Clone_button.png|300px]]<br />
<br />
In the pop-up that appears, click on the "copy" button to the right of the upper text field. This will copy the URL for the fork onto your clipboard.<br />
<br />
[[File:Copy_the_URL.png|300px]]<br />
<br />
== Add the fork to your source checkout ==<br />
Next, open your terminal app and navigate to the location where the project's repo lives (i.e. <tt>~/kde/src/[the project name]</tt>).<br />
<br />
You need to add your fork as a '''remote''' to the existing repo:<br />
<br />
{{Input|1=<nowiki><br />
git remote add fork [the URL you copied to your clipboard]<br />
</nowiki>}}<br />
<br />
Run <tt>git remote -v</tt>. You should see something like this:<br />
<br />
<pre><br />
$ git remote -v<br />
fork git@invent.kde.org:ngraham/kid3.git (fetch)<br />
fork git@invent.kde.org:ngraham/kid3.git (push)<br />
origin https://invent.kde.org/kde/kid3.git (fetch)<br />
origin https://invent.kde.org/kde/kid3.git (push)<br />
</pre><br />
<br />
This means you have two remotes set up for your repo: "origin" points to the original repo, and "fork" points to your fork of it.<br />
<br />
== Make a branch and commit ==<br />
Now that you have your fork set up, it's time to create a branch to track your work and make a commit.<br />
<br />
{{Input|1=<nowiki><br />
git checkout -b my_awesome_feature<br />
git add [the files you changed]<br />
git commit<br />
</nowiki>}}<br />
<br />
== Write a good commit message ==<br />
With Gitlab, once a merge request is merged, the first commit in the merge request will wind up in the Git history, so it is very important that the first commit in the merge request be formatted properly.<br />
<br />
Please follow [https://chris.beams.io/posts/git-commit/#seven-rules commit message best practices]: write a descriptive title in the form of an imperative sentence (e.g. "Fix button disappearing when view is changed") and on the next line, write at least one sentence describing your change and why it is necessary, adding more details if necessary.<br />
<br />
If your patch is intended to fix a Bugzilla ticket, include the following on its own line:<br />
<pre><br />
BUG: 385942<br />
</pre><br />
(The number should be just the Bugzilla ticket number, '''not the full URL''')<br />
<br />
[https://techbase.kde.org/Development/Git/Configuration#Commit_Template Here is more information] about other special messages that interact with Bugzilla tickets.<br />
<br />
{{Note|These keywords will only work if the email address in your <tt>~/.config/git/config</tt> file matches the email address used for your https://bugs.kde.org account (See [https://techbase.kde.org/Development/Git/Configuration#Basic_Settings this page] for more information).}}<br />
<br />
{{Note|If a merge request has only a single commit in it, then this commit will be added to the immutable hit history with no opportunity to change it from the web interface before merging. This is different from Phabricator, in which the patch's title and summary are transformed into a commit message, overwriting the underlying commit message itself. So it is very important to format your commit message well. The web interface can only be used to rewrite a commit message when a merge request has at least two commits, and they are squashed when merging, and the person doing the merging writes a new commit message in the process of squashing.}}<br />
<br />
== Push to your fork ==<br />
At this point you have a branch in your local repo called "my_awesome_feature" (Hopefully in reality it is named something a bit more appropriate!) that has a commit on it with your work. Now push it to your fork:<br />
<br />
{{Input|1=<nowiki><br />
git push fork my_awesome_feature<br />
</nowiki>}}<br />
<br />
This will produce a message somewhat like this:<br />
<br />
<pre><br />
$ git push fork my_awesome_feature<br />
Enumerating objects: 5, done.<br />
Counting objects: 100% (5/5), done.<br />
Delta compression using up to 4 threads<br />
Compressing objects: 100% (3/3), done.<br />
Writing objects: 100% (3/3), 303 bytes | 303.00 KiB/s, done.<br />
Total 3 (delta 2), reused 0 (delta 0)<br />
remote: This commit is available for viewing at:<br />
remote: https://invent.kde.org/ngraham/kid3/commit/23a702439f494806cf3cfe14f212df58a0075bba<br />
remote: <br />
remote: To create a merge request for my_awesome_feature, visit:<br />
remote: https://invent.kde.org/ngraham/kid3/merge_requests/new?merge_request%5Bsource_branch%5D=my_awesome_feature<br />
remote: <br />
To invent.kde.org:ngraham/kid3.git<br />
* [new branch] my_awesome_feature -> my_awesome_feature<br />
</pre><br />
<br />
== Create the Merge Request ==<br />
Notice the "To create a merge request for my_awesome_feature..." message above. You can copy-and-paste the URL shown below it into a web browser. On some terminal apps, such as Konsole and Yakuake, you can ctrl+click on the link to go right there!<br />
<br />
You will be taken to a web page that looks like this:<br />
<br />
[[File:Merge_Request_info.png|800px]]<br />
<br />
In the Description section, write at least one sentence describing your change and why it is necessary, adding more details if needed. For Merge Requests that change something about the appearance or user interface, it's customary to include a screenshot of how the interface looks after your change has been applied. Bonus points for including a "Before" image too, so reviewers can easily compare them.<br />
<br />
In the section below, it is very important that you make sure all three checkboxes are checked:<br />
<br />
[[File:Check_all_checkboxes!.png|500px]]<br />
<br />
Once you're done with that, click the "Submit Merge Request" button!<br />
<br />
[[File:Submit_Merge_Request.png|400px]]<br />
<br />
<br />
== What happens next? ==<br />
After you've submitted your Merge Request, KDE developers who work with the software in question will review it and provide feedback. This can often take a few days. However, if nobody has responded after a week, it's likely that the review was overlooked (sorry about that!) and it's appropriate to make a comment saying, "Ping!" or something to that effect.<br />
<br />
Once the Merge Request is accepted, KDE Developers will merge it for you!<br />
<br />
<br />
= Testing someone else's Merge Request =<br />
First you'll need a development environment set up. If you haven't done that yet, it's time to do so. Follow the instructions on [[Get_Involved/development#Set_up_your_development_environment]]. It is also advisable to set up the <tt>git mr</tt> tool, which makes testing Merge Requests a breeze. Here's how to install it, depending on your operating system:<br />
<br />
''' Arch / Manjaro '''<br />
{{Input|1=<nowiki><br />
yay -S git-mr<br />
</nowiki>}}<br />
<br />
'''Debian/Ubuntu/KDE Neon'''<br />
{{Input|1=<nowiki><br />
sudo apt install git-extras<br />
</nowiki>}}<br />
<br />
''' Fedora '''<br />
{{Input|1=<nowiki><br />
sudo dnf install git-extras<br />
</nowiki>}}<br />
<br />
''' OpenSUSE '''<br />
{{Input|1=<nowiki><br />
sudo zypper install git-mr<br />
</nowiki>}}<br />
<br />
== Check out the Merge Request and compile the software ==<br />
First check out or enter the source repository for the software that's being patched. For example, let's say you want to test a Merge Request for Okular. If you've never built it before, check it out and build it once first:<br />
{{Input|1=<nowiki><br />
kdesrc-build okular<br />
</nowiki>}}<br />
<br />
Now go to its source directory:<br />
{{Input|1=<nowiki><br />
cd ~/kde/src/okular<br />
</nowiki>}}<br />
<br />
Find the Merge Request's ID. For example, for https://invent.kde.org/kde/okular/merge_requests/80, the ID is <tt>80</tt>.<br />
<br />
...and apply the Merge Request:<br />
{{Input|1=<nowiki><br />
git mr 80<br />
</nowiki>}}<br />
<br />
Now it's time to compile and run the software to make sure that the Merge Request does what it says it does and doesn't cause any regressions! Compile the patched source code:<br />
{{Input|1=<nowiki><br />
kdesrc-build okular --no-src --resume-from okular<br />
</nowiki>}}<br />
Those arguments will tell <tt>kdesrc-build</tt> to not update the source code and to not build any dependencies.<br />
<br />
If it didn't compile, that's reason alone to reject the Merge Request! Go to the web page for the Merge Request and report your findings.<br />
<br />
== Perform QA ==<br />
If it did compile, then it's time to perform QA, because it's important to thoroughly test Merge Requests to ensure that bad code and regressions don't slip in. '''This is the entire purpose of having a review infrastructure'''; it is very important.<br />
<br />
First make sure the unit tests all pass:<br />
{{Input|1=<nowiki><br />
cd ~kde/build/kde/applications/okular<br />
ctest<br />
</nowiki>}}<br />
<br />
If any tests fail, report this through a comment on the Merge Request's web page.<br />
<br />
Does everything all still work for you? If not, return to the web page and request changes, writing a detailed comment explaining what didn't work. It is permissible to do this even if you have not been specified as a reviewer! '''Anyone can reject a Merge Request on the grounds that it does not work, does not do what it says it does, or causes regressions.'''<br />
<br />
Next, try to break the Merge Request. Here are some ideas:<br />
# Remove the program's configuration file (<tt>~/.config/<program name>rc</tt> ) and re-open it<br />
# Try the program with a HiDPI scale factor (or without one) or with a different default font size<br />
# If it's a new feature, feed it unexpected input<br />
# Test related functionality<br />
<br />
A good Merge Request will handle corner cases and variations in configuration. The price of configurability is vigilant testing! We owe it to our users to test using many configurations, not just the defaults or our personal settings.<br />
<br />
== Perform code review ==<br />
{{Note|1= Need a section on code review here, preferably written by a core KDE developer or another very experienced developer}}<br />
<br />
== Engage with the author and other reviewers ==<br />
After you have run the program and evaluated the Merge Request, it's time to leave some review comments on the webpage. If you have been specified as a reviewer, or are a member of a group that has been specified as a reviewer, it is permissible to Accept the Merge Request. But keep in mind that reviewing involves responsibility: you are giving a thumbs-up to code that will be run potentially by millions of people. If you accept and land a Merge Request that causes regressions, you will be expected to help fix it if the original author cannot or has disappeared. It's important to take the reviewer role seriously.<br />
<br />
== Make sure the target branch is set correctly ==<br />
When the "Merge" button is clicked, the Merge Request will be landed on the target branch. If the target branch is not correct, or reviewers have decided that it should be landed on a different branch (for example, perhaps the change is a low-risk bugfix suitable for landing on the stable branch), then the target branch must be changed before merging. To do this, edit the merge request by clicking the "Edit" button in the top-right corner of the page and choose a different target branch:<br />
<br />
[[File:Editing_merge_request_target_branch.png]]<br />
<br />
If this results in conflicts, then pull down the remote branch, rebase it locally, and push to the remote branch.<br />
<br />
Note that after committing to the stable branch you are expected to merge that branch to <tt>master</tt> afterwards:<br />
{{Input|1=<nowiki><br />
git checkout release/19.12<br />
git pull<br />
git checkout master<br />
git pull<br />
git merge -s recursive -Xours release/19.12<br />
git push<br />
</nowiki>}}<br />
<br />
{{warning|If at any time you feel nervous or run into trouble, ask your reviewers for help. This can be tricky, and undoing bad merges is a pain in the neck.}}<br />
<br />
= Pushing commits to somebody else's fork =<br />
Sometimes someone will say "hey let's work on my branch together." So you will be pushing commits not to origin, and not to your fork, but to someone else's fork. Let's say you want to work on joliveira's "gsoc2019_numberFormat" branch.<br />
<br />
First you would need to add the URL for his fork as a remote:<br />
<br />
<pre><br />
$ cd ~/kde/src/okular<br />
$ git remote add joliveira_fork git@invent.kde.org:joliveira/okular.git<br />
$ git remote -v<br />
aacid_fork git@invent.kde.org:aacid/okular.git (fetch)<br />
aacid_fork git@invent.kde.org:aacid/okular.git (push)<br />
joliveira_fork git@invent.kde.org:joliveira/okular.git (fetch)<br />
joliveira_fork git@invent.kde.org:joliveira/okular.git (push)<br />
origin https://invent.kde.org/kde/okular.git (fetch)<br />
origin https://invent.kde.org/kde/okular.git (push)<br />
</pre><br />
<br />
Notice how there are now multiple forks set up as remotes.<br />
<br />
Next, you need to fetch all the repo metadata from the new remote:<br />
<br />
{{Input|1=<nowiki><br />
git fetch joliveira_fork<br />
</nowiki>}}<br />
<br />
This will download the list of branches. The next step is to switch to the one you want to collaborate on:<br />
<br />
{{Input|1=<nowiki><br />
git checkout --track joliveira_fork/gsoc2019_numberFormat<br />
</nowiki>}}<br />
<br />
This will create a local branch named "gsoc2019_numberFormat" from the contents of the remote branch joliveira_fork/gsoc2019_numberFormat and that also "tracks" it. This means that if someone else pushes changes to a remote version of that branch, you can run <tt>git pull --rebase</tt> while on your local "gsoc2019_numberFormat" branch to bring it up to date.<br />
<br />
Next, make your changes, add and commit. Then push the changes to the remote joliveira_fork remote:<br />
<br />
{{Input|1=<nowiki><br />
git push joliveira_fork gsoc2019_numberFormat<br />
</nowiki>}}<br />
<br />
= Generating "eternal" URLs to commits or objects in a repository =<br />
<br />
History has taught that no system used by KDE around the code repositories stays forever.<br />
Quickgit, CGit, Phabricator & Co. came and at one point were replaced again. Sadly also taking with them the service-specific URLs (and host names).<br />
<br />
To give documentation, blog posts, commit messages and other long-living documents a way to reference commits or objects in the repository like directories or files, at given branches or at given tags, the service commits.kde.org exists. It maps and forwards URLs to the respective current service URLs.<br />
<br />
The pattern for URLs to commits is this:<br />
<pre><br />
https://commits.kde.org/<repo-id>/<commit-id><br />
</pre><br />
<br />
Example:<br />
<pre><br />
https://commits.kde.org/kcoreaddons/d2f4d353327b322ee6bfcc303169190ae44393f0<br />
</pre><br />
<br />
The pattern for URLs to objects is like this:<br />
<pre><br />
https://commits.kde.org/<repo-id>[?[path=<pathToFileOrDirectory]&[branch=<branch>|tag=<tag>]]<br />
</pre><br />
<path> should be without a leading /. It defaults to the top-level directory if not set. Either a branch or tag can be passed at which the objects should be shown. It defaults to the main branch (master usually).<br />
<br />
Examples:<br />
<pre><br />
https://commits.kde.org/kcoreaddons?path=src # points to src/ directory in master branch<br />
https://commits.kde.org/kcoreaddons?path=README.md&tag=v5.0.0 # points to README.md file at tag v5.0.0<br />
https://commits.kde.org/kdelibs?path=kdecore/tests&branch=KDE/3.5 # points to kdecore/tests directory in branch KDE/3.5<br />
</pre><br />
<br />
There currently is no service to generate commit.kde.org URLs from URLs for the actual system. This has to be done manually.</div>Gjditchfieldhttps://community.kde.org/index.php?title=Infrastructure/GitLab&diff=88389Infrastructure/GitLab2020-06-03T18:19:18Z<p>Gjditchfield: missing word</p>
<hr />
<div>KDE uses a GitLab instance at https://invent.kde.org for code review (as well as hosting and other important collaboration tasks). This page is intended to serve as a general-purpose introduction to the most important aspects: submitting and reviewing Merge Requests.<br />
<br />
{{Info|The KDE community does not generally use GitLab for bug reporting. Please continue to submit bug reports on https://bugs.kde.org. Task management is transitioning to Invent/GitLab but most projects still use https://phabricator.kde.org for now.}}<br />
<br />
<br />
= Workflow =<br />
The sanest and easiest way to submit code to KDE is by following a typical '''feature branch workflow''': keep your master branch synchronized with the origin repository, and make all changes on separate branches. '''Each Merge Request needs its own private, temporary branch.''' Once your Merge Request has been merged, delete the feature branch, and make another new branch for the next Merge Request. In this way, you can be working on multiple changes at once without them colliding with one another because each one lives on its own branch.<br />
<br />
<br />
= Logging in =<br />
Navigate to https://invent.kde.org/users/sign_in and log in using the username and password for your KDE Identity account. If you don't have one, you can [https://identity.kde.org/index.php?r=registration/index sign up for one here].<br />
<br />
== Setting up git ==<br />
<br />
You will need to set up git to use you account details to help identify your work: <br />
<br />
<syntaxhighlight><br />
git config user.name <Your Real Name><br />
git config user.email <Your identity.kde.org email><br />
</syntaxhighlight><br />
<br />
(You can set it up globally with --global)<br />
<br />
= Submitting a merge request =<br />
Contributing to KDE code using GitLab involves '''submitting a Merge Request'''. A Merge Request is a request to merge some of your code into the project's permanent source code repo. Here's how:<br />
<br />
== Build the project from source and make your change ==<br />
First you need to check out the project, compile it from source, and make some changes that you would like to submit to KDE! Instructions for doing this can be found at [[Get Involved/development]]. You will wind up with a checkout of the project at <tt>~/kde/src/[the project name]</tt> with some local changes applied to it.<br />
<br />
{{Info|If you prefer a different organizational structure for source code on your machine, you can of course check out the local copy of your KDE repos wherever you want. However for the purposes of this documentation, we will assume that they are located inside <tt>~/kde/src/</tt>}}<br />
<br />
== Fork the project ==<br />
Once you have made some local changes that you would like to submit to KDE, you need to create a personal fork of the project and push your changes to the forked copy.<br />
<br />
Navigate to https://invent.kde.org/kde and locate the project. If it is not visible in the list, you can use the search field. Once you find the project, click on it:<br />
<br />
[[File:Find_the_project.png|600px]]<br />
<br />
On the project's page, click on the "Fork" button in the top-right corner of the screen:<br />
<br />
[[File:Click_the_Fork_button.png|300px]]<br />
<br />
This will take you to a page asking you what namespace you want to create the project under. Click on yourself: <br />
<br />
[[File:Choose_the_fork_namespace.png|400px]]<br />
<br />
After a moment, the system will finish creating the fork and take you to the page for your fork. On that page, click on the blue "Clone" button in the top-right corner:<br />
<br />
[[File:Click_the_Clone_button.png|300px]]<br />
<br />
In the pop-up that appears, click on the "copy" button to the right of the upper text field. This will copy the URL for the fork onto your clipboard.<br />
<br />
[[File:Copy_the_URL.png|300px]]<br />
<br />
== Add the fork to your source checkout ==<br />
Next, open your terminal app and navigate to the location where the project's repo lives (i.e. <tt>~/kde/src/[the project name]</tt>).<br />
<br />
You need to add your fork as a '''remote''' to the existing repo:<br />
<br />
{{Input|1=<nowiki><br />
git remote add fork [the URL you copied to your clipboard]<br />
</nowiki>}}<br />
<br />
Run <tt>git remote -v</tt>. You should see something like this:<br />
<br />
<pre><br />
$ git remote -v<br />
fork git@invent.kde.org:ngraham/kid3.git (fetch)<br />
fork git@invent.kde.org:ngraham/kid3.git (push)<br />
origin https://invent.kde.org/kde/kid3.git (fetch)<br />
origin https://invent.kde.org/kde/kid3.git (push)<br />
</pre><br />
<br />
This means you have two remotes set up for your repo: "origin" points to the original repo, and "fork" points to your fork of it.<br />
<br />
== Make a branch and commit ==<br />
Now that you have your fork set up, it's time to create a branch to track your work and make a commit.<br />
<br />
{{Input|1=<nowiki><br />
git checkout -b my_awesome_feature<br />
git add [the files you changed]<br />
git commit<br />
</nowiki>}}<br />
<br />
== Write a good commit message ==<br />
With Gitlab, once a merge request is merged, the first commit in the merge request will wind up in the Git history, so it is very important that the first commit in the merge request be formatted properly.<br />
<br />
Please follow [https://chris.beams.io/posts/git-commit/#seven-rules commit message best practices]: write a descriptive title in the form of an imperative sentence (e.g. "Fix button disappearing when view is changed") and on the next line, write at least one sentence describing your change and why it is necessary, adding more details if necessary.<br />
<br />
If your patch is intended to fix a Bugzilla ticket, include the following on its own line:<br />
<pre><br />
BUG: 385942<br />
</pre><br />
(The number should be just the Bugzilla ticket number, '''not the full URL''')<br />
<br />
[https://techbase.kde.org/Development/Git/Configuration#Commit_Template Here is more information] about other special messages that interact with Bugzilla tickets.<br />
<br />
{{Note|These keywords will only work if the email address in your <tt>~/.config/git/config</tt> file matches the email address used for your https://bugs.kde.org account (See [https://techbase.kde.org/Development/Git/Configuration#Basic_Settings this page] for more information).}}<br />
<br />
{{Note|If a merge request has only a single commit in it, then this commit will be added to the immutable hit history with no opportunity to change it from the web interface before merging. This is different from Phabricator, in which the patch's title and summary are transformed into a commit message, overwriting the underlying commit message itself. So it is very important to format your commit message well. The web interface can only be used to rewrite a commit message when a merge request has at least two commits, and they are squashed when merging, and the person doing the merging writes a new commit message in the process of squashing.}}<br />
<br />
== Push to your fork ==<br />
At this point you have a branch in your local repo called "my_awesome_feature" (Hopefully in reality it is named something a bit more appropriate!) that has a commit on it with your work. Now push it to your fork:<br />
<br />
{{Input|1=<nowiki><br />
git push fork my_awesome_feature<br />
</nowiki>}}<br />
<br />
This will produce a message somewhat like this:<br />
<br />
<pre><br />
$ git push fork my_awesome_feature<br />
Enumerating objects: 5, done.<br />
Counting objects: 100% (5/5), done.<br />
Delta compression using up to 4 threads<br />
Compressing objects: 100% (3/3), done.<br />
Writing objects: 100% (3/3), 303 bytes | 303.00 KiB/s, done.<br />
Total 3 (delta 2), reused 0 (delta 0)<br />
remote: This commit is available for viewing at:<br />
remote: https://invent.kde.org/ngraham/kid3/commit/23a702439f494806cf3cfe14f212df58a0075bba<br />
remote: <br />
remote: To create a merge request for my_awesome_feature, visit:<br />
remote: https://invent.kde.org/ngraham/kid3/merge_requests/new?merge_request%5Bsource_branch%5D=my_awesome_feature<br />
remote: <br />
To invent.kde.org:ngraham/kid3.git<br />
* [new branch] my_awesome_feature -> my_awesome_feature<br />
</pre><br />
<br />
== Create the Merge Request ==<br />
Notice the "To create a merge request for my_awesome_feature..." message above. You can copy-and-paste the URL shown below it into a web browser. On some terminal apps, such as Konsole and Yakuake, you can ctrl+click on the link to go right there!<br />
<br />
You will be taken to a web page that looks like this:<br />
<br />
[[File:Merge_Request_info.png|800px]]<br />
<br />
In the Description section, write at least one sentence describing your change and why it is necessary, adding more details if needed. For Merge Requests that change something about the appearance or user interface, it's customary to include a screenshot of how the interface looks after your change has been applied. Bonus points for including a "Before" image too, so reviewers can easily compare them.<br />
<br />
In the section below, it is very important that you make sure all three checkboxes are checked:<br />
<br />
[[File:Check_all_checkboxes!.png|500px]]<br />
<br />
Once you're done with that, click the "Submit Merge Request" button!<br />
<br />
[[File:Submit_Merge_Request.png|400px]]<br />
<br />
<br />
== What happens next? ==<br />
After you've submitted your Merge Request, KDE developers who work with the software in question will review it and provide feedback. This can often take a few days. However, if nobody has responded after a week, it's likely that the review was overlooked (sorry about that!) and it's appropriate to make a comment saying, "Ping!" or something to that effect.<br />
<br />
Once the Merge Request is accepted, KDE Developers will merge it for you!<br />
<br />
<br />
= Testing someone else's Merge Request =<br />
First you'll need a development environment set up. If you haven't done that yet, it's time to do so. Follow the instructions on [[Get_Involved/development#Set_up_your_development_environment]]. It is also advisable to set up the <tt>git mr</tt> tool, which makes testing Merge Requests a breeze. Here's how to install it, depending on your operating system:<br />
<br />
''' Arch / Manjaro '''<br />
{{Input|1=<nowiki><br />
yay -S git-mr<br />
</nowiki>}}<br />
<br />
'''Debian/Ubuntu/KDE Neon'''<br />
{{Input|1=<nowiki><br />
sudo apt install git-extras<br />
</nowiki>}}<br />
<br />
''' Fedora '''<br />
{{Input|1=<nowiki><br />
sudo dnf install git-extras<br />
</nowiki>}}<br />
<br />
''' OpenSUSE '''<br />
{{Input|1=<nowiki><br />
sudo zypper install git-mr<br />
</nowiki>}}<br />
<br />
== Check out the Merge Request and compile the software ==<br />
First check out or enter the source repository for the software that's being patched. For example, let's say you want to test a Merge Request for Okular. If you've never built it before, check it out and build it once first:<br />
{{Input|1=<nowiki><br />
kdesrc-build okular<br />
</nowiki>}}<br />
<br />
Now go to its source directory:<br />
{{Input|1=<nowiki><br />
cd ~/kde/src/okular<br />
</nowiki>}}<br />
<br />
Find the Merge Request's ID. For example, for https://invent.kde.org/kde/okular/merge_requests/80, the ID is <tt>80</tt>.<br />
<br />
...and apply the Merge Request:<br />
{{Input|1=<nowiki><br />
git mr 80<br />
</nowiki>}}<br />
<br />
Now it's time to compile and run the software to make sure that the Merge Request does what it says it does and doesn't cause any regressions! Compile the patched source code:<br />
{{Input|1=<nowiki><br />
kdesrc-build okular --no-src --resume-from okular<br />
</nowiki>}}<br />
Those arguments will tell <tt>kdesrc-build</tt> to not update the source code and to not build any dependencies.<br />
<br />
If it didn't compile, that's reason alone to reject the Merge Request! Go to the web page for the Merge Request and report your findings.<br />
<br />
== Perform QA ==<br />
If it did compile, then it's time to perform QA, because it's important to thoroughly test Merge Requests to ensure that bad code and regressions don't slip in. '''This is the entire purpose of having a review infrastructure'''; it is very important.<br />
<br />
First make sure the unit tests all pass:<br />
{{Input|1=<nowiki><br />
cd ~kde/build/kde/applications/okular<br />
ctest<br />
</nowiki>}}<br />
<br />
If any tests fail, report this through a comment on the Merge Request's web page.<br />
<br />
Does everything all still work for you? If not, return to the web page and request changes, writing a detailed comment explaining what didn't work. It is permissible to do this even if you have not been specified as a reviewer! '''Anyone can reject a Merge Request on the grounds that it does not work, does not do what it says it does, or causes regressions.'''<br />
<br />
Next, try to break the Merge Request. Here are some ideas:<br />
# Remove the program's configuration file (<tt>~/.config/<program name>rc</tt> ) and re-open it<br />
# Try the program with a HiDPI scale factor (or without one) or with a different default font size<br />
# If it's a new feature, feed it unexpected input<br />
# Test related functionality<br />
<br />
A good Merge Request will handle corner cases and variations in configuration. The price of configurability is vigilant testing! We owe it to our users to test using many configurations, not just the defaults or our personal settings.<br />
<br />
== Perform code review ==<br />
{{Note|1= Need a section on code review here, preferably written by a core KDE developer or another very experienced developer}}<br />
<br />
== Engage with the author and other reviewers ==<br />
After you have run the program and evaluated the Merge Request, it's time to leave some review comments on the webpage. If you have been specified as a reviewer, or are a member of a group that has been specified as a reviewer, it is permissible to Accept the Merge Request. But keep in mind that reviewing involves responsibility: you are giving a thumbs-up to code that will be run potentially by millions of people. If you accept and land a Merge Request that causes regressions, you will be expected to help fix it if the original author cannot or has disappeared. It's important to take the reviewer role seriously.<br />
<br />
== Make sure the target branch is set correctly ==<br />
When the "Merge" button is clicked, the Merge Request will be landed on the target branch. If the target branch is not correct, or reviewers have decided that it should be landed on a different branch (for example, perhaps the change is a low-risk bugfix suitable for landing on the stable branch), then the target branch must be changed before merging. To do this, edit the merge request by clicking the "Edit" button in the top-right corner of the page and choose a different target branch:<br />
<br />
[[File:Editing_merge_request_target_branch.png]]<br />
<br />
If this results in conflicts, then pull down the remote branch, rebase it locally, and push to the remote branch.<br />
<br />
Note that after committing to the stable branch you are expected to merge that branch to <tt>master</tt> afterwards:<br />
{{Input|1=<nowiki><br />
git checkout master<br />
git pull<br />
git merge -s recursive -Xours release/19.12<br />
git push<br />
</nowiki>}}<br />
<br />
{{warning|If at any time you feel nervous or run into trouble, ask your reviewers for help. This can be tricky, and undoing bad merges is a pain in the neck.}}<br />
<br />
= Pushing commits to somebody else's fork =<br />
Sometimes someone will say "hey let's work on my branch together." So you will be pushing commits not to origin, and not to your fork, but to someone else's fork. Let's say you want to work on joliveira's "gsoc2019_numberFormat" branch.<br />
<br />
First you would need to add the URL for his fork as a remote:<br />
<br />
<pre><br />
$ cd ~/kde/src/okular<br />
$ git remote add joliveira_fork git@invent.kde.org:joliveira/okular.git<br />
$ git remote -v<br />
aacid_fork git@invent.kde.org:aacid/okular.git (fetch)<br />
aacid_fork git@invent.kde.org:aacid/okular.git (push)<br />
joliveira_fork git@invent.kde.org:joliveira/okular.git (fetch)<br />
joliveira_fork git@invent.kde.org:joliveira/okular.git (push)<br />
origin https://invent.kde.org/kde/okular.git (fetch)<br />
origin https://invent.kde.org/kde/okular.git (push)<br />
</pre><br />
<br />
Notice how there are now multiple forks set up as remotes.<br />
<br />
Next, you need to fetch all the repo metadata from the new remote:<br />
<br />
{{Input|1=<nowiki><br />
git fetch joliveira_fork<br />
</nowiki>}}<br />
<br />
This will download the list of branches. The next step is to switch to the one you want to collaborate on:<br />
<br />
{{Input|1=<nowiki><br />
git checkout --track joliveira_fork/gsoc2019_numberFormat<br />
</nowiki>}}<br />
<br />
This will create a local branch named "gsoc2019_numberFormat" from the contents of the remote branch joliveira_fork/gsoc2019_numberFormat and that also "tracks" it. This means that if someone else pushes changes to a remote version of that branch, you can run <tt>git pull --rebase</tt> while on your local "gsoc2019_numberFormat" branch to bring it up to date.<br />
<br />
Next, make your changes, add and commit. Then push the changes to the remote joliveira_fork remote:<br />
<br />
{{Input|1=<nowiki><br />
git push joliveira_fork gsoc2019_numberFormat<br />
</nowiki>}}</div>Gjditchfieldhttps://community.kde.org/index.php?title=Infrastructure/Phabricator&diff=86532Infrastructure/Phabricator2019-10-25T20:24:37Z<p>Gjditchfield: /* Step 2: Update your diff in response to review comments */</p>
<hr />
<div>[http://phabricator.kde.org/ Phabricator] is KDE's task management and patch review system. This page is intended to serve as a general-purpose introduction to the most important aspects: submitting and reviewing patches. It should not take long before you are happily using Phabricator.<br />
<br />
{{Info|The KDE community does not use the Phabricator bug reporting function. We continue to use bugzilla at https://bugs.kde.org}}<br />
<br />
<br />
<br />
= Basic Tasks =<br />
== Logging in ==<br />
Log in to [http://phabricator.kde.org/ Phabricator] with your KDE Identity account. If you don't have one, you can [https://identity.kde.org/index.php?r=registration/index sign up for one here]. At the Phabricator home page, click the "Log in" button on the top of the page and enter your KDE Identity username and password:<br />
<br />
[[File:Phabricator login 2.png ]]<br />
If your KDE Identity account works on http://identity.kde.org but not on http://phabricator.kde.org, please contact the KDE sysadmins at sysadmin@kde.org.<br />
<br />
== Getting help ==<br />
The official documentation is in the [https://secure.phabricator.com/book/phabricator/ Phabricator book] and [https://phacility.com/phabricator/ on their website] -- note that since everything is under rapid development, most of the documentation is incomplete. A good way to find the information you're looking for is to search [https://secure.phabricator.com/ Phabricator upstream].<br />
<br />
== Posting a Patch using the website ==<br />
Once you have [https://community.kde.org/Get_Involved/development#Set_up_your_development_environment set up your development environment] and [https://community.kde.org/Get_Involved/development#Submit_a_patch created a patch], you can submit it using Phabricator!<br />
<br />
Log in to Phabricator and click on <tt>Code Review</tt> in the list on the left. Then, click the <tt>+Create Diff</tt> button in the upper-right corner of the screen. Paste the text of the diff or upload the file using the file dialog. Reviewers are mostly added automatically, but if your patch includes any visual or user interface changes, please add <tt>#vdg</tt> as a reviewer to make sure the [[Get Involved/design | KDE Visual Design Group]] sees it! Please make sure to add a screenshot, too.<br />
<br />
==Formatting your patch ==<br />
The Title of your patch will become the git commit message, so please follow [https://chris.beams.io/posts/git-commit/#seven-rules commit message best practices] when creating a title for the patch.<br />
<br />
In the Summary section, write at least one sentence describing your change and why it is necessary, adding more details if necessary.<br />
<br />
=== Add special keywords ===<br />
If your patch is intended to fix a Bugzilla ticket, include one of the following on its own line in the Summary section:<br />
<pre><br />
BUG: 385942<br />
</pre><br />
or<br />
<pre><br />
FEATURE: 384936<br />
</pre><br />
(Just the Bugzilla ticket number, '''not the full URL''')<br />
<br />
Use <tt>BUG:</tt> If the Bugzilla ticket describes a bug, and <tt>FEATURE:</tt> if the Bugzilla ticket describes a feature request. Either of these tags will cause that Bugzilla ticket to be automatically closed when the patch is committed.<br />
<br />
If you added the <tt>BUG:</tt> or <tt>FEATURE:</tt> tag, also add another tag indicating what version receives the fix or new feature:<br />
<pre><br />
FIXED-IN: [version]<br />
</pre><br />
Replace <tt>[version]</tt> with an appropriate version string; see [[Guidelines and HOWTOs/Write a version string]] to find out how to write one. If you can't figure it out, don't worry about it and just omit the tag; a KDE developer will help you add it during code review.<br />
<br />
[https://techbase.kde.org/Development/Git/Configuration#Commit_Template Here is more information] about other special messages that interact with Bugzilla tickets. You can also add special messages that interact with [https://secure.phabricator.com/T5132 other Phabricator tools] (e.g. Maniphest tasks).<br />
<br />
{{Note|These keywords will only work if the email address in your <tt>~/.config/git/config</tt> file matches the email address used for your https://bugs.kde.org account (See [https://techbase.kde.org/Development/Git/Configuration#Basic_Settings this page] for more information).}}<br />
<br />
=== Include some screenshots ===<br />
For patches that change something about the user interface, it's customary to include a screenshot of how the interface looks with your patch. Bonus points for including a "Before" image too, so reviewers can easily compare them.<br />
<br />
== What happens next? ==<br />
After you've submitted your patch, KDE developers who work with the software in question will review it and provide feedback. This can often take a few days. However, if nobody has responded after a week, it's likely that the review was overlooked (sorry about that!) and it's appropriate to make a comment saying, "Ping!" or something to that effect.<br />
<br />
Once the patch is accepted, KDE Developers will land it for you!<br />
<br />
<br />
<br />
= Using Arcanist to post patches =<br />
After you've posted a few patches, using the web UI to post patches gets tiresome. Arcanist is a tool to simplify and speed up the process of posting, updating, and merging Phabricator patches. Setting it up is easy:<br />
<br />
== Installing Arcanist ==<br />
=== Debian/Ubuntu/KDE Neon ===<br />
{{Input|1=<nowiki><br />
sudo apt install arcanist<br />
</nowiki>}}<br />
<br />
=== Fedora ===<br />
kanarip/phabricator is not an official Fedora repository, so errors may occur.<br />
{{Input|1=<nowiki><br />
sudo dnf copr enable kanarip/phabricator<br />
sudo dnf install arcanist<br />
</nowiki>}}<br />
<br />
Alternatively, install arc manually from the github repository:<br />
{{Input|1=<nowiki><br />
sudo dnf install php-{common,cli}<br />
mkdir somewhere/<br />
cd somewhere/<br />
git clone https://github.com/phacility/libphutil.git<br />
git clone https://github.com/phacility/arcanist.git<br />
ln -s $(pwd)/arcanist/bin/arc ~/.local/bin/arc<br />
</nowiki>}}<br />
<br />
=== openSUSE ===<br />
Tumbleweed:<br />
{{Input|1=<nowiki><br />
sudo zypper ar -f https://download.opensuse.org/repositories/devel:tools:scm/openSUSE_Tumbleweed/devel:tools:scm.repo<br />
sudo zypper install arcanist<br />
</nowiki>}}<br />
<br />
Leap 15:<br />
{{Input|1=<nowiki><br />
sudo zypper ar -f https://download.opensuse.org/repositories/devel:tools:scm/openSUSE_Leap_15.0/devel:tools:scm.repo<br />
sudo zypper install arcanist<br />
</nowiki>}}<br />
<br />
=== Arch / Manjaro ===<br />
{{Input|1=<nowiki><br />
trizen -S arcanist-git<br />
</nowiki>}}<br />
<br />
=== Gentoo ===<br />
The default portage tree does not have an ebuild, so unless you already have it, you need to add an overlay, e.g. the kde one:<br />
<br />
{{Input|1=<nowiki><br />
sudo layman -a kde<br />
</nowiki>}}<br />
<br />
Once that is done, you can emerge it:<br />
<br />
{{Input|1=<nowiki><br />
sudo emerge -av arcanist<br />
</nowiki>}}<br />
<br />
You might have to install and possibly unmask a few php related packages, so follow the instructions portage gives you.<br />
<br />
Keep in mind that it is a vcs version (9999), which means it doesn't have release updates and you are in charge of keeping it up to date.<br />
<br />
=== Windows ===<br />
[https://secure.phabricator.com/book/phabricator/article/arcanist_windows/ Arcanist User Guide: Windows]<br />
<br />
The most non-obvious step is that you will need to configure your [http://php.net/manual/en/install.windows.manual.php PHP installation] to use Curl. This requires editing the <tt>php.ini</tt> configuration file to add the line <tt>extension_dir = "ext"</tt> and add <tt>php_curl</tt> to the list of extensions. After adding <tt>php.exe</tt> to your <tt>PATH</tt> and installing arcanist/libphutil, you can run <tt>arc</tt> by defining a function in your Powershell profile:<br />
<pre><br />
function arc { php -f "C:\path\to\arcanist.php" -- $args }<br />
</pre><br />
<br />
<br/><br />
<br />
After you have installed Arc, you can learn more using <tt>man arc</tt> or <tt>arc --help</tt>. Another command useful for getting a feel for Phabricator's style is <tt>arc anoid</tt>.<br />
<br />
<br />
=== Other operating systems ===<br />
<br />
Follow the instructions from the [https://secure.phabricator.com/book/phabricator/article/arcanist_quick_start/ Arcanist Quick Start] guide.<br />
<br />
== Perform one-time setup steps ==<br />
Before you are able to use <tt>arc</tt> for work you will need to install a certificate that will allow it to login to Phabricator:<br />
{{Input|1=<nowiki><br />
arc install-certificate https://phabricator.kde.org<br />
</nowiki>}}<br />
Just follow the instructions it provides: you will have to visit the page it indicates and copy the API token you find on that page back to the shell.<br />
<br />
Next, you will need to tell git who you really are, so your patches will include correct authorship information and can be attributed to yourself:<br />
{{Input|1=<nowiki><br />
git config --global user.name "<Your real name>"<br />
git config --global user.email "<Your identity.kde.org email address>"<br />
</nowiki>}}<br />
Both of these steps only need to be done once.<br />
<br />
== Workflow ==<br />
The sanest and easiest way to use <tt>arc</tt> is to follow a typical '''feature branch workflow''': keep your master branch synchronized with the upstream repository, and make all changes on separate branches. '''Each patch needs its own private, temporary branch.''' Once your patch has been merged, delete the feature branch, and make another new branch for the next patch.<br />
<br />
The following commands all need to be executed in your source directory. The hidden file/directory "<tt>.arcconfig</tt>" and "<tt>.git</tt>" tell <tt>arc</tt> what to do so you don't have to.<br />
<br />
=== Step 1: Create a new diff ===<br />
Before editing anything, create a new branch for your patch:<br />
{{Input|1=<nowiki><br />
arc feature <name-for-your-new-branch><br />
</nowiki>}}(For Git experts: this is equivalent to `git checkout -t -b <name-for-your-new-branch>`)<br />
<br />
Now make changes on the feature branch. When you're ready to have your changes reviewed, enter the following command:<br />
{{Input|1=<nowiki><br />
arc diff # this will do the `git add` and `git commit` for you; if it asks about ignoring untracked files, enter 'y'<br />
</nowiki>}}<br />
When you run <tt>arc diff</tt>, you will go through a series of dialogs. At the end, you will be asked to rewrite your Git commit message to fit the standard Differential format, like so: <br />
<pre><br />
<first line of original git commit message><br />
<br />
Summary: <rest of original commit message><br />
<br />
Test Plan:<br />
<br />
Reviewers:<br />
<br />
Subscribers: <br />
</pre><br />
<br />
<tt><first line of original git commit message></tt> will become the commit message, so please follow [https://chris.beams.io/posts/git-commit/#seven-rules commit message best practices].<br />
<br />
As when using the web UI, enter any special Bugzilla keywords (such as <tt>BUG: 385942</tt>) on their own lines in the "Summary" section.<br />
<br />
As when using the web UI, there is no need to specifically add anyone under the "Reviewers" or "Subscribers" sections unless your patch includes any visual or user interface changes. In this case, please add <tt>#vdg</tt> as a reviewer to make sure the [[Get Involved/design | KDE Visual Design Group]] sees it!<br />
<br />
=== Step 2: Update your diff in response to review comments ===<br />
After <tt>arc</tt> uploads the patch to Phabricator, the project's reviewers will take a look and give you some comments. If you get a thumbs up immediately, you can skip this step. But often you will get a review like "looks good, we can take it if you fix problems x, y, and z." Make the necessary changes, add an extra commit to the git branch, and run <tt>arc diff</tt> to update the Phabricator revision. Your commit message will be added to the Phabricator revision as a new comment, so you can use it to explain your changes.<br />
{{Input|1=<nowiki><br />
[implement changes based on review comments]<br />
arc diff<br />
</nowiki>}}<br />
<br />
=== Step 3: Land your diff ===<br />
If you do not have a [[Infrastructure/Get_a_Contributor_Account | KDE Developer Account]], then someone who does will have to land your patch for you. Otherwise, you can do it yourself once the patch has been accepted and reviewers have given you permission to "ship it!"<br />
<br />
First, make sure that the world is sane, and that only your patch will be landed:<br />
{{Input|1=<nowiki><br />
[make sure you are on your feature branch]<br />
arc land --preview<br />
</nowiki>}}<br />
The output of that command should show only the commit message for your patch. If you see more than that, stop and ask your reviewers for help.<br />
<br />
==== Landing on master ====<br />
This is simple:<br />
{{Input|1=<nowiki><br />
arc land<br />
</nowiki>}}<br />
<br />
==== Landing on the "Stable branch" ====<br />
By default, arc will land the patch onto whatever branch was the parent. For example, if you branched from master, <tt>arc</tt> will land on master; if you branched from <tt>Applications/18.12</tt>, arc will land on that branch instead, and so on.<br />
<br />
If you branched your patch from <tt>master</tt> but it is a relatively low-risk bugfix, you will often be a asked to land it on the "stable branch" instead of <tt>master</tt>. The easiest way to do this is to cherry-pick the commit from your feature branch onto the appropriate stable branch and then merge forward.<br />
<br />
{{warning|If at any time you feel nervous or run into trouble, ask your reviewers for help. This can be tricky, and undoing bad merges is a pain in the neck.}}<br />
Here is an example of how to do this with a patch branched from <tt>master</tt> that needs to go into the <tt>Applications/18.12</tt> stable branch (replace <tt>Applications/18.12</tt> with the appropriate stable branch name when you do this yourself, obviously).<br />
<br />
{{Input|1=<nowiki><br />
[make sure you are on your feature branch]<br />
git log -n 1 --pretty=format:"%H"<br />
[copy that commit hash]<br />
git checkout Applications/18.12<br />
git pull<br />
git cherry-pick [the commit hash]<br />
git push<br />
git branch -D [the name of your feature branch]<br />
</nowiki>}}<br />
<br />
Note that after committing to the stable branch you are expected to merge that branch to <tt>master</tt> afterwards:<br />
{{Input|1=<nowiki><br />
git checkout master<br />
git pull<br />
git merge -s recursive -Xours Applications/18.12<br />
git push<br />
</nowiki>}}<br />
<br />
==== Landing someone else's diff ====<br />
If you have a contributor account and you are helping someone without one through the process, you will need to land their diff for them once it's been accepted. Here's how:<br />
{{Input|1=<nowiki><br />
arc patch <revision ID><br />
git show HEAD<br />
</nowiki>}}<br />
<br />
At this point, you need to verify that the authorship information shown is correct. If it's not, you will need to ask the patch author for their real name and email address. Then you use that information to update the local commit for the patch like so:<br />
{{Input|1=<nowiki><br />
# Make sure you're on the branch that corresponds to the patch!<br />
git commit --amend --author="firstname lastname <email address>"<br />
</nowiki>}}<br />
<br />
At this point, you can land the diff normally, [[#Step 3: Land your diff|as described above]].<br />
<br />
<br />
<br />
== Arcanist Tips & Tricks ==<br />
<br />
=== Look before you diff ===<br />
You can check with <tt>arc which</tt> what Arcanist will do before performing the actual upload with <tt>arc diff</tt> if you are unsure what will happen. In particular, look for which commits will be included in your Diff.<br />
<br />
=== arc diff: specify a revision manually ===<br />
Sometimes - if you messed up with your git branches - arc cannot properly determine which revision (D12345) should be updated. In this case you can use <tt>arc diff --update D12345</tt>. See <tt>arc help diff</tt>.<br />
<br />
=== Updating the summary of the Differential from the local Git commit message ===<br />
If you changed the commit message locally and want to update the text in the summary in Differential, call Arc like this:<br />
{{Input|1=<nowiki><br />
arc diff --edit --verbatim<br />
</nowiki>}}<br />
<br />
=== Updating the local Git commit message from changes done on Phabricator ===<br />
If you or someone else updated the title, summary, test plan, reviewers or subscribers of a Diff using the web editor in Phabricator, Arcanist allows to sync those changes back to your local Git commit message:<br />
{{Input|1=<nowiki><br />
arc amend<br />
</nowiki>}}<br />
<br />
Note that in general Arcanist will do this automatically for you once you <tt>arc land</tt>.<br />
<br />
== Advanced Tasks ==<br />
Once in a while, a reviewer will tell you to do specific things. This section will help you figure out what is meant to be done.<br />
<br />
=== "Please do that in a separate commit" ===<br />
Should your patch contain an unrelated change, your reviewer will ask you to undo that part and possibly open a new Diff for that. Here is what you can do:<br />
<br />
If the change in question is in a separate commit on your local branch:<br />
{{Input|1=<nowiki><br />
git revert <unrelated change><br />
arc diff # this updates the first Diff<br />
git checkout -b <new branch name> master<br />
git cherry-pick <unrelated change><br />
arc diff # this creates a new Diff for the unrelated change<br />
</nowiki>}}<br />
<br />
If you mixed different changes into a single commit on your local branch:<br />
{{Input|1=<nowiki><br />
git reset HEAD^<br />
git add -p # type "?" for help, then pick all hunks you want to keep<br />
git stash # the stash now contains the hunks for the second patch<br />
git commit<br />
arc diff # this updates the first Diff<br />
git checkout -b <new branch name> master<br />
git stash pop<br />
git commit<br />
arc diff # this creates a new Diff for the unrelated change<br />
</nowiki>}}<br />
<br />
In case this does not work for you, there's always the plain old copy-and-paste. In general, it is best to avoid adding unrelated changes from the beginning. :-)<br />
<br />
=== Marking patches as dependent on other patches ===<br />
Sometimes you will want to submit a patch that depends on another patch, creating a '''dependency chain'''.<br />
<br />
==== If each patch is intended for a different project ====<br />
Example: you submit a patch to add a new feature to KIO, and then submit another patch for Dolphin that uses that feature. Here's what you do:<br />
<ol><br />
<li>Create your first patch as above</li><br />
<li>When creating the second patch, add the following to its own line in the "Summary" section:<br />
{{Input|1=<nowiki>Depends on DXXXX</nowiki>}}<br />
(Replace "DXXXX" with the ID of the dependent patch, '''not the full URL)'''</li><br />
</ol><br />
<br />
==== If the patches are all for the same project ====<br />
Example: you are implementing multiple new features for a single project that each depend on the patch for the prior feature. Here's what you do:<br />
<ol><br />
<li>Create a branch to track the first feature:<br />
{{Input|1=<nowiki><br />
git checkout -b <branch name for feature 1> --track origin/master<br />
</nowiki>}}<br />
Then implement the feature and make a commit.<br />
</li><br />
<li>Then create a branch for your second feature, ''tracking the first branch:''<br />
{{Input|1=<nowiki><br />
git checkout -b <branch name for feature 2> --track <branch name for feature 1><br />
</nowiki>}}<br />
As above, implement the feature and make a commit. Continue this pattern for any other required dependent features.<br />
</li><br />
<br />
<li><br />
When you're ready to turn your dependency chain feature branches into patches, do the following:<br />
{{Input|1=<nowiki><br />
git checkout <branch name for feature 1><br />
arc diff [then go through the process of creating the patch normally]<br />
git checkout <branch name for feature 2><br />
git commit --amend [then add the special text "Depends on DXXXX", replacing DXXXX with the ID of the first patch<br />
arc diff [then go through the process of creating the patch normally]<br />
</nowiki>}}<br />
...And so on.<br />
</li><br />
<br />
<li>After you get comments, you will have to make changes to your patches and re-base dependent patches:<br />
{{Input|1=<nowiki><br />
git checkout <branch name for feature 1><br />
[Make changes]<br />
git add -u<br />
git commit<br />
arc diff<br />
git checkout <branch name for feature 2><br />
git rebase <branch name for feature 1><br />
git add -u<br />
git commit<br />
arc diff<br />
</nowiki>}}<br />
...And so on.<br />
</li><br />
<br />
<li>When you're ready to land any or all of your patches, do it in sequence, starting from the patch with no unmet dependencies:<br />
{{Input|1=<nowiki><br />
git checkout <branch name for feature 1><br />
arc land<br />
git checkout <branch name for feature 2><br />
git rebase origin/<target branch><br />
arc land<br />
</nowiki>}}<br />
</li><br />
</ol><br />
<br />
<br />
<br />
= How to review someone else's patch =<br />
Arcanist (<tt>arc</tt>) makes it easy to review someone's patch. But first you'll need a development environment set up. If you haven't done that yet, it's time to do so. See [[Get_Involved/development#Set_up_your_development_environment]]. Follow the instructions to compile and run the program.<br />
<br />
== Apply the patch and compile the software ==<br />
Find the patch's revision ID. For example, for https://phabricator.kde.org/D11184, the ID is <tt>D11184</tt>.<br />
<br />
Now check out or enter the source repository for the software that's being patched. The repository is listed on the web UI: <br />
[[File:Konsole repository for patch.png]]<br />
...So this would be a patch for Konsole.<br />
<br />
If you've never built it before, check it out and build it once first:<br />
{{Input|1=<nowiki><br />
kdesrc-build konsole<br />
</nowiki>}}<br />
<br />
Now go to its source directory:<br />
{{Input|1=<nowiki><br />
cd ~/kde/src/konsole<br />
</nowiki>}}<br />
<br />
...and apply the patch:<br />
{{Input|1=<nowiki><br />
arc patch <revision ID><br />
</nowiki>}}<br />
<br />
Answer <tt>y</tt> to any questions that are posed. Arc will automatically create a branch named <tt>arcpatch-<revision ID></tt> for the patch, so it won't damage your checkout at all.<br />
<br />
Now it's time to compile and run the software to make sure that the patch does what it says it does and doesn't cause any regressions! Compile the patched source code:<br />
{{Input|1=<nowiki><br />
kdesrc-build konsole --no-src --resume-from konsole<br />
</nowiki>}}<br />
Those arguments will tell <tt>kdesrc-build</tt> to not update the source code after you applied the patch, and to not build any dependencies.<br />
<br />
If it didn't compile, that's reason alone to reject the patch! Go to the web UI and report your findings, and apply a "Request Changes" status.<br />
<br />
== Perform QA ==<br />
If it did compile, then it's time to perform QA, because it's important to thoroughly test patches to ensure that bad code and regressions don't slip in. '''This is the entire purpose of having a review infrastructure'''; it is very important.<br />
<br />
First make sure the unit tests all pass:<br />
{{Input|1=<nowiki><br />
cd ~kde/build/kde/applications/konsole<br />
ctest<br />
</nowiki>}}<br />
<br />
If any tests fail, report this through a comment on the patch's web page (https://phabricator.kde.org/<revision ID>).<br />
<br />
Next, execute the Test Plan that the submitter wrote. If the patch does not have a Test Plan, request one. Does it all still work for you? If not, return to the web UI and Request Changes, writing a detailed comment explaining what didn't work. It is permissible to do this even if you have not been specified as a reviewer! '''Anyone can reject a patch on the grounds that it does not work, does not do what it says it does, or causes regressions.'''<br />
<br />
If the original Test Plan succeeds, try to break the patch. Here are some ideas:<br />
# Remove the program's configuration file (<tt>~/.config/<program name>rc</tt> ) and re-open it<br />
# Try the program with a HiDPI scale factor (or without one) or with a different default font size<br />
# If it's a new feature, feed it unexpected input<br />
# Test related functionality<br />
<br />
'''Try to break it!''' A good patch will handle corner cases and variations in configuration. The price of configurability is vigilant testing! We owe it to our users to test using many configurations, not just the defaults or our personal settings.<br />
<br />
== Perform code review ==<br />
{{Note|1= Need a section on code review here, preferably written by a core KDE developer or another very experienced developer}}<br />
<br />
== Engage with the author and other reviewers ==<br />
After you have run the program and evaluated the patch, it's time to leave some review comments on the webpage (which again is at https://phabricator.kde.org/<revision ID>). If you have been specified as a reviewer, or are a member of a group that has been specified as a reviewer, it is permissible to Accept the patch. But keep in mind that reviewing involves responsibility: you are giving a thumbs-up to code that will be run potentially by millions of people. If you accept and land a patch that causes regressions, you will share some of the blame. It's important to take the reviewer role seriously.<br />
<br />
= Customization =<br />
== Creating custom dashboard feeds ==<br />
You can customize your Phabricator homepage by creating a new dashboard. However, the selection of what you can post on your dashboard is limited. The defaults will show all tasks from all projects. <br />
<br />
To narrow this down, you need to define a custom query to serve as a filter. For example, if you work on Plasma Mobile and want to monitor the to-do list, perhaps you want to show only tasks that are in Plasma Mobile and are tagged as open. To do that, enter Maniphest, select "advanced search," select the appropriate terms, then click "save custom query." You can give your query a name. Once it is saved, the query will become available as a new filter for creating feeds on your dashboard. (In Differential you seem to need to perform the test search before the "save query" button becomes visible.)</div>Gjditchfieldhttps://community.kde.org/index.php?title=Infrastructure/Phabricator&diff=86531Infrastructure/Phabricator2019-10-25T20:12:36Z<p>Gjditchfield: /* Step 1: Create a new diff */</p>
<hr />
<div>[http://phabricator.kde.org/ Phabricator] is KDE's task management and patch review system. This page is intended to serve as a general-purpose introduction to the most important aspects: submitting and reviewing patches. It should not take long before you are happily using Phabricator.<br />
<br />
{{Info|The KDE community does not use the Phabricator bug reporting function. We continue to use bugzilla at https://bugs.kde.org}}<br />
<br />
<br />
<br />
= Basic Tasks =<br />
== Logging in ==<br />
Log in to [http://phabricator.kde.org/ Phabricator] with your KDE Identity account. If you don't have one, you can [https://identity.kde.org/index.php?r=registration/index sign up for one here]. At the Phabricator home page, click the "Log in" button on the top of the page and enter your KDE Identity username and password:<br />
<br />
[[File:Phabricator login 2.png ]]<br />
If your KDE Identity account works on http://identity.kde.org but not on http://phabricator.kde.org, please contact the KDE sysadmins at sysadmin@kde.org.<br />
<br />
== Getting help ==<br />
The official documentation is in the [https://secure.phabricator.com/book/phabricator/ Phabricator book] and [https://phacility.com/phabricator/ on their website] -- note that since everything is under rapid development, most of the documentation is incomplete. A good way to find the information you're looking for is to search [https://secure.phabricator.com/ Phabricator upstream].<br />
<br />
== Posting a Patch using the website ==<br />
Once you have [https://community.kde.org/Get_Involved/development#Set_up_your_development_environment set up your development environment] and [https://community.kde.org/Get_Involved/development#Submit_a_patch created a patch], you can submit it using Phabricator!<br />
<br />
Log in to Phabricator and click on <tt>Code Review</tt> in the list on the left. Then, click the <tt>+Create Diff</tt> button in the upper-right corner of the screen. Paste the text of the diff or upload the file using the file dialog. Reviewers are mostly added automatically, but if your patch includes any visual or user interface changes, please add <tt>#vdg</tt> as a reviewer to make sure the [[Get Involved/design | KDE Visual Design Group]] sees it! Please make sure to add a screenshot, too.<br />
<br />
==Formatting your patch ==<br />
The Title of your patch will become the git commit message, so please follow [https://chris.beams.io/posts/git-commit/#seven-rules commit message best practices] when creating a title for the patch.<br />
<br />
In the Summary section, write at least one sentence describing your change and why it is necessary, adding more details if necessary.<br />
<br />
=== Add special keywords ===<br />
If your patch is intended to fix a Bugzilla ticket, include one of the following on its own line in the Summary section:<br />
<pre><br />
BUG: 385942<br />
</pre><br />
or<br />
<pre><br />
FEATURE: 384936<br />
</pre><br />
(Just the Bugzilla ticket number, '''not the full URL''')<br />
<br />
Use <tt>BUG:</tt> If the Bugzilla ticket describes a bug, and <tt>FEATURE:</tt> if the Bugzilla ticket describes a feature request. Either of these tags will cause that Bugzilla ticket to be automatically closed when the patch is committed.<br />
<br />
If you added the <tt>BUG:</tt> or <tt>FEATURE:</tt> tag, also add another tag indicating what version receives the fix or new feature:<br />
<pre><br />
FIXED-IN: [version]<br />
</pre><br />
Replace <tt>[version]</tt> with an appropriate version string; see [[Guidelines and HOWTOs/Write a version string]] to find out how to write one. If you can't figure it out, don't worry about it and just omit the tag; a KDE developer will help you add it during code review.<br />
<br />
[https://techbase.kde.org/Development/Git/Configuration#Commit_Template Here is more information] about other special messages that interact with Bugzilla tickets. You can also add special messages that interact with [https://secure.phabricator.com/T5132 other Phabricator tools] (e.g. Maniphest tasks).<br />
<br />
{{Note|These keywords will only work if the email address in your <tt>~/.config/git/config</tt> file matches the email address used for your https://bugs.kde.org account (See [https://techbase.kde.org/Development/Git/Configuration#Basic_Settings this page] for more information).}}<br />
<br />
=== Include some screenshots ===<br />
For patches that change something about the user interface, it's customary to include a screenshot of how the interface looks with your patch. Bonus points for including a "Before" image too, so reviewers can easily compare them.<br />
<br />
== What happens next? ==<br />
After you've submitted your patch, KDE developers who work with the software in question will review it and provide feedback. This can often take a few days. However, if nobody has responded after a week, it's likely that the review was overlooked (sorry about that!) and it's appropriate to make a comment saying, "Ping!" or something to that effect.<br />
<br />
Once the patch is accepted, KDE Developers will land it for you!<br />
<br />
<br />
<br />
= Using Arcanist to post patches =<br />
After you've posted a few patches, using the web UI to post patches gets tiresome. Arcanist is a tool to simplify and speed up the process of posting, updating, and merging Phabricator patches. Setting it up is easy:<br />
<br />
== Installing Arcanist ==<br />
=== Debian/Ubuntu/KDE Neon ===<br />
{{Input|1=<nowiki><br />
sudo apt install arcanist<br />
</nowiki>}}<br />
<br />
=== Fedora ===<br />
kanarip/phabricator is not an official Fedora repository, so errors may occur.<br />
{{Input|1=<nowiki><br />
sudo dnf copr enable kanarip/phabricator<br />
sudo dnf install arcanist<br />
</nowiki>}}<br />
<br />
Alternatively, install arc manually from the github repository:<br />
{{Input|1=<nowiki><br />
sudo dnf install php-{common,cli}<br />
mkdir somewhere/<br />
cd somewhere/<br />
git clone https://github.com/phacility/libphutil.git<br />
git clone https://github.com/phacility/arcanist.git<br />
ln -s $(pwd)/arcanist/bin/arc ~/.local/bin/arc<br />
</nowiki>}}<br />
<br />
=== openSUSE ===<br />
Tumbleweed:<br />
{{Input|1=<nowiki><br />
sudo zypper ar -f https://download.opensuse.org/repositories/devel:tools:scm/openSUSE_Tumbleweed/devel:tools:scm.repo<br />
sudo zypper install arcanist<br />
</nowiki>}}<br />
<br />
Leap 15:<br />
{{Input|1=<nowiki><br />
sudo zypper ar -f https://download.opensuse.org/repositories/devel:tools:scm/openSUSE_Leap_15.0/devel:tools:scm.repo<br />
sudo zypper install arcanist<br />
</nowiki>}}<br />
<br />
=== Arch / Manjaro ===<br />
{{Input|1=<nowiki><br />
trizen -S arcanist-git<br />
</nowiki>}}<br />
<br />
=== Gentoo ===<br />
The default portage tree does not have an ebuild, so unless you already have it, you need to add an overlay, e.g. the kde one:<br />
<br />
{{Input|1=<nowiki><br />
sudo layman -a kde<br />
</nowiki>}}<br />
<br />
Once that is done, you can emerge it:<br />
<br />
{{Input|1=<nowiki><br />
sudo emerge -av arcanist<br />
</nowiki>}}<br />
<br />
You might have to install and possibly unmask a few php related packages, so follow the instructions portage gives you.<br />
<br />
Keep in mind that it is a vcs version (9999), which means it doesn't have release updates and you are in charge of keeping it up to date.<br />
<br />
=== Windows ===<br />
[https://secure.phabricator.com/book/phabricator/article/arcanist_windows/ Arcanist User Guide: Windows]<br />
<br />
The most non-obvious step is that you will need to configure your [http://php.net/manual/en/install.windows.manual.php PHP installation] to use Curl. This requires editing the <tt>php.ini</tt> configuration file to add the line <tt>extension_dir = "ext"</tt> and add <tt>php_curl</tt> to the list of extensions. After adding <tt>php.exe</tt> to your <tt>PATH</tt> and installing arcanist/libphutil, you can run <tt>arc</tt> by defining a function in your Powershell profile:<br />
<pre><br />
function arc { php -f "C:\path\to\arcanist.php" -- $args }<br />
</pre><br />
<br />
<br/><br />
<br />
After you have installed Arc, you can learn more using <tt>man arc</tt> or <tt>arc --help</tt>. Another command useful for getting a feel for Phabricator's style is <tt>arc anoid</tt>.<br />
<br />
<br />
=== Other operating systems ===<br />
<br />
Follow the instructions from the [https://secure.phabricator.com/book/phabricator/article/arcanist_quick_start/ Arcanist Quick Start] guide.<br />
<br />
== Perform one-time setup steps ==<br />
Before you are able to use <tt>arc</tt> for work you will need to install a certificate that will allow it to login to Phabricator:<br />
{{Input|1=<nowiki><br />
arc install-certificate https://phabricator.kde.org<br />
</nowiki>}}<br />
Just follow the instructions it provides: you will have to visit the page it indicates and copy the API token you find on that page back to the shell.<br />
<br />
Next, you will need to tell git who you really are, so your patches will include correct authorship information and can be attributed to yourself:<br />
{{Input|1=<nowiki><br />
git config --global user.name "<Your real name>"<br />
git config --global user.email "<Your identity.kde.org email address>"<br />
</nowiki>}}<br />
Both of these steps only need to be done once.<br />
<br />
== Workflow ==<br />
The sanest and easiest way to use <tt>arc</tt> is to follow a typical '''feature branch workflow''': keep your master branch synchronized with the upstream repository, and make all changes on separate branches. '''Each patch needs its own private, temporary branch.''' Once your patch has been merged, delete the feature branch, and make another new branch for the next patch.<br />
<br />
The following commands all need to be executed in your source directory. The hidden file/directory "<tt>.arcconfig</tt>" and "<tt>.git</tt>" tell <tt>arc</tt> what to do so you don't have to.<br />
<br />
=== Step 1: Create a new diff ===<br />
Before editing anything, create a new branch for your patch:<br />
{{Input|1=<nowiki><br />
arc feature <name-for-your-new-branch><br />
</nowiki>}}(For Git experts: this is equivalent to `git checkout -t -b <name-for-your-new-branch>`)<br />
<br />
Now make changes on the feature branch. When you're ready to have your changes reviewed, enter the following command:<br />
{{Input|1=<nowiki><br />
arc diff # this will do the `git add` and `git commit` for you; if it asks about ignoring untracked files, enter 'y'<br />
</nowiki>}}<br />
When you run <tt>arc diff</tt>, you will go through a series of dialogs. At the end, you will be asked to rewrite your Git commit message to fit the standard Differential format, like so: <br />
<pre><br />
<first line of original git commit message><br />
<br />
Summary: <rest of original commit message><br />
<br />
Test Plan:<br />
<br />
Reviewers:<br />
<br />
Subscribers: <br />
</pre><br />
<br />
<tt><first line of original git commit message></tt> will become the commit message, so please follow [https://chris.beams.io/posts/git-commit/#seven-rules commit message best practices].<br />
<br />
As when using the web UI, enter any special Bugzilla keywords (such as <tt>BUG: 385942</tt>) on their own lines in the "Summary" section.<br />
<br />
As when using the web UI, there is no need to specifically add anyone under the "Reviewers" or "Subscribers" sections unless your patch includes any visual or user interface changes. In this case, please add <tt>#vdg</tt> as a reviewer to make sure the [[Get Involved/design | KDE Visual Design Group]] sees it!<br />
<br />
=== Step 2: Update your diff in response to review comments ===<br />
After <tt>arc</tt> uploads the patch to Phabricator, the project's reviewers will take a look and give you some comments. If you get a thumbs up immediately, you can skip this step. But often you will get a review like "looks good, we can take it if you fix problems x, y, and z." Make the necessary changes, add an extra commit to the git branch, and update the Phabricator revision:<br />
{{Input|1=<nowiki><br />
[implement changes based on review comments]<br />
arc diff<br />
</nowiki>}}<br />
<br />
=== Step 3: Land your diff ===<br />
If you do not have a [[Infrastructure/Get_a_Contributor_Account | KDE Developer Account]], then someone who does will have to land your patch for you. Otherwise, you can do it yourself once the patch has been accepted and reviewers have given you permission to "ship it!"<br />
<br />
First, make sure that the world is sane, and that only your patch will be landed:<br />
{{Input|1=<nowiki><br />
[make sure you are on your feature branch]<br />
arc land --preview<br />
</nowiki>}}<br />
The output of that command should show only the commit message for your patch. If you see more than that, stop and ask your reviewers for help.<br />
<br />
==== Landing on master ====<br />
This is simple:<br />
{{Input|1=<nowiki><br />
arc land<br />
</nowiki>}}<br />
<br />
==== Landing on the "Stable branch" ====<br />
By default, arc will land the patch onto whatever branch was the parent. For example, if you branched from master, <tt>arc</tt> will land on master; if you branched from <tt>Applications/18.12</tt>, arc will land on that branch instead, and so on.<br />
<br />
If you branched your patch from <tt>master</tt> but it is a relatively low-risk bugfix, you will often be a asked to land it on the "stable branch" instead of <tt>master</tt>. The easiest way to do this is to cherry-pick the commit from your feature branch onto the appropriate stable branch and then merge forward.<br />
<br />
{{warning|If at any time you feel nervous or run into trouble, ask your reviewers for help. This can be tricky, and undoing bad merges is a pain in the neck.}}<br />
Here is an example of how to do this with a patch branched from <tt>master</tt> that needs to go into the <tt>Applications/18.12</tt> stable branch (replace <tt>Applications/18.12</tt> with the appropriate stable branch name when you do this yourself, obviously).<br />
<br />
{{Input|1=<nowiki><br />
[make sure you are on your feature branch]<br />
git log -n 1 --pretty=format:"%H"<br />
[copy that commit hash]<br />
git checkout Applications/18.12<br />
git pull<br />
git cherry-pick [the commit hash]<br />
git push<br />
git branch -D [the name of your feature branch]<br />
</nowiki>}}<br />
<br />
Note that after committing to the stable branch you are expected to merge that branch to <tt>master</tt> afterwards:<br />
{{Input|1=<nowiki><br />
git checkout master<br />
git pull<br />
git merge -s recursive -Xours Applications/18.12<br />
git push<br />
</nowiki>}}<br />
<br />
==== Landing someone else's diff ====<br />
If you have a contributor account and you are helping someone without one through the process, you will need to land their diff for them once it's been accepted. Here's how:<br />
{{Input|1=<nowiki><br />
arc patch <revision ID><br />
git show HEAD<br />
</nowiki>}}<br />
<br />
At this point, you need to verify that the authorship information shown is correct. If it's not, you will need to ask the patch author for their real name and email address. Then you use that information to update the local commit for the patch like so:<br />
{{Input|1=<nowiki><br />
# Make sure you're on the branch that corresponds to the patch!<br />
git commit --amend --author="firstname lastname <email address>"<br />
</nowiki>}}<br />
<br />
At this point, you can land the diff normally, [[#Step 3: Land your diff|as described above]].<br />
<br />
<br />
<br />
== Arcanist Tips & Tricks ==<br />
<br />
=== Look before you diff ===<br />
You can check with <tt>arc which</tt> what Arcanist will do before performing the actual upload with <tt>arc diff</tt> if you are unsure what will happen. In particular, look for which commits will be included in your Diff.<br />
<br />
=== arc diff: specify a revision manually ===<br />
Sometimes - if you messed up with your git branches - arc cannot properly determine which revision (D12345) should be updated. In this case you can use <tt>arc diff --update D12345</tt>. See <tt>arc help diff</tt>.<br />
<br />
=== Updating the summary of the Differential from the local Git commit message ===<br />
If you changed the commit message locally and want to update the text in the summary in Differential, call Arc like this:<br />
{{Input|1=<nowiki><br />
arc diff --edit --verbatim<br />
</nowiki>}}<br />
<br />
=== Updating the local Git commit message from changes done on Phabricator ===<br />
If you or someone else updated the title, summary, test plan, reviewers or subscribers of a Diff using the web editor in Phabricator, Arcanist allows to sync those changes back to your local Git commit message:<br />
{{Input|1=<nowiki><br />
arc amend<br />
</nowiki>}}<br />
<br />
Note that in general Arcanist will do this automatically for you once you <tt>arc land</tt>.<br />
<br />
== Advanced Tasks ==<br />
Once in a while, a reviewer will tell you to do specific things. This section will help you figure out what is meant to be done.<br />
<br />
=== "Please do that in a separate commit" ===<br />
Should your patch contain an unrelated change, your reviewer will ask you to undo that part and possibly open a new Diff for that. Here is what you can do:<br />
<br />
If the change in question is in a separate commit on your local branch:<br />
{{Input|1=<nowiki><br />
git revert <unrelated change><br />
arc diff # this updates the first Diff<br />
git checkout -b <new branch name> master<br />
git cherry-pick <unrelated change><br />
arc diff # this creates a new Diff for the unrelated change<br />
</nowiki>}}<br />
<br />
If you mixed different changes into a single commit on your local branch:<br />
{{Input|1=<nowiki><br />
git reset HEAD^<br />
git add -p # type "?" for help, then pick all hunks you want to keep<br />
git stash # the stash now contains the hunks for the second patch<br />
git commit<br />
arc diff # this updates the first Diff<br />
git checkout -b <new branch name> master<br />
git stash pop<br />
git commit<br />
arc diff # this creates a new Diff for the unrelated change<br />
</nowiki>}}<br />
<br />
In case this does not work for you, there's always the plain old copy-and-paste. In general, it is best to avoid adding unrelated changes from the beginning. :-)<br />
<br />
=== Marking patches as dependent on other patches ===<br />
Sometimes you will want to submit a patch that depends on another patch, creating a '''dependency chain'''.<br />
<br />
==== If each patch is intended for a different project ====<br />
Example: you submit a patch to add a new feature to KIO, and then submit another patch for Dolphin that uses that feature. Here's what you do:<br />
<ol><br />
<li>Create your first patch as above</li><br />
<li>When creating the second patch, add the following to its own line in the "Summary" section:<br />
{{Input|1=<nowiki>Depends on DXXXX</nowiki>}}<br />
(Replace "DXXXX" with the ID of the dependent patch, '''not the full URL)'''</li><br />
</ol><br />
<br />
==== If the patches are all for the same project ====<br />
Example: you are implementing multiple new features for a single project that each depend on the patch for the prior feature. Here's what you do:<br />
<ol><br />
<li>Create a branch to track the first feature:<br />
{{Input|1=<nowiki><br />
git checkout -b <branch name for feature 1> --track origin/master<br />
</nowiki>}}<br />
Then implement the feature and make a commit.<br />
</li><br />
<li>Then create a branch for your second feature, ''tracking the first branch:''<br />
{{Input|1=<nowiki><br />
git checkout -b <branch name for feature 2> --track <branch name for feature 1><br />
</nowiki>}}<br />
As above, implement the feature and make a commit. Continue this pattern for any other required dependent features.<br />
</li><br />
<br />
<li><br />
When you're ready to turn your dependency chain feature branches into patches, do the following:<br />
{{Input|1=<nowiki><br />
git checkout <branch name for feature 1><br />
arc diff [then go through the process of creating the patch normally]<br />
git checkout <branch name for feature 2><br />
git commit --amend [then add the special text "Depends on DXXXX", replacing DXXXX with the ID of the first patch<br />
arc diff [then go through the process of creating the patch normally]<br />
</nowiki>}}<br />
...And so on.<br />
</li><br />
<br />
<li>After you get comments, you will have to make changes to your patches and re-base dependent patches:<br />
{{Input|1=<nowiki><br />
git checkout <branch name for feature 1><br />
[Make changes]<br />
git add -u<br />
git commit<br />
arc diff<br />
git checkout <branch name for feature 2><br />
git rebase <branch name for feature 1><br />
git add -u<br />
git commit<br />
arc diff<br />
</nowiki>}}<br />
...And so on.<br />
</li><br />
<br />
<li>When you're ready to land any or all of your patches, do it in sequence, starting from the patch with no unmet dependencies:<br />
{{Input|1=<nowiki><br />
git checkout <branch name for feature 1><br />
arc land<br />
git checkout <branch name for feature 2><br />
git rebase origin/<target branch><br />
arc land<br />
</nowiki>}}<br />
</li><br />
</ol><br />
<br />
<br />
<br />
= How to review someone else's patch =<br />
Arcanist (<tt>arc</tt>) makes it easy to review someone's patch. But first you'll need a development environment set up. If you haven't done that yet, it's time to do so. See [[Get_Involved/development#Set_up_your_development_environment]]. Follow the instructions to compile and run the program.<br />
<br />
== Apply the patch and compile the software ==<br />
Find the patch's revision ID. For example, for https://phabricator.kde.org/D11184, the ID is <tt>D11184</tt>.<br />
<br />
Now check out or enter the source repository for the software that's being patched. The repository is listed on the web UI: <br />
[[File:Konsole repository for patch.png]]<br />
...So this would be a patch for Konsole.<br />
<br />
If you've never built it before, check it out and build it once first:<br />
{{Input|1=<nowiki><br />
kdesrc-build konsole<br />
</nowiki>}}<br />
<br />
Now go to its source directory:<br />
{{Input|1=<nowiki><br />
cd ~/kde/src/konsole<br />
</nowiki>}}<br />
<br />
...and apply the patch:<br />
{{Input|1=<nowiki><br />
arc patch <revision ID><br />
</nowiki>}}<br />
<br />
Answer <tt>y</tt> to any questions that are posed. Arc will automatically create a branch named <tt>arcpatch-<revision ID></tt> for the patch, so it won't damage your checkout at all.<br />
<br />
Now it's time to compile and run the software to make sure that the patch does what it says it does and doesn't cause any regressions! Compile the patched source code:<br />
{{Input|1=<nowiki><br />
kdesrc-build konsole --no-src --resume-from konsole<br />
</nowiki>}}<br />
Those arguments will tell <tt>kdesrc-build</tt> to not update the source code after you applied the patch, and to not build any dependencies.<br />
<br />
If it didn't compile, that's reason alone to reject the patch! Go to the web UI and report your findings, and apply a "Request Changes" status.<br />
<br />
== Perform QA ==<br />
If it did compile, then it's time to perform QA, because it's important to thoroughly test patches to ensure that bad code and regressions don't slip in. '''This is the entire purpose of having a review infrastructure'''; it is very important.<br />
<br />
First make sure the unit tests all pass:<br />
{{Input|1=<nowiki><br />
cd ~kde/build/kde/applications/konsole<br />
ctest<br />
</nowiki>}}<br />
<br />
If any tests fail, report this through a comment on the patch's web page (https://phabricator.kde.org/<revision ID>).<br />
<br />
Next, execute the Test Plan that the submitter wrote. If the patch does not have a Test Plan, request one. Does it all still work for you? If not, return to the web UI and Request Changes, writing a detailed comment explaining what didn't work. It is permissible to do this even if you have not been specified as a reviewer! '''Anyone can reject a patch on the grounds that it does not work, does not do what it says it does, or causes regressions.'''<br />
<br />
If the original Test Plan succeeds, try to break the patch. Here are some ideas:<br />
# Remove the program's configuration file (<tt>~/.config/<program name>rc</tt> ) and re-open it<br />
# Try the program with a HiDPI scale factor (or without one) or with a different default font size<br />
# If it's a new feature, feed it unexpected input<br />
# Test related functionality<br />
<br />
'''Try to break it!''' A good patch will handle corner cases and variations in configuration. The price of configurability is vigilant testing! We owe it to our users to test using many configurations, not just the defaults or our personal settings.<br />
<br />
== Perform code review ==<br />
{{Note|1= Need a section on code review here, preferably written by a core KDE developer or another very experienced developer}}<br />
<br />
== Engage with the author and other reviewers ==<br />
After you have run the program and evaluated the patch, it's time to leave some review comments on the webpage (which again is at https://phabricator.kde.org/<revision ID>). If you have been specified as a reviewer, or are a member of a group that has been specified as a reviewer, it is permissible to Accept the patch. But keep in mind that reviewing involves responsibility: you are giving a thumbs-up to code that will be run potentially by millions of people. If you accept and land a patch that causes regressions, you will share some of the blame. It's important to take the reviewer role seriously.<br />
<br />
= Customization =<br />
== Creating custom dashboard feeds ==<br />
You can customize your Phabricator homepage by creating a new dashboard. However, the selection of what you can post on your dashboard is limited. The defaults will show all tasks from all projects. <br />
<br />
To narrow this down, you need to define a custom query to serve as a filter. For example, if you work on Plasma Mobile and want to monitor the to-do list, perhaps you want to show only tasks that are in Plasma Mobile and are tagged as open. To do that, enter Maniphest, select "advanced search," select the appropriate terms, then click "save custom query." You can give your query a name. Once it is saved, the query will become available as a new filter for creating feeds on your dashboard. (In Differential you seem to need to perform the test search before the "save query" button becomes visible.)</div>Gjditchfieldhttps://community.kde.org/index.php?title=Infrastructure/Phabricator&diff=86530Infrastructure/Phabricator2019-10-25T20:10:58Z<p>Gjditchfield: /* Step 1: Create a new diff */</p>
<hr />
<div>[http://phabricator.kde.org/ Phabricator] is KDE's task management and patch review system. This page is intended to serve as a general-purpose introduction to the most important aspects: submitting and reviewing patches. It should not take long before you are happily using Phabricator.<br />
<br />
{{Info|The KDE community does not use the Phabricator bug reporting function. We continue to use bugzilla at https://bugs.kde.org}}<br />
<br />
<br />
<br />
= Basic Tasks =<br />
== Logging in ==<br />
Log in to [http://phabricator.kde.org/ Phabricator] with your KDE Identity account. If you don't have one, you can [https://identity.kde.org/index.php?r=registration/index sign up for one here]. At the Phabricator home page, click the "Log in" button on the top of the page and enter your KDE Identity username and password:<br />
<br />
[[File:Phabricator login 2.png ]]<br />
If your KDE Identity account works on http://identity.kde.org but not on http://phabricator.kde.org, please contact the KDE sysadmins at sysadmin@kde.org.<br />
<br />
== Getting help ==<br />
The official documentation is in the [https://secure.phabricator.com/book/phabricator/ Phabricator book] and [https://phacility.com/phabricator/ on their website] -- note that since everything is under rapid development, most of the documentation is incomplete. A good way to find the information you're looking for is to search [https://secure.phabricator.com/ Phabricator upstream].<br />
<br />
== Posting a Patch using the website ==<br />
Once you have [https://community.kde.org/Get_Involved/development#Set_up_your_development_environment set up your development environment] and [https://community.kde.org/Get_Involved/development#Submit_a_patch created a patch], you can submit it using Phabricator!<br />
<br />
Log in to Phabricator and click on <tt>Code Review</tt> in the list on the left. Then, click the <tt>+Create Diff</tt> button in the upper-right corner of the screen. Paste the text of the diff or upload the file using the file dialog. Reviewers are mostly added automatically, but if your patch includes any visual or user interface changes, please add <tt>#vdg</tt> as a reviewer to make sure the [[Get Involved/design | KDE Visual Design Group]] sees it! Please make sure to add a screenshot, too.<br />
<br />
==Formatting your patch ==<br />
The Title of your patch will become the git commit message, so please follow [https://chris.beams.io/posts/git-commit/#seven-rules commit message best practices] when creating a title for the patch.<br />
<br />
In the Summary section, write at least one sentence describing your change and why it is necessary, adding more details if necessary.<br />
<br />
=== Add special keywords ===<br />
If your patch is intended to fix a Bugzilla ticket, include one of the following on its own line in the Summary section:<br />
<pre><br />
BUG: 385942<br />
</pre><br />
or<br />
<pre><br />
FEATURE: 384936<br />
</pre><br />
(Just the Bugzilla ticket number, '''not the full URL''')<br />
<br />
Use <tt>BUG:</tt> If the Bugzilla ticket describes a bug, and <tt>FEATURE:</tt> if the Bugzilla ticket describes a feature request. Either of these tags will cause that Bugzilla ticket to be automatically closed when the patch is committed.<br />
<br />
If you added the <tt>BUG:</tt> or <tt>FEATURE:</tt> tag, also add another tag indicating what version receives the fix or new feature:<br />
<pre><br />
FIXED-IN: [version]<br />
</pre><br />
Replace <tt>[version]</tt> with an appropriate version string; see [[Guidelines and HOWTOs/Write a version string]] to find out how to write one. If you can't figure it out, don't worry about it and just omit the tag; a KDE developer will help you add it during code review.<br />
<br />
[https://techbase.kde.org/Development/Git/Configuration#Commit_Template Here is more information] about other special messages that interact with Bugzilla tickets. You can also add special messages that interact with [https://secure.phabricator.com/T5132 other Phabricator tools] (e.g. Maniphest tasks).<br />
<br />
{{Note|These keywords will only work if the email address in your <tt>~/.config/git/config</tt> file matches the email address used for your https://bugs.kde.org account (See [https://techbase.kde.org/Development/Git/Configuration#Basic_Settings this page] for more information).}}<br />
<br />
=== Include some screenshots ===<br />
For patches that change something about the user interface, it's customary to include a screenshot of how the interface looks with your patch. Bonus points for including a "Before" image too, so reviewers can easily compare them.<br />
<br />
== What happens next? ==<br />
After you've submitted your patch, KDE developers who work with the software in question will review it and provide feedback. This can often take a few days. However, if nobody has responded after a week, it's likely that the review was overlooked (sorry about that!) and it's appropriate to make a comment saying, "Ping!" or something to that effect.<br />
<br />
Once the patch is accepted, KDE Developers will land it for you!<br />
<br />
<br />
<br />
= Using Arcanist to post patches =<br />
After you've posted a few patches, using the web UI to post patches gets tiresome. Arcanist is a tool to simplify and speed up the process of posting, updating, and merging Phabricator patches. Setting it up is easy:<br />
<br />
== Installing Arcanist ==<br />
=== Debian/Ubuntu/KDE Neon ===<br />
{{Input|1=<nowiki><br />
sudo apt install arcanist<br />
</nowiki>}}<br />
<br />
=== Fedora ===<br />
kanarip/phabricator is not an official Fedora repository, so errors may occur.<br />
{{Input|1=<nowiki><br />
sudo dnf copr enable kanarip/phabricator<br />
sudo dnf install arcanist<br />
</nowiki>}}<br />
<br />
Alternatively, install arc manually from the github repository:<br />
{{Input|1=<nowiki><br />
sudo dnf install php-{common,cli}<br />
mkdir somewhere/<br />
cd somewhere/<br />
git clone https://github.com/phacility/libphutil.git<br />
git clone https://github.com/phacility/arcanist.git<br />
ln -s $(pwd)/arcanist/bin/arc ~/.local/bin/arc<br />
</nowiki>}}<br />
<br />
=== openSUSE ===<br />
Tumbleweed:<br />
{{Input|1=<nowiki><br />
sudo zypper ar -f https://download.opensuse.org/repositories/devel:tools:scm/openSUSE_Tumbleweed/devel:tools:scm.repo<br />
sudo zypper install arcanist<br />
</nowiki>}}<br />
<br />
Leap 15:<br />
{{Input|1=<nowiki><br />
sudo zypper ar -f https://download.opensuse.org/repositories/devel:tools:scm/openSUSE_Leap_15.0/devel:tools:scm.repo<br />
sudo zypper install arcanist<br />
</nowiki>}}<br />
<br />
=== Arch / Manjaro ===<br />
{{Input|1=<nowiki><br />
trizen -S arcanist-git<br />
</nowiki>}}<br />
<br />
=== Gentoo ===<br />
The default portage tree does not have an ebuild, so unless you already have it, you need to add an overlay, e.g. the kde one:<br />
<br />
{{Input|1=<nowiki><br />
sudo layman -a kde<br />
</nowiki>}}<br />
<br />
Once that is done, you can emerge it:<br />
<br />
{{Input|1=<nowiki><br />
sudo emerge -av arcanist<br />
</nowiki>}}<br />
<br />
You might have to install and possibly unmask a few php related packages, so follow the instructions portage gives you.<br />
<br />
Keep in mind that it is a vcs version (9999), which means it doesn't have release updates and you are in charge of keeping it up to date.<br />
<br />
=== Windows ===<br />
[https://secure.phabricator.com/book/phabricator/article/arcanist_windows/ Arcanist User Guide: Windows]<br />
<br />
The most non-obvious step is that you will need to configure your [http://php.net/manual/en/install.windows.manual.php PHP installation] to use Curl. This requires editing the <tt>php.ini</tt> configuration file to add the line <tt>extension_dir = "ext"</tt> and add <tt>php_curl</tt> to the list of extensions. After adding <tt>php.exe</tt> to your <tt>PATH</tt> and installing arcanist/libphutil, you can run <tt>arc</tt> by defining a function in your Powershell profile:<br />
<pre><br />
function arc { php -f "C:\path\to\arcanist.php" -- $args }<br />
</pre><br />
<br />
<br/><br />
<br />
After you have installed Arc, you can learn more using <tt>man arc</tt> or <tt>arc --help</tt>. Another command useful for getting a feel for Phabricator's style is <tt>arc anoid</tt>.<br />
<br />
<br />
=== Other operating systems ===<br />
<br />
Follow the instructions from the [https://secure.phabricator.com/book/phabricator/article/arcanist_quick_start/ Arcanist Quick Start] guide.<br />
<br />
== Perform one-time setup steps ==<br />
Before you are able to use <tt>arc</tt> for work you will need to install a certificate that will allow it to login to Phabricator:<br />
{{Input|1=<nowiki><br />
arc install-certificate https://phabricator.kde.org<br />
</nowiki>}}<br />
Just follow the instructions it provides: you will have to visit the page it indicates and copy the API token you find on that page back to the shell.<br />
<br />
Next, you will need to tell git who you really are, so your patches will include correct authorship information and can be attributed to yourself:<br />
{{Input|1=<nowiki><br />
git config --global user.name "<Your real name>"<br />
git config --global user.email "<Your identity.kde.org email address>"<br />
</nowiki>}}<br />
Both of these steps only need to be done once.<br />
<br />
== Workflow ==<br />
The sanest and easiest way to use <tt>arc</tt> is to follow a typical '''feature branch workflow''': keep your master branch synchronized with the upstream repository, and make all changes on separate branches. '''Each patch needs its own private, temporary branch.''' Once your patch has been merged, delete the feature branch, and make another new branch for the next patch.<br />
<br />
The following commands all need to be executed in your source directory. The hidden file/directory "<tt>.arcconfig</tt>" and "<tt>.git</tt>" tell <tt>arc</tt> what to do so you don't have to.<br />
<br />
=== Step 1: Create a new diff ===<br />
Before editing anything, create a new branch for your patch:<br />
{{Input|1=<nowiki><br />
$ arc feature <name-for-your-new-branch><br />
</nowiki>}}(For Git experts: this is equivalent to `git checkout -t -b <name-for-your-new-branch>`)<br />
<br />
Now make changes on the feature branch. When you're ready to have your changes reviewed, enter the following command:<br />
{{Input|1=<nowiki><br />
$ arc diff # this will do the `git add` and `git commit` for you; if it asks about ignoring untracked files, enter 'y'<br />
</nowiki>}}<br />
When you run <tt>arc diff</tt>, you will go through a series of dialogs. At the end, you will be asked to rewrite your Git commit message to fit the standard Differential format, like so: <br />
<pre><br />
<first line of original git commit message><br />
<br />
Summary: <rest of original commit message><br />
<br />
Test Plan:<br />
<br />
Reviewers:<br />
<br />
Subscribers: <br />
</pre><br />
<br />
<tt><first line of original git commit message></tt> will become the commit message, so please follow [https://chris.beams.io/posts/git-commit/#seven-rules commit message best practices].<br />
<br />
As when using the web UI, enter any special Bugzilla keywords (such as <tt>BUG: 385942</tt>) on their own lines in the "Summary" section.<br />
<br />
As when using the web UI, there is no need to specifically add anyone under the "Reviewers" or "Subscribers" sections unless your patch includes any visual or user interface changes. In this case, please add <tt>#vdg</tt> as a reviewer to make sure the [[Get Involved/design | KDE Visual Design Group]] sees it!<br />
<br />
=== Step 2: Update your diff in response to review comments ===<br />
After <tt>arc</tt> uploads the patch to Phabricator, the project's reviewers will take a look and give you some comments. If you get a thumbs up immediately, you can skip this step. But often you will get a review like "looks good, we can take it if you fix problems x, y, and z." Make the necessary changes, add an extra commit to the git branch, and update the Phabricator revision:<br />
{{Input|1=<nowiki><br />
[implement changes based on review comments]<br />
arc diff<br />
</nowiki>}}<br />
<br />
=== Step 3: Land your diff ===<br />
If you do not have a [[Infrastructure/Get_a_Contributor_Account | KDE Developer Account]], then someone who does will have to land your patch for you. Otherwise, you can do it yourself once the patch has been accepted and reviewers have given you permission to "ship it!"<br />
<br />
First, make sure that the world is sane, and that only your patch will be landed:<br />
{{Input|1=<nowiki><br />
[make sure you are on your feature branch]<br />
arc land --preview<br />
</nowiki>}}<br />
The output of that command should show only the commit message for your patch. If you see more than that, stop and ask your reviewers for help.<br />
<br />
==== Landing on master ====<br />
This is simple:<br />
{{Input|1=<nowiki><br />
arc land<br />
</nowiki>}}<br />
<br />
==== Landing on the "Stable branch" ====<br />
By default, arc will land the patch onto whatever branch was the parent. For example, if you branched from master, <tt>arc</tt> will land on master; if you branched from <tt>Applications/18.12</tt>, arc will land on that branch instead, and so on.<br />
<br />
If you branched your patch from <tt>master</tt> but it is a relatively low-risk bugfix, you will often be a asked to land it on the "stable branch" instead of <tt>master</tt>. The easiest way to do this is to cherry-pick the commit from your feature branch onto the appropriate stable branch and then merge forward.<br />
<br />
{{warning|If at any time you feel nervous or run into trouble, ask your reviewers for help. This can be tricky, and undoing bad merges is a pain in the neck.}}<br />
Here is an example of how to do this with a patch branched from <tt>master</tt> that needs to go into the <tt>Applications/18.12</tt> stable branch (replace <tt>Applications/18.12</tt> with the appropriate stable branch name when you do this yourself, obviously).<br />
<br />
{{Input|1=<nowiki><br />
[make sure you are on your feature branch]<br />
git log -n 1 --pretty=format:"%H"<br />
[copy that commit hash]<br />
git checkout Applications/18.12<br />
git pull<br />
git cherry-pick [the commit hash]<br />
git push<br />
git branch -D [the name of your feature branch]<br />
</nowiki>}}<br />
<br />
Note that after committing to the stable branch you are expected to merge that branch to <tt>master</tt> afterwards:<br />
{{Input|1=<nowiki><br />
git checkout master<br />
git pull<br />
git merge -s recursive -Xours Applications/18.12<br />
git push<br />
</nowiki>}}<br />
<br />
==== Landing someone else's diff ====<br />
If you have a contributor account and you are helping someone without one through the process, you will need to land their diff for them once it's been accepted. Here's how:<br />
{{Input|1=<nowiki><br />
arc patch <revision ID><br />
git show HEAD<br />
</nowiki>}}<br />
<br />
At this point, you need to verify that the authorship information shown is correct. If it's not, you will need to ask the patch author for their real name and email address. Then you use that information to update the local commit for the patch like so:<br />
{{Input|1=<nowiki><br />
# Make sure you're on the branch that corresponds to the patch!<br />
git commit --amend --author="firstname lastname <email address>"<br />
</nowiki>}}<br />
<br />
At this point, you can land the diff normally, [[#Step 3: Land your diff|as described above]].<br />
<br />
<br />
<br />
== Arcanist Tips & Tricks ==<br />
<br />
=== Look before you diff ===<br />
You can check with <tt>arc which</tt> what Arcanist will do before performing the actual upload with <tt>arc diff</tt> if you are unsure what will happen. In particular, look for which commits will be included in your Diff.<br />
<br />
=== arc diff: specify a revision manually ===<br />
Sometimes - if you messed up with your git branches - arc cannot properly determine which revision (D12345) should be updated. In this case you can use <tt>arc diff --update D12345</tt>. See <tt>arc help diff</tt>.<br />
<br />
=== Updating the summary of the Differential from the local Git commit message ===<br />
If you changed the commit message locally and want to update the text in the summary in Differential, call Arc like this:<br />
{{Input|1=<nowiki><br />
arc diff --edit --verbatim<br />
</nowiki>}}<br />
<br />
=== Updating the local Git commit message from changes done on Phabricator ===<br />
If you or someone else updated the title, summary, test plan, reviewers or subscribers of a Diff using the web editor in Phabricator, Arcanist allows to sync those changes back to your local Git commit message:<br />
{{Input|1=<nowiki><br />
arc amend<br />
</nowiki>}}<br />
<br />
Note that in general Arcanist will do this automatically for you once you <tt>arc land</tt>.<br />
<br />
== Advanced Tasks ==<br />
Once in a while, a reviewer will tell you to do specific things. This section will help you figure out what is meant to be done.<br />
<br />
=== "Please do that in a separate commit" ===<br />
Should your patch contain an unrelated change, your reviewer will ask you to undo that part and possibly open a new Diff for that. Here is what you can do:<br />
<br />
If the change in question is in a separate commit on your local branch:<br />
{{Input|1=<nowiki><br />
git revert <unrelated change><br />
arc diff # this updates the first Diff<br />
git checkout -b <new branch name> master<br />
git cherry-pick <unrelated change><br />
arc diff # this creates a new Diff for the unrelated change<br />
</nowiki>}}<br />
<br />
If you mixed different changes into a single commit on your local branch:<br />
{{Input|1=<nowiki><br />
git reset HEAD^<br />
git add -p # type "?" for help, then pick all hunks you want to keep<br />
git stash # the stash now contains the hunks for the second patch<br />
git commit<br />
arc diff # this updates the first Diff<br />
git checkout -b <new branch name> master<br />
git stash pop<br />
git commit<br />
arc diff # this creates a new Diff for the unrelated change<br />
</nowiki>}}<br />
<br />
In case this does not work for you, there's always the plain old copy-and-paste. In general, it is best to avoid adding unrelated changes from the beginning. :-)<br />
<br />
=== Marking patches as dependent on other patches ===<br />
Sometimes you will want to submit a patch that depends on another patch, creating a '''dependency chain'''.<br />
<br />
==== If each patch is intended for a different project ====<br />
Example: you submit a patch to add a new feature to KIO, and then submit another patch for Dolphin that uses that feature. Here's what you do:<br />
<ol><br />
<li>Create your first patch as above</li><br />
<li>When creating the second patch, add the following to its own line in the "Summary" section:<br />
{{Input|1=<nowiki>Depends on DXXXX</nowiki>}}<br />
(Replace "DXXXX" with the ID of the dependent patch, '''not the full URL)'''</li><br />
</ol><br />
<br />
==== If the patches are all for the same project ====<br />
Example: you are implementing multiple new features for a single project that each depend on the patch for the prior feature. Here's what you do:<br />
<ol><br />
<li>Create a branch to track the first feature:<br />
{{Input|1=<nowiki><br />
git checkout -b <branch name for feature 1> --track origin/master<br />
</nowiki>}}<br />
Then implement the feature and make a commit.<br />
</li><br />
<li>Then create a branch for your second feature, ''tracking the first branch:''<br />
{{Input|1=<nowiki><br />
git checkout -b <branch name for feature 2> --track <branch name for feature 1><br />
</nowiki>}}<br />
As above, implement the feature and make a commit. Continue this pattern for any other required dependent features.<br />
</li><br />
<br />
<li><br />
When you're ready to turn your dependency chain feature branches into patches, do the following:<br />
{{Input|1=<nowiki><br />
git checkout <branch name for feature 1><br />
arc diff [then go through the process of creating the patch normally]<br />
git checkout <branch name for feature 2><br />
git commit --amend [then add the special text "Depends on DXXXX", replacing DXXXX with the ID of the first patch<br />
arc diff [then go through the process of creating the patch normally]<br />
</nowiki>}}<br />
...And so on.<br />
</li><br />
<br />
<li>After you get comments, you will have to make changes to your patches and re-base dependent patches:<br />
{{Input|1=<nowiki><br />
git checkout <branch name for feature 1><br />
[Make changes]<br />
git add -u<br />
git commit<br />
arc diff<br />
git checkout <branch name for feature 2><br />
git rebase <branch name for feature 1><br />
git add -u<br />
git commit<br />
arc diff<br />
</nowiki>}}<br />
...And so on.<br />
</li><br />
<br />
<li>When you're ready to land any or all of your patches, do it in sequence, starting from the patch with no unmet dependencies:<br />
{{Input|1=<nowiki><br />
git checkout <branch name for feature 1><br />
arc land<br />
git checkout <branch name for feature 2><br />
git rebase origin/<target branch><br />
arc land<br />
</nowiki>}}<br />
</li><br />
</ol><br />
<br />
<br />
<br />
= How to review someone else's patch =<br />
Arcanist (<tt>arc</tt>) makes it easy to review someone's patch. But first you'll need a development environment set up. If you haven't done that yet, it's time to do so. See [[Get_Involved/development#Set_up_your_development_environment]]. Follow the instructions to compile and run the program.<br />
<br />
== Apply the patch and compile the software ==<br />
Find the patch's revision ID. For example, for https://phabricator.kde.org/D11184, the ID is <tt>D11184</tt>.<br />
<br />
Now check out or enter the source repository for the software that's being patched. The repository is listed on the web UI: <br />
[[File:Konsole repository for patch.png]]<br />
...So this would be a patch for Konsole.<br />
<br />
If you've never built it before, check it out and build it once first:<br />
{{Input|1=<nowiki><br />
kdesrc-build konsole<br />
</nowiki>}}<br />
<br />
Now go to its source directory:<br />
{{Input|1=<nowiki><br />
cd ~/kde/src/konsole<br />
</nowiki>}}<br />
<br />
...and apply the patch:<br />
{{Input|1=<nowiki><br />
arc patch <revision ID><br />
</nowiki>}}<br />
<br />
Answer <tt>y</tt> to any questions that are posed. Arc will automatically create a branch named <tt>arcpatch-<revision ID></tt> for the patch, so it won't damage your checkout at all.<br />
<br />
Now it's time to compile and run the software to make sure that the patch does what it says it does and doesn't cause any regressions! Compile the patched source code:<br />
{{Input|1=<nowiki><br />
kdesrc-build konsole --no-src --resume-from konsole<br />
</nowiki>}}<br />
Those arguments will tell <tt>kdesrc-build</tt> to not update the source code after you applied the patch, and to not build any dependencies.<br />
<br />
If it didn't compile, that's reason alone to reject the patch! Go to the web UI and report your findings, and apply a "Request Changes" status.<br />
<br />
== Perform QA ==<br />
If it did compile, then it's time to perform QA, because it's important to thoroughly test patches to ensure that bad code and regressions don't slip in. '''This is the entire purpose of having a review infrastructure'''; it is very important.<br />
<br />
First make sure the unit tests all pass:<br />
{{Input|1=<nowiki><br />
cd ~kde/build/kde/applications/konsole<br />
ctest<br />
</nowiki>}}<br />
<br />
If any tests fail, report this through a comment on the patch's web page (https://phabricator.kde.org/<revision ID>).<br />
<br />
Next, execute the Test Plan that the submitter wrote. If the patch does not have a Test Plan, request one. Does it all still work for you? If not, return to the web UI and Request Changes, writing a detailed comment explaining what didn't work. It is permissible to do this even if you have not been specified as a reviewer! '''Anyone can reject a patch on the grounds that it does not work, does not do what it says it does, or causes regressions.'''<br />
<br />
If the original Test Plan succeeds, try to break the patch. Here are some ideas:<br />
# Remove the program's configuration file (<tt>~/.config/<program name>rc</tt> ) and re-open it<br />
# Try the program with a HiDPI scale factor (or without one) or with a different default font size<br />
# If it's a new feature, feed it unexpected input<br />
# Test related functionality<br />
<br />
'''Try to break it!''' A good patch will handle corner cases and variations in configuration. The price of configurability is vigilant testing! We owe it to our users to test using many configurations, not just the defaults or our personal settings.<br />
<br />
== Perform code review ==<br />
{{Note|1= Need a section on code review here, preferably written by a core KDE developer or another very experienced developer}}<br />
<br />
== Engage with the author and other reviewers ==<br />
After you have run the program and evaluated the patch, it's time to leave some review comments on the webpage (which again is at https://phabricator.kde.org/<revision ID>). If you have been specified as a reviewer, or are a member of a group that has been specified as a reviewer, it is permissible to Accept the patch. But keep in mind that reviewing involves responsibility: you are giving a thumbs-up to code that will be run potentially by millions of people. If you accept and land a patch that causes regressions, you will share some of the blame. It's important to take the reviewer role seriously.<br />
<br />
= Customization =<br />
== Creating custom dashboard feeds ==<br />
You can customize your Phabricator homepage by creating a new dashboard. However, the selection of what you can post on your dashboard is limited. The defaults will show all tasks from all projects. <br />
<br />
To narrow this down, you need to define a custom query to serve as a filter. For example, if you work on Plasma Mobile and want to monitor the to-do list, perhaps you want to show only tasks that are in Plasma Mobile and are tagged as open. To do that, enter Maniphest, select "advanced search," select the appropriate terms, then click "save custom query." You can give your query a name. Once it is saved, the query will become available as a new filter for creating feeds on your dashboard. (In Differential you seem to need to perform the test search before the "save query" button becomes visible.)</div>Gjditchfieldhttps://community.kde.org/index.php?title=Guidelines_and_HOWTOs/UnitTests&diff=85289Guidelines and HOWTOs/UnitTests2019-07-21T18:56:46Z<p>Gjditchfield: /* Tutorial 6: Integrating with CMake */</p>
<hr />
<div>: '''Author:''' Brad Hards, Sigma Bravo Pty Limited<br />
<br />
== Abstract ==<br />
<br />
This article provides guidance on writing unittests for software based on Qt<br />
and KDE frameworks. It uses the [http://doc.qt.io/qt-5/qttest-index.html QtTestLib framework] provided starting with Qt 4.1. It provides an introduction to the ideas behind unit testing, tutorial material on the [http://doc.qt.io/qt-5/qttest-index.html QtTestLib framework], and suggestions for getting the most value for your effort.<br />
<br />
== About Unit Testing ==<br />
<br />
A unit test is a test that checks the functionality, behaviour and correctness of a single software component. In Qt code unit tests are almost always used to test a single C++ class (although testing a macro or C function is also possible).<br />
<br />
Unit tests are a key part of Test Driven Development, however they are useful for all software development processes. It is not essential that all of the code is covered by unit tests (although that is obviously very desirable!). Even a single test is a useful step to improving code quality.<br />
<br />
Note that unit tests are dynamic tests (i.e. they run, using the compiled code) rather than static analysis tests (which operate on the source or some intermediate representation).<br />
<br />
Even if they don't call them "unit tests", most programmers have written some "throwaway" code that they use to check an implementation. If that code was cleaned up a little, and built into the development system, then it could be used over and over to check that the implementation is still OK. To make that work a little easier, we can use test frameworks.<br />
<br />
Note that it is sometimes tempting to treat the unit test as a pure verification tool. While it is true that unit tests do help to ensure correct functionality and behaviour, they also assist with other aspects of code quality. Writing a unit test requires a slightly different approach to coding up a class, and thinking about what inputs need to be tested can help to identify logic flaws in the code (even before the tests get run). In addition, the need to make the code testable is a very useful driver to ensure that classes do not suffer from close coupling.<br />
<br />
Anyway, enough of the conceptual stuff - lets talk about a specific tool that can reduce some of the effort and let us get on with the job.<br />
<br />
==About QtTestLib==<br />
<br />
QtTestlib is a lightweight testing library developed by the Qt Project and released under the LGPL (a commercial version is also available, for those who need alternative licensing). It is written in C++, and is cross-platform. It is provided as part of the tools included in Qt.<br />
<br />
In addition to normal unit testing capabilities, QtTestLib also offers basic GUI testing, based on sending QEvents. This allows you to test GUI widgets, but is not generally suitable for testing full applications.<br />
<br />
Each testcase is a standalone test application. Unlike CppUnit or JUnit, there is no Runner type class. Instead, each testcase is an executable which is simply run.<br />
<br />
== Tutorial 1: A simple test of a date class ==<br />
<br />
In this tutorial, we will build a simple test for a class that represents a date, using QtTestLib as the test framework. To avoid too much detail on how the date class works, we'll just use the QDate class that comes with Qt. In a normal unittest, you would more likely be testing code that you've written yourself.<br />
<br />
The code below is the entire testcase.<br />
<br />
<br />
'''Example 1. QDate test code'''<br />
<syntaxhighlight lang="cpp-qt" line="GESHI_NORMAL_LINE_NUMBERS"><br />
#include <QTest><br />
#include <QDate><br />
<br />
class testDate: public QObject<br />
{<br />
Q_OBJECT<br />
private slots:<br />
void testValidity();<br />
void testMonth();<br />
};<br />
<br />
void testDate::testValidity()<br />
{<br />
// 11 March 1967<br />
QDate date( 1967, 3, 11 );<br />
QVERIFY( date.isValid() );<br />
}<br />
<br />
void testDate::testMonth()<br />
{<br />
// 11 March 1967<br />
QDate date;<br />
date.setDate( 1967, 3, 11 );<br />
QCOMPARE( date.month(), 3 );<br />
QCOMPARE( QDate::longMonthName(date.month()),<br />
QString("March") );<br />
}<br />
<br />
<br />
QTEST_MAIN(testDate)<br />
#include "tutorial1.moc"<br />
</syntaxhighlight><br />
Save as autotests/tutorial1.cpp in your project's autotests directory following the example of [https://cgit.kde.org/okular.git Okular]<br />
<br />
Stepping through the code, the first line imports the header files for the QtTest namespace. The second line imports the headers for the QDate class. Lines 4 to 10 give us the test class, testData. Note that testDate inherits from QObject and has the Q_OBJECT macro - QtTestLib requires specific Qt functionality that is present in QObject.<br />
<br />
Lines 12 to 17 provide our first test, which checks that a date is valid. Note the use of the QVERIFY macro, which checks that the condition is true. So if <tt>date.isValid()</tt> returns true, then the test will pass, otherwise the test will fail. QVERIFY is similar to ASSERT in other test suites.<br />
<br />
Similarly, lines 19 to 27 provide another test, which checks a setter, and a couple of accessor routines. In this case, we are using QCOMPARE, which checks that the conditions are equal. So if date.month() returns 3, then that part of that test will pass, otherwise the test will fail.<br />
<br />
{{Warning|As soon as a QVERIFY evaluates to false or a QCOMPARE does not have two equal values, the whole test is marked as failed and the next test will be started. So in the example above, if the check at line 24 fails, then the check at lines 25 and 26 will not be run.}}<br />
<br />
In a later tutorial we will see how to work around problems that this behaviour can cause.<br />
<br />
Line 30 uses the QTEST_MAIN which creates an entry point routine for us, with appropriate calls to invoke the testDate unit test class.<br />
<br />
Line 31 includes the Meta-Object compiler output, so we can make use of our QObject functionality.<br />
<br />
The qmake project file that corresponds to that code is shown below. You would then use qmake to turn this into a Makefile and then compile it with make.<br />
<br />
'''Example 2. QDate unit test project'''<br />
<pre><br />
CONFIG += qtestlib<br />
TEMPLATE = app<br />
TARGET +=<br />
DEPENDPATH += .<br />
INCLUDEPATH += .<br />
<br />
# Input<br />
SOURCES += tutorial1.cpp<br />
</pre><br />
Save as tutorial1.pro<br />
<br />
This is a fairly normal project file, except for the addition of the <tt>CONFIG += qtestlib</tt>. This adds the right header and library setup to the Makefile.<br />
<br />
Create an empty file called tutorial1.h and compile with <tt>qmake; make</tt> The output looks like the following:<br />
<br />
'''Example 3. QDate unit test output'''<br />
<pre><br />
$ ./tutorial1<br />
********* Start testing of testDate *********<br />
Config: Using QTest library 4.1.0, Qt 4.1.0-snapshot-20051003<br />
PASS : testDate::initTestCase()<br />
PASS : testDate::testValidity()<br />
PASS : testDate::testMonth()<br />
PASS : testDate::cleanupTestCase()<br />
Totals: 4 passed, 0 failed, 0 skipped<br />
********* Finished testing of testDate *********<br />
</pre><br />
<br />
Looking at the output above, you can see that the output includes the version of the test library and Qt itself, and then the status of each test that is run. In addition to the testValidity and testMonth tests that we defined, there is also a setup routine (initTestCase) and a teardown routine (cleanupTestCase) that can be used to do additional configuration if required.<br />
<br />
===Failing tests===<br />
<br />
If we had made an error in either the production code or the unit test code, then the results would show an error. An example is shown below:<br />
<br />
'''Example 4. QDate unit test output showing failure'''<br />
<pre><br />
$ ./tutorial1<br />
********* Start testing of testDate *********<br />
Config: Using QTest library 4.1.0, Qt 4.1.0-snapshot-20051003<br />
PASS : testDate::initTestCase()<br />
PASS : testDate::testValidity()<br />
FAIL! : testDate::testMonth() Compared values are not the same<br />
Actual (date.month()): 4<br />
Expected (3): 3<br />
Loc: [tutorial1.cpp(25)]<br />
PASS : testDate::cleanupTestCase()<br />
Totals: 3 passed, 1 failed, 0 skipped<br />
********* Finished testing of testDate *********<br />
</pre><br />
<br />
===Running selected tests===<br />
<br />
When the number of test functions increases, and some of the functions take a long time to run, it can be useful to only run a selected function. For example, if you only want to run the testMonth function, then you just specify that on the command line, as shown below:<br />
<br />
'''Example 5. QDate unit test output - selected function'''<br />
<br />
<pre><br />
$ ./tutorial1 testValidity<br />
********* Start testing of testDate *********<br />
Config: Using QTest library 4.1.0, Qt 4.1.0-snapshot-20051003<br />
PASS : testDate::initTestCase()<br />
PASS : testDate::testValidity()<br />
PASS : testDate::cleanupTestCase()<br />
Totals: 3 passed, 0 failed, 0 skipped<br />
********* Finished testing of testDate *********<br />
</pre><br />
<br />
Note that the initTestCase and cleanupTestCase routines are always run, so that any necessary setup and cleanup will still be done.<br />
<br />
You can get a list of the available functions by passing the -functions option, as shown below:<br />
<br />
'''Example 6. QDate unit test output - listing functions'''<br />
<pre><br />
$ ./tutorial1 -functions<br />
testValidity()<br />
testMonth()<br />
</pre><br />
<br />
===Verbose output options===<br />
<br />
You can get more verbose output by using the -v1, -v2 and -vs options. -v1 produces a message on entering each test function. I found this is useful when it looks like a test is hanging. This is shown below:<br />
<br />
'''Example 7. QDate unit test output - verbose output'''<br />
<br />
<pre><br />
$ ./tutorial1 -v1<br />
********* Start testing of testDate *********<br />
Config: Using QTest library 4.1.0, Qt 4.1.0-snapshot-20051003<br />
INFO : testDate::initTestCase() entering<br />
PASS : testDate::initTestCase()<br />
INFO : testDate::testValidity() entering<br />
PASS : testDate::testValidity()<br />
INFO : testDate::testMonth() entering<br />
PASS : testDate::testMonth()<br />
INFO : testDate::cleanupTestCase() entering<br />
PASS : testDate::cleanupTestCase()<br />
Totals: 4 passed, 0 failed, 0 skipped<br />
********* Finished testing of testDate *********<br />
</pre><br />
<br />
The -v2 option shows each QVERIFY, QCOMPARE and QTEST, as well as the message on entering each test function. I found this useful for verifying that a particular step is being run. This is shown below:<br />
<br />
''''Example 8. QDate unit test output - more verbose output'''<br />
<br />
<pre><br />
$ ./tutorial1 -v2<br />
********* Start testing of testDate *********<br />
Config: Using QTest library 4.1.0, Qt 4.1.0-snapshot-20051003<br />
INFO : testDate::initTestCase() entering<br />
PASS : testDate::initTestCase()<br />
INFO : testDate::testValidity() entering<br />
INFO : testDate::testValidity() QVERIFY(date.isValid())<br />
Loc: [tutorial1.cpp(17)]<br />
PASS : testDate::testValidity()<br />
INFO : testDate::testMonth() entering<br />
INFO : testDate::testMonth() COMPARE()<br />
Loc: [tutorial1.cpp(25)]<br />
INFO : testDate::testMonth() COMPARE()<br />
Loc: [tutorial1.cpp(27)]<br />
PASS : testDate::testMonth()<br />
INFO : testDate::cleanupTestCase() entering<br />
PASS : testDate::cleanupTestCase()<br />
Totals: 4 passed, 0 failed, 0 skipped<br />
********* Finished testing of testDate *********<br />
</pre><br />
<br />
The -vs option shows each signal that is emitted. In our example, there are no signals, so -vs has no effect. Getting a list of signals is useful for debugging failing tests, especially GUI tests which we will see in the third tutorial.<br />
<br />
===Output to a file===<br />
<br />
If you want to output the results of your testing to a file, you can use the -o filename, where you replace filename with the name of the file you want to save output to.<br />
<br />
==Tutorial 2: Data driven testing of a date class==<br />
<br />
In the previous example, we looked at how we can test a date class. If we decided that we really needed to test a lot more dates, then we'd be cutting and pasting a lot of code. If we subsequently changed the name of a function, then it has to be changed in a lot of places. As an alternative to introducing these types of maintenance problems into our tests, QtTestLib offers support for data driven testing.<br />
<br />
The easiest way to understand data driven testing is by an example, as shown below:<br />
<br />
'''Example 9. QDate test code, data driven version'''<br />
<syntaxhighlight lang="cpp-qt" line="GESHI_NORMAL_LINE_NUMBERS"><br />
#include <QtTest><br />
#include <QtCore><br />
<br />
<br />
class testDate: public QObject<br />
{<br />
Q_OBJECT<br />
private slots:<br />
void testValidity();<br />
void testMonth_data();<br />
void testMonth();<br />
};<br />
<br />
void testDate::testValidity()<br />
{<br />
// 12 March 1967<br />
QDate date( 1967, 3, 12 );<br />
QVERIFY( date.isValid() );<br />
}<br />
<br />
void testDate::testMonth_data()<br />
{<br />
QTest::addColumn<int>("year"); // the year we are testing<br />
QTest::addColumn<int>("month"); // the month we are testing<br />
QTest::addColumn<int>("day"); // the day we are testing<br />
QTest::addColumn<QString>("monthName"); // the name of the month<br />
<br />
QTest::newRow("1967/3/11") << 1967 << 3 << 11 << QString("March");<br />
QTest::newRow("1966/1/10") << 1966 << 1 << 10 << QString("January");<br />
QTest::newRow("1999/9/19") << 1999 << 9 << 19 << QString("September");<br />
// more rows of dates can go in here...<br />
}<br />
<br />
void testDate::testMonth()<br />
{<br />
QFETCH(int, year);<br />
QFETCH(int, month);<br />
QFETCH(int, day);<br />
QFETCH(QString, monthName);<br />
<br />
QDate date;<br />
date.setDate( year, month, day);<br />
QCOMPARE( date.month(), month );<br />
QCOMPARE( QDate::longMonthName(date.month()), monthName );<br />
}<br />
<br />
<br />
QTEST_MAIN(testDate)<br />
#include "tutorial2.moc"<br />
</syntaxhighlight><br />
<br />
As you can see, we've introduced a new method - testMonth_data, and moved the specific test date out of testMonth. We've had to add some more code (which will be explained soon), but the result is a separation of the data we are testing, and the code we are using to test it.<br />
The names of the functions are important - you must use the _data suffix for the data setup routine, and the first part of the data setup routine must match the name of the driver routine.<br />
<br />
It is useful to visualise the data as being a table, where the columns are the various data values required for a single run through the driver, and the rows are different runs. In our example, there are four columns (three integers, one for each part of the date; and one QString ), added in lines 19 through 22. The addColumn template obviously requires the type of variable to be added, and also requires a variable name argument. We then add as many rows as required using the newRow function, as shown in lines 23 through 26. The string argument to newRow is a label, which is handy for determining what is going on with failing tests, but doesn't have any effect on the test itself.<br />
<br />
To use the data, we simply use QFETCH to obtain the appropriate data from each row. The arguments to QFETCH are the type of the variable to fetch, and the name of the column (which is also the local name of the variable it gets fetched into). You can then use this data in a QCOMPARE or QVERIFY check. The code is run for each row, which you can see below:<br />
<br />
'''Example 10. Results of data driven testing, showing QFETCH'''<br />
<br />
<pre><br />
$ ./tutorial2 -v2<br />
********* Start testing of testDate *********<br />
Config: Using QTest library 4.1.0, Qt 4.1.0-snapshot-20051020<br />
INFO : testDate::initTestCase() entering<br />
PASS : testDate::initTestCase()<br />
INFO : testDate::testValidity() entering<br />
INFO : testDate::testValidity() QVERIFY(date.isValid())<br />
Loc: [tutorial2.cpp(19)]<br />
PASS : testDate::testValidity()<br />
INFO : testDate::testMonth() entering<br />
INFO : testDate::testMonth(1967/3/11) COMPARE()<br />
Loc: [tutorial2.cpp(44)]<br />
INFO : testDate::testMonth(1967/3/11) COMPARE()<br />
Loc: [tutorial2.cpp(45)]<br />
INFO : testDate::testMonth(1966/1/10) COMPARE()<br />
Loc: [tutorial2.cpp(44)]<br />
INFO : testDate::testMonth(1966/1/10) COMPARE()<br />
Loc: [tutorial2.cpp(45)]<br />
INFO : testDate::testMonth(1999/9/19) COMPARE()<br />
Loc: [tutorial2.cpp(44)]<br />
INFO : testDate::testMonth(1999/9/19) COMPARE()<br />
Loc: [tutorial2.cpp(45)]<br />
PASS : testDate::testMonth()<br />
INFO : testDate::cleanupTestCase() entering<br />
PASS : testDate::cleanupTestCase()<br />
Totals: 4 passed, 0 failed, 0 skipped<br />
********* Finished testing of testDate *********<br />
</pre><br />
<br />
=== The QTEST macro ===<br />
<br />
As an alternative to using QFETCH and QCOMPARE, you may be able to use the QTEST macro instead. QTEST takes two arguments, and if one is a string, it looks up that string as an argument in the current row. You can see how this can be used below, which is equivalent to the testMonth() code in the previous example.<br />
<br />
'''Example 11. QDate test code, data driven version using QTEST'''<br />
<br />
<syntaxhighlight lang="cpp-qt" line="GESHI_NORMAL_LINE_NUMBERS"><br />
void testDate::testMonth()<br />
{<br />
QFETCH(int, year);<br />
QFETCH(int, month);<br />
QFETCH(int, day);<br />
<br />
QDate date;<br />
date.setDate( year, month, day);<br />
QCOMPARE( date.month(), month );<br />
QTEST( QDate::longMonthName(date.month()), "monthName" );<br />
}<br />
</syntaxhighlight><br />
<br />
In the example above, note that monthName is enclosed in quotes, and we no longer have a QFETCH call for monthName.<br />
<br />
The other QCOMPARE could also have been converted to use QTEST, however this would be less efficient, because we already needed to use QFETCH to get month for the setDate in the line above.<br />
<br />
===Running selected tests with selected data===<br />
<br />
In the previous tutorial, we saw how to run a specific test by specifying the name of the test as a command line argument. In data driven testing, you can select which data you want the test run with, by adding a colon and the label for the data row. For example, if we just want to run the testMonth test for the first row, we would use <pre>./tutorial2 -v2 testMonth:1967/3/11</pre>. The result of this is shown below.<br />
<br />
'''Example 12. QDate unit test output - selected function and data'''<br />
<pre><br />
$ ./tutorial2 -v2 testMonth:1967/3/11<br />
********* Start testing of testDate *********<br />
Config: Using QTest library 4.1.0, Qt 4.1.0-snapshot-20051020<br />
INFO : testDate::initTestCase() entering<br />
PASS : testDate::initTestCase()<br />
INFO : testDate::testMonth() entering<br />
INFO : testDate::testMonth(1967/3/11) COMPARE()<br />
Loc: [tutorial2.cpp(44)]<br />
INFO : testDate::testMonth(1967/3/11) COMPARE()<br />
Loc: [tutorial2.cpp(45)]<br />
PASS : testDate::testMonth()<br />
INFO : testDate::cleanupTestCase() entering<br />
PASS : testDate::cleanupTestCase()<br />
Totals: 3 passed, 0 failed, 0 skipped<br />
********* Finished testing of testDate *********<br />
</pre><br />
<br />
==Tutorial 3: Testing Graphical User Interfaces==<br />
<br />
In the previous two tutorials, we've tested a date management class. This is an pretty typical use of unit testing. However Qt and KDE applications will make use graphical classes that take user input (typically from a keyboard and mouse). QtTestLib offers support for testing these classes, which we'll see in this tutorial.<br />
<br />
Again, we'll use an existing class as our test environment, and again it will be date related - the standard Qt {{qt|QDateEdit}} class. For those not familiar with this class, it is a simple date entry widget (although with some powerful back end capabilities). A picture of the widget is shown below.<br />
<br />
[[Image:Qdateedit_dlg.png|thumb|'''Figure 1. QDateEdit widget screenshot''']]<br />
<br />
The way QtTestLib provides GUI testing is by injecting {{qt|QInputEvent}} events. To the application, these input events appear the same as normal key press/release and mouse clicks/drags. However the mouse and keyboard are unaffected, so that you can continue to use the machine normally while tests are being run.<br />
<br />
An example of how you can use the GUI functionality of QtTestLib is shown below.<br />
<br />
'''Example 13. QDateEdit test code'''<br />
<br />
<syntaxhighlight lang="cpp-qt" line="GESHI_NORMAL_LINE_NUMBERS"><br />
#include <QtTest><br />
#include <QtCore><br />
#include <QtGui><br />
Q_DECLARE_METATYPE(QDate)<br />
<br />
class testDateEdit: public QObject<br />
{<br />
Q_OBJECT<br />
private slots:<br />
void testChanges();<br />
void testValidator_data();<br />
void testValidator();<br />
};<br />
<br />
void testDateEdit::testChanges()<br />
{<br />
// 11 March 1967<br />
QDate date( 1967, 3, 11 );<br />
QDateEdit dateEdit( date );<br />
<br />
// up-arrow should increase day by one<br />
QTest::keyClick( &dateEdit, Qt::Key_Up );<br />
QCOMPARE( dateEdit.date(), date.addDays(1) );<br />
<br />
// we click twice on the "reduce" arrow at the bottom RH corner<br />
// first we need the widget size to know where to click<br />
QSize editWidgetSize = dateEdit.size();<br />
QPoint clickPoint(editWidgetSize.rwidth()-2, editWidgetSize.rheight()-2);<br />
// issue two clicks<br />
QTest::mouseClick( &dateEdit, Qt::LeftButton, 0, clickPoint);<br />
QTest::mouseClick( &dateEdit, Qt::LeftButton, 0, clickPoint);<br />
// and we should have decreased day by two (one less than original)<br />
QCOMPARE( dateEdit.date(), date.addDays(-1) );<br />
<br />
QTest::keyClicks( &dateEdit, "25122005" );<br />
QCOMPARE( dateEdit.date(), QDate( 2005, 12, 25 ) );<br />
<br />
QTest::keyClick( &dateEdit, Qt::Key_Tab, Qt::ShiftModifier );<br />
QTest::keyClicks( &dateEdit, "08" );<br />
QCOMPARE( dateEdit.date(), QDate( 2005, 8, 25 ) );<br />
}<br />
<br />
void testDateEdit::testValidator_data()<br />
{<br />
qRegisterMetaType<QDate>("QDate");<br />
<br />
QTest::addColumn<QDate>( "initialDate" );<br />
QTest::addColumn<QString>( "keyclicks" );<br />
QTest::addColumn<QDate>( "finalDate" );<br />
<br />
QTest::newRow( "1968/4/12" ) << QDate( 1967, 3, 11 )<br />
<< QString( "12041968" )<br />
<< QDate( 1968, 4, 12 );<br />
<br />
QTest::newRow( "1967/3/14" ) << QDate( 1967, 3, 11 )<br />
<< QString( "140abcdef[" )<br />
<< QDate( 1967, 3, 14 );<br />
// more rows can go in here<br />
}<br />
<br />
void testDateEdit::testValidator()<br />
{<br />
QFETCH( QDate, initialDate );<br />
QFETCH( QString, keyclicks );<br />
QFETCH( QDate, finalDate );<br />
<br />
QDateEdit dateEdit( initialDate );<br />
// this next line is just to start editing<br />
QTest::keyClick( &dateEdit, Qt::Key_Enter );<br />
QTest::keyClicks( &dateEdit, keyclicks );<br />
QCOMPARE( dateEdit.date(), finalDate );<br />
}<br />
<br />
QTEST_MAIN(testDateEdit)<br />
#include "tutorial3.moc"<br />
</syntaxhighlight><br />
<br />
Much of this code is common with previous examples, so I'll focus on the new elements and the more important changes as we work through the code line-by-line.<br />
<br />
Lines 1 to 3 import the various Qt declarations, as before.<br />
<br />
Line 4 is a macro that is required for the data-driven part of this test, which I'll come to soon.<br />
<br />
Lines 5 to 12 declare the test class - while the names have changed, it is pretty similar to the previous example. Note the testValidator and testValidator_data functions - we will be using data driven testing again in this example.<br />
<br />
Our first real test starts in line 13. Line 16 creates a QDate, and line 17 uses that date as the initial value for a QDateEdit widget.<br />
<br />
Lines 19 and 20 show how we can test what happens when we press the up-arrow key. The QTest::keyClick function takes a pointer to a widget, and a symbolic key name (a char or a Qt::Key). At line 20, we check that the effect of that event was to increment the date by a day. The QTest:keyClick function also takes an optional keyboard modifier (such as Qt::ShiftModifier for the shift key) and an optional delay value (in milliseconds). As an alternative to using QTest::keyClick, you can use QTest::keyPress and QTest::keyRelease to construct more complex keyboard sequences.<br />
<br />
Lines 23 to 29 show a similar test to the previous one, but in this case we are simulating a mouse click. We need to click in the lower right hand part of the widget (to hit the decrement arrow - see Figure 1), and that requires knowing how large the widget is. So lines 23 and 24 calculate the correct point based off the size of the widget. Line 26 (and the identical line 27) simulates clicking with the left-hand mouse button at the calculated point. The arguments to Qt::mouseClick are:<br />
<br />
*a pointer to the widget that the click event should be sent to.<br />
*the mouse button that is being clicked.<br />
*an optional keyboard modifier (such a Qt::ShiftModifier), or 0 for no modifiers.<br />
*an optional click point - this defaults to the middle of the widget if not specified.<br />
*an optional mouse delay.<br />
<br />
In addition to QTest::mouseClick, there is also QTest::mousePress, QTest::mouseRelease, QTest::mouseDClick (providing double-click) and QTest::mouseMove. The first three are used in the same way as QTest::mouseClick. The last takes a point to move the mouse to. You can use these functions in combination to simulate dragging with the mouse.<br />
<br />
Lines 30 and 31 show another approach to keyboard entry, using the QTest::keyClicks. Where QTest::keyClick sends a single key press, QTest::keyClicks takes a QString (or something equivalent, in line 30 a character array) that represents a sequence of key clicks to send. The other arguments are the same.<br />
<br />
Lines 32 to 34 show how you may need to use a combination of functions. After we've entered a new date in line 30, the cursor is at the end of the widget. At line 32, we use a Shift-Tab combination to move the cursor back to the month value. Then at line 33 we enter a new month value. Of course we could have used individual calls to QTest::keyClick, however that wouldn't have been as clear, and would also have required more code.<br />
<br />
=== Data-driven GUI testing ===<br />
<br />
Lines 50 to 60 show a data-driven test - in this case we are checking that the validator on QDateEdit is performing as expected. This is a case where data-driven testing can really help to ensure that things are working the way they should.<br />
<br />
At lines 52 to 54, we fetch in an initial value, a series of key-clicks, and an expected result. These are the columns that are set up in lines 39 to 41. However note that we are now pulling in a QDate, where in previous examples we used three integers and then build the QDate from those. However QDate isn't a registered type for {{qt|QMetaType}}, and so we need to register it before we can use it in our data-driven testing. This is done using the Q_DECLARE_METATYPE macro in line 4 and the qRegisterMetaType function in line 38.<br />
<br />
Lines 42 to 47 add in a couple of sample rows. Lines 42 to 44 represent a case where the input is valid, and lines 45 to 47 are a case where the input is only partly valid (the day part). A real test will obviously contain far more combinations than this.<br />
<br />
Those test rows are actually tested in lines 55 to 59. We construct the QDateEdit widget in line 55, using the initial value. We then send an Enter key click in line 57, which is required to get the widget into edit mode. At line 58 we simulate the data entry, and at line 59 we check whether the results are what was expected.<br />
<br />
Lines 61 and 62 are the same as we've seen in previous examples.<br />
<br />
=== Re-using test elements ===<br />
<br />
If you are re-using a set of events a number of times, then it may be an advantage to build a list of events, and then just replay them. This can improve maintainability and clarity of a set of tests, especially for mouse movements.<br />
<br />
The key class for building a list of test events is imaginatively known as QTestEventList. It is a QList of QTestEvents. The normal approach is to create the list, and then use various member functions to add key and mouse events. The normal functions that you'll need are addKeyClick and addMouseClick, which are very similar to the QTest::keyClick and QTest::mouseClick functions we used earlier in this tutorial. For finer grained operations, you can also use addKeyPress, addKeyRelease, addKeyEvent, addMousePress, addMouseRelease, addMouseDClick and addMouseMove to build up more complex event lists. You can also use addDelay to add a specified delay between events. When the list has been built up, you just call simulate on each widget.<br />
<br />
You can see how this works in the example below, which is the QDateEdit example (from above) converted to use QTestEventList.<br />
<br />
'''Example 14. QDateEdit test code, using QTestEventList'''<br />
<syntaxhighlight lang="cpp-qt" line="GESHI_NORMAL_LINE_NUMBERS"><br />
#include <QtTest><br />
#include <QtCore><br />
#include <QtGui><br />
Q_DECLARE_METATYPE(QDate)<br />
<br />
class testDateEdit: public QObject<br />
{<br />
Q_OBJECT<br />
private slots:<br />
void testChanges();<br />
void testValidator_data();<br />
void testValidator();<br />
};<br />
<br />
void testDateEdit::testChanges()<br />
{<br />
// 11 March 1967<br />
QDate date( 1967, 3, 11 );<br />
QDateEdit dateEdit( date );<br />
<br />
// up-arrow should increase day by one<br />
QTest::keyClick( &dateEdit, Qt::Key_Up );<br />
QCOMPARE( dateEdit.date(), date.addDays(1) );<br />
<br />
// we click twice on the "reduce" arrow at the bottom RH corner<br />
// first we need the widget size to know where to click<br />
QSize editWidgetSize = dateEdit.size();<br />
QPoint clickPoint(editWidgetSize.rwidth()-2, editWidgetSize.rheight()-2);<br />
// build a list that contains two clicks<br />
QTestEventList list1;<br />
list1.addMouseClick( Qt::LeftButton, 0, clickPoint);<br />
list1.addMouseClick( Qt::LeftButton, 0, clickPoint);<br />
// call that list on the widget<br />
list1.simulate( &dateEdit );<br />
// and we should have decreased day by two (one less than original)<br />
QCOMPARE( dateEdit.date(), date.addDays(-1) );<br />
<br />
QTest::keyClicks( &dateEdit, "25122005" );<br />
QCOMPARE( dateEdit.date(), QDate( 2005, 12, 25 ) );<br />
<br />
QTestEventList list2;<br />
list2.addKeyClick( Qt::Key_Tab, Qt::ShiftModifier );<br />
list2.addKeyClicks( "08" );<br />
list2.simulate( &dateEdit );<br />
QCOMPARE( dateEdit.date(), QDate( 2005, 8, 25 ) );<br />
}<br />
<br />
void testDateEdit::testValidator_data()<br />
{<br />
qRegisterMetaType<QDate>("QDate");<br />
<br />
QTest::addColumn<QDate>( "initialDate" );<br />
QTest::addColumn<QTestEventList>( "events" );<br />
QTest::addColumn<QDate>( "finalDate" );<br />
<br />
QTestEventList eventsList1;<br />
// this next line is just to start editing<br />
eventsList1.addKeyClick( Qt::Key_Enter );<br />
eventsList1.addKeyClicks( "12041968" );<br />
<br />
QTest::newRow( "1968/4/12" ) << QDate( 1967, 3, 11 )<br />
<< eventsList1<br />
<< QDate( 1968, 4, 12 );<br />
<br />
QTestEventList eventsList2;<br />
eventsList2.addKeyClick( Qt::Key_Enter );<br />
eventsList2.addKeyClicks( "140abcdef[" );<br />
<br />
QTest::newRow( "1967/3/14" ) << QDate( 1967, 3, 11 )<br />
<< eventsList2<br />
<< QDate( 1967, 3, 14 );<br />
// more rows can go in here<br />
}<br />
<br />
void testDateEdit::testValidator()<br />
{<br />
QFETCH( QDate, initialDate );<br />
QFETCH( QTestEventList, events );<br />
QFETCH( QDate, finalDate );<br />
<br />
QDateEdit dateEdit( initialDate );<br />
<br />
events.simulate( &dateEdit);<br />
<br />
QCOMPARE( dateEdit.date(), finalDate );<br />
}<br />
<br />
QTEST_MAIN(testDateEdit)<br />
#include "tutorial3a.moc"<br />
</syntaxhighlight><br />
<br />
This example is pretty much the same as the previous version, up to line 25. In line 26, we create a QTestEventList. We add events to the list in lines 27 and 28 - note that we don't specify the widget we are calling them on at this stage. In line 30, we simulate each event on the widget. If we had multiple widgets, we could call simulate using the same set of events.<br />
<br />
Lines 31 to 34 are as per the previous example.<br />
<br />
We create another list in lines 35 to 37, although this time we are using addKeyClick and addKeyClicks instead of adding mouse events. Note that an event list can contain combinations of mouse and keyboard events - it just didn't make sense in this test to have such a combination. We use the second list at line 38, and check the results in line 39.<br />
<br />
You can also build lists of events in data driven testing as well, as shown in lines 41 to 70. The key difference is that instead of fetching a QString in each row, we are fetching a QTestEventList. This requires that we add a column of QTestEventList, rather than QString (see line 45). At lines 47 to 50, we create a list of events. At line 52 we add those events to the applicable row. We create a second list at lines 54 to 56, and add that second list to the applicable row in line 58.<br />
<br />
We fetch the events in line 65, and use them in line 68. If we had multiple widgets, then we could use the same event list several times.<br />
<br />
==Tutorial 4 - Testing for failure and avoiding tests==<br />
<br />
Under some conditions, it is impossible to avoid tests failing. In this section, we'll see how to deal with these cases.<br />
<br />
===Skipping tests===<br />
<br />
Where a test doesn't make sense to run (for example, if the required test files aren't available, or the feature is architecture or operating system dependent), the cleanest solution is to skip the test.<br />
<br />
Tests are skipped using the QSKIP macro. QSKIP takes two arguments - a label string that should be used to describe why the test is being skipped, and a enumerated constant that controls how much of the test is skipped. If you pass SkipSingle, and the test is data driven, then only the current row is skipped. If you pass SkipAll and the test is data driven, then all following rows are skipped. If the test is not data driven, then it doesn't matter which one is used.<br />
<br />
You can see how QSKIP works in the example below:<br />
<br />
'''Example 15. Unit test showing skipped tests'''<br />
<syntaxhighlight lang="cpp-qt" line="GESHI_NORMAL_LINE_NUMBERS"><br />
void testDate::testSkip_data()<br />
{<br />
QTest::addColumn<int>("val1");<br />
QTest::addColumn<int>("val2");<br />
<br />
QTest::newRow("1") << 1 << 1;<br />
QTest::newRow("2") << 1 << 2;<br />
QTest::newRow("3") << 3 << 3;<br />
QTest::newRow("5") << 5 << 5;<br />
QTest::newRow("4") << 4 << 5;<br />
}<br />
<br />
void testDate::testSkip()<br />
{<br />
QFETCH(int, val1);<br />
QFETCH(int, val2);<br />
<br />
if ( val2 == 2 )<br />
QSKIP("Two isn't good, not doing it", SkipSingle);<br />
if ( val1 == 5 )<br />
QSKIP("Five! I've had enough, bailing here", SkipAll);<br />
QCOMPARE( val1, val2 );<br />
}<br />
</syntaxhighlight><br />
<br />
'''Example 16. Output of unit test showing skipped tests'''<br />
<pre><br />
$ ./tutorial4 testSkip -v2<br />
********* Start testing of testDate *********<br />
Config: Using QTest library 4.1.0, Qt 4.1.0-snapshot-20051107<br />
INFO : testDate::initTestCase() entering<br />
PASS : testDate::initTestCase()<br />
INFO : testDate::testSkip() entering<br />
INFO : testDate::testSkip(1) COMPARE()<br />
Loc: [tutorial4.cpp(82)]<br />
SKIP : testDate::testSkip(2) Two isn't good, not doing it<br />
Loc: [tutorial4.cpp(79)]<br />
INFO : testDate::testSkip(3) COMPARE()<br />
Loc: [tutorial4.cpp(82)]<br />
SKIP : testDate::testSkip(5) Five! I've had enough, bailing here<br />
Loc: [tutorial4.cpp(81)]<br />
PASS : testDate::testSkip()<br />
INFO : testDate::cleanupTestCase() entering<br />
PASS : testDate::cleanupTestCase()<br />
Totals: 3 passed, 0 failed, 2 skipped<br />
********* Finished testing of testDate *********<br />
</pre><br />
<br />
from the verbose output, you can see that the test was run on the first and third rows. The second row wasn't run because of the QSKIP call with a SkipSingle argument. Similarly, the fourth and fifth rows weren't run because the fourth row triggered a QSKIP call with a SkipAll argument.<br />
<br />
Also note that the test didn't fail, even though there were two calls to QSKIP. Conceptually, a skipped test is a test that didn't make sense to run for test validity reasons, rather than a test that is valid but will fail because of bugs or lack of features in the code being tested.<br />
<br />
===Handling expected failures===<br />
<br />
If you have valid tests, but the code that you are testing doesn't pass them, then ideally you fix the code you are testing. However sometimes that isn't possible in the time that you have available, or because of a need to avoid binary incompatible changes. In this case, it is undesirable to delete or modify the unit tests - it is better to flag the test as "expected to fail", using the QEXPECT_FAIL macro. An example of this is shown below:<br />
<br />
'''Example 17. Unit test showing expected failures'''<br />
<br />
<syntaxhighlight lang="cpp-qt" line="GESHI_NORMAL_LINE_NUMBERS"><br />
void testDate::testExpectedFail()<br />
{<br />
QEXPECT_FAIL("", "1 is not 2, even for very large 1", Continue);<br />
QCOMPARE( 1, 2 );<br />
QCOMPARE( 2, 2 );<br />
<br />
QEXPECT_FAIL("", "1 is not 2, even for very small 2", Abort);<br />
QCOMPARE( 1, 2 );<br />
// The next line will not be run, because we Abort on previous failure<br />
QCOMPARE( 3, 3 );<br />
}<br />
</syntaxhighlight><br />
<br />
'''Example 18. Output of unit test showing expected failures'''<br />
<br />
<pre><br />
$ ./tutorial4/tutorial4 testExpectedFail -v2<br />
********* Start testing of testDate *********<br />
Config: Using QTest library 4.1.0, Qt 4.1.0-snapshot-20051107<br />
INFO : testDate::initTestCase() entering<br />
PASS : testDate::initTestCase()<br />
INFO : testDate::testExpectedFail() entering<br />
INFO : testDate::testExpectedFail() Compared values are not the same<br />
Actual (1): 1<br />
Expected (2): 2<br />
Loc: [tutorial4.cpp(41)]<br />
XFAIL : testDate::testExpectedFail() 1 is not 2, even for very large 1<br />
Loc: [tutorial4.cpp(41)]<br />
INFO : testDate::testExpectedFail() COMPARE()<br />
Loc: [tutorial4.cpp(42)]<br />
INFO : testDate::testExpectedFail() Compared values are not the same<br />
Actual (1): 1<br />
Expected (2): 2<br />
Loc: [tutorial4.cpp(45)]<br />
XFAIL : testDate::testExpectedFail() 1 is not 2, even for very small 2<br />
Loc: [tutorial4.cpp(45)]<br />
PASS : testDate::testExpectedFail()<br />
INFO : testDate::cleanupTestCase() entering<br />
PASS : testDate::cleanupTestCase()<br />
Totals: 3 passed, 0 failed, 0 skipped<br />
********* Finished testing of testDate *********<br />
</pre><br />
<br />
As you can see from the verbose output, we expect a failure from each time we do a QCOMPARE( 1, 2);. In the first call to QEXPECT_FAIL, we use the Continue argument, so the rest of the tests will still be run. However in the second call to QEXPECT_FAILwe use the Abort and the test bails at this point. Generally it is better to use Continue unless you have a lot of closely related tests that would each need a QEXPECT_FAIL entry.<br />
<br />
Also note that tests that are marked as expected failures are not considered to be failures, so the test function above is considered to be a pass.<br />
<br />
If a test that is marked to be an expected failure, and it unexpectedly passes, then that is flagged as an error, as shown below:<br />
<br />
'''Example 19. Unit test showing unexpected pass'''<br />
<syntaxhighlight lang="cpp-qt" line="GESHI_NORMAL_LINE_NUMBERS"><br />
void testDate::testUnexpectedPass()<br />
{<br />
QEXPECT_FAIL("", "1 is not 2, even for very large 1", Continue);<br />
QCOMPARE( 1, 1 );<br />
QCOMPARE( 2, 2 );<br />
<br />
QEXPECT_FAIL("", "1 is not 2, even for very small 2", Abort);<br />
QCOMPARE( 1, 1 );<br />
QCOMPARE( 3, 3 );<br />
}<br />
</syntaxhighlight><br />
<br />
'''Example 20. Output of unit test showing unexpected pass'''<br />
<br />
<pre><br />
$ ./tutorial4/tutorial4 testUnexpectedPass -v2<br />
********* Start testing of testDate *********<br />
Config: Using QTest library 4.1.0, Qt 4.1.0-snapshot-20051107<br />
INFO : testDate::initTestCase() entering<br />
PASS : testDate::initTestCase()<br />
INFO : testDate::testUnexpectedPass() entering<br />
INFO : testDate::testUnexpectedPass() COMPARE()<br />
Loc: [tutorial4.cpp(53)]<br />
XPASS : testDate::testUnexpectedPass() COMPARE()<br />
Loc: [tutorial4.cpp(53)]<br />
INFO : testDate::testUnexpectedPass() COMPARE()<br />
Loc: [tutorial4.cpp(54)]<br />
INFO : testDate::testUnexpectedPass() COMPARE()<br />
Loc: [tutorial4.cpp(57)]<br />
XPASS : testDate::testUnexpectedPass() COMPARE()<br />
Loc: [tutorial4.cpp(57)]<br />
INFO : testDate::cleanupTestCase() entering<br />
PASS : testDate::cleanupTestCase()<br />
Totals: 2 passed, 2 failed, 0 skipped<br />
********* Finished testing of testDate *********<br />
</pre><br />
<br />
The effect of unexpected passes on the running of the test is controlled by the second argument to QEXPECT_FAIL. If the argument is Continue and the test unexpectedly passes, then the rest of the test function will be run. If the argument is Abort, then the test will stop.<br />
<br />
===Checking debug messages and warnings===<br />
<br />
If you are testing border cases, you will likely run across the case where some kind of message will be produced using the qDebug or qWarning functions. Where a test produces a debug or warning message, that message will be logged in the test output (although it will still be considered a pass unless some other check fails), as shown in the example below:<br />
<br />
Example 21. Unit test producing warning and debug messages<br />
<syntaxhighlight lang="cpp-qt" line="GESHI_NORMAL_LINE_NUMBERS"><br />
void testDate::testQdebug()<br />
{<br />
qWarning("warning");<br />
qDebug("debug");<br />
qCritical("critical");<br />
}<br />
</syntaxhighlight><br />
<br />
'''Example 22. Output of unit test producing warning and debug messages'''<br />
<pre><br />
$ ./tutorial4 testQdebug<br />
********* Start testing of testDate *********<br />
Config: Using QTest library 4.1.0, Qt 4.1.0-snapshot-20051107<br />
PASS : testDate::initTestCase()<br />
QWARN : testDate::testQdebug() warning<br />
QDEBUG : testDate::testQdebug() debug<br />
QSYSTEM: testDate::testQdebug() critical<br />
PASS : testDate::testQdebug()<br />
PASS : testDate::cleanupTestCase()<br />
Totals: 3 passed, 0 failed, 0 skipped<br />
********* Finished testing of testDate *********<br />
</pre><br />
<br />
Note that while this example produces the debug and warning messages within the test function (testQdebug), those messages would normally be propagated up from the code being tested. However the source of the messages does not make any difference to how they are handled.<br />
<br />
If your test needs include either a clean output, or verification that appropriate messages are generated, then you will probably need the QtTest::ignoreMessage function.<br />
<br />
{{Tip|'''Note:''' The ignoreMessage function can be used to ignore a message, however it might be clearer to think of this function as checking for the presence of an expected message. In particular, it is a test failure if you call ignoreMessage and the message is not generated.}}<br />
<br />
An example of how ignoreMessage works is shown below.<br />
<br />
'''Example 23. Example of using ignoreMessage'''<br />
<syntaxhighlight lang="cpp-qt"><br />
void testDate::testValidity()<br />
{<br />
QTest::ignoreMessage(QtWarningMsg, "validity warning");<br />
qWarning("validity warning");<br />
}<br />
<br />
void testDate::testValiditi()<br />
{<br />
QTest::ignoreMessage(QtWarningMsg, "validity warning");<br />
qWarning("validiti warning");<br />
}<br />
</syntaxhighlight><br />
<br />
<br />
'''Example 24. Output of ignoreMessage example'''<br />
<pre><br />
$ ./tutorial4 testValidity testValiditi<br />
********* Start testing of testDate *********<br />
Config: Using QTest library 4.1.0, Qt 4.1.0-snapshot-20051107<br />
PASS : testDate::initTestCase()<br />
PASS : testDate::testValidity()<br />
QWARN : testDate::testValiditi() validiti warning<br />
INFO : testDate::testValiditi() Did not receive message: "validity warning"<br />
FAIL! : testDate::testValiditi() Not all expected messages were received<br />
PASS : testDate::cleanupTestCase()<br />
Totals: 3 passed, 1 failed, 0 skipped<br />
********* Finished testing of testDate *********<br />
</pre><br />
<br />
Note that the warning message in testDate::testValidity has been "swallowed" by thecall to ignoreMessage.<br />
<br />
By contrast, the warning message in testDate::testValiditi still causes a warning to be logged, because the ignoreMessage call does not match the text in the warning message. In addition, because a we expected a particular warning message and it wasn't received, the testDate::testValiditi test function fails.<br />
<br />
==Tutorial 5: Testing Qt slots and signals==<br />
<br />
An important part of Qt programming is the use of signals and slots. This section covers the support for testing of these features.<br />
<br />
{{Tip|'''Note:''' If you are not familiar with Qt signals and slots, you probably should review the introduction to this feature provided in your Qt documentation. It is also available at http://doc.qt.io/qt-5/signalsandslots.html.}}<br />
<br />
===Testing slots===<br />
<br />
Testing slots is very easy, because a slot is just a specially annotated method. You can call slots just like any other method you'd like to test, as shown below:<br />
<br />
'''Example 25. QLabel test code, showing testing of a couple of slots'''<br />
<syntaxhighlight lang="cpp-qt" line="GESHI_NORMAL_LINE_NUMBERS"><br />
#include <QtTest><br />
#include <QtCore><br />
#include <QtGui><br />
<br />
class testLabel: public QObject<br />
{<br />
Q_OBJECT<br />
private slots:<br />
void testChanges();<br />
};<br />
<br />
void testLabel::testChanges()<br />
{<br />
QLabel label;<br />
<br />
// setNum() is a QLabel slot, but we can just call it like any<br />
// other method.<br />
label.setNum( 3 );<br />
QCOMPARE( label.text(), QString("3") );<br />
<br />
// clear() is also a slot.<br />
label.clear();<br />
QVERIFY( label.text().isEmpty() );<br />
}<br />
<br />
QTEST_MAIN(testLabel)<br />
#include "tutorial5.moc"<br />
</syntaxhighlight><br />
<br />
===Testing signals===<br />
Testing of signals is a little more difficult than testing of slots, however, Qt offers a very useful class called QSignalSpy that helps a lot.<br />
<br />
{{qt|QSignalSpy}} is a class provided with Qt that allows you to record the signals that have been emitted from a particular QObject subclass object. You can then check that the right number of signals have been emitted and that the right kind of signals was emitted. You can find more information on the QSignalSpy class in your Qt documentation.<br />
<br />
An example of how you can use QSignalSpy to test a class that has signals is shown below.<br />
<br />
'''Example 26. QCheckBox test code, showing testing of signals'''<br />
<br />
<syntaxhighlight lang="cpp-qt" line="GESHI_NORMAL_LINE_NUMBERS"><br />
#include <QtTest><br />
#include <QtCore><br />
#include <QtGui><br />
<br />
class testCheckBox: public QObject<br />
{<br />
Q_OBJECT<br />
private slots:<br />
void testSignals();<br />
};<br />
<br />
void testCheckBox::testSignals()<br />
{<br />
// You don't need to use an object created with "new" for<br />
// QSignalSpy, I just needed it in this case to test the emission<br />
// of a destroyed() signal.<br />
QCheckBox *xbox = new QCheckBox;<br />
<br />
// We are going to have two signal monitoring classes in use for<br />
// this test.<br />
// The first monitors the stateChanged() signal.<br />
// Also note that QSignalSpy takes a pointer to the object.<br />
QSignalSpy stateSpy( xbox, SIGNAL( stateChanged(int) ) );<br />
<br />
// Not strictly necessary, but I like to check that I have set up<br />
// my QSignalSpy correctly.<br />
QVERIFY( stateSpy.isValid() );<br />
<br />
// Now we check to make sure we don't have any signals already<br />
QCOMPARE( stateSpy.count(), 0 );<br />
<br />
// Here is a second monitoring class - this one for the<br />
// destroyed() signal.<br />
QSignalSpy destroyedSpy( xbox, SIGNAL( destroyed() ) );<br />
QVERIFY( destroyedSpy.isValid() );<br />
<br />
// A sanity check to verify the initial state<br />
// This also shows that you can mix normal method checks with<br />
// signal checks.<br />
QCOMPARE( xbox->checkState(), Qt::Unchecked );<br />
<br />
// Shouldn't already have any signals<br />
QCOMPARE( destroyedSpy.count(), 0 );<br />
<br />
// If we change the state, we should get a signal.<br />
xbox->setCheckState( Qt::Checked );<br />
QCOMPARE( stateSpy.count(), 1 );<br />
<br />
xbox->setCheckState( Qt::Unchecked );<br />
QCOMPARE( stateSpy.count(), 2 );<br />
<br />
xbox->setCheckState( Qt::PartiallyChecked );<br />
QCOMPARE( stateSpy.count(), 3 );<br />
<br />
// If we destroy the object, the signal should be emitted.<br />
delete xbox;<br />
<br />
// So the count of objects should increase.<br />
QCOMPARE( destroyedSpy.count(), 1 );<br />
<br />
// We can also review the signals that we collected<br />
// QSignalSpy is really a QList of QLists, so we take the first<br />
// list, which corresponds to the arguments for the first signal<br />
// we caught.<br />
QList<QVariant> firstSignalArgs = stateSpy.takeFirst();<br />
// stateChanged() only has one argument - an enumerated type (int)<br />
// So we take that argument from the list, and turn it into an integer.<br />
int firstSignalState = firstSignalArgs.at(0).toInt();<br />
// We can then check we got the right kind of signal.<br />
QCOMPARE( firstSignalState, static_cast<int>(Qt::Checked) );<br />
<br />
// check the next signal - note that takeFirst() removes from the list<br />
QList<QVariant> nextSignalArgs = stateSpy.takeFirst();<br />
// this shows another way of fudging the argument types<br />
Qt::CheckState nextSignalState = (Qt::CheckState)nextSignalArgs.at(0).toInt();<br />
QCOMPARE( nextSignalState, Qt::Unchecked );<br />
<br />
// and again for the third signal<br />
nextSignalArgs = stateSpy.takeFirst();<br />
nextSignalState = (Qt::CheckState)nextSignalArgs.at(0).toInt();<br />
QCOMPARE( nextSignalState, Qt::PartiallyChecked );<br />
}<br />
<br />
QTEST_MAIN(testCheckBox)<br />
#include "tutorial5a.moc"<br />
</syntaxhighlight><br />
<br />
The first 11 lines are essentially unchanged from previous examples that we've seen. Line 15 creates the object that will be tested - as noted in the comments in lines 12-14, the only reason that I'm creating it with new is because I need to delete it in line 45 to cause the destroyed() signal to be emitted.<br />
<br />
Line 20 sets up the first of our two QSignalSpy instances. The one in line 20 monitors the stateChanged(int) signal, and the one in line 29 monitors the destroyed() signal. If you get the name or signature of the signal wrong (for example, if you use stateChanged() instead of stateChanged(int)), then this will not be caught at compile time, but will result in a runtime failure. You can test if things were set up correctly using the isValid(), as shown in lines 24 and 30.<br />
<br />
As shown in line 34, there is no reason why you cannot test normal methods, signals and slots in the same test.<br />
<br />
Line 38 changes the state of the object under test, which is supposed to result in a stateChanged(int) signal being emitted. Line 39 checks that the number of signals increases from zero to one. Lines 40 and 41 repeat the process, and again in lines 42 and 43.<br />
<br />
Line 45 deletes the object under test, and line 47 tests that the destroyed() signal has been emitted.<br />
<br />
For signals that have arguments (such as our stateChanged(int) signal), you may also wish to check that the arguments were correct. You can do this by looking at the list of signal arguments. Exactly how you do this is fairly flexible, however for simple tests like the one in the example, you can manually work through the list using takeFirst() and check that each argument is correct. This is shown in line 52, 55 and 57 for the first signal. The same approach is shown in lines 59, 61 and 62 for the second signal, and the in lines 64 to 66 for the third signal. For a more complex set of tests, you may wish to apply some data driven techniques.<br />
<br />
{{Tip|'''Note:''' You should be aware that, for some class implementations, you may need to return control to the event loop to have signals emitted. If you need this, try using the {{qt|QTest}}::qWait() function.}}<br />
<br />
==Tutorial 6: Integrating with CMake==<br />
<br />
The KDE build tool is [http://www.cmake.org CMake], and I assume that you are familiar with the use of CMake. If not, you should review the [[Guidelines and HOWTOs/CMake|CMake Tutorial]] first.<br />
<br />
CMake offers quite good support for unit testing, and QTestLib tests can be easily integrated into any CMake build system.<br />
<br />
=== Configuring for Testing ===<br />
<br />
Tests are not built by default - you have to enable the test system, and build the tests.<br />
<br />
You enable tests by adding an '''ENABLE_TESTING()''' line to the top of your CMakeLists.txt file.<br />
<br />
In some configurations, there may be a build system option to turn on (or off) the compilation of tests. At this stage, you have to enable the '''BUILD_TESTING''' option in KDE4 modules, however this may go away in the near future, as later version of CMake can build the test applications on demand.<br />
<br />
If the tests are still not building, you might want to issue make buildtests in tests directory.<br />
<br />
=== Adding Tests ===<br />
<br />
You add a single test to the list of all tests that can be run by using<br />
'''ecm_add_test''', which looks like this<br />
<br />
include(ECMAddTests)<br />
include_directories(AFTER "${CMAKE_CURRENT_SOURCE_DIR}/..")<br />
ecm_add_test(<br />
tutorial1.cpp<br />
TEST_NAME Tut1<br />
LINK_LIBRARIES Qt5::Test<br />
NAME_PREFIX "tut1-"<br />
)<br />
<br />
* The first argument is the filename.<br />
* There are named arguments for other options.<br />
<br />
Note that '''ecm_add_test''' does nothing if '''ENABLE_TESTING()''' has not been run.<br />
<br />
By convention tests are put in the '''auototests''' directory. See [https://cgit.kde.org/okular.git okular] for an example.<br />
<br />
=== KDE4 CMake Recipe for QTestLib ===<br />
<br />
If you are working in a KDE4 environment, then it is pretty easy to get CMake set up to build and run a test on demand.<br />
<br />
<pre><br />
cmake_minimum_required(VERSION 2.8)<br />
<br />
FIND_PACKAGE ( KDE4 REQUIRED )<br />
FIND_PACKAGE ( Qt4 REQUIRED QT_USE_QT* )<br />
<br />
INCLUDE( ${QT_USE_FILE} )<br />
include(KDE4Defaults)<br />
<br />
set( kwhatevertest_SRCS kwhatevertest.cpp )<br />
<br />
kde4_add_unit_test( kwhatevertest<br />
TESTNAME ksubmodule-kwhatevertest<br />
${kwhatevertest_SRCS}<br />
)<br />
<br />
target_link_libraries( kwhatevertest<br />
${KDE4_KDECORE_LIBS}<br />
${QT_QTTEST_LIBRARY}<br />
${KDE4_KDEUI_LIBS}<br />
)<br />
<br />
</pre><br />
<br />
You are meant to replace "kwhatevertest" with the name of your test application. The target_link_libraries() line will need to contain whatever libraries are needed for the feature you are testing, so if it is a GUI feature, you'll likely need to use "${KDE4_KDEUI_LIBS}.<br />
<br />
=== Running the Tests ===<br />
To run all tests, you can just "make test". This will work through each of the tests that have been added (at any lower level) using '''kde4_add_unit_test''', provided that you have '''include(KDE4Defaults)''' in your CMakeLists.txt.<br />
<br />
This is equivalent to running the "ctest" executable with no arguments. If you want finer grained control over which tests are run or the output format, you can use additional arguments. These are explained in the ctest man page ("man ctest" on a *nix system, or run "ctest --help-full").<br />
<br />
To run a single test, use '''./tutorial1.shell''' rather than just '''./tutorial1''', this will make it use the locally-built version of the shared libraries you're testing, rather than the installed ones.<br />
<br />
Some tests are written so that the expected local is English and fail if it is not.<br />
If your local is not English you case use for instance `LANG=C ctest` or `LANG=C make` to force English when running the test.<br />
<br />
=== Further Reading ===<br />
<br />
Chapter 8 of the [http://www.kitware.com/products/cmakebook.html CMake Book] provides a detailed description of how to do testing with CMake. Also see Appendix B for more on CTest and the special commands you can use.<br />
<br />
Various sections of the CMake Wiki, especially [https://gitlab.kitware.com/cmake/community/wikis/doc/ctest/Testing-With-CTest CTest testing]<br />
<br />
==Tutorial 7: Integrating with qmake==<br />
<br />
TODO<br />
<br />
==Tutorial 8: XML output==<br />
TODO<br />
<br />
==Tutorial 9: KDE specifics ==<br />
For KDE specific enhancements see [http://api.kde.org/4.0-api/kdelibs-apidocs/kdecore/html/qtest__kde_8h.html API dox]<br />
<br />
TODO<br />
<br />
== Alternative tools for testing ==<br />
<br />
There are a range of alternative testing approaches that can<br />
be used either with unit tests, or as an addition to the unit tests.<br />
<br />
===Static tests===<br />
As noted in the introduction, unit tests are dynamic tests - they exercise the compiled code. Static tests are slightly different - they look for problems in the source code, rather than making sure that the object code runs correctly.<br />
<br />
Static test tools tend to identify completely different types of problems to unit tests, and you should seek to use them both.<br />
<br />
For more information on using static tests, see [[../Code Checking|the Code Checking tutorial]].<br />
<br />
===Coverage tools and CI===<br />
Add this option in the configuration of your project's CI build.<br />
# http://quickgit.kde.org/?p=websites%2Fbuild-kde-org.<br />
# git clone kde:websites/build-kde-org<br />
<br />
[DEFAULT]<br />
configureExtraArgs=-DBUILD_COVERAGE=ON<br />
<br />
=== GUI application testing - Squish and KDExecutor ===<br />
[http://www.froglogic.de/pg?id=Products&category=squish&sub=overview&subsub=overview Squish] by [http://www.froglogic.de froglogic] and [http://www.kdab.net/?page=products&sub=kdexecutor KDExecutor for Qt3/KDE3] by [http://www.kdab.net Klarälvdalens Datakonsult (KDAB)] are commercial tools that facilitate GUI testing.</div>Gjditchfield