如何使用 qmake 将文件从我的项目复制到输出目录?
我正在 Linux 上编译,但将来我会在 Mac 和 Windows 上编译它。
您可以使用 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
这是我们的一个项目中的一个例子。它显示了如何将文件复制到DESTDIRWindows 和 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))
}
}
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.
如果你使用 make install,你可以使用qmake 的 INSTALLS 变量。这是一个例子:
images.path = $${DESTDIR}/images
images.files += images/splashscreen.png
images.files += images/logo.png
INSTALLS += images
然后执行make install.
copy_files.prf在 qmake 用于配置功能的路径之一中创建一个文件。该文件应如下所示:
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
我发现我不得不修改 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 将源文件的完整路径提供给复制命令。
除了Jake 的回答和@Phlucious 评论之外,还可以使用defineReplace更适合此用例的 qmake 函数。使用提供的示例后,我遇到了一个问题,其中 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 以获得更大的灵活性。
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 scriptBut 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)
}
首先,定义以下两个函数以同时支持 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)