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 554 555 556 557 558 559 560 561 562 563 564 565 566 567 568 569 570 571 572 573 574 575 576 577 578 579 580 581 582 583 584 585 586 587 588 589 590 591 592 593 594 595 596 597 598 599 600 601 602 603 604 605 606 607 608 609 610 611 612 613 614 615 616 617 618 619 620 621 622 623 624 625 626 627 628 629 630 631 632 633 634 635 636 637 638 639 640 641 642 643 644 645 646 647 648 649 650 651 652 653 654 655 656 657 658 659 660 661 662 663 664 665 666 667 668 669 670 671 672 673 674 675 676 677 678 679 680 681 682 683 684 685 686 687 688 689 690 691 692 693 694 695 696 697 698 699 700 701 702 703 704 705 706 707 708 709 710 711 712 713 714 715 716 717 718 719 720 721 722 723 724 725 726 727 728 729
|
/****************************************************************************
**
** 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 tutorial-1.html
\title Tutorial
The tutorial describes the process of creating a typical \QBS project and explains core
concepts.
\list
\li \l{tutorial-1.html}{Console 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}
\li \l{tutorial-9.html}{Version Header}
\li \l{tutorial-10.html}{C++20 Modules}
\endlist
*/
/*!
\previouspage tutorial.html
\page tutorial-1.html
\nextpage tutorial-2.html
\title Console 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 tutorial-9.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
*/
/*!
\previouspage tutorial-8.html
\page tutorial-9.html
\nextpage tutorial-10.html
\title Version Header
To create new files, such as version headers, during the build, use \l{Rules}. Every command
\QBS executes, such as compile or linker commands, is described by a corresponding Rule in a
Module or a Product.
In this section, we will create a simple header with a string constant based on the variable
in our project.
First, we add this variable to our \c mybuildconfig module:
\snippet ../tutorial/chapter-9/qbs/modules/mybuildconfig/mybuildconfig.qbs 0
Next, we create a new file, \c{version.h.in}, located in the \c{version-header} directory. This
file contains the template for our header:
\snippet ../tutorial/chapter-9/version-header/version.h.in 0
Now we create a file called \c{version-header.qbs} in the same directory. This file contains
a \l{Product} with a \l{Rule} that turns the \c{version.h.in} into a real header.
Let's go through the contents of the file:
\snippet ../tutorial/chapter-9/version-header/version-header.qbs 0
First, we import \l{TextFile Service}{TextFile}. We will need this class to read the template
and write the resulting header. Second, we declare a new \l{Product} named \c{"version_header"}
and with the \c{"hpp"} type. This is the type of the artifact we will create in the Rule.
Third, we add the dependency on the \c mybuildconfig module to use the new
\c mybuildconfig.productVersion variable.
We also add a \l{Group} with the template file and assign the \c{version_h_in} tag to the file:
\snippet ../tutorial/chapter-9/version-header/version-header.qbs 1
\QBS Rules work with file tags, instead of working with files directly, which makes
it easy to reuse rules. The name of the tag is chosen arbitrarily here. We could use the name
of the file as a tag, but to avoid confusion between file name and file tag, we use underscores
in the tag instead of dots.
Now we can create a Rule itself:
\snippet ../tutorial/chapter-9/version-header/version-header.qbs 2
Here, we specify that our Rule takes files tagged as \c "version_h_in" and produces an
\l{Artifact} with the name \c "version.h" and tagged \c "hpp". By default, files are created in
the \l{Product::destinationDirectory}{Product.destinationDirectory} folder. We add the \c "hpp"
tag for the header as this is the tag the \l{cpp}{cpp module} uses for headers. That way, \QBS
can track changes and process our generated file the same way it treats all other
headers. Note that earlier we set the product type to \c "hpp" as well. \QBS requires that
artifact type should match the product type directly or be accessible via the chain of Rules.
Otherwise, the Rule won't be executed. For details, see the \l{Rules and Product Types}
section.
The actual code generation happens in the \l{Rule::prepare}{Rule.prepare} script:
\snippet ../tutorial/chapter-9/version-header/version-header.qbs 3
In this script, we create a \l {JavaScriptCommand} object and set some meta properties, such as
the description and highlight. For details about Commands, see
\l{Command and JavaScriptCommand}. In the sourceCode variable, we create a JavaScript
function that opens the \l{The inputs and outputs Variables}{input file}, reads its content
using the \l{TextFile Service}{TextFile} object, replaces the \c "${PRODUCT_VERSION}"
placeholder with the actual value in the \c product.mybuildconfig.productVersion variable, and
writes the resulting content into the \l{The inputs and outputs Variables}{output file}.
Finally, we export the \l{Product::buildDirectory}{exportingProduct.buildDirectory} so that
products that depend on this product can include our generated header:
\snippet ../tutorial/chapter-9/version-header/version-header.qbs 4
The full content of the file should look like this:
\snippet ../tutorial/chapter-9/version-header/version-header.qbs 5
Let's now add our Product into the root project so \QBS will be aware of it:
\snippet ../tutorial/chapter-9/myproject.qbs 0
We also need to add the dependency on the \c "version_header" to our application:
\snippet ../tutorial/chapter-9/app/app.qbs 0
Now we can include the header in the \c main.c file and print the contents of the string
constant:
\snippet ../tutorial/chapter-9/app/main.c 0
Let's try and run our application. You should see something like this:
\code
$ qbs run -p "My Application"
Starting target. Full command line: .../default/install-root/usr/local/bin/myapp
Hello, world
Hello from library
ProductVersion = 1.0.0
\endcode
*/
/*!
\previouspage tutorial-9.html
\page tutorial-10.html
\nextpage howtos.html
\title C++20 Modules
This tutorial implies you have some knowledge about C++20 modules. If not, see
\l{https://learn.microsoft.com/en-us/cpp/cpp/modules-cpp}{Overview of modules in C++} for
introduction.
\section1 Named Modules
Using C++20 modules with \QBS is pretty straightforward.
Let's suppose you have a module file that exports a single function \c printHello:
\snippet ../tutorial/chapter-10-1/hello.cppm 0
\note Currently, Clang only recognizes \c .cppm files as modules, however, for GCC and MSVC
\QBS also recognizes \c .ixx files as C++ modules. \QBS assigns the \c "cppm" file tag to these
files. You can assign this tag manually to module files with different extensions.
This function is later used in the \c main.cpp file as follows:
\snippet ../tutorial/chapter-10-1/main.cpp 0
The project file simply lists files and sets the
\l{cpp::forceUseCxxModules}{cpp.forceUseCxxModules} property to \c true.
\code
// myproject.qbs
CppApplication {
consoleApplication: true
install: true
files: ["hello.cppm", "main.cpp"]
cpp.cxxLanguageVersion: "c++20"
cpp.forceUseCxxModules: true
}
\endcode
Now, you can build the project by simply calling \c{qbs}, assuming that your compiler supports
C++20 modules.
\section1 Module Partitions
\l{https://learn.microsoft.com/en-us/cpp/cpp/modules-cpp?view=msvc-170#module-partitions}{Module partitions}
are treated as regular modules and should also have the same extension or assigned the
\c "cppm" tag manually. See this
\l{https://github.com/qbs/qbs/tree/2.5/examples/cxx-modules/mod3}{example} on how to
use both interface module and partitions.
\section1 Modules and Libraries
Using modules in dynamic libraries requires using the same export/import macros as it was shown
in the \l{tutorial-3.html}{Dynamic Library} section:
\snippet ../tutorial/chapter-10-2/lib/hello.cppm 0
As shown in that section, the library \c .qbs file should also define the \c MYLIB_LIBRARY macro
in order to mark symbols as exported:
\code
// lib/lib.qbs
DynamicLibrary {
name: "mylib"
files: ["hello.cppm", "lib_global.h"]
version: "1.0.0"
install: true
Depends { name: "cpp" }
cpp.defines: "MYLIB_LIBRARY"
cpp.cxxLanguageVersion: "c++20"
cpp.forceUseCxxModules: true
// ...
}
\endcode
For more details, see the complete
\l{https://github.com/qbs/qbs/tree/2.5/tutorial/chapter-10-2}{example}.
*/
|