Qbs

Blog Documentation Get Qbs
  • Qbs Manual
  • Dynamic Library
  • Qbs 2.3.1
  • 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 rpaths.

    Some compilers, like MSVC, require us to mark which symbols we want to export or import. The canonical way would be to define some helper macros. Let's start with a header that defines those helper macros:

    // 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

    This header defines the MYLIB_EXPORT macro that expands either to an "export" or to an "import" directive based on the presence of the MYLIB_LIBRARY macro. We can use this macro to mark a function as follows:

    // lib/lib.h
    #include "lib_global.h"
    
    MYLIB_EXPORT const char *get_string();

    The modified library product may look like this:

    // 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
    }

    The most important change is that we changed the item type from StaticLibrary to DynamicLibrary. We also added the MYLIB_LIBRARY to the list of 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 cpp.sonamePrefix to "@rpath". This is required only for Apple platforms, see Run-Path Dependent Libraries for details.

    It is also required to set cpp.rpaths in our application file. Since the library is installed to the lib directory and the application is installed to the bin directory, we need to tell the loader to look in the lib directory. The FileInfo.relativePath method can help us:

    cpp.rpaths: {
        if (!cpp.rpathOrigin)
            return [];
        return [
            FileInfo.joinPaths(
                cpp.rpathOrigin,
                FileInfo.relativePath(
                    FileInfo.joinPaths("/", product.installDir),
                    FileInfo.joinPaths("/", "lib")))
        ];
    }

    On Linux, this expression would be equivalent to this: cpp.rpaths: ["$ORIGIN/../lib"]. Don't forget to import qbs.FileInfo in order to be able to use the jsextension-fileinfo.html extension.

    To make the example complete, here's how the full app/app.qbs file should look like:

    // 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
    
        cpp.rpaths: {
            if (!cpp.rpathOrigin)
                return [];
            return [
                FileInfo.joinPaths(
                    cpp.rpathOrigin,
                    FileInfo.relativePath(
                        FileInfo.joinPaths("/", product.installDir),
                        FileInfo.joinPaths("/", "lib")))
            ];
        }
    }