QMake - 如何将文件复制到输出目录

54
我怎样才能使用qmake将文件从我的项目复制到输出目录?
我正在Linux上编译,但未来会在Mac和Windows上编译。
9个回答

53
您可以使用qmake函数来实现可重用性:
# 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)
}

接着按如下方式使用:

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

15
注意:我使用$$OUT_PWD代替$$DESTDIR以使它能够工作。参考一下,$$OUT_PWD是程序构建的文件夹,而$$PWD是正在构建程序的文件夹 -换句话说,它就是.pro文件所在的位置。 - Logan
为什么这里使用 defineTest 而不是 defineReplace?我无法通过 defineReplace 让 qmake 构建此项目,但我不明白为什么。文档中说:“这种类型的函数只应在条件表达式中使用”,但在这种情况下并不正确。 - Phlucious
当复制多个文件时,我在MinGW平台上遇到了奇怪的格式问题。 我不得不修改QMAKE_POST_LINK行为QMAKE_POST_LINK += $$QMAKE_COPY $$quote($$FILE) $$quote($$DDIR)$$escape_expand(\\n\\t\\n\\t)。 多个换行符是必要的,因为Windows路径可能以\\结尾,但这会转义该行并使其继续到下一行。 - Ross Rogers
1
不错的函数,虽然(在Qt 5.11中)我不得不将$$quote替换为$$shell_quote,将$$DESTDIR替换为$$OUT_PWD - retif
这对我很有效,不需要修改(Qt 6.0.1)。作为长期使用 Qt 的用户,我发现使用 QMake 执行简单操作有多么艰难真是奇怪。它几乎跟 CMake 差不多。 - aatwo

33

Qt 5.6 添加了这个作为一个未记录的功能:

CONFIG += file_copies

想一个名称来描述您想要复制的文件:

COPIES += myDocumentation

列出要复制的文件,包括在其 .files 成员中:

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

.path成员中指定目标路径:

myDocumentation.path = $$OUT_PWD/documentation

可选地指定要从源路径中删除的基础路径:

myDocumentation.base = $$PWD/text/docs

它的工作原理基本上与其他答案所述的许多方法相同。请参见file_copies.prf,了解详细信息。

该接口与INSTALLS非常相似。


太棒了!谢谢您的发布。如果没有这个,$(COPY_DIR)注入起来非常痛苦。 - phyatt
2
我不知道为什么找到这个方法就像在大海捞针一样困难,但是省点麻烦,使用这种方法吧——它应该是被接受的答案。 - brld
由于某些原因,它对我不起作用,不确定为什么。我正在使用Qt 5.12.9... - user3405291
1
谢谢,非常好用,救了我的一天,可与Qt5.14.2和Qt5.15.2一起使用。 - Valimo Ral

32
这是我们项目中的一个例子,展示了如何将文件复制到Windows和Linux的DESTDIR目录。
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))
    }
}

1
这怎么可能是正确的呢?在Linux Qt5.2.0中,$${DESTDIR}会扩展为空字符串。${FILE}的路径是相对于构建目录而不是源目录的。 - Cuadue
对于现代Qt构建(5.6+),请尝试查看下面@Oktalist的答案。它使用CONFIG += file_copies,非常干净。 - phyatt

16
如果您使用make install,可以使用qmake的INSTALLS变量。以下是一个示例:
images.path    = $${DESTDIR}/images
images.files   += images/splashscreen.png
images.files   += images/logo.png
INSTALLS       += images

然后执行make install

我能否在QT Creator中构建时自动调用“make install”?我喜欢这种平台无关的方法,但希望在QT Creator中构建时始终复制一些(少量)文件。 - Horst Walter
1
@HorstWalter:查看QMAKE_POST_LINK变量。您可以定义要执行的自定义命令。不过,需要注意的是,它指出某些后端不支持它,因此我不知道它在跨平台方面的表现如何。如果文件始终存在,则还可以创建自定义目标,并使生成的可执行文件依赖于自定义目标,该自定义目标将复制这些文件。 - Caleb Huitt - cjhuitt

4
在qmake用于配置特性的路径之一中创建一个名为copy_files.prf的文件。该文件应如下所示:
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}

它是如何工作的

第一部分定义了一个额外编译器,该编译器将从COPY_FILES变量中读取输入文件名。接下来定义了一个函数,用于合成每个输入对应的输出文件名。然后我们定义了用于调用此“编译器”的命令,这取决于我们使用的shell类型。

然后我们定义了一个额外的makefile目标copy_files.cookie,它依赖于目标compiler_copy_files_make_all。后者是qmake为我们在第一步中定义的额外编译器生成的目标名称。这意味着当构建copy_files.cookie目标时,它将调用额外的编译器来复制文件。

我们指定一个命令由此目标运行,它将“touch”文件“copy_files.cookie”和“compiler_copy_files_make_all”。通过触摸这些文件,我们确保“make”不会尝试再次复制文件,除非它们的时间戳比触摸的文件更新。最后,我们将“copy_files.cookie”添加到“make all”目标的依赖列表中。
如何使用:
在您的“.pro”文件中,将“copy_files”添加到“CONFIG”变量中。
CONFIG += copy_files

然后将文件添加到COPY_FILES变量中:
COPY_FILES += docs/*.txt

3

我发现我需要修改sje397提供的答案,用于与Qt5 Beta1和QtCreator 2.5.2一起使用。在构建完成后,我使用此脚本将qml文件复制到目标目录作为额外步骤。

我的.pro文件中有以下代码

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

请注意,我使用$$PWD_WIN来提供源文件的完整路径,以便将其复制到目标位置。


1
在你的项目中做如下操作:
include($$PWD/functions.pri) #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"

但是,为了使其工作,需要在某处定义下面的内容(来自XD框架),比如在functions.pri文件中:

# --------------------------------------
# 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)
}

请注意,在链接操作完成之前,所有文件都会被复制(这可能很有用)。 xd_functions.prf完整脚本
但是当您需要像copyFileLater(source, destination)这样的东西,只在构建完成后才复制文件,那么请考虑使用下面的代码(来自Apache 2.0许可下的XD框架):
# --------------------------------------
# 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)
}

0

除了Jake's answer和@Phlucious评论中提到的方法,还可以使用qmake的defineReplace函数,这个函数适用于这种情况。在使用提供的示例后,我遇到了一个问题,即qmake跳过了我添加的最后一个链接操作。可能是变量导出的问题,尽管内容一直看起来很好。长话短说,这里是修改后的代码

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

这个通用的复制函数可以被一些方便的函数使用,比如这个。

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

第二个只需要一个参数(一个或多个文件),并提供了一个固定的路径。基本上与参考答案相同。

但现在请注意调用的区别。

QMAKE_POST_LINK += $$copyToBuilddir(deploy.bat)

正如您所看到的,您可以将返回的命令附加到QMAKE_PRE_LINK以获得更大的灵活性。


是的,有目标目录版本是值得拥有的。我也需要这个,因为DESTDIR并不总是设置。 - jkj yuio

0

首先,定义以下两个函数以支持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)
}

接下来,您可以使用以下代码调用之前定义的函数将文件复制到特定文件夹,并在必要时创建目录。该代码已在Win32下测试通过,欢迎进行Linux测试。

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)

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接