aboutsummaryrefslogtreecommitdiffstats
path: root/doc/tutorial.qdoc
blob: 59aed2888910007fa6eb0d1ef0e73dad43b0204b (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
/****************************************************************************
**
** 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}
        \li  \l{tutorial-5.html}{Autotest}
        \li  \l{tutorial-6.html}{Project Properties}
        \li  \l{tutorial-7.html}{Buildconfig Module}
        \li  \l{tutorial-8.html}{Configurable Library}
    \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 tutorial-5.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']}).
*/

/*!
    \previouspage tutorial-4.html
    \page tutorial-5.html
    \nextpage tutorial-6.html

    \title Autotest

    Now that we can re-use our base items, let's add a simple autotest. We inherit the new item
    from the \c MyApplication item and add an \c "autotest" type:

    \snippet ../tutorial/chapter-5/qbs/imports/MyAutoTest.qbs 0

    This additional type is required for the helper \l{AutotestRunner} item. This item runs all
    products with the \c "autotest" type when it is being built. It also implicitly depends on all
    such products, so they will be built before running as well.

    Let's add this item to our top-level \l{Project} item:

    \code
    Project {
        name: "My Project"
        minimumQbsVersion: "2.0"
        // ...
        AutotestRunner {
            timeout: 60
        }
    }
    \endcode
    Here we set the \l{AutotestRunner::timeout}{timeout} property to 60 seconds so that our runner
    kills tests that run way too long.

    Now we need to add our first test. Let's create a new product with the following content:

    \snippet ../tutorial/chapter-5/test/test.qbs 0

    Here we depend on our library (which we are going to test), set the product
    \l{Product::name}{name}, and specify the source file, which looks like this:
    \snippet ../tutorial/chapter-5/test/test.c 0

    The test compares the value from the library with the value from the command line.

    Don't forget to add the new test product to the references property in the Project:
    \snippet ../tutorial/chapter-5/myproject.qbs 0

    Now we can build the autotest product - this will also launch our test:
    \code
    $ qbs build -p "autotest-runner"
    ...
    running test mytest [autotest-runner]
    Build done for configuration default.
    \endcode
*/

/*!
    \previouspage tutorial-5.html
    \page tutorial-6.html
    \nextpage tutorial-7.html

    \title Project Properties

    It would be nice if our project was configurable. Let's add some properties to our root project
    file:
    \snippet ../tutorial/chapter-6/myproject.qbs 0

    Now we can use those properties in our helper items and in products:

    \snippet ../tutorial/chapter-6/qbs/imports/MyApplication.qbs 0

    Here, we access the project file using the special
    \l{special-property-values.html#project}{project} value. If the nearest project in the project
    tree does not have the desired property, \QBS looks it up in the parent project, potentially
    all the way up to the top-level project.
    We also use the \l{Application::installDebugInformation}{installDebugInformation}
    property here. By default, it has the same value as \l{Application::install}{install} but we
    want to make the debug information install optional.

    Let's now disable the tests if \c project.withTests is \c false:

    \snippet ../tutorial/chapter-6/myproject.qbs 1

    Here we use the \l{Properties} item within the \l{SubProject} item. This item allows to
    override a subproject's properties which can be useful when adding some other \QBS project as a
    Git submodule. Of course, it is not very useful in our particular case since we only set the
    \l{Project::condition}{Project.condition} property. We could achieve the same effect by
    setting the \l{SubProject::condition}{condition} property of the \l{SubProject} item:
    \code
        SubProject {
            filePath: "test/test.qbs"
            condition: parent.withTests
        }
    \endcode
    Another way would be to disable the test product. That way, an IDE would still show the whole
    project tree including disabled tests:
    \code
        // qbs/imports/MyAutoTest.qbs
        MyApplication {
            condition: project.withTests
            type: base.concat(["autotest"])
        }
    \endcode

    Let's finally make our \l{AutotestRunner} configurable too:
    \snippet ../tutorial/chapter-6/myproject.qbs 2

    There are several ways to override project properties from the command line. First, the special
    \c project variable can be used to set the properties of the top-level project:
    \code
        $ qbs resolve project.withTests:false
    \endcode

    You can also refer to properties using project's \l{Project::name}{name}:

    \code
        $ qbs resolve "projects.My Project.withTests:false"
    \endcode

    This can again be useful for accessing the properties of external sub-projects. Note that since
    the project name contains spaces, we use quotes here.
*/

/*!
    \previouspage tutorial-6.html
    \page tutorial-7.html
    \nextpage tutorial-8.html

    \title Buildconfig Module

    In the previous chapter, we added some properties to our main \l{Project} file. While this is a
    perfect approach for \e public properties of the project, sometimes we want
    to add some \e private properties for better tuning. Of course, we could put everything
    in the \l{Project} file, but that would make it very convoluted. Also, accessing the top-level
    project all the way from products makes things strongly tied.

    You can also use a \l{Module} that \l{Product}{products} may depend on. That way, a
    \l{Product} only uses properties of the module it depends on without the need to know about
    the top-level project.

    Let's create a file named \c{mybuildconfig.qbs} and put it into the
    \c{qbs/modules/mybuildconfig} directory, near the \c{qbs/imports} directory:
    \code
        // qbs/modules/mybuildconfig.qbs
        Module {
        }
    \endcode

    So far, this is just an empty \l{Module} so let's add some properties to it:
    \snippet ../tutorial/chapter-7/qbs/modules/mybuildconfig/mybuildconfig.qbs 0

    We added the \c appInstallDir and \c libInstallDir properties that will allow us to configure
    the installation location of the our application and library, respectively.

    Now we can use our module in the \c{MyApplication.qbs} item:
    \snippet ../tutorial/chapter-7/qbs/imports/MyApplication.qbs 0

    We pull in the new module using the \l{Depends} item, similar to how we pulled in
    the \l{cpp} module dependency earlier. We also set the \l{Application::installDir}{installDir}
    property to the corresponding module property, namely to \c{mybuildconfig.appInstallDir}.

    \QBS \l{Module}{modules} have the feature to automatically export properties of other modules.
    Those exported properties are merged in the resulting product. We can use this feature to set
    the \l{cpp::rpaths}{cpp.rpaths} in our module rather than in products:

    \snippet ../tutorial/chapter-7/qbs/modules/mybuildconfig/mybuildconfig.qbs 1

    Here, we inject the dependency on the \l{cpp} module and calculate the \c{libRPaths} property.
    This is a relative path from the \c{product.installDir} (which is either \c{"bin"}
    or \c{"lib"}, depending on product type to \c{libInstallDir}). Finally, we set
    \l{cpp::rpaths}{cpp.rpaths} to this property. This way, those \c rpaths will be automatically
    exported to all products that depend on the \c{mybuildconfig} module.

    Now, we can also use our new module in the library item:
    \snippet ../tutorial/chapter-7/qbs/imports/MyLibrary.qbs 0

    Let's change the library folder name from \c{"lib"} to \c{"lib64"} when building our project:
    \code
        $ qbs modules.mybuildconfig.libDirName:lib64
        ...
        $ ls default/install-root/usr/local/
        bin    lib64
    \endcode
*/

/*!
    \previouspage tutorial-7.html
    \page tutorial-8.html
    \nextpage howtos.html

    \title Configurable Library

    In this chapter, we will make our library more configurable. We will add configuration options
    to be able to choose between static and dynamic builds.

    We start with some new properties in the \c mybuildconfig module:
    \code
    Module {
        ...
        property bool staticBuild: false
        property bool installStaticLib: true
        ...
    }
    \endcode

    We need to do some modifications to export macros in the \c lib/lib_global.h header:
    \snippet ../tutorial/chapter-8/lib/lib_global.h 0

    Here' we added a new branch when \c MYLIB_STATIC_LIBRARY is defined. In that case, we make
    the \c MY_LIB_EXPORT macro empty.

    Now, let's modify the \c qbs/imports/MyLibrary.qbs file as follows:
    \snippet ../tutorial/chapter-8/qbs/imports/MyLibrary.qbs 0

    First thing to notice is that we changed the type of the item from \l{DynamicLibrary} to the
    \l{Library} item which is a common base item for dynamic and static libraries.

    Second, we change the \l{Product::type}{type} of the product so it depends on the \c staticBuild
    property:
    \code
    type: mybuildconfig.staticBuild ? "staticlibrary" : "dynamiclibrary"
    \endcode

    Third, we change our \e{export macro} to be different in the static and dynamic builds to
    correspond with the changes we made to the \c lib_global.h:
    \code
    readonly property string _nameUpper : name.replace(" ", "_").toUpperCase()
    property string libraryMacro: _nameUpper + "_LIBRARY"
    property string staticLibraryMacro: _nameUpper + "_STATIC_LIBRARY"
    cpp.defines: mybuildconfig.staticBuild ? [staticLibraryMacro] : [libraryMacro]
    \endcode

    Note that in a static build we export the \c MYLIB_STATIC_LIBRARY macro so that the dependent
    products will know whether they link to a static or dynamic library:
    \code
    Export {
        ...
        cpp.defines: exportingProduct.mybuildconfig.staticBuild
            ? [exportingProduct.staticLibraryMacro] : []
    }
    \endcode

    Now we can build our project in dynamic or static mode:
    \code
    $ qbs resolve modules.mybuildconfig.staticBuild:false && qbs build
    ...
    $ ls default/install-root/usr/local/lib/
        libmylib.1.0.0.so
        libmylib.1.0.so
        libmylib.1.so
        libmylib.so
    $ rm -r default
    $ qbs resolve modules.mybuildconfig.staticBuild:true && qbs build
    ...
    $ $ ls default/install-root/usr/local/lib/
        libmylib.a
    \endcode
*/