29

There are several questions on SO regarding how to create a pre-build step for qmake, I can do that with this in my .pro file:

versionTarget.target = ../VersionData/versioning.h
versionTarget.depends = FORCE
win32: versionTarget.commands = cd $$PWD; python.exe ./version_getter.py -p $$TARGET
else:  versionTarget.commands = cd $$PWD; python ./version_getter.py -p $$TARGET

PRE_TARGETDEPS += ../VersionData/versioning.h
QMAKE_EXTRA_TARGETS += versionTarget

Now, the problem is that this approach is not a build step per se but just another build target, so if I have the -j flag configured for make it runs my script in parallel with the other build jobs. This is very bad, because my script creates/updates a header file - having it change part way through the compilation is not acceptable.

So, is there anyway I can have this script executed before any compilation is ran? I know I can create another script and call the version_getter.py and qmake in sequence from that, but this is not desirable as I would have to compile from the command line rather than from within Qt Creator.


Update

The complete .pri file that is included by every one of my sub-projects is below:

CONFIG += thread
QT += core \
      gui

versionTarget.target = ../VersionData/versioning.h
versionTarget.depends = FORCE
win32: versionTarget.commands = cd $$PWD; python.exe ./version_getter.py -p $$TARGET
else:  versionTarget.commands = cd $$PWD; python ./version_getter.py -p $$TARGET

PRE_TARGETDEPS += ../VersionData/versioning.h
QMAKE_EXTRA_TARGETS += versionTarget

DEPENDPATH += ../VersionData
INCLUDEPATH += ../VersionData
HEADERS += ../VersionData/versioning.h

UI_HEADERS_DIR = $${_PRO_FILE_PWD_}/include/Qui
DESTDIR = $(SYREN_PATH)

!win32-msvc {
    QMAKE_CXXFLAGS += -std=c++0x
}

But this still results in the same parallel behaviour. I thought it may have been due to my use of ccache, but turning it off made no difference (other than being much slower of course).

cmannett85
  • 21,725
  • 8
  • 76
  • 119

4 Answers4

14

Another option would be to start with the project file snippet in your original question, and also ensure that qmake is aware that versioning.h is a dependency for the other build targets in your project file —

  • Add the full path to versioning.h to your HEADERS variable.
  • Add the folder in which versioning.h resides to your DEPENDPATH variable.

(Caveat: if you run qmake when versioning.h doesn't exist, it will emit "WARNING: Failure to find: versioning.h" — the only workaround for that warning is to use the system() command, as I described in my other answer.)

Example

Create test.pro containing the following:

versionTarget.target = ../versioning.h
versionTarget.depends = FORCE
versionTarget.commands = sleep 5s ; touch ../versioning.h
PRE_TARGETDEPS += ../versioning.h
QMAKE_EXTRA_TARGETS += versionTarget

SOURCES = test.c
HEADERS = ../versioning.h
DEPENDPATH = ..

Create test.c containing the following:

#include "../versioning.h"

Run qmake. It will output WARNING: Failure to find: ../versioning.h.

Run make -j9. It will run versionTarget.commands (which sleeps for 5 seconds to exaggerate any multiprocessing problems), and, after that is done, run the command to compile test.c.

(And if you examine the generated Makefile, you'll see that test.o depends on both test.c and ../versioning.h, so Make should correctly figure out that it can't run the command to compile test.c before the command to create/update ../versioning.h.)

Community
  • 1
  • 1
smokris
  • 11,740
  • 2
  • 39
  • 59
  • Would you mind posting an example? I added `DEPENDPATH += ../VersionData HEADERS += ../VersionData/versioning.h` after the code I posted in the question and the behaviour is no different. – cmannett85 Apr 08 '13 at 14:46
  • It works perfectly the first time, but building immediately afterwards causes it to run in parallel again. I tried adding a `system(touch ../versionData/versioning.h)` to 'trick' `make` into thinking it has changed, but it's not that stupid... I think I may have to concede defeat with `qmake` and use your first answer! – cmannett85 Apr 08 '13 at 16:48
  • I've updated my question with my complete `.pri`, can you spot anything stupid with it? I `include "versioning.h"` in the source file of an 'About' dialog. – cmannett85 Apr 08 '13 at 17:15
  • Only the about dialog object file has that dependency, would I need to add it to every source file!? – cmannett85 Apr 08 '13 at 17:38
  • Well, you might not have a problem, then. If you do what I described in this answer, and if all the source files that need to use the values defined in `versioning.h` actually include that header file, then `qmake` should automatically figure out dependencies and generate a Makefile reflecting those dependencies. `make` may choose to build some targets that don't depend on `versioning.h` in parallel with `versioning.h`. – smokris Apr 08 '13 at 18:12
  • Of course! I was being an idiot, `make` has outsmarted me. Again. – cmannett85 Apr 08 '13 at 19:16
  • I get the warning you mention and I realised that it is because qmake is generating the makefile wrongly. When the dependency is specified the path is in the form d:\Work\...\version.h and when the rule is specified it is in the form D:/Work/.../version.h. Obviously these don't match. If I manually edit the makefile the warning goes away. – AlastairG Dec 06 '17 at 08:40
8

Use the system() qmake command — it runs when you run qmake, which happens before make runs any build commands.

win32: PYTHON=python.exe
else:  PYTHON=python
system(cd $$PWD; $$PYTHON ./version_getter.py -p ../VersionData/versioning.h)
smokris
  • 11,740
  • 2
  • 39
  • 59
  • 2
    There's a serious problem with this approach, because `qmake` is ran for clean builds and even if there are no changes. This is why using scripts as targets is promoted. – cmannett85 Apr 08 '13 at 08:06
  • system() worked for me. Under Windows using Qt 5.9.2 I could not get my pre-build scripts to execute using PRE_TARGETDEPS. However, PRE_TARGETDEPS worked fine under macOS target builds. The problem with calling pre-build script from system() is that it will not run after doing a clean and I remove my auto-generated version.h. It only runs when qmake is run. – Ed of the Mountain Oct 13 '17 at 12:29
  • smokris: Your example doesn't work – you have to separate commands with `&&`, not a semicolon. – Andrej Repiský May 22 '19 at 07:34
0

I noticed that if you inspect the Makefile produced by qmake there is always the first Makefile rule named "first" that depends on another rule debug (or release) that contains the instructions for the build. Id est, something like this :

...
MAKEFILE      = Makefile
first: debug
...

In order to create a pre-build step, we should hack that rule to depend on another with higher priority.

Something like

...
MAKEFILE      = Makefile
first: prebuild debug
prebuild:
    do_your_instructions
...

This would actually be equal to something like this:

...
MAKEFILE      = Makefile
first: debug
...
first: prebuild 
prebuild:
    do_your_instructions
...

which could be easily hacked in a qmake project by doing:

# $$PWD/test_prebuild is a batch with the instructions to execute before every build
!build_pass:prebuild.commands = $$PWD/test_prebuild
!build_pass:first.depends = prebuild
QMAKE_EXTRA_TARGETS += prebuild first

Please note that "!build_pass:" ensures that you write this prebuild rule only in Makefile (and not in Makefile.Debug or Makefile.Release) preventing the execution of test_prebuild multiple times. Please note that this is hack is possible because "first" is not reserved (though is names a qmake primitive.

In my case it worked fine: I hope this trick could help others as well.

pgallir
  • 1
  • 1
  • I also noticed that, due to the way qmake builds the makefile, if the rule is named FORCE instead of first the commands are run not only before any build phase, but also after any regeneration of the makefile – pgallir Sep 09 '19 at 13:23
0

There is a better solution to this! You can leverage QMAKE_EXTRA_COMPILERS to force a synchronous prebuild operation, and this works when using parallel compilation (make -j flag)! I can't take credit for this solution, I found it at this SO post: https://stackoverflow.com/a/50619989/3220983

BuvinJ
  • 10,221
  • 5
  • 83
  • 96