QMake - how to copy a file to the output

49,670

Solution 1

Here's an example from one of our projects. It shows how to copy files to the DESTDIR for Windows and Linux.

linux-g++{
    #...
    EXTRA_BINFILES += \
        $${THIRDPARTY_PATH}/gstreamer-0.10/linux/plugins/libgstrtp.so \
        $${THIRDPARTY_PATH}/gstreamer-0.10/linux/plugins/libgstvideo4linux2.so
    for(FILE,EXTRA_BINFILES){
        QMAKE_POST_LINK += $$quote(cp $${FILE} $${DESTDIR}$$escape_expand(\n\t))
    }
}

win32 {
    #...
    EXTRA_BINFILES += \
        $${THIRDPARTY_PATH}/glib-2.0/win32/bin/libglib-2.0.dll \
        $${THIRDPARTY_PATH}/glib-2.0/win32/bin/libgmodule-2.0.dll
    EXTRA_BINFILES_WIN = $${EXTRA_BINFILES}
    EXTRA_BINFILES_WIN ~= s,/,\\,g
        DESTDIR_WIN = $${DESTDIR}
    DESTDIR_WIN ~= s,/,\\,g
    for(FILE,EXTRA_BINFILES_WIN){
                QMAKE_POST_LINK +=$$quote(cmd /c copy /y $${FILE} $${DESTDIR_WIN}$$escape_expand(\n\t))
    }
}

Solution 2

You can use a qmake function for reusability:

# Copies the given files to the destination directory
defineTest(copyToDestdir) {
    files = $$1

    for(FILE, files) {
        DDIR = $$DESTDIR

        # Replace slashes in paths with backslashes for Windows
        win32:FILE ~= s,/,\\,g
        win32:DDIR ~= s,/,\\,g

        QMAKE_POST_LINK += $$QMAKE_COPY $$quote($$FILE) $$quote($$DDIR) $$escape_expand(\\n\\t)
    }

    export(QMAKE_POST_LINK)
}

then use it as follows:

copyToDestdir($$OTHER_FILES) # a variable containing multiple paths
copyToDestdir(run.sh) # a single filename
copyToDestdir(run.sh README) # multiple files

Solution 3

Qt 5.6 added this as an undocumented feature:

CONFIG += file_copies

Invent a name to describe the files you want to copy:

COPIES += myDocumentation

List the files that you want to copy, in its .files member:

myDocumentation.files = $$files(text/docs/*.txt)

Specify the destination path in the .path member:

myDocumentation.path = $$OUT_PWD/documentation

Optionally specify a base path to be trimmed from the source paths:

myDocumentation.base = $$PWD/text/docs

It basically works by doing the same things as many of the other answers here. See file_copies.prf for the gory details.

The interface is very similar to that for INSTALLS.

Solution 4

If you use make install, you can use the INSTALLS variable of qmake. Here is an example:

images.path    = $${DESTDIR}/images
images.files   += images/splashscreen.png
images.files   += images/logo.png
INSTALLS       += images

then execute make install.

Solution 5

Create a file copy_files.prf in one of the paths which qmake uses for config features. The file should look like this:

QMAKE_EXTRA_COMPILERS += copy_files
copy_files.name = COPY
copy_files.input = COPY_FILES
copy_files.CONFIG = no_link

copy_files.output_function = fileCopyDestination
defineReplace(fileCopyDestination) {
    return($$shadowed($$1))
}

win32:isEmpty(MINGW_IN_SHELL) {
    # Windows shell
    copy_files.commands = copy /y ${QMAKE_FILE_IN} ${QMAKE_FILE_OUT}
    TOUCH = copy /y nul
}
else {
    # Unix shell
    copy_files.commands = mkdir -p `dirname ${QMAKE_FILE_OUT}` && cp ${QMAKE_FILE_IN} ${QMAKE_FILE_OUT}
    TOUCH = touch
}

QMAKE_EXTRA_TARGETS += copy_files_cookie
copy_files_cookie.target = copy_files.cookie
copy_files_cookie.depends = compiler_copy_files_make_all

win32:!mingw {
    # NMake/MSBuild
    copy_files_cookie.commands = $$TOUCH $** && $$TOUCH $@
}
else {
    # GNU Make
    copy_files_cookie.commands = $$TOUCH $<  && $$TOUCH $@
}

PRE_TARGETDEPS += $${copy_files_cookie.target}

How it works

The first part defines an extra compiler which will read input filenames from the COPY_FILES variable. The next part defines the function which it will use to synthesize an output filename corresponding to each input. Then we define the commands used to invoke this "compiler", depending on which kind of shell we are in.

Then we define an extra makefile target copy_files.cookie, which depends on the target compiler_copy_files_make_all. The latter is the name of the target which qmake generates for the extra compiler we defined in the first step. This means that when the copy_files.cookie target is built, it will invoke the extra compiler to copy the files.

We specify a command to be run by this target, which will touch the files copy_files.cookie and compiler_copy_files_make_all. By touching these files, we ensure that make will not try to copy the files again unless their timestamps are more recent than the touched files. Finally, we add copy_files.cookie to the list of dependencies of the make all target.

How to use it

In your .pro file, add copy_files to the CONFIG variable:

CONFIG += copy_files

Then add the files to the COPY_FILES variable:

COPY_FILES += docs/*.txt
Share:
49,670
Raphael
Author by

Raphael

I've been programming professionally for the past 9 years. Currently, my speciality is iOS development (though I still dwell in the backend world from time to time using Node or Python). I've been using Swift since it was first released in 2014 and the app that I've worked on for the past 3 years (Hole19) has been consistently rated 5 stars, with 99% of crash-free users and it was often featured on the App Store. On behalf of Hole19, I have attended WWDC 2016 in San Francisco. Before working on Hole19, I worked on a few consulting agencies and had extensive freelancing experience. Over the years, I have developed several mobile and web apps both big and small.

Updated on July 18, 2022

Comments

  • Raphael
    Raphael almost 2 years

    How can I copy a file from my project to the output directory with qmake?

    I'm compiling on Linux but in the future I'll compile it on Mac and Windows.

  • Logan
    Logan about 12 years
    Just a note: I used $$OUT_PWD instead of $$DESTDIR to make it work. For reference $$OUT_PWD is the folder that the program is built to, and $$PWD is the folder that the program is Being built from -in other words it's where the .pro file is.
  • Horst Walter
    Horst Walter almost 12 years
    Can I somehow always automatically call 'make install' in QT Creator whenever I build? I like this platform independent approach, but want some (few) files always being copied whenever I build in QT Creator.
  • Caleb Huitt - cjhuitt
    Caleb Huitt - cjhuitt almost 12 years
    @HorstWalter: Look into the QMAKE_POST_LINK variable. You can define a custom command to be executed. Notably, however, it says that some backends don't support it, so I don't know how cross-platform it would be. If the files are always around, you could also make a custom target and make the resulting executable file depend on the custom target, which would copy those files.
  • Cuadue
    Cuadue over 9 years
    How can this be correct? On Linux Qt5.2.0 $${DESTDIR} expands to the empty string. The path to ${FILE} is relative to the build directory, not to the source directory.
  • Phlucious
    Phlucious almost 9 years
    Why does this have defineTest instead of defineReplace? I couldn't get qmake to build this with defineReplace but I don't understand why. The docs say that "This type of function should be used in conditional expressions only" which is not true in this case.
  • jkj yuio
    jkj yuio almost 8 years
    Yes. worth having the version with destination directory. I needed this too since DESTDIR is not always set.
  • Ross Rogers
    Ross Rogers over 7 years
    I ran into weird formatting problems with the MinGW platform when more than one file was copied. I had to modify the QMAKE_POST_LINK line to QMAKE_POST_LINK += $$QMAKE_COPY $$quote($$FILE) $$quote($$DDIR)$$escape_expand(\\n\\t\\n\\t) The multiple newlines were necessary because a windows path may end in `\`, but that escapes the line and makes it continue to the next.
  • retif
    retif about 6 years
    Nice function, although (with Qt 5.11) I had to replace $$quote with $$shell_quote and $$DESTDIR with $$OUT_PWD.
  • phyatt
    phyatt over 5 years
    Amazing! Thank you for posting. $(COPY_DIR) is pretty painful to inject without this.
  • phyatt
    phyatt over 5 years
    For modern Qt builds (5.6+) try looking at @Oktalist 's answer below. It uses CONFIG += file_copies and is really clean.
  • brld
    brld almost 4 years
    I don't know why finding this was like finding a needle in a haystack but save yourself the trouble and use this method--it should be the accepted answer.
  • user3405291
    user3405291 over 3 years
    For some reason it didn't work for me, not sure why. I'm using Qt 5.12.9 ...
  • Valimo Ral
    Valimo Ral over 3 years
    Thanks, works very well, save my day, use with qt5.14.2 and qt 5.15.2
  • aatwo
    aatwo over 3 years
    This worked well for me, no modifications required (Qt 6.0.1). As a long time Qt user I find it bizarre how hard it is to perform simple operations with QMake. It's barely better than CMake.