50

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.

sashoalm
  • 63,456
  • 96
  • 348
  • 677
Raphael
  • 7,388
  • 12
  • 55
  • 81

9 Answers9

51

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
Jake Petroules
  • 21,796
  • 34
  • 136
  • 218
  • 15
    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. – Logan May 18 '12 at 07:14
  • 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](http://doc.qt.io/qt-5/qmake-language.html#test-functions) say that "This type of function should be used in conditional expressions only" which is not true in this case. – Phlucious Aug 13 '15 at 18:12
  • 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. – Ross Rogers Nov 18 '16 at 18:21
  • 1
    Nice function, although (with Qt 5.11) I had to replace `$$quote` with `$$shell_quote` and `$$DESTDIR` with `$$OUT_PWD`. – retif Jun 06 '18 at 09:53
  • 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. – aatwo Feb 15 '21 at 17:17
30

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))
    }
}
sje397
  • 38,979
  • 7
  • 80
  • 102
  • 1
    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. – Cuadue Jan 28 '15 at 22:09
  • For modern Qt builds (5.6+) try looking at @Oktalist 's answer below. It uses `CONFIG += file_copies` and is really clean. – phyatt Mar 15 '19 at 14:47
25

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.

Oktalist
  • 13,098
  • 1
  • 38
  • 56
14

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.

tro
  • 6,152
  • 5
  • 42
  • 63
Caleb Huitt - cjhuitt
  • 14,132
  • 2
  • 40
  • 49
  • 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. – Horst Walter Jul 21 '12 at 14:14
  • 1
    @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. – Caleb Huitt - cjhuitt Aug 09 '12 at 20:57
3

I found I had to modify the answer given by sje397. for Qt5 Beta1 together with QtCreator 2.5.2. I use this script to copy the qml files to the destination directory as an addition step after the build is complete.

My .pro file has the following code

OTHER_FILES += \
    Application.qml

# Copy qml files post build
win32 {
    DESTDIR_WIN = $${DESTDIR}
    DESTDIR_WIN ~= s,/,\\,g
    PWD_WIN = $${PWD}
    PWD_WIN ~= s,/,\\,g
    for(FILE, OTHER_FILES){
        QMAKE_POST_LINK += $$quote(cmd /c copy /y $${PWD_WIN}\\$${FILE} $${DESTDIR_WIN}$$escape_expand(\\n\\t))
    }
}
unix {
    for(FILE, OTHER_FILES){
        QMAKE_POST_LINK += $$quote(cp $${PWD}/$${FILE} $${DESTDIR}$$escape_expand(\\n\\t))
}

}

Note that I use use $$PWD_WIN to supply the full path to the source file to the copy command.

eatyourgreens
  • 993
  • 1
  • 11
  • 15
3

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
Oktalist
  • 13,098
  • 1
  • 38
  • 56
1

As addition to Jake's answer and @Phlucious comment one may use qmake defineReplace function which suites better for this use case. After using the provided example I encountered a problem, where qmake skipped the last post link action I added. That might be an issue with the export of the variable though the content looked pretty well all the time. Long story short, here ist the modified code

defineReplace(copyToDir) {
    files = $$1
    DIR = $$2
    LINK =

    for(FILE, files) {
        LINK += $$QMAKE_COPY $$shell_path($$FILE) $$shell_path($$DIR) $$escape_expand(\\n\\t)
    }
    return($$LINK)
}

This general copy function may be used by some convenience functions like this one

defineReplace(copyToBuilddir) {
    return($$copyToDir($$1, $$OUT_PWD))
}

The second one take only one argument (one or more files) and provides a fixed path. Pretty much the same as in the references answer.

But now note the invocation difference

QMAKE_POST_LINK += $$copyToBuilddir(deploy.bat)

As you may see, you can attach the returned command to QMAKE_PRE_LINK for even more flexibility.

Community
  • 1
  • 1
maxik
  • 987
  • 12
  • 33
  • Yes. worth having the version with destination directory. I needed this too since DESTDIR is not always set. – jkj yuio Aug 22 '16 at 19:52
1

First, define the following two function for supporting both Windows/Unix.

defineReplace(nativePath) {
    OUT_NATIVE_PATH = $$1
    # Replace slashes in paths with backslashes for Windows
    win32:OUT_NATIVE_PATH ~= s,/,\\,g
    return($$OUT_NATIVE_PATH)
}

# Copies the given files to the destination directory
defineReplace(copyToDestDirCommands) {
    variable_files = $$1
    files = $$eval($$variable_files)
    DDIR = $$nativePath($$2)
    win32:DDIR ~= s,/,\\,g
    POST_LINK = echo "Copying files to $$DDIR" $$escape_expand(\\n\\t)

    win32 {
        POST_LINK += $$QMAKE_MKDIR $$quote($$DDIR) 2>&1 & set errorlevel=0 $$escape_expand(\\n\\t)
    }
    !win32 {
        POST_LINK += $$QMAKE_MKDIR -p $$quote($$DDIR) $$escape_expand(\\n\\t)
    }

    for(ORIGINAL_FILE, files) {
        FILE = $$nativePath($$ORIGINAL_FILE)
        POST_LINK += $$QMAKE_COPY $$quote($$FILE) $$quote($$DDIR) $$escape_expand(\\n\\t)
    }

    return ($$POST_LINK)
}

Then you can use the following code to call the funcitons that defined before to copy files into specific folder, and also creating the directory when necessary. This is tested under Win32, Linux tests are welcome.

BATOS_FILES = \
    $$BATOS_BIN_ROOT/batos-core.dll \
    $$BATOS_BIN_ROOT/batos-pfw.dll \
    $$BATOS_BIN_ROOT/dre.dll \
    $$BATOS_BIN_ROOT/log4qt.dll

QMAKE_POST_LINK += $$copyToDestDirCommands(BATOS_FILES, $$DESTDIR)

BATOS_PLUGINS_FILES = \
    $$BATOS_BIN_ROOT/plugins/com.xaf.plugin-manager.dll \
    $$BATOS_BIN_ROOT/plugins/org.commontk.eventadmin.dll

QMAKE_POST_LINK += $$copyToDestDirCommands(BATOS_PLUGINS_FILES, $$DESTDIR/plugins)
lygstate
  • 410
  • 5
  • 11
1

First, define below (which is from XD framework) somewhere, like inside functions.prf file:

# --------------------------------------
# This file defines few useful functions
# --------------------------------------

#copyDir(source, destination)
# using "shell_path()" to correct path depending on platform
# escaping quotes and backslashes for file paths
defineTest(copyDir) {
    #append copy command
    !isEmpty(xd_copydir.commands): xd_copydir.commands += && \\$$escape_expand(\n\t)
    xd_copydir.commands += ( $(COPY_DIR) \"$$shell_path($$1)\" \"$$shell_path($$2)\" || echo \"copy failed\" )
    #the qmake generated MakeFile contains "first" and we depend that on "xd_copydir"
    first.depends *= xd_copydir
    QMAKE_EXTRA_TARGETS *= first xd_copydir

    export(first.depends)
    export(xd_copydir.commands)
    export(QMAKE_EXTRA_TARGETS)
}

#copy(source, destination) (i.e. the name "copyFile" was reserved)
defineTest(copyFile) {
    #append copy command
    !isEmpty(xd_copyfile.commands): xd_copyfile.commands += && \\$$escape_expand(\n\t)
    xd_copyfile.commands += ( $(COPY_FILE) \"$$shell_path($$1)\" \"$$shell_path($$2)\" || echo \"copy failed\" )
    #the qmake generated MakeFile contains "first" and we depend that on "xd_copyfile"
    first.depends *= xd_copyfile
    QMAKE_EXTRA_TARGETS *= first xd_copyfile

    export(first.depends)
    export(xd_copyfile.commands)
    export(QMAKE_EXTRA_TARGETS)
}

and use it in your project like:

include($$PWD/functions.prf) #optional

copyFile($$PWD/myfile1.txt, $$DESTDIR/myfile1.txt)
copyFile($$PWD/README.txt, $$DESTDIR/README.txt)
copyFile($$PWD/LICENSE, $$DESTDIR/LICENSE)
copyDir($$PWD/redist, $$DESTDIR/redist) #copy "redist" folder to "$$DESTDIR"

Note that all files would get copied before the link operation is done (which can be useful).

xd_functions.prf full script

But when you need something like copyFileLater(source, destination), to only copy files once build is done then consider using below code (which is from XD framework under Apache 2.0 license):

# --------------------------------------
# This file defines few useful functions
# --------------------------------------

xd_command_count = 0

#xd_prebuild(prefix, command)
defineTest(xd_prebuild) {
    #generate target name with number
    xd_command_count = $$num_add($$xd_command_count, 1)
    name = $$1$$xd_command_count
    #append command
    eval( $${name}.commands += ( \$\$2 ) );
    #the qmake generated "MakeFile" should contain "first"
    #   and we depend that on new command
    !contains( first.depends, $$name ) {
        !isEmpty(first.depends): first.depends += \\$$escape_expand(\\n)
        first.depends += $$name
    }

    QMAKE_EXTRA_TARGETS *= first $$name

    export(xd_command_count)
    export($${name}.commands)
    export(first.depends)
    export(QMAKE_EXTRA_TARGETS)

    #eval( warning(xd_push_command: $${name}.commands += \$\${$${name}.commands}) )
}
#xd_postbuild(command)
defineTest(xd_postbuild) {
    !isEmpty(QMAKE_POST_LINK): QMAKE_POST_LINK = $$QMAKE_POST_LINK$$escape_expand(\\n\\t)
    QMAKE_POST_LINK = $${QMAKE_POST_LINK}$$quote(-$$1)

    export(QMAKE_POST_LINK)
}
#xd_escape(path)
#   resolves path like built-in functions (i.e. counts input relative to $$PWD)
defineReplace(xd_escape) {
    1 = $$absolute_path($$1)
    #using "shell_path()" to correct path depending on platform
    #   escaping quotes and backslashes for file paths
    1 = $$shell_path($$1)
    return($$quote($$1))
}

#copyFile(source, destination)
#   this will both copy and rename "source" to "destination", However like "copy_file()":
#       if "destination" is path to existing directory or ends with slash (i.e. "/" or "\\"),
#       will just copy to existing "destination" directory without any rename
#
#   note: this is executed before build, but after qmake did exit
#       so use "copy_file(...)" instead if the output file is required in qmake script
#       like for example if "write_file(...)" is called on the output...
defineTest(copyFile) {
    #note that "$(COPY_FILE)" is generated by qmake from "$$QMAKE_COPY_FILE"
    xd_prebuild(xd_copyfile, $(COPY_FILE) $$xd_escape($$1) $$xd_escape($$2) || echo copyFile-failed)
}

#copyFileLater(source, destination = $(DESTDIR))
#   note: this is executed after build is done, hence the name copy-later
defineTest(copyFileLater) {
    destDir = $$2
    isEmpty(destDir): destDir = $(DESTDIR)
    #append copy command
    xd_postbuild($(COPY_FILE) $$xd_escape($$1) $$xd_escape($$destDir) || echo copyFileLater-failed)

    #!build_pass:warning(copyFile: $$1 to: $$destDir)
}

#copyDir(source, destination)
defineTest(copyDir) {
    xd_prebuild(xd_copydir, $(COPY_DIR) $$xd_escape($$1) $$xd_escape($$2) || echo copyDir-failed)
}
#copyDirLater(source, destination = $(DESTDIR))
#   note: this is executed after build is done, hence the name copy-later
defineTest(copyDirLater) {
    destDir = $$2
    isEmpty(destDir): destDir = $(DESTDIR)
    #append copy command
    xd_postbuild($(COPY_DIR) $$xd_escape($$1) $$xd_escape($$destDir) || echo copyDirLater-failed)

    #!build_pass:warning(copyFile: $$1 to: $$destDir)
}

#makeDir(destination)
defineTest(makeDir) {
    xd_prebuild(xd_makedir, $(MKDIR) $$xd_escape($$1) || echo makeDir-failed: \"$$1\")
}
defineTest(makeDirLater) {
    xd_postbuild( $(MKDIR) $$xd_escape($$1) || echo makeDirLater-failed )
    #!build_pass:warning(makeDirLater: $$1)
}

defineTest(deleteFile) {
    xd_prebuild(xd_delfile, $(DEL_FILE) $$xd_escape($$1) || echo deleteFile-failed)
}
defineTest(deleteFileLater) {
    xd_postbuild( $(DEL_FILE) $$xd_escape($$1) || echo deleteFileLater-failed )
    #!build_pass:warning(deleteFileLater: $$1)
}
defineTest(deleteDir) {
    xd_prebuild(xd_delfile, $(DEL_DIR) $$xd_escape($$1) || echo deleteDir-failed)
}
defineTest(deleteDirLater) {
    xd_postbuild( $(DEL_DIR) $$xd_escape($$1) || echo deleteDirLater-failed )
    #!build_pass:warning(deleteFileLater: $$1)
}

#qmakeLater(qmake-script-file-path-to-run)
#   note that inside the script runned by this method
#   $$OUT_PWD will be same as original $$OUT_PWD of qmakeLater(...) caller project
#   since there is the "Makefile" that executes our custom qmake
defineTest(qmakeRun) {
    xd_postbuild( $(QMAKE) $$xd_escape($$1) -r -spec \"$$shell_path($$QMAKESPEC)\" )
    #!build_pass:warning(qmakeLater: $$1)
}
defineTest(qmakeLater) {
    xd_postbuild( $(QMAKE) $$xd_escape($$1) -r -spec \"$$shell_path($$QMAKESPEC)\" )
    #!build_pass:warning(qmakeLater: $$1)
}
Top-Master
  • 2,934
  • 1
  • 17
  • 34