【问题标题】:QMake - how to copy a file to the outputQMake - 如何将文件复制到输出
【发布时间】:2011-04-28 09:35:38
【问题描述】:

如何使用 qmake 将文件从我的项目复制到输出目录?

我正在 Linux 上编译,但将来我会在 Mac 和 Windows 上编译。

【问题讨论】:

    标签: qt qt-creator qmake


    【解决方案1】:

    您可以使用 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
    

    【讨论】:

    • 请注意:我使用 $$OUT_PWD 而不是 $$DESTDIR 来使其工作。作为参考,$$OUT_PWD 是构建程序的文件夹,$$PWD 是构建程序的文件夹 - 换句话说,它是 .pro 文件所在的位置。
    • 为什么这里有defineTest 而不是defineReplace?我无法让 qmake 用defineReplace 构建它,但我不明白为什么。 docs 说“这种类型的函数只能在条件表达式中使用”在这种情况下是不正确的。
    • 在复制多个文件时,我在 MinGW 平台上遇到了奇怪的格式问题。我不得不将 QMAKE_POST_LINK 行修改为 QMAKE_POST_LINK += $$QMAKE_COPY $$quote($$FILE) $$quote($$DDIR)$$escape_expand(\\n\\t\\n\\t) 多个换行符是必要的,因为 Windows 路径可能以 `\` 结尾,但这会转义该行并使其继续到下一个。
    • 不错的功能,虽然(使用 Qt 5.11)我不得不用 $$shell_quote$$DESTDIR 替换 $$quote$$OUT_PWD
    • 这对我来说效果很好,不需要修改(Qt 6.0.1)。作为一个长期使用 Qt 的用户,我发现使用 QMake 执行简单的操作有多么困难,这很奇怪。它比 CMake 差不了多少。
    【解决方案2】:

    以下是我们其中一个项目的示例。它展示了如何将文件复制到 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))
        }
    }
    

    【讨论】:

    • 这怎么可能是正确的?在 Linux Qt5.2.0 $${DESTDIR} 上扩展为空字符串。 ${FILE} 的路径是相对于构建目录的,而不是相对于源目录的。
    • 对于现代 Qt 构建(5.6+),请尝试查看以下 @Oktalist 的答案。它使用CONFIG += file_copies,非常干净。
    【解决方案3】:

    Qt 5.6 added 这是一个未记录的功能:

    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) 注入是相当痛苦的。
    • 我不知道为什么找到这个就像大海捞针一样,但可以省去麻烦并使用这种方法——它应该是公认的答案。
    • 由于某种原因它对我不起作用,不知道为什么。我正在使用 Qt 5.12.9 ...
    • 谢谢,效果很好,节省我的时间,与 qt5.14.2 和 qt 5.15.2 一起使用
    【解决方案4】:

    如果你使用make install,你可以使用INSTALLS variable of qmake。这是一个例子:

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

    然后执行make install

    【讨论】:

    • 每当我构建时,我能以某种方式在 QT Creator 中自动调用“make install”吗?我喜欢这种独立于平台的方法,但希望在构建 QT Creator 时始终复制一些(少数)文件。
    • @HorstWalter:查看 QMAKE_POST_LINK 变量。您可以定义要执行的自定义命令。然而,值得注意的是,它说一些后端不支持它,所以我不知道它的跨平台程度如何。如果文件始终存在,您还可以创建自定义目标并使生成的可执行文件依赖于自定义目标,这将复制这些文件。
    【解决方案5】:

    在 qmake 用于config features 的路径之一中创建一个文件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}
    

    工作原理

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

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

    我们指定了一个由该目标运行的命令,它将touch 文件copy_files.cookiecompiler_copy_files_make_all。通过触摸这些文件,我们确保make 不会再次尝试复制文件,除非它们的时间戳比被触摸的文件更新。最后,我们将copy_files.cookie添加到make all目标的依赖列表中。

    如何使用

    在您的.pro 文件中,将copy_files 添加到CONFIG 变量中:

    CONFIG += copy_files
    

    然后将文件添加到COPY_FILES 变量中:

    COPY_FILES += docs/*.txt
    

    【讨论】:

      【解决方案6】:

      我发现我不得不修改 sje397 给出的答案。 Qt5 Beta1QtCreator 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 将源文件的完整路径提供给复制命令。

      【讨论】:

        【解决方案7】:

        除了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 并不总是设置。
        【解决方案8】:

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

        【讨论】:

          【解决方案9】:

          首先,在下面的某个地方定义(来自XD 框架),比如在functions.prf 文件中:

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

          并在您的项目中使用它,例如:

          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"
          

          请注意,在链接操作完成之前,所有文件都会被复制(这很有用)。

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

          【讨论】:

            猜你喜欢
            • 1970-01-01
            • 1970-01-01
            • 2017-03-10
            • 2014-06-17
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 2021-09-16
            • 2010-12-19
            相关资源
            最近更新 更多