aboutsummaryrefslogtreecommitdiffstats
path: root/doc
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 /doc
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>
Diffstat (limited to 'doc')
-rw-r--r--doc/doc.qbs1
-rw-r--r--doc/qbs.qdoc10
-rw-r--r--doc/tutorial.qdoc293
3 files changed, 302 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']}).
+*/