首页 qmake中实现拷贝文件
文章
取消

qmake中实现拷贝文件

需求

在项目开发时,特别是Qt项目开发中,采用Shadow文件夹构建,即构建的目录与源码目录分离。我们经常碰到一个需求,即需要将源码中部分文件夹拷贝到生成目录(特别是exe所在目录)。或者是将运行时需要的动态库拷贝过来。

为方便项目构建,我们希望不需要每个成员编译时进行手动拷贝,而是编译中自动完成。在VS或Qt的IDE中,可以添加构建前/后的步骤,添加拷贝命令。

或者是告诉编译器由编译器完成。这里针对QMake项目,提出QMake实现文件拷贝的方法

下面描述整个探索过程,省流找方案可跳转至总结的最后。

QMake拷贝文件

单纯从拷贝文件/目录的需求来说,应该是个很简单的需求(事实上也是),无非是一个copy命令,附带特定的参数。

事情复杂在于:

  • 不同平台的拷贝命令有一些差别,包括指定的参数。以Qt的跨平台特性来说,写qmake也应该实现能跨平台。
  • 特指Windows平台,会有一些坑。首先是拷贝命令不一定好用,主流分为用copy和xcopy,处理能否自动新建目录的情况。其次,Windows平台目录路径要用反斜杠\,而Qt中一般路径是用正斜杠/,比如一些预定义宏PWD等。要自己处理路径问题
  • 还有使用不同的方法命令,复制的时机可能不一样。

所以综合几种情况,会导致很多简单处理的示例无法生效,让人迷惑。

首先给出一个完整的示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
debug: compiled = debug
else: compiled = release
EXE_DIR = $$OUT_PWD/$$compiled

SrcIncludeDIR = $$PWD/test/*.txt
DLL_DIR = $$PWD/TEST_1/*.dll
win32{
    SrcIncludeFile = $$replace(SrcIncludeDIR, /, \\)
    EXE_DIR = $$replace(EXE_DIR, /, \\)
    DLL_DIR = $$replace(DLL_DIR, /, \\)
}

QMAKE_PRE_LINK += $$QMAKE_COPY_DIR $$SrcIncludeFile $$EXE_DIR\\test
QMAKE_PRE_LINK += && $$QMAKE_COPY_DIR $$DLL_DIR $$EXE_DIR\\TEST_1

针对上述几种问题处理逐一解决:

  • 针对不同平台的拷贝命令,这里不使用具体的命令和参数,而是使用QMAKE_COPY_DIR。如最后注释所看见的,该变量会展开为xcopy /s /q /y /i 。具体来说,在"D:\Qt\5.12.1\msvc2017_64\mkspecs\features\spec_post.prf"这个路径下定义了相关命名:

        QMAKE_COPY              = copy /y
        QMAKE_COPY_FILE         = $$QMAKE_COPY
        QMAKE_COPY_DIR          = xcopy /s /q /y /i
    
  • 然后针对于Windows平台,要对文件路径进行处理,处理时使用replace函数,替换正斜杠为反斜杠。

  • 最后,这里拷贝是添加到QMAKE_PRE_LINK中,该变量在编译器build链接前生效,执行命令。要注意的是,需要首次构建或者重新构建才能生效。Qt描述这个命令:

    1
    2
    
    QMAKE_PRE_LINK
    Specifies the command to execute before linking the TARGET together. This variable is normally empty and therefore nothing is executed.
    

总结和测试一下各种的命令,其生效方式和使用方法。

QMAKE_PRE_LINK/QMAKE_POST_LINK

QMAKE_PRE_LINK即上面第一个示例的用法。QMAKE_POST_LINK:一样的效果,只是复制发生在link之后。

  • 写法:如前面第一个示例。

  • 命令来源:QMake指令,能添加使用各种命令,这个使用的命令是QMAKE_COPY_DIR。命令解释参考上面Qt的帮助文档描述。

  • 命令展开:打开Makefile,可以看到命令展开如下。即在Link命令前/后,直接展开添加的命令。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    
    first: all
    all: Makefile.Debug  debug\QtTest.exe
      
    debug\QtTest.exe: ui_mainwindow.h $(OBJECTS) 
    	xcopy /s /q /y /i F:\Program\qt\QtTest\test\*.txt F:\Program\qt\build-QtTest-Desktop_Qt_5_12_1_MSVC2017_64bit-Debug\debug\test
    	$(LINKER) $(LFLAGS) /MANIFEST:embed /OUT:$(DESTDIR_TARGET) @<<
    debug\main.obj debug\mainwindow.obj debug\test1.obj debug\moc_mainwindow.obj
    $(LIBS)
    <<
    	xcopy /s /q /y /i F:\Program\qt\QtTest\TEST_1\*.dll F:\Program\qt\build-QtTest-Desktop_Qt_5_12_1_MSVC2017_64bit-Debug\debug\TEST_1
    
  • 生效方式首次构建或重新构建。如前面Makefile看到的,是生成exe对象(即发生链接过程)时执行命令,意味着如果原本已经有exe对象,不需要重新链接的时候,不会执行命令。 编译输出:

    1
    2
    3
    4
    5
    
    xcopy /s /q /y /i F:\Program\qt\QtTest\test\*.txt F:\Program\qt\build-QtTest-Desktop_Qt_5_12_1_MSVC2017_64bit-Debug\debug\test
    复制了 1 个文件
    	link /NOLOGO /DYNAMICBASE /NXCOMPAT /DEBUG /SUBSYSTEM:WINDOWS "/MANIFESTDEPENDENCY:type='win32' name='Microsoft.Windows.Common-Controls' version='6.0.0.0' publicKeyToken='6595b64144ccf1df' language='*' processorArchitecture='*'" /MANIFEST:embed /OUT:debug\QtTest.exe @F:\Data\Temp\QtTest.exe.10860.1688.jom
    	xcopy /s /q /y /i F:\Program\qt\QtTest\TEST_1\*.dll F:\Program\qt\build-QtTest-Desktop_Qt_5_12_1_MSVC2017_64bit-Debug\debug\TEST_1
    复制了 1 个文件
    

COPIES

  • 命令来源应该属于普通Makefile的指令,不属于预定义指令,可能也是QMake提供的。其解释如下:

  • 写法:这里写法比较方便的是,可以直接使用正斜杠,而不需要自己进行处理。(为什么不需要:是因为这个命令最后展开成$(QINSTALL),而不是具体的xcopy这种依赖于windows平台的命令,qt自己的命令肯定经过了跨平台处理)

    1
    2
    3
    4
    5
    6
    
    # 用两个拷贝示例
    cp_kk.files += $$PWD/test/*.txt
    cp_kk.path  += $$OUT_PWD/$$compiled/copy
    cp_dll.files += $$DLL_DIR
    cp_dll.path  += $$EXE_DIR
    COPIES +=  cp_kk cp_dll
    
  • 命令展开:打开Makefile查看如下。即将命令展开成:$(QINSTALL).

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    
    first: all
    all: Makefile.Debug  debug\QtTest.exe
      
    debug\QtTest.exe: ui_mainwindow.h debug\copy\1.txt debug\1.DLL $(OBJECTS) 
    	$(LINKER) $(LFLAGS) /MANIFEST:embed /OUT:$(DESTDIR_TARGET) @<<
    debug\main.obj debug\mainwindow.obj debug\test1.obj debug\moc_mainwindow.obj
    $(LIBS)
    <<
      
    ...
    compiler_copy_cp_kk_make_all: debug\copy\1.txt
    debug\copy\1.txt: ..\QtTest\test\1.txt
    	$(QINSTALL) ..\QtTest\test\1.txt debug\copy\1.txt
      
    compiler_copy_cp_dll_make_all: debug\1.DLL
    debug\1.DLL: ..\QtTest\TEST_1\1.DLL
    	$(QINSTALL) ..\QtTest\TEST_1\1.DLL debug\1.DLL
    

    第一部分exe生成那里,依赖于拷贝的文件,这个展开是类似于上面那个的。不过这里没有展开拷贝命令。而是在最后构建的目标:compiler_copy_cp_dll_make_all. 在这里展开为QINSTALL(所以说COPIES大概也是QMake提供的)。

  • 生效方式每次构建时即生效,因为命令不是依赖于构建exe,而是单独构建一个compiler_copy_cp_dll_make_all的目标。编译输出如下:

    1
    2
    3
    4
    
    	D:\Qt\Tools\QtCreator\bin\jom.exe -f Makefile.Debug
    	D:\Qt\5.12.1\msvc2017_64\bin\qmake.exe -install qinstall ..\QtTest\test\1.txt debug\copy\1.txt
    	D:\Qt\5.12.1\msvc2017_64\bin\qmake.exe -install qinstall ..\QtTest\TEST_1\1.DLL debug\1.DLL
    	link /NOLOGO /DYNAMICBASE /NXCOMPAT /DEBUG /SUBSYSTEM:WINDOWS "/MANIFESTDEPENDENCY:type='win32' name='Microsoft.Windows.Common-Controls' version='6.0.0.0' publicKeyToken='6595b64144ccf1df' language='*' processorArchitecture='*'" /MANIFEST:embed /OUT:debug\QtTest.exe @F:\Data\Temp\QtTest.exe.280.46.jom
    

QMAKE_EXTRA_TARGETS

  • 命令来源:类似于QMAKE_PRE_LINK等,由QMake预定义的变量。Qt帮助文档的解释:

    QMAKE_EXTRA_TARGETS

    Specifies a list of additional qmake targets.

  • 写法:这里除了QMAKE_EXTRA_TARGETS添加,还需要PRE_TARGETDEPS添加,使得构建exe时,依赖于自己额外添加的对象,构成构建关系链。另外,这里具体命令还是使用QMAKE_COPY_DIR,会展开成xcopy,因此需要处理路径问题。

    1
    2
    3
    
    cp_ext.commands += $$QMAKE_COPY_DIR $$SrcIncludeFile $$EXE_DIR\\test
    QMAKE_EXTRA_TARGETS += cp_ext
    PRE_TARGETDEPS += cp_ext
    
  • 命令展开

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    
    first: all
    all: Makefile.Debug  debug\QtTest.exe
      
    debug\QtTest.exe: cp_ext ui_mainwindow.h $(OBJECTS) 
    	$(LINKER) $(LFLAGS) /MANIFEST:embed /OUT:$(DESTDIR_TARGET) @<<
    debug\main.obj debug\mainwindow.obj debug\test1.obj debug\moc_mainwindow.obj
    $(LIBS)
    <<
    ...
    cp_ext:
    	xcopy /s /q /y /i F:/Program/qt/QtTest/test/*.txt F:/Program/qt/build-QtTest-Desktop_Qt_5_12_1_MSVC2017_64bit-Debug/debug/copy
      
    

    其实跟COPIES非常类似,这是COPIES是预定义为拷贝的一种特殊情况,无需自行指定命令。

  • 生效方式:Make构建时生效。编译输出:

    1
    2
    3
    4
    
    D:\Qt\Tools\QtCreator\bin\jom.exe -f Makefile.Debug
    	xcopy /s /q /y /i F:\Program\qt\QtTest\test\*.txt F:\Program\qt\build-QtTest-Desktop_Qt_5_12_1_MSVC2017_64bit-Debug\debug\test
    复制了 1 个文件
    	link /NOLOGO /DYNAMICBASE /NXCOMPAT /DEBUG /SUBSYSTEM:WINDOWS "/MANIFESTDEPENDENCY:type='win32' name='Microsoft.Windows.Common-Controls' version='6.0.0.0' publicKeyToken='6595b64144ccf1df' language='*' processorArchitecture='*'" /MANIFEST:embed /OUT:debug\QtTest.exe @F:\Data\Temp\QtTest.exe.11948.47.jom
    

QMAKE_SUBSTITUTES

写法:

1
2
3
cp_et.input = $$PWD/test/1.txt
cp_et.output = $$OUT_PWD/$$compiled/copy
QMAKE_SUBSTITUTES += cp_et

实现一个文件拷贝到另一个文件里面。具体展开为:

1
D:\Qt\5.12.1\msvc2017_64\mkspecs\features\lex.prf ..\QtTest\QtTest.pro ..\QtTest\test\1.txt D:\Qt\5.12.1\msvc2017_64\lib\Qt5Widgetsd.prl 

关于这里使用到lex.prf:

# Lex extra-compiler for handling files specified in the LEXSOURCES variable

PRF作为QMake中高级用法的扩展:

高级用法 |Qmake手册 (qt.io)

总结

完整的示例如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
debug: compiled = debug
else: compiled = release
EXE_DIR = $$OUT_PWD/$$compiled

SrcIncludeDIR = $$PWD/test/*.txt
DLL_DIR = $$PWD/TEST_1/*.dll
win32{
    SrcIncludeFile = $$replace(SrcIncludeDIR, /, \\)
    EXE_DIR = $$replace(EXE_DIR, /, \\)
    DLL_DIR = $$replace(DLL_DIR, /, \\)
}

# 拷贝方法1:QMAKE_PRE_LINK
QMAKE_PRE_LINK += $$QMAKE_COPY_DIR $$SrcIncludeFile $$EXE_DIR\\test # 注意路径中用\\
#QMAKE_PRE_LINK += && $$QMAKE_COPY_DIR $$DLL_DIR $$EXE_DIR\\TEST_1
QMAKE_POST_LINK += $$QMAKE_COPY_DIR $$DLL_DIR $$EXE_DIR\\TEST_1 # 测试POST

# 拷贝方法2:COPIES
cp_kk.files += $$PWD/test/*.txt
cp_kk.path  += $$OUT_PWD/$$compiled/copy
cp_dll.files += $$DLL_DIR
cp_dll.path  += $$EXE_DIR
COPIES +=  cp_kk cp_dll
COPIES += $$PWD/test/1.txt $$OUT_PWD/$$compiled

# 拷贝方法3:QMAKE_EXTRA_TARGETS
cp_ext.commands += $$QMAKE_COPY_DIR $$SrcIncludeFile $$EXE_DIR\\test
QMAKE_EXTRA_TARGETS += cp_ext
PRE_TARGETDEPS += cp_ext

# 拷贝方法4:
cp_et.input = $$PWD/test/1.txt
cp_et.output = $$OUT_PWD/$$compiled/copy
QMAKE_SUBSTITUTES += cp_et

主要的抽象包括:

  • 将拷贝的行为作为构建链中的什么步骤、什么环节:一般是link前后的一个命令、或者作为一个构建目标等。为使构建每次都能生效(因为拷贝文件可能会发生变更),较好的抽象是将拷贝动作作为一个单独的构建目标
  • 拷贝动作具体使用什么命令:最坏的做法是考虑具体平台的命令xcopycopy,比较好是使用QMake变量,如QMAKE_COPY_DIR,让其自行展开为特定平台的命令。比较特别的是COPIES,展开为$(QINSTALL).
  • 考虑最后展开成的命令其接收的参数:如果最后展开为特定平台的命令,那么其接收参数就要考虑特定平台的约束。其中最主要是传递的文件路径参数,windows平台需要使用反斜杠。可以使用replace进行替换。最方便的是$(QINSTALL),无需自行处理参数。

综上所述(省流):拷贝文件,使用COPIES贴上QMake代码:

1
2
3
cp_kk.files += $$PWD/test/*.txt
cp_kk.path  += $$OUT_PWD/$$compiled/copy
COPIES +=  cp_kk 

Reference

  1. (31条消息) QT .pro 拷贝文件—windows版本_qt pro 拷贝文件_明月清风-精进不止的博客-CSDN博客:成功的完整示例。
  2. QMAKE_COPY 不同平台对应的命令 - lsgxeva - 博客园 (cnblogs.com): QMAKE_COPY命令。
  3. 使用qmake编译时拷贝文件 - soso101 - 博客园 (cnblogs.com):拷贝的各种方式。

后言

写这篇博客的过程还是有不少感悟的,有一些一直的想法,比如:

  • 搞这个需求也不是一次两次了,但每次再碰到的时候,总觉得不太满意。明明看起来应该有更好的方案,但一直用不了,用的都是比较ugly的方法。所以一个问题重复出现以后,还是要刨根究底的搞清楚,形成自己的东西。
  • 当然了,形成自己的东西。按照我比较笨的理解能力,基本上等同于像写书一样一步一步记录的完整过程。特别是形成可归档化的博客,耗时确实是挺长的。

当然原有的想法一直如此,那学习过程也是这样一直下去。也有新的一些念头和总结:

  • 积累消化的过程,如果包括累次研究这个需求的时间,其实耗时是特别长的。相比于这个任务而言。本质上讲这篇博客基本是沿着Reference 3这篇博客的思路,包括有哪些方案,以及从Makefile角度深入了解的方向,写得已经很好了。从这个意义上面讲,这篇博客本质上没有什么创新和值得记录的。如果花费两分钟搜索找到前面这篇博客,然后就能回忆、找到这个解决方案的话。
  • 那还是耗时这么久去做这件事是为什么呢,是因为上面这篇博客可能是基于Unix平台写的,没有考虑Windows平台上路径斜杠的问题,导致部分代码在Windows平台是无法直接运行的。之前可能拷贝代码尝试了一下发现不生效就跳过了这篇博客。
  • 其实如果花费一点时间研究为什么不成功,再稍微多走一步,就已经可以快速解决这个问题。有很多通俗的道理描述这个问题,比如“行百里者半九十”。再比如程序员的深度优先和广度优先遍历。我太喜欢先把所有方案先列一遍,为了找一个最优解。但沿着一个方向搞透,太重要了。告诉自己:坚持、再坚持一下。
  • 另外是信源的可信度问题。在研究这个问题上,研究很多次了,多次去搜索、找解决方案。看了很多。以及最近AI出了,这个过程也直接问AI。作为解决问题、寻找解决方案的核心能力,快速的一套搜索、检验、测试、归纳的能力很重要。速度和质量总是要精巧的进行平衡。像这里的对比,上面那篇博客质量就很好,但是我追求速度,一旦检验到一点问题就快速抛弃了,其实另外作为cnblog上面的博客,作为信源应该重视一点。相反,在询问AI过程,得到答案的速度很快,但经常胡说八道(特别是在这个问题上),那检验的时间上限就要降低。
  • AI时代,成为工具的主人。追求一个目标,精准而优雅地解决,是能力的关键。
  • 吐槽:为什么用AI呢?因为Qt关于QMake的文档真的太少了…坑。
本文由作者按照 CC BY 4.0 进行授权

PDF目录生成

【工作记录】Qt+VS项目迁移时配置问题