如何使用

本页提供了常见场景的具体说明。

如何构建基于 Qt 的项目?

首先,您的项目文件需要声明对 Qt 模块的依赖。

要构建项目,您需要一个匹配的 配置文件。以下命令设置并使用一个 Qt 特定的配置文件

$ qbs setup-qt /usr/bin/qmake qt
$ cd my_project
$ qbs profile:qt

如果您打算经常使用此配置文件,请考虑将其设置为默认配置

$ qbs config defaultProfile qt
$ cd my_project
$ qbs

有关更多详细信息,请参阅 管理 Qt 版本

注意:这些说明仅适用于命令行构建。如果使用 Qt Creator,则会自动从 Kit 中的信息设置配置文件。

如何使我的应用程序库能够构建我的库?

这通过在两个产品之间引入一个 依赖关系 来实现,使用 Depends 项。这里有一个简单但完整的示例

Project {
    CppApplication {
        name : "the-app"
        files : [ "main.cpp" ]
        Depends { name: "the-lib" }
    }
    DynamicLibrary {
        name: "the-lib"
        Depends { name: "cpp" }
        files: [
            "lib.cpp",
            "lib.h",
        ]
        Export {
            Depends { name: "cpp" }
            cpp.includePaths: [exportingProduct.sourceDirectory]
       }
    }
}

产品 the-lib 是一个动态库。它期望其他产品针对它进行构建,为此,它导出一个包含路径(通过一个 Export 项),因此这些产品的源文件可以包含库的头文件。

产品 the-app 是一个应用程序,通过声明对这个产品的依赖来表明它想将其链接起来。现在 main.cpp 可以包含 lib.h(因为导出的包含路径),并且应用程序二进制文件将链接到该库(因为 rulecpp 模块中将库依赖视为输入)。

注意: 在一个非平凡的项目中,这两个产品不会被定义在同一个文件中。相反,您应该将它们分别放入自己的文件中,并使用 Project.references 属性将它们拉入项目。产品定义将完全相同。特别是,它们在项目树中的位置与它们之间的关系无关。

动态和静态构建的 Qt 项目之间的选择

要根据 Qt 的构建方式将 "the-lib" 构建为动态或静态库,您可以使用以下代码

Product {
    name: "the-lib"
    type: Qt.core.staticBuild ? "staticlibrary" : "dynamiclibrary"

    Depends { name: "Qt.core" }
    // ...
}

如何构建带有调试信息的发布版本?

您可以直接使用 "profiling" qbs.buildVariant

qbs build qbs.buildVariant:profiling

如何分离并安装调试符号?

首先,您需要将 cpp.debugInformationcpp.separateDebugInformation 属性设置为 true 或在您的产品中使用一些条件表达式

CppApplication {
    // ...
    cpp.debugInformation: qbs.buildVariant !== "release"
    cpp.separateDebugInformation: true
}

现在,您可以将您的 应用程序动态库可加载模块及其调试符号如下安装

CppApplication {
    // ...
    install: true
    installDir: "bin"
    installDebugInformation: true
    debugInformationInstallDir: "bin"
}

如果您没有使用 方便项,则可以使用 Group 项手动安装调试符号。如果将 cpp.separateDebugInformation 属性设置为 true,Qbs 将创建带有相应文件标签的调试符号 "debuginfo_app" (对于应用程序)、 "debuginfo_dll" (对于动态库)或 "debuginfo_loadablemodule" (对于 macOS 插件)。

Product {
    type: "application"
    Depends { name: "cpp" }
    cpp.debugInformation: qbs.buildVariant !== "release"
    cpp.separateDebugInformation: true
    Group {
        fileTagsFilter: cpp.separateDebugInformation ? ["debuginfo_app"] : []
        qbs.install: true
        qbs.installDir: "bin"
        qbs.installSourceBase: buildDirectory
    }
}

如果您正在构建共享库,则需要使用 "debuginfo_dll" 标签

Product {
    type: "dynamic_library"
    // ...
    Group {
        fileTagsFilter: cpp.separateDebugInformation ? ["debuginfo_dll"] : []
        qbs.install: true
        qbs.installDir: "lib"
        qbs.installSourceBase: buildDirectory
    }
}

如果您正在构建 macOS 插件,则需要使用 "debuginfo_loadablemodule" 标签

Product {
    type: "loadablemodule"
    // ...
    Group {
        fileTagsFilter: cpp.separateDebugInformation ? ["debuginfo_loadablemodule"] : []
        qbs.install: true
        qbs.installDir: "PlugIns"
        qbs.installSourceBase: buildDirectory
    }
}

如何使用预编译头文件?

如果您使用 Group 项将预编译头文件添加到产品中,并用相关文件标签(c_pch_srccpp_pch_srcobjc_pch_srcobjcpp_pch_src)标记它,它将自动使用。

每个产品和语言只允许一个预编译头文件。

例如

CppApplication {
    name: "the-app"
    files: ["main.cpp"]

    Group {
        files: ["precompiled-header.pch"]
        fileTags: ["cpp_pch_src"]
    }
}

如何使用 rpaths?

rpath 指定 UNIX 平台上的动态链接器在加载库时使用的运行时搜索路径。该概念不适用于 Windows。

假设您有一个项目,其中有两个动态库产品 LibraryALibraryB 以及一个依赖的应用程序产品。另外,LibraryB 依赖于 LibraryA。应用安装在 bin 文件夹中,库安装在紧邻 bin 文件夹的 lib 文件夹中。您希望应用程序能够相对其自身位置找到依赖的库。这可以通过使用 cpp.rpaths 属性来实现。

首先,您需要在您的库中设置 cpp.rpaths,这样它们就可以在它们所在文件夹中找到依赖库。以下是操作方法:

DynamicLibrary {
    Depends { name: "cpp" }
    Depends { name: "bundle" }
    name: "LibraryA"
    bundle.isBundle: false
    cpp.sonamePrefix: qbs.targetOS.contains("macos") ? "@rpath" : undefined
    cpp.rpaths: cpp.rpathOrigin
    cpp.cxxLanguageVersion: "c++11"
    cpp.minimumMacosVersion: "10.8"
    files: [
        "objecta.cpp",
        "objecta.h",
    ]
    install: true
    installDir: "examples/lib"
}

我们将 cpp.rpaths 设置为 cpp.rpathOrigin,它在 Linux 上扩展为 "$ORIGIN",在 macOS 上扩展为 "@loader_path"

在 macOS 上,您还需要将 cpp.sonamePrefix 设置为 "@rpath",以通知动态链接器在加载此库时使用 RPATH。

LibraryB 的外观完全相同

DynamicLibrary {
    Depends { name: "cpp" }
    Depends { name: "bundle" }
    Depends { name: "LibraryA" }
    name: "LibraryB"
    bundle.isBundle: false
    cpp.cxxLanguageVersion: "c++11"
    cpp.minimumMacosVersion: "10.8"
    cpp.sonamePrefix: qbs.targetOS.contains("macos") ? "@rpath" : undefined
    cpp.rpaths: cpp.rpathOrigin
    files: [
        "objectb.cpp",
        "objectb.h",
    ]
    install: true
    installDir: "examples/lib"
}

在实际项目中,将常用属性移动到某些基础项目并将继承应用到库项目中可能是个好主意。

应用程序项目略有不同。它将 cpp.rpaths 设置为位于 bin 文件夹上一级的 "lib" 文件夹

CppApplication {
    Depends { name: "bundle" }
    Depends { name: "LibraryA" }
    Depends { name: "LibraryB" }
    name: "rpaths-app"
    files: "main.cpp"
    consoleApplication: true
    bundle.isBundle: false
    cpp.rpaths: FileInfo.joinPaths(cpp.rpathOrigin, "..", "lib")
    cpp.cxxLanguageVersion: "c++11"
    cpp.minimumMacosVersion: "10.8"
    install: true
    installDir: "examples/bin"
}

我如何确保生成的源文件正在编译?

Qbs项目中的规则不考虑其输入是files属性右侧列出的实际源文件,还是由另一个规则生成的工件。例如,C++编译器规则认为所有类型为 "cpp" 的输入文件,无论它们如何进入产品。以下示例项目演示了这一点。其中一个是存储库中的源文件,另一个是在构建时生成的。它们都会以相同的方式进行编译。

注意:不要尝试将生成的文件添加到 files 属性。仅将它们声明为规则输出,Qbs即可了解它们。

import qbs.TextFile
CppApplication {
    files: ["impl.cpp", "impl.h"]
    cpp.includePaths: sourceDirectory
    Rule {
        multiplex: true
        Artifact { filePath: "main.cpp"; fileTags: "cpp" }
        prepare: {
            var cmd = new JavaScriptCommand();
            cmd.description = "generating " + output.fileName;
            cmd.sourceCode = function() {
                var f = new TextFile(output.filePath, TextFile.WriteOnly);
                f.writeLine("#include <impl.h>");
                f.writeLine("int main()");
                f.writeLine("{");
                f.writeLine("    return functionFromImpl();");
                f.writeLine("}");
                f.close();
            };
            return cmd;
        }
    }
}

我如何运行我的自动测试?

在您的项目中,您需要执行两个简单的操作。首先,您将测试可执行文件标记为测试。这是通过向产品类型添加标签 "autotest" 来完成的。

CppApplication {
    name: "test1"
    type: base.concat("autotest")
    // ...
}

第二步是在项目中实例化一个 AutotestRunner 产品

Project {
    // ...
    AutotestRunner { name: "run_my_tests" }
}

构建 AutotestRunner 产品不会生成工件,但它会触发所有产品标记为自动测试的应用程序的执行

$ qbs -p run_my_tests
test1: PASS
test2: PASS
test3: FAIL
...

有关如何微调行为的详细信息,请参阅 AutotestRunner 文档

我如何使用 ccache?

ccache 是 Unix 上流行的 C/C++ 编译器缓存,用于加速编译相同内容多次。

Qbs 在跟踪依赖关系和避免不必要的重新编译方面表现出色,因此对于单个项目和配置的线性开发来说,使用 ccache 有很少的好处。但是,如果您在项目修订版之间切换,或者用不同的配置构建相同的项目,全局缓存如 ccache 可以显著加速编译。

可以通过设置文件系统中的编译器可执行文件(例如 g++gcc)的符号链接来使用 ccache。在这种设置中,Qbs 可以完全透明地使用 ccache。如果您更喜欢显式地调用 ccache,则应将 cpp.compilerWrapper 设置为 ccache

注意:预编译头可能会阻止 ccache 使用缓存的输出。为了解决这个问题,您可以在本地 ccache 选项中设置 sloppiness=pch_defines,time_macros。有关详细信息,请参阅 ccache 关于预编译头的文档

如何为第三方库创建模块?

如果您的源代码树中有预构建的二进制文件,您可以为其创建模块,然后引入项目和模块之间的依赖关系,以便引入第三方库的功能。

创建以下文件夹结构来存储模块文件

$projectroot/modules/ThirdParty

然后在该目录中创建一个文件,指定每个受支持工具链的模块属性。文件名必须具有 .qbs 扩展名。如果产品声明了对此模块的依赖关系,则将检索该模块。

在以下示例中,lib1.dylib 是一个包含32位和64位代码的多架构库。

---ThirdParty.qbs---

Module {
    Depends { name: "cpp" }
    cpp.includePaths: ["/somewhere/include"]
    Properties {
        condition: qbs.targetOS.includes("android")
        cpp.dynamicLibraries: ["/somewhere/android/" + Android.ndk.abi + "/lib1.so"]
    }
    Properties {
        condition: qbs.targetOS.includes("macos")
        cpp.dynamicLibraries: ["/somewhere/macos/lib1.dylib"]
    }
    Properties {
        condition: qbs.targetOS.includes("windows") && qbs.architecture === "x86"
        cpp.dynamicLibraries: ["/somewhere/windows_x86/lib1.lib"]
    }
    Properties {
        condition: qbs.targetOS.includes("windows") && qbs.architecture === "x86_64"
        cpp.dynamicLibraries: ["/somewhere/windows_x86_64/lib1.lib"]
    }
}

最后,在您的项目中声明对 ThirdParty 的依赖关系

CppApplication {
    name: "the-app"
    files: ["main.cpp"]
    Depends { name: "ThirdParty" }
}

如何在iOS、macOS、tvOS和watchOS上创建应用程序包和框架?

通过引入对 bundle 模块的依赖并设置 bundle.isBundle 属性为 true,可以实现创建应用程序包或框架。

以下是一个简单的应用程序示例

Application {
    Depends { name: "cpp" }
    Depends { name: "bundle" }
    bundle.isBundle: true
    name: "the-app"
    files: ["main.cpp"]
}

以及一个框架的示例

DynamicLibrary {
    Depends { name: "cpp" }
    Depends { name: "bundle" }
    bundle.isBundle: true
    name: "the-lib"
    files: ["lib.cpp", "lib.h"]
}

Qbs 也支持构建静态框架。您可以通过在上面的示例中将 DynamicLibrary 项目替换为 StaticLibrary 项目来创建一个。

注意:使用 Application 项目(或方便项目,如 CppApplicationDynamicLibraryStaticLibrary)时,您的产品将默认在 Apple 平台上构建为包(此行为在未来的版本中可能会更改)。

要显式控制您的产品是否作为包构建,请设置 bundle.isBundle 属性。设置您产品的 consoleApplication 属性也将影响您的产品是否构建为包。

将您的应用程序与框架构建相同,就像连接正常的动态或静态库一样;请参阅如何使我的应用程序构建针对我的库?部分中的示例。

如何构建针对提供 pkg-config 文件的库?

只需添加一个与 pkg-config 模块名称匹配的 Depends 项目,Qbs 将自动使用 pkg-config 查找头文件和库,如果找不到匹配的 Qbs 模块。例如,要针对 OpenSSL 库进行构建,您将编写以下代码

Depends { name: "openssl" }

这样就可以了。可以通过 pkgconfig 模块微调 pkg-config 行为,但通常您不需要显式将其检索过来。

在内部,此功能是通过 模块提供程序 实现的

如何把我产品的部分文件应用于 C/C++ 预处理器宏?

使用 Group 项目来定义项目文件的一个子集。要向组内添加宏,您需要使用 outer.concat 属性,因为您正在将宏添加到外层作用域指定的那些。

以下示例中,MACRO_EVERYWHERE被用于产品中的所有文件,除非有组重写了该宏,而MACRO_GROUP仅定义于groupFile.cpp中。

Product {
    Depends { name: "cpp" }
    cpp.defines: ["MACRO_EVERYWHERE"]
    Group {
        cpp.defines: outer.concat("MACRO_GROUP")
        files: "groupFile.cpp"
    }
}

Group内的cpp.defines语句仅作用于该组中的文件,因此不能使用Group包含文件集合并定义全局可见的宏。如果需要让宏在组外部可见,必须在与Group同一等级的Properties项中指定。

Product {
    Depends { name: "cpp" }
    Group {
        condition: project.supportMyFeature
        files: "myFile.cpp"
    }

    property stringList commonDefines: ["ONE", "TWO"]

    Properties {
        condition: project.supportMyFeature
        cpp.defines: commonDefines.concat("MYFEATURE_SUPPORTED")
    }
}

如何禁用编译器警告?

您可以使用cpp.commonCompilerFlags属性将标志传递给编译器。例如,要禁用弃用警告

CppApplication {
    // ...

    readonly property bool isMsvc: qbs.toolchain.includes("msvc")

    cpp.commonCompilerFlags: isMsvc ? "/wd4996" : "-Wno-deprecated-declarations"
}

您也可以通过将cpp.warningLevel属性设置为"none"来一次性禁用所有警告。通常这种方法是不推荐的,但在某些情况下可能很有用,比如在编译第三方代码时

Group {
    cpp.warningLevel: "none"

    files: [
        "3rdparty.h",
        "3rdparty.cpp"
    ]
}

如何使Git仓库的状态可用给我的源文件?

将产品中的vcs模块添加到产品中

CppApplication {
    // ...
    Depends { name: "vcs" }
    // ...
}

现在,源文件将能够访问到一个代表当前Git或Subversion头字符串的宏的值

#include <vcs-repo-state.h>
#include <iostream>

int main()
{
    std::cout << "I was built from " << VCS_REPO_STATE << std::endl;
}

此值也通过vcs.repoState属性提供。

如何仅限制链接器的并发作业数量?

虽然通常希望按CPU核心的数量运行尽可能多的编译器作业,但对于链接器作业来说并不一样。原因是链接器通常是I/O受限而不是CPU受限。当构建大型库时,他们也倾向于使用大量内存。因此,我们希望确保同时运行仅少数链接器,而不限制其他类型的作业。在Qbs中,这是通过工作池实现的。有几种方法可以利用它们。

首先,您可以通过命令行提供限制

$ qbs --job-limits linker:4

上面的调用指示Qbs最多同时运行四个链接器实例,同时保留并发作业的常规数字,其默认值由CPU核心数导出。命令行上的linker字符串指的是由相同的cpp module分配给所有调用链接器命令的工作池。

其次,您可以通过设置(无论是通用设置还是特定配置文件)设置限制

$ qbs config preferences.jobLimit.linker 4
$ qbs config profiles.myprofile.preferences.jobLimit.linker 2

最后,您还可以使用JobLimit项按项目或产品设置限制

Product {
    name: "my_huge_library"
    JobLimit {
        jobPool: "linker"
        jobCount: 1
    }
    // ...
}

上述结构确保此特定库永远不会与其他项目的任何二进制同时连接。

命令行上的作业限制将覆盖设置中的作业限制,而设置将覆盖项目内定义的作业限制。使用--enforce-project-job-limits选项可以为通过JobLimit项定义的作业限制赋予最高优先级。

如何将QML文件添加到项目中?

将QML文件添加到项目的最简单方法是将其添加到Qt资源文件中。

QtGuiApplication {
    // ...

    files: "main.cpp"

    Group {
        prefix: "qml/"
        files: ["main.qml", "HomePage.qml"]
        fileTags: ["qt.qml.qml", "qt.core.resource_data"]
    }
}

在上面的例子中,我们将每个 QML 文件声明为具有 "qt.core.resource_data" 文件标签。这确保它被添加到生成的资源文件中。

如何定义可以包含在其他 Qbs 文件中的可重用文件组?

假设你有一个应用程序以及该应用程序的测试,且项目结构如下

├── app
│   ├── app.qbs
│   ├── ...
│   └── qml
│       └── ui
│           ├── AboutPopup.qml
│           └── ...
├── my-project.qbs
└── tests
    ├── tst_app.cpp
    ├── ...
    └── tests.qbs

这两个项目都需要访问应用程序使用的 QML 文件。为了演示如何做到这一点,我们将在 app/qml/ui 目录中创建一个名为 qml-ui.qbs 的文件

Group {
    prefix: path + "/"
    fileTags: ["qt.qml.qml", "qt.core.resource_data"]
    files: [
        "AboutPopup.qml",
        // ...
    ]
}

此组是上文所述部分的一个变体。

如果没有指定前缀,则 files 属性中列出的文件名相对于 导入 产品(例如 app.qbs)的目录解析。因此,我们将前缀设置为告诉 Qbs 文件名应相对于 导入 项目解析:qml-ui.qbs。方便的是,这也意味着我们不需要为每个文件指定路径前缀。

然后应用程序可以按如下方式导入文件

import "qml/ui/qml-ui.qbs" as QmlUiFiles

QtGuiApplication {
    // ...

    files: "main.cpp"

    QmlUiFiles {}
}

测试可以使用相对路径来导入文件

import "../app/qml/ui/qml-ui.qbs" as QmlUiFiles

QtGuiApplication {
    // ...

    files: "tst_app.cpp"

    QmlUiFiles {}
}

如何访问基类型的属性?

您可以使用 base 属性。例如,要向来自基类型的文件列表中追加,可以使用 base.concat()

// TestBase.qbs

QtGuiApplication {
    files: [
        "TestCaseBase.h",
        "TestCaseBase.cpp"
    ]
}
// tst_stuff.qbs
TestBase {
    files: base.concat(["tst_stuff.cpp"])
}

有关详细信息,请参阅特殊属性值

如何打印属性的值?

使用 console API。例如,假设您的项目没有按照预期构建,且您怀疑 qbs.targetOS 的值不正确

readonly property bool unix: qbs.targetOS.includes("unix")

要获取 qbs.targetOS 的值,请使用 console.info()

readonly property bool unix: {
    console.info("qbs.targetOS: " + qbs.targetOS)
    return qbs.targetOS.includes("unix")
}

也可以抛出一个包含文字说明什么的异常 - 如果属性包含无效或不支持的值,这可能很有用

readonly property bool unix: {
    if (qbs.targetOS.includes("darwin"))
        throw "Apple platforms are not supported";
    return qbs.targetOS.includes("unix")
}

如何调试 Qbs 脚本?

要调试特定属性的值,请参阅如何打印属性的值部分。

规则.js文件中也可以使用类似的调试技术。

还可以使用qbs build命令的--more-verbose-v)选项来增加 Qbs 的日志级别

qbs build -v config:release

Qbs 使用 Qt 品类日志系统,这允许多种方式配置日志类别。例如,要为 moduleloader 类别启用调试日志,请使用以下命令

QT_LOGGING_RULES="qbs.moduleloader.debug=true" qbs resolve

要列出项目目录中的所有文件,并显示在各自的配置中它们是否已知于 qbs,请使用 qbs status 命令

qbs status config:release

如何为 Apple 平台上的应用程序签名?

为 Apple 平台上的应用程序签名,您需要使用 codesign 模块。

Depends { name: "codesign" }

应设置以下属性以进行签名。

请确保包和团队标识符与用于签名的标识符匹配

bundle.identifierPrefix: "com.johndoe"
codesign.teamIdentifier: "John Doe"

也可以使用团队标识符的 ID 而不是名称

codesign.teamIdentifier: "1234ABCDEF"

Qbs将会基于codesign.signingType尝试找到匹配的签名身份和配置文件。

您也可以手动指定codesign.signingIdentity

codesign.signingIdentity: "Apple Development: [email protected] (ABCDEF1234)"

您还可以使用签名身份的ID而不是名称。

codesign.signingIdentity: "ABCDEF1234567890ABCDEF1234567890ABCDEF12"

如果Qbs找不到合适的配置文件,您也可以手动指定。

codesign.provisioningProfile: "abcdef12-1234-5678-1111-abcdef123456"

©2023 Qt公司有限公司版权所有。此处包含的文档贡献属于各自所有者的版权。此处提供的文档是根据自由软件开发基金会发布的GNU自由文档许可证1.3版许可的。Qt及其相应标志是Qt公司 Ltd在芬兰以及/或在其他国家和地区的商标。所有其他商标均为各自所有者的财产。