aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorIvan Komissarov <abbapoh@gmail.com>2023-07-20 17:17:43 +0300
committerIvan Komissarov <ABBAPOH@gmail.com>2024-01-30 09:47:48 +0000
commit271e6c9365f61ec9463861a4f2fb1c4d7c6c2502 (patch)
treed9360cfd3e0a1acd65d95ebcd89733af052261d6
parentb38fad7c10d68ec76856b26ca520bd6746ddda20 (diff)
Tutorial. Part 1
This change adds a step-by step tutorial that helps to explain Qbs concepts and best practices for new users. Change-Id: I5c669f8fa0f89b8300f241bb8e4ed7cd4b3bb4c6 Reviewed-by: Christian Kandeler <christian.kandeler@qt.io>
-rw-r--r--doc/doc.qbs1
-rw-r--r--doc/qbs.qdoc10
-rw-r--r--doc/tutorial.qdoc293
-rw-r--r--qbs.qbs1
-rw-r--r--tests/auto/auto.qbs1
-rw-r--r--tests/auto/blackbox/CMakeLists.txt9
-rw-r--r--tests/auto/blackbox/blackbox-tutorial.qbs21
-rw-r--r--tests/auto/blackbox/tst_blackboxtutorial.cpp72
-rw-r--r--tests/auto/blackbox/tst_blackboxtutorial.h48
-rw-r--r--tutorial/chapter-1/app/app.qbs12
-rw-r--r--tutorial/chapter-1/app/main.c9
-rw-r--r--tutorial/chapter-1/myproject.qbs9
-rw-r--r--tutorial/chapter-2/app/app.qbs11
-rw-r--r--tutorial/chapter-2/app/main.c10
-rw-r--r--tutorial/chapter-2/lib/lib.c15
-rw-r--r--tutorial/chapter-2/lib/lib.h11
-rw-r--r--tutorial/chapter-2/lib/lib.qbs28
-rw-r--r--tutorial/chapter-2/myproject.qbs10
-rw-r--r--tutorial/chapter-3/app/app.qbs29
-rw-r--r--tutorial/chapter-3/app/main.c10
-rw-r--r--tutorial/chapter-3/lib/lib.c16
-rw-r--r--tutorial/chapter-3/lib/lib.h11
-rw-r--r--tutorial/chapter-3/lib/lib.qbs26
-rw-r--r--tutorial/chapter-3/lib/lib_global.h23
-rw-r--r--tutorial/chapter-3/myproject.qbs8
-rw-r--r--tutorial/chapter-4/app/app.qbs10
-rw-r--r--tutorial/chapter-4/app/main.c10
-rw-r--r--tutorial/chapter-4/lib/lib.c10
-rw-r--r--tutorial/chapter-4/lib/lib.h8
-rw-r--r--tutorial/chapter-4/lib/lib.qbs13
-rw-r--r--tutorial/chapter-4/lib/lib_global.h18
-rw-r--r--tutorial/chapter-4/myproject.qbs11
-rw-r--r--tutorial/chapter-4/qbs/imports/MyApplication.qbs23
-rw-r--r--tutorial/chapter-4/qbs/imports/MyLibrary.qbs21
-rw-r--r--tutorial/tutorial.qbs5
35 files changed, 821 insertions, 2 deletions
diff --git a/doc/doc.qbs b/doc/doc.qbs
index 03c82e18c..0dfc5f474 100644
--- a/doc/doc.qbs
+++ b/doc/doc.qbs
@@ -30,6 +30,7 @@ Project {
"fixnavi.pl",
"howtos.qdoc",
"qbs.qdoc",
+ "tutorial.qdoc",
"qbs-online.qdocconf",
"config/*.qdocconf",
"config/style/qt5-sidebar.html",
diff --git a/doc/qbs.qdoc b/doc/qbs.qdoc
index 9ea88763e..76bbf2a61 100644
--- a/doc/qbs.qdoc
+++ b/doc/qbs.qdoc
@@ -70,6 +70,13 @@
\li \l{Special Property Values}
\li \l{Module Providers}
\endlist
+ \li \l{Tutorial}
+ \list
+ \li \l{tutorial-1.html}{Application}
+ \li \l{tutorial-2.html}{Static Library}
+ \li \l{tutorial-3.html}{Dynamic Library}
+ \li \l{tutorial-4.html}{Convenience Items}
+ \endlist
\li \l{How-tos}
\li \l{Reference}
\list
@@ -1068,7 +1075,6 @@
*/
-
/*!
\previouspage usage.html
\page language-introduction.html
@@ -1881,7 +1887,7 @@
/*!
\previouspage special-property-values.html
\page module-providers.html
- \nextpage howtos.html
+ \nextpage tutorial.html
\title Module Providers
diff --git a/doc/tutorial.qdoc b/doc/tutorial.qdoc
new file mode 100644
index 000000000..9f8dc5a44
--- /dev/null
+++ b/doc/tutorial.qdoc
@@ -0,0 +1,293 @@
+/****************************************************************************
+**
+** Copyright (C) 2024 Ivan Komissarov (abbapoh@gmail.com).
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of Qbs.
+**
+** $QT_BEGIN_LICENSE:FDL$
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and The Qt Company. For licensing terms
+** and conditions see https://www.qt.io/terms-conditions. For further
+** information use the contact form at https://www.qt.io/contact-us.
+**
+** GNU Free Documentation License Usage
+** Alternatively, this file may be used under the terms of the GNU Free
+** Documentation License version 1.3 as published by the Free Software
+** Foundation and appearing in the file included in the packaging of
+** this file. Please review the following information to ensure
+** the GNU Free Documentation License version 1.3 requirements
+** will be met: https://www.gnu.org/licenses/fdl-1.3.html.
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+/*!
+ \previouspage module-providers.html
+ \page tutorial.html
+ \nextpage howtos.html
+
+ \title Tutorial
+
+ The tutorial describes the process of creating a typical \QBS project and explains core
+ concepts.
+
+ \list
+ \li \l{tutorial-1.html}{Application}
+ \li \l{tutorial-2.html}{Static Library}
+ \li \l{tutorial-3.html}{Dynamic Library}
+ \li \l{tutorial-4.html}{Convenience Items}
+ \endlist
+*/
+
+/*!
+ \previouspage tutorial.html
+ \page tutorial-1.html
+ \nextpage tutorial-2.html
+
+ \title Application
+
+ Let's start with a mandatory Hello World example. There is a Hello World example in the
+ \l{overview.html#well-defined-language}{overview section}, but this time we will create a
+ real-world project.
+
+ We will start with a simple \QBS project file:
+ \snippet ../tutorial/chapter-1/myproject.qbs 0
+
+ Here, we set the \l{Project::name}{name} of the project to \c "My Project" - it is mainly
+ used for IDEs but can also be used to reference a project when setting properties from
+ command-line. We also set the \l{Project::minimumQbsVersion}{minimumQbsVersion} - this property
+ can be useful if the project depends on features that are not present in earlier \QBS
+ versions.
+
+ The \l{Project::references}{references} property contains the path to a file
+ that describes our application. This file is located in a separate \c app directory -
+ typically, projects tend to have quite a complicated structure but \QBS does not enforce any
+ specific layout, so we will simply put each product in a separate directory.
+
+ The application file sets several properties:
+ \snippet ../tutorial/chapter-1/app/app.qbs 0
+
+ The \l{Product::name}{name} property identifies the product.
+ The \l{Product::targetName}{targetName} property sets the name of the resulting binary
+ (without the \c .exe extension on Windows, which is appended automatically). By default,
+ \l{Product::targetName}{targetName} defaults to \l{Product::name}{name}. The
+ \l{Product::files}{files} property contains a single \c main.c file which is a trivial
+ \e{hello world} application:
+ \snippet ../tutorial/chapter-1/app/main.c 0
+
+ We set \l{Product::consoleApplication}{consoleApplication} to \c true to indicate that our
+ application is going to be used from a terminal. For example, on Windows, this will spawn a
+ new console window if you double-click the resulting binary, while on macOS it will create a
+ standalone binary file instead of an
+ \l{https://developer.apple.com/library/archive/documentation/CoreFoundation/Conceptual/CFBundles/BundleTypes/BundleTypes.html}{application bundle}.
+
+ By default, the \l{Application::install}{CppApplication.install} property is \c false and thus
+ \QBS does not install the binary. If the \l{Application::install}{install} property is
+ \c true, when building a project, \QBS will also install it into an \e{installation root}
+ folder which by default is named \c install-root and located under the build directory. This
+ folder will contain only resulting artifacts without the build leftovers and files will have
+ the same layout as in the target system. The \l{Application::install}{install} property should
+ be set to \c true for all products that are to be distributed.
+ See the \l{installing-files.html}{Installing Files} section for more details.
+
+ We can now build and run our application from the project directory:
+ \code
+ chapter-1 $ qbs build
+ ...
+ Building for configuration default
+ compiling main.c [My Application]
+ ...
+ linking myapp [My Application]
+ ...
+ Build done for configuration default.
+
+ chapter-1 $ qbs run
+ ...
+ Starting target. Full command line: .../default/install-root/usr/local/bin/myapp
+ Hello, world
+ \endcode
+
+ The \QBS output to console and default installation location may vary between different
+ platforms.
+
+ In most cases, \QBS should be able to find the default compiler (for example, GCC or
+ Clang on Linux, Visual Studio on Windows, or Xcode on macOS), but if that's not the
+ case, see the \l{configuring.html}{configuring} section.
+
+ In the following chapters, we will continue to improve our project: we will add a library and
+ make it configurable.
+*/
+
+/*!
+ \previouspage tutorial-1.html
+ \page tutorial-2.html
+ \nextpage tutorial-3.html
+
+ \title Static Library
+
+ Let's add a static library to our project so we can reuse some code. Analogous to what we did
+ for the application, we put the file into the \c{lib} directory and add it to the
+ \l{Project::references}{references} property in our project. The modified project may look
+ like this:
+
+ \snippet ../tutorial/chapter-2/myproject.qbs 0
+
+ Let's take a look at the the library file now:
+
+ \snippet ../tutorial/chapter-2/lib/lib.qbs 0
+ It contains the \l{StaticLibrary} item which sets the \l{Product::type}{type} of the product
+ to \c "staticlibrary" and sets some defaults like where to install that library.
+ As before, we set the \l{Product::files}{files} property with a header:
+ \snippet ../tutorial/chapter-2/lib/lib.h 0
+ And we set the implementation file of our library:
+ \snippet ../tutorial/chapter-2/lib/lib.c 0
+
+ We will keep our library really simple - it only contains one function, which we will later use
+ in our application. The source file requires a \c "CRUCIAL_DEFINE" to be
+ passed to a preprocessor. That is why we set the \l{cpp::defines}{cpp.defines} property:
+ \snippet ../tutorial/chapter-2/lib/lib.qbs 1
+
+ Note that unlike the \l{CppApplication} item, the \l{StaticLibrary} does not pull in the
+ dependency on the \l{cpp} module automatically - that is why we have to pull it in manually
+ using the \l{Depends} item. Without the \l{cpp} module, \QBS would not know how to turn a
+ \c{.c} file into an object file and the object file into a library. See
+ \l{Rules and Product Types} for details.
+
+ Next, we need to tell \QBS where to look for public headers of our library when building
+ products that depend on it. By default, \QBS knows nothing about the layout of our
+ library, so we tell it to look for headers in the library's source directory using the
+ \l{Export} item:
+
+ \snippet ../tutorial/chapter-2/lib/lib.qbs 2
+ You can export any \l{Module} property within the \l{Export} item - it will be merged in the
+ depending product with other properties. For example, you can export
+ \l{cpp::defines}{cpp.defines} or specific \l{cpp::commonCompilerFlags}{compiler flags} that
+ are required to use this product.
+
+ We depend on the \l{cpp} module twice - once within the \l{StaticLibrary}
+ item and once in the \l{Export} item. This is because by default \QBS does not export anything
+ when depending on this product and the dependencies in this item (as well as
+ properties set in this item) are private to this product while dependencies and properties
+ set in the \l{Export} item are for export only.
+
+ Finally, we have some Apple-specific settings. You can skip this part of the tutorial if you
+ are using some other platform. We depend on the \l{bundle} module and set the
+ \l{bundle::isBundle}{bundle.isBundle} to false:
+ \snippet ../tutorial/chapter-2/lib/lib.qbs 3
+ By default, \QBS builds static and dynamic libraries as
+ \l{https://developer.apple.com/library/archive/documentation/MacOSX/Conceptual/BPFrameworks/Concepts/WhatAreFrameworks.html}{Frameworks}
+ on macOS. So, to keep things simple, we disable the framework build and build a plain old
+ static library file here.
+*/
+
+/*!
+ \previouspage tutorial-2.html
+ \page tutorial-3.html
+ \nextpage tutorial-4.html
+
+ \title Dynamic Library
+
+ Let's now turn our static library into a dynamic library. It is a bit trickier with dynamic
+ libraries since several things should be tweaked. First, we need to be able to mark methods
+ (or classes) in our library as exported. Second, we need to tell our application where to look
+ for our library at run time using \l{https://en.wikipedia.org/wiki/Rpath}{rpaths}.
+
+ Some compilers, like MSVC, require us to mark which symbols we want to export or import. The
+ \l{https://stackoverflow.com/a/6840659}{canonical} way would be to define some helper macros.
+ Let's start with a header that defines those helper macros:
+ \snippet ../tutorial/chapter-3/lib/lib_global.h 0
+
+ This header defines the \c MYLIB_EXPORT macro that expands either to an "export" or to an
+ "import" directive based on the presence of the \c MYLIB_LIBRARY macro. We can use this macro
+ to mark a function as follows:
+ \snippet ../tutorial/chapter-3/lib/lib.h 0
+
+ The modified library product may look like this:
+
+ \snippet ../tutorial/chapter-3/lib/lib.qbs 0
+
+ The most important change is that we changed the item type from \l{StaticLibrary} to
+ \l{DynamicLibrary}. We also added the \c MYLIB_LIBRARY to the list of
+ \l{cpp::defines}{defines}. We do this only when building the library but we are not exporting
+ it - that way the users of our library will have methods marked for import rather than export.
+
+ Finally, we set \l{cpp::sonamePrefix}{cpp.sonamePrefix} to \c "@rpath". This is required only
+ for Apple platforms, see
+ \l{https://developer.apple.com/library/archive/documentation/DeveloperTools/Conceptual/DynamicLibraries/100-Articles/RunpathDependentLibraries.html}{Run-Path Dependent Libraries}
+ for details.
+
+ It is also required to set \l{cpp::rpaths}{cpp.rpaths} in our application file. Since the
+ library is installed to the \c{lib} directory and the application is installed to the \c{bin}
+ directory, we need to tell the loader to look in the \c{lib} directory. The
+ \l{jsextension-fileinfo.html#relativePath}{FileInfo.relativePath} method can help us:
+ \snippet ../tutorial/chapter-3/app/app.qbs 0
+
+ On Linux, this expression would be equivalent to this: \c{cpp.rpaths: ["$ORIGIN/../lib"]}.
+ Don't forget to \c{import qbs.FileInfo} in order to be able to use the
+ \l{jsextension-fileinfo.html} extension.
+
+ To make the example complete, here's how the full \c app/app.qbs file should look like:
+ \snippet ../tutorial/chapter-3/app/app.qbs 1
+*/
+
+/*!
+ \previouspage tutorial-3.html
+ \page tutorial-4.html
+ \nextpage howtos.html
+
+ \title Convenience Items
+
+ As you might have noticed, we are repeating ourselves when setting the same properties in our
+ products - we set \l{Product::version}{version}, \l{Application::install}{install},
+ \l{cpp::rpaths}{cpp.rpaths}, and so on. For a single application and a library, that is not a
+ big deal, but what if we have a dozen libraries? Luckily, this can be achieved using item
+ \l{language-introduction.html#reusing-project-file-code}{inheritance} - we move the common code
+ to the base item and in the real product we will only set what is specific to that product (for
+ example, the list of \l{Product::files}{files}).
+
+ First, we need to tell \QBS where to look for our new base items. This can be achieved using
+ the \l{Project::qbsSearchPaths}{qbsSearchPaths} property. We set this property to \c "qbs" so
+ that \QBS will search our items in the \c{qbs} directory located in the project directory:
+ \snippet ../tutorial/chapter-4/myproject.qbs 0
+
+ \note This directory has a pre-defined structure: base items should be located under the
+ \c{imports} subdirectory. See \l{Custom Modules and Items} for details.
+
+ Let's create a base item for all our applications and move common code there:
+ \snippet ../tutorial/chapter-4/qbs/imports/MyApplication.qbs 0
+
+ As you see, we managed to extract most of the code here, and our application file now only
+ contains what's relevant to it:
+ \snippet ../tutorial/chapter-4/app/app.qbs 0
+
+ Now let's do the same for our library:
+ \snippet ../tutorial/chapter-4/qbs/imports/MyLibrary.qbs 0
+
+ Here, we introduce a helper property, \c libraryMacro, with a default value calculated based
+ on the capitalized product name. Since the name of out library product is \c "mylib", this
+ property will expand to \c "MYLIB_LIBRARY". We can also override the default value
+ for the macro in products that inherit our item like this:
+ \code
+ MyLibrary {
+ libraryMacro: "SOME_OTHER_LIBRARY_MACRO"
+ }
+ \endcode
+
+ Let's take a look at the refactored library file:
+ \snippet ../tutorial/chapter-4/lib/lib.qbs 0
+
+ We managed to extract the reusable parts to common base items leaving the actual products clean
+ and simple.
+
+ Unfortunately, item inheritance comes with a price - when both parent and child items set the
+ same property (\l{cpp::defines}{cpp.defines} in our case), the value in the child item wins.
+ To work around this, the special \l{special-property-values.html#base}{base} value
+ exists - it gives access to the base item's value of the current property and makes it possible
+ to extend its value rather than override it. Here, we concatenate the list of defines from the
+ base item \c{["MYLIB_LIBRARY"]} with a new list, specific to this product (namely,
+ \c{['CRUCIAL_DEFINE']}).
+*/
diff --git a/qbs.qbs b/qbs.qbs
index 2ebb58226..4b2a184db 100644
--- a/qbs.qbs
+++ b/qbs.qbs
@@ -12,6 +12,7 @@ Project {
"examples/examples.qbs",
"share/share.qbs",
"scripts/scripts.qbs",
+ "tutorial/tutorial.qbs",
]
SubProject {
diff --git a/tests/auto/auto.qbs b/tests/auto/auto.qbs
index bf6b3d219..b042d180a 100644
--- a/tests/auto/auto.qbs
+++ b/tests/auto/auto.qbs
@@ -11,6 +11,7 @@ Project {
"blackbox/blackbox-joblimits.qbs",
"blackbox/blackbox-providers.qbs",
"blackbox/blackbox-qt.qbs",
+ "blackbox/blackbox-tutorial.qbs",
"blackbox/blackbox-windows.qbs",
"blackbox/blackbox.qbs",
"buildgraph/buildgraph.qbs",
diff --git a/tests/auto/blackbox/CMakeLists.txt b/tests/auto/blackbox/CMakeLists.txt
index 1d90ec95b..88e19acdf 100644
--- a/tests/auto/blackbox/CMakeLists.txt
+++ b/tests/auto/blackbox/CMakeLists.txt
@@ -80,6 +80,15 @@ add_qbs_test(blackbox-qt
tst_blackboxqt.h
)
+add_qbs_test(blackbox-tutorial
+ SOURCES
+ ../shared.h
+ tst_blackboxbase.cpp
+ tst_blackboxbase.h
+ tst_blackboxtutorial.h
+ tst_blackboxtutorial.cpp
+ )
+
add_qbs_test(blackbox-windows
SOURCES
../shared.h
diff --git a/tests/auto/blackbox/blackbox-tutorial.qbs b/tests/auto/blackbox/blackbox-tutorial.qbs
new file mode 100644
index 000000000..174821ffa
--- /dev/null
+++ b/tests/auto/blackbox/blackbox-tutorial.qbs
@@ -0,0 +1,21 @@
+import qbs.Utilities
+
+QbsAutotest {
+ testName: "blackbox-tutorial"
+ Depends { name: "qbs_app" }
+ Depends { name: "qbs-setup-toolchains" }
+ Group {
+ name: "testdata"
+ prefix: "../../../tutorial/"
+ files: ["**/*"]
+ fileTags: []
+ }
+ files: [
+ "../shared.h",
+ "tst_blackboxtutorial.cpp",
+ "tst_blackboxtutorial.h",
+ "tst_blackboxbase.cpp",
+ "tst_blackboxbase.h",
+ ]
+ cpp.defines: base.concat(["SRCDIR=" + Utilities.cStringQuote(path)])
+}
diff --git a/tests/auto/blackbox/tst_blackboxtutorial.cpp b/tests/auto/blackbox/tst_blackboxtutorial.cpp
new file mode 100644
index 000000000..49de448a1
--- /dev/null
+++ b/tests/auto/blackbox/tst_blackboxtutorial.cpp
@@ -0,0 +1,72 @@
+/****************************************************************************
+**
+** Copyright (C) 2024 Ivan Komissarov (abbapoh@gmail.com)
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of Qbs.
+**
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and The Qt Company. For licensing terms and
+** conditions see http://www.qt.io/terms-conditions. For further information
+** use the contact form at http://www.qt.io/contact-us.
+**
+** GNU Lesser General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU Lesser
+** General Public License version 2.1 or version 3 as published by the Free
+** Software Foundation and appearing in the file LICENSE.LGPLv21 and
+** LICENSE.LGPLv3 included in the packaging of this file. Please review the
+** following information to ensure the GNU Lesser General Public License
+** requirements will be met: https://www.gnu.org/licenses/lgpl.html and
+** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
+**
+** In addition, as a special exception, The Qt Company gives you certain additional
+** rights. These rights are described in The Qt Company LGPL Exception
+** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
+**
+****************************************************************************/
+
+#include "tst_blackboxtutorial.h"
+
+#include <QtCore/qdir.h>
+#include <QtCore/qdiriterator.h>
+
+static QStringList collectProjects(const QString &dirPath)
+{
+ QStringList result;
+ QDir dir(dirPath);
+ const auto subDirs = dir.entryList(QDir::Dirs | QDir::NoDotAndDotDot, QDir::Name);
+ for (const auto &subDir : subDirs) {
+ const auto path = dir.filePath(subDir);
+ if (!QFileInfo::exists(path + "/myproject.qbs"))
+ continue;
+ result.append(dir.relativeFilePath(path));
+ }
+ return result;
+}
+
+TestBlackboxTutorial::TestBlackboxTutorial()
+ : TestBlackboxBase(SRCDIR "/../../../tutorial/", "blackbox-tutorial")
+{}
+
+void TestBlackboxTutorial::tutorial_data()
+{
+ QTest::addColumn<QString>("project");
+
+ const auto projects = collectProjects(testDataDir);
+ for (const auto &project : projects) {
+ QTest::newRow(project.toUtf8().data()) << project;
+ }
+}
+
+void TestBlackboxTutorial::tutorial()
+{
+ QFETCH(QString, project);
+
+ QVERIFY(QDir::setCurrent(testDataDir + "/" + project));
+ QCOMPARE(runQbs(), 0);
+}
+
+QTEST_MAIN(TestBlackboxTutorial)
diff --git a/tests/auto/blackbox/tst_blackboxtutorial.h b/tests/auto/blackbox/tst_blackboxtutorial.h
new file mode 100644
index 000000000..2e84d6a96
--- /dev/null
+++ b/tests/auto/blackbox/tst_blackboxtutorial.h
@@ -0,0 +1,48 @@
+/****************************************************************************
+**
+** Copyright (C) 2024 Ivan Komissarov (abbapoh@gmail.com)
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of Qbs.
+**
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and The Qt Company. For licensing terms and
+** conditions see http://www.qt.io/terms-conditions. For further information
+** use the contact form at http://www.qt.io/contact-us.
+**
+** GNU Lesser General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU Lesser
+** General Public License version 2.1 or version 3 as published by the Free
+** Software Foundation and appearing in the file LICENSE.LGPLv21 and
+** LICENSE.LGPLv3 included in the packaging of this file. Please review the
+** following information to ensure the GNU Lesser General Public License
+** requirements will be met: https://www.gnu.org/licenses/lgpl.html and
+** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
+**
+** In addition, as a special exception, The Qt Company gives you certain additional
+** rights. These rights are described in The Qt Company LGPL Exception
+** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
+**
+****************************************************************************/
+
+#ifndef TST_BLACKBOXTUTORIAL_H
+#define TST_BLACKBOXTUTORIAL_H
+
+#include "tst_blackboxbase.h"
+
+class TestBlackboxTutorial : public TestBlackboxBase
+{
+ Q_OBJECT
+
+public:
+ TestBlackboxTutorial();
+
+private slots:
+ void tutorial_data();
+ void tutorial();
+};
+
+#endif // TST_BLACKBOXTUTORIAL_H
diff --git a/tutorial/chapter-1/app/app.qbs b/tutorial/chapter-1/app/app.qbs
new file mode 100644
index 000000000..8d0831dc1
--- /dev/null
+++ b/tutorial/chapter-1/app/app.qbs
@@ -0,0 +1,12 @@
+//! [0]
+CppApplication {
+ name: "My Application"
+ targetName: "myapp"
+ files: "main.c"
+ version: "1.0.0"
+
+ consoleApplication: true
+ install: true
+ installDebugInformation: true
+}
+//! [0]
diff --git a/tutorial/chapter-1/app/main.c b/tutorial/chapter-1/app/main.c
new file mode 100644
index 000000000..3f3560d25
--- /dev/null
+++ b/tutorial/chapter-1/app/main.c
@@ -0,0 +1,9 @@
+//! [0]
+#include <stdio.h>
+
+int main()
+{
+ printf("Hello, world\n");
+ return 0;
+}
+//! [0]
diff --git a/tutorial/chapter-1/myproject.qbs b/tutorial/chapter-1/myproject.qbs
new file mode 100644
index 000000000..b936f3738
--- /dev/null
+++ b/tutorial/chapter-1/myproject.qbs
@@ -0,0 +1,9 @@
+//! [0]
+Project {
+ name: "My Project"
+ minimumQbsVersion: "2.0"
+ references: [
+ "app/app.qbs"
+ ]
+}
+//! [0]
diff --git a/tutorial/chapter-2/app/app.qbs b/tutorial/chapter-2/app/app.qbs
new file mode 100644
index 000000000..a3ee757ab
--- /dev/null
+++ b/tutorial/chapter-2/app/app.qbs
@@ -0,0 +1,11 @@
+CppApplication {
+ Depends { name: "mylib" }
+ name: "My Application"
+ targetName: "myapp"
+ files: "main.c"
+ version: "1.0.0"
+
+ consoleApplication: true
+ install: true
+ installDebugInformation: true
+}
diff --git a/tutorial/chapter-2/app/main.c b/tutorial/chapter-2/app/main.c
new file mode 100644
index 000000000..a560d1ba1
--- /dev/null
+++ b/tutorial/chapter-2/app/main.c
@@ -0,0 +1,10 @@
+#include <stdio.h>
+
+#include "lib.h"
+
+int main()
+{
+ printf("Hello, world\n");
+ printf("%s\n", get_string());
+ return 0;
+}
diff --git a/tutorial/chapter-2/lib/lib.c b/tutorial/chapter-2/lib/lib.c
new file mode 100644
index 000000000..9fe020ba2
--- /dev/null
+++ b/tutorial/chapter-2/lib/lib.c
@@ -0,0 +1,15 @@
+//! [0]
+
+// lib/lib.cpp
+#include "lib.h"
+
+#ifndef CRUCIAL_DEFINE
+# error CRUCIAL_DEFINE not defined
+#endif
+
+const char *get_string()
+{
+ return "Hello from library";
+}
+
+//! [0]
diff --git a/tutorial/chapter-2/lib/lib.h b/tutorial/chapter-2/lib/lib.h
new file mode 100644
index 000000000..efc56ae0b
--- /dev/null
+++ b/tutorial/chapter-2/lib/lib.h
@@ -0,0 +1,11 @@
+//! [0]
+
+// lib/lib.h
+#ifndef LIB_H
+#define LIB_H
+
+const char *get_string();
+
+#endif // LIB_H
+
+//! [0]
diff --git a/tutorial/chapter-2/lib/lib.qbs b/tutorial/chapter-2/lib/lib.qbs
new file mode 100644
index 000000000..378d855a6
--- /dev/null
+++ b/tutorial/chapter-2/lib/lib.qbs
@@ -0,0 +1,28 @@
+//! [0]
+StaticLibrary {
+ name: "mylib"
+ files: [
+ "lib.c",
+ "lib.h",
+ ]
+ version: "1.0.0"
+ install: true
+
+ //! [1]
+ Depends { name: 'cpp' }
+ cpp.defines: ['CRUCIAL_DEFINE']
+ //! [1]
+
+ //! [2]
+ Export {
+ Depends { name: "cpp" }
+ cpp.includePaths: [exportingProduct.sourceDirectory]
+ }
+ //! [2]
+
+ //! [3]
+ Depends { name: 'bundle' }
+ bundle.isBundle: false
+ //! [3]
+}
+//! [0]
diff --git a/tutorial/chapter-2/myproject.qbs b/tutorial/chapter-2/myproject.qbs
new file mode 100644
index 000000000..17ef04ac9
--- /dev/null
+++ b/tutorial/chapter-2/myproject.qbs
@@ -0,0 +1,10 @@
+//! [0]
+Project {
+ name: "My Project"
+ minimumQbsVersion: "2.0"
+ references: [
+ "app/app.qbs",
+ "lib/lib.qbs"
+ ]
+}
+//! [0]
diff --git a/tutorial/chapter-3/app/app.qbs b/tutorial/chapter-3/app/app.qbs
new file mode 100644
index 000000000..bb7108581
--- /dev/null
+++ b/tutorial/chapter-3/app/app.qbs
@@ -0,0 +1,29 @@
+//! [1]
+// app/app.qbs
+import qbs.FileInfo
+
+CppApplication {
+ Depends { name: "mylib" }
+ name: "My Application"
+ targetName: "myapp"
+ files: "main.c"
+ version: "1.0.0"
+
+ consoleApplication: true
+ install: true
+
+ //! [0]
+ cpp.rpaths: {
+ if (!cpp.rpathOrigin)
+ return [];
+ return [
+ FileInfo.joinPaths(
+ cpp.rpathOrigin,
+ FileInfo.relativePath(
+ FileInfo.joinPaths("/", product.installDir),
+ FileInfo.joinPaths("/", "lib")))
+ ];
+ }
+ //! [0]
+}
+//! [1]
diff --git a/tutorial/chapter-3/app/main.c b/tutorial/chapter-3/app/main.c
new file mode 100644
index 000000000..a560d1ba1
--- /dev/null
+++ b/tutorial/chapter-3/app/main.c
@@ -0,0 +1,10 @@
+#include <stdio.h>
+
+#include "lib.h"
+
+int main()
+{
+ printf("Hello, world\n");
+ printf("%s\n", get_string());
+ return 0;
+}
diff --git a/tutorial/chapter-3/lib/lib.c b/tutorial/chapter-3/lib/lib.c
new file mode 100644
index 000000000..1cbc5ef21
--- /dev/null
+++ b/tutorial/chapter-3/lib/lib.c
@@ -0,0 +1,16 @@
+
+//! [0]
+
+// lib/lib.cpp
+#include "lib.h"
+
+#ifndef CRUCIAL_DEFINE
+# error CRUCIAL_DEFINE not defined
+#endif
+
+const char *get_string()
+{
+ return "Hello from library";
+}
+
+//! [0]
diff --git a/tutorial/chapter-3/lib/lib.h b/tutorial/chapter-3/lib/lib.h
new file mode 100644
index 000000000..772e1d82b
--- /dev/null
+++ b/tutorial/chapter-3/lib/lib.h
@@ -0,0 +1,11 @@
+#ifndef LIB_H
+#define LIB_H
+
+//! [0]
+// lib/lib.h
+#include "lib_global.h"
+
+MYLIB_EXPORT const char *get_string();
+//! [0]
+
+#endif // LIB_H
diff --git a/tutorial/chapter-3/lib/lib.qbs b/tutorial/chapter-3/lib/lib.qbs
new file mode 100644
index 000000000..a52c29d97
--- /dev/null
+++ b/tutorial/chapter-3/lib/lib.qbs
@@ -0,0 +1,26 @@
+//! [0]
+// lib/lib.qbs
+
+DynamicLibrary {
+ name: "mylib"
+ files: [
+ "lib.c",
+ "lib.h",
+ "lib_global.h",
+ ]
+ version: "1.0.0"
+ install: true
+
+ Depends { name: "cpp" }
+ cpp.defines: ["MYLIB_LIBRARY", "CRUCIAL_DEFINE"]
+ cpp.sonamePrefix: qbs.targetOS.contains("darwin") ? "@rpath" : undefined
+
+ Export {
+ Depends { name: "cpp" }
+ cpp.includePaths: [exportingProduct.sourceDirectory]
+ }
+
+ Depends { name: "bundle" }
+ bundle.isBundle: false
+}
+//! [0]
diff --git a/tutorial/chapter-3/lib/lib_global.h b/tutorial/chapter-3/lib/lib_global.h
new file mode 100644
index 000000000..07b7978ef
--- /dev/null
+++ b/tutorial/chapter-3/lib/lib_global.h
@@ -0,0 +1,23 @@
+//! [0]
+// lib/lib_global.h
+
+#ifndef LIB_GLOBAL_H
+#define LIB_GLOBAL_H
+
+#if defined(_WIN32) || defined(WIN32)
+#define MYLIB_DECL_EXPORT __declspec(dllexport)
+#define MYLIB_DECL_IMPORT __declspec(dllimport)
+#else
+#define MYLIB_DECL_EXPORT __attribute__((visibility("default")))
+#define MYLIB_DECL_IMPORT __attribute__((visibility("default")))
+#endif
+
+#if defined(MYLIB_LIBRARY)
+#define MYLIB_EXPORT MYLIB_DECL_EXPORT
+#else
+#define MYLIB_EXPORT MYLIB_DECL_IMPORT
+#endif
+
+#endif // LIB_GLOBAL_H
+
+//! [0]
diff --git a/tutorial/chapter-3/myproject.qbs b/tutorial/chapter-3/myproject.qbs
new file mode 100644
index 000000000..152d6cce5
--- /dev/null
+++ b/tutorial/chapter-3/myproject.qbs
@@ -0,0 +1,8 @@
+Project {
+ name: "My Project"
+ minimumQbsVersion: "2.0"
+ references: [
+ "app/app.qbs",
+ "lib/lib.qbs"
+ ]
+}
diff --git a/tutorial/chapter-4/app/app.qbs b/tutorial/chapter-4/app/app.qbs
new file mode 100644
index 000000000..7051ff554
--- /dev/null
+++ b/tutorial/chapter-4/app/app.qbs
@@ -0,0 +1,10 @@
+//! [0]
+// app/app.qbs
+
+MyApplication {
+ Depends { name: "mylib" }
+ name: "My Application"
+ targetName: "myapp"
+ files: "main.c"
+}
+//! [0]
diff --git a/tutorial/chapter-4/app/main.c b/tutorial/chapter-4/app/main.c
new file mode 100644
index 000000000..a560d1ba1
--- /dev/null
+++ b/tutorial/chapter-4/app/main.c
@@ -0,0 +1,10 @@
+#include <stdio.h>
+
+#include "lib.h"
+
+int main()
+{
+ printf("Hello, world\n");
+ printf("%s\n", get_string());
+ return 0;
+}
diff --git a/tutorial/chapter-4/lib/lib.c b/tutorial/chapter-4/lib/lib.c
new file mode 100644
index 000000000..5dcae0de3
--- /dev/null
+++ b/tutorial/chapter-4/lib/lib.c
@@ -0,0 +1,10 @@
+#include "lib.h"
+
+#ifndef CRUCIAL_DEFINE
+# error CRUCIAL_DEFINE not defined
+#endif
+
+const char *get_string()
+{
+ return "Hello from library";
+}
diff --git a/tutorial/chapter-4/lib/lib.h b/tutorial/chapter-4/lib/lib.h
new file mode 100644
index 000000000..ef39ca4a1
--- /dev/null
+++ b/tutorial/chapter-4/lib/lib.h
@@ -0,0 +1,8 @@
+#ifndef LIB_H
+#define LIB_H
+
+#include "lib_global.h"
+
+MYLIB_EXPORT const char *get_string();
+
+#endif // LIB_H
diff --git a/tutorial/chapter-4/lib/lib.qbs b/tutorial/chapter-4/lib/lib.qbs
new file mode 100644
index 000000000..1f7bf6e6b
--- /dev/null
+++ b/tutorial/chapter-4/lib/lib.qbs
@@ -0,0 +1,13 @@
+//! [0]
+// lib/lib.qbs
+
+MyLibrary {
+ name: "mylib"
+ files: [
+ "lib.c",
+ "lib.h",
+ "lib_global.h",
+ ]
+ cpp.defines: base.concat(["CRUCIAL_DEFINE"])
+}
+//! [0]
diff --git a/tutorial/chapter-4/lib/lib_global.h b/tutorial/chapter-4/lib/lib_global.h
new file mode 100644
index 000000000..76cce8d36
--- /dev/null
+++ b/tutorial/chapter-4/lib/lib_global.h
@@ -0,0 +1,18 @@
+#ifndef LIB_GLOBAL_H
+#define LIB_GLOBAL_H
+
+#if defined(_WIN32) || defined(WIN32)
+#define MYLIB_DECL_EXPORT __declspec(dllexport)
+#define MYLIB_DECL_IMPORT __declspec(dllimport)
+#else
+#define MYLIB_DECL_EXPORT __attribute__((visibility("default")))
+#define MYLIB_DECL_IMPORT __attribute__((visibility("default")))
+#endif
+
+#if defined(MYLIB_LIBRARY)
+#define MYLIB_EXPORT MYLIB_DECL_EXPORT
+#else
+#define MYLIB_EXPORT MYLIB_DECL_IMPORT
+#endif
+
+#endif // LIB_GLOBAL_H
diff --git a/tutorial/chapter-4/myproject.qbs b/tutorial/chapter-4/myproject.qbs
new file mode 100644
index 000000000..d429542e7
--- /dev/null
+++ b/tutorial/chapter-4/myproject.qbs
@@ -0,0 +1,11 @@
+//! [0]
+Project {
+ name: "My Project"
+ minimumQbsVersion: "2.0"
+ references: [
+ "app/app.qbs",
+ "lib/lib.qbs"
+ ]
+ qbsSearchPaths: "qbs"
+}
+//! [0]
diff --git a/tutorial/chapter-4/qbs/imports/MyApplication.qbs b/tutorial/chapter-4/qbs/imports/MyApplication.qbs
new file mode 100644
index 000000000..e98794f3c
--- /dev/null
+++ b/tutorial/chapter-4/qbs/imports/MyApplication.qbs
@@ -0,0 +1,23 @@
+//! [0]
+// qbs/imports/MyApplication.qbs
+
+import qbs.FileInfo
+
+CppApplication {
+ version: "1.0.0"
+ consoleApplication: true
+ install: true
+
+ cpp.rpaths: {
+ if (!cpp.rpathOrigin)
+ return [];
+ return [
+ FileInfo.joinPaths(
+ cpp.rpathOrigin,
+ FileInfo.relativePath(
+ FileInfo.joinPaths("/", product.installDir),
+ FileInfo.joinPaths("/", "lib")))
+ ];
+ }
+}
+//! [0]
diff --git a/tutorial/chapter-4/qbs/imports/MyLibrary.qbs b/tutorial/chapter-4/qbs/imports/MyLibrary.qbs
new file mode 100644
index 000000000..c819fa38e
--- /dev/null
+++ b/tutorial/chapter-4/qbs/imports/MyLibrary.qbs
@@ -0,0 +1,21 @@
+//! [0]
+// qbs/imports/MyLibrary.qbs
+
+DynamicLibrary {
+ version: "1.0.0"
+ install: true
+
+ Depends { name: 'cpp' }
+ property string libraryMacro: name.replace(" ", "_").toUpperCase() + "_LIBRARY"
+ cpp.defines: [libraryMacro]
+ cpp.sonamePrefix: qbs.targetOS.contains("darwin") ? "@rpath" : undefined
+
+ Export {
+ Depends { name: "cpp" }
+ cpp.includePaths: [exportingProduct.sourceDirectory]
+ }
+
+ Depends { name: 'bundle' }
+ bundle.isBundle: false
+}
+//! [0]
diff --git a/tutorial/tutorial.qbs b/tutorial/tutorial.qbs
new file mode 100644
index 000000000..0d2ae3514
--- /dev/null
+++ b/tutorial/tutorial.qbs
@@ -0,0 +1,5 @@
+Product {
+ files: [
+ "*/**",
+ ]
+}