Sometimes you need to copy certain files from source directory to build directory together with your application binaries. And Qt’s qmake tool is capable of performing such operations.

Qt qmake

Some geniuses might actually want to use scripts for that, but it’s better not to, because it’s the cross-platform development we’re talking about, and your xcopy from Windows works only on Windows, so it’s better to find some universal method. And qmake has such a thing, although it is not properly documented.

QMAKE_COPY - performs file copying, and on different platforms it transforms into xcopy, or cp, or else.

QMAKE_COPY_DIR - same thing, but for directories. For instance, on Linux/Mac it adds -r option to cp.

So, let’s take the following structure of the source folder:

webengine-recipes/
├── README.md
├── main.cpp
├── resources
│   ├── pages
│   │   ├── assets
│   │   │   ├── 3rdparty
│   │   │   │   ├── markdown.css
│   │   │   │   ├── marked.min.js
│   │   │   │   └── qt_attribution.json
│   │   │   ├── custom.css
│   │   │   └── custom.js
│   │   ├── burger.html
│   │   ├── cupcakes.html
│   │   ├── images
│   │   │   ├── burger.jpg
│   │   │   ├── cupcakes.jpg
│   │   │   ├── pasta.jpg
│   │   │   ├── pizza.jpg
│   │   │   ├── skewers.jpg
│   │   │   ├── soup.jpg
│   │   │   └── steak.jpg
│   │   ├── pasta.html
│   │   ├── pizza.html
│   │   ├── skewers.html
│   │   ├── soup.html
│   │   └── steak.html
│   ├── qml
│   │   ├── RecipeList.qml
│   │   └── main.qml
│   └── resources.qrc
├── webengine-recipes.pro
└── webengine-recipes.pro.user

We want to copy pages directory to the build directory, so it would be near the application executable (so we could use relative paths to .html files).

Here’s how to do it in the .pro file:

TEMPLATE = app

QT += quick quickcontrols2 webengine

SOURCES += main.cpp

RESOURCES += resources/resources.qrc

DISTFILES +=

OTHER_FILES += \
    $$PWD/resources/pages

!isEmpty(target.path): INSTALLS += target

# copies the given files to the destination directory
defineTest(copyToDestDir) {
    files = $$1
    dir = $$2
    # replace slashes in destination path for Windows
    win32:dir ~= s,/,\\,g

    for(file, files) {
        # replace slashes in source path for Windows
        win32:file ~= s,/,\\,g

        QMAKE_POST_LINK += $$QMAKE_COPY_DIR $$shell_quote($$file) $$shell_quote($$dir) $$escape_expand(\\n\\t)
    }

    export(QMAKE_POST_LINK)
}

copyToDestDir($$OTHER_FILES, $$OUT_PWD/resources/)

Thanks to Jake for the function. Although, I had to modify it a bit: replace $$quote with $$shell_quote and $$DESTDIR with $$OUT_PWD.

Note the trailing / and the absence of it in paths to source and destination. Turned out, Windows is quite sensitive to such things.

So, files are copied, problem is solved.

However, since I mentioned cross-platform development, you should know, that on different platforms build directory is organized differently.

Linux - everything is in the same directory:

build-webengine-recipes-Desktop_Qt_5_11_0_GCC_64bit-Debug/
├── [some-other-files]
├── resources
│   ├── assets
│   │   ├── 3rdparty
│   │   │   ├── markdown.css
│   │   │   ├── marked.min.js
│   │   │   └── qt_attribution.json
│   │   ├── custom.css
│   │   └── custom.js
│   ├── burger.html
│   ├── cupcakes.html
│   ├── images
│   │   ├── burger.jpg
│   │   ├── cupcakes.jpg
│   │   ├── pasta.jpg
│   │   ├── pizza.jpg
│   │   ├── skewers.jpg
│   │   ├── soup.jpg
│   │   └── steak.jpg
│   ├── pasta.html
│   ├── pizza.html
│   ├── skewers.html
│   ├── soup.html
│   └── steak.html
└── webengine-recipes

Windows - build directory has debug and release folders:

build-webengine-recipes-Desktop_Qt_5_11_0_MSVC2017_64bit-Debug
¦   [some-other-files]
¦
+---debug
¦       [some-other-files]
¦       webengine-recipes.exe
¦
+---release
+---resources
    ¦   burger.html
    ¦   cupcakes.html
    ¦   pasta.html
    ¦   pizza.html
    ¦   skewers.html
    ¦   soup.html
    ¦   steak.html
    ¦
    +---assets
    ¦   ¦   custom.css
    ¦   ¦   custom.js
    ¦   ¦
    ¦   +---3rdparty
    ¦           markdown.css
    ¦           marked.min.js
    ¦           qt_attribution.json
    ¦
    +---images
            burger.jpg
            cupcakes.jpg
            pasta.jpg
            pizza.jpg
            skewers.jpg
            soup.jpg
            steak.jpg

Mac OS - there is an .app bundle and everything else is just lying “outside”:

build-webengine-recipes-Desktop_Qt_5_11_0_clang_64bit-Debug/
├── [some-other-files]
├── resources
│   ├── assets
│   │   ├── 3rdparty
│   │   │   ├── markdown.css
│   │   │   ├── marked.min.js
│   │   │   └── qt_attribution.json
│   │   ├── custom.css
│   │   └── custom.js
│   ├── burger.html
│   ├── cupcakes.html
│   ├── images
│   │   ├── burger.jpg
│   │   ├── cupcakes.jpg
│   │   ├── pasta.jpg
│   │   ├── pizza.jpg
│   │   ├── skewers.jpg
│   │   ├── soup.jpg
│   │   └── steak.jpg
│   ├── pasta.html
│   ├── pizza.html
│   ├── skewers.html
│   ├── soup.html
│   └── steak.html
└── webengine-recipes.app
    └── Contents
        ├── Info.plist
        ├── MacOS
        │   └── webengine-recipes
        ├── PkgInfo
        └── Resources
            └── empty.lproj

As you can see, relative path to our resources directory is different on each platform, so we need to add some #ifdef stuff in order for this to work:

QString webPagesPathLastMile = "/resources/";
#if defined(Q_OS_MAC)
    webPagesPathLastMile = "/../../../resources/";
#elif defined(Q_OS_WIN)
    webPagesPathLastMile = "/../resources/";
#endif
    QString webPagesPath = QString("file:///%1%2")
            .arg(app.applicationDirPath())
            .arg(webPagesPathLastMile);
    qDebug() << webPagesPath;

Speaking of Mac OS, if you only need to target this one, then everything is dramatically easier for you. Instead of using this function with QMAKE_COPY_DIR, just add these lines into your .pro file:

...
OTHER_FILES += \
    $$PWD/resources/pages

APP_QML_FILES.files = $$OTHER_FILES
APP_QML_FILES.path = Contents/Resources
QMAKE_BUNDLE_DATA += APP_QML_FILES

And then you’ll get the following structure in your build directory:

build-webengine-recipes-Desktop_Qt_5_11_0_clang_64bit-Debug/
├── [some-other-files]
└── webengine-recipes.app
    └── Contents
        ├── Info.plist
        ├── MacOS
        │   └── webengine-recipes
        ├── PkgInfo
        └── Resources
            ├── empty.lproj
            └── pages
                ├── assets
                │   ├── 3rdparty
                │   │   ├── markdown.css
                │   │   ├── marked.min.js
                │   │   └── qt_attribution.json
                │   ├── custom.css
                │   └── custom.js
                ├── burger.html
                ├── cupcakes.html
                ├── images
                │   ├── burger.jpg
                │   ├── cupcakes.jpg
                │   ├── pasta.jpg
                │   ├── pizza.jpg
                │   ├── skewers.jpg
                │   ├── soup.jpg
                │   └── steak.jpg
                ├── pasta.html
                ├── pizza.html
                ├── skewers.html
                ├── soup.html
                └── steak.html