3

I'm using a custom ressource management (in replacement to qrc) and I'm trying to integrate it to QtCreaor.

I have a Python script that generates a source file to be compiled. I use QMAKE_EXTRA_TARGETS/PRE_TARGETDEPS to tell QMake that this script must be executed before files are compiled. So I do, in my pro file:

CONFIG += ordered

generated_file.target   = my_custom_target
generated_file.commands = echo "Generating..." && d:/dev/vobs_ext_2015/tools_ext/python/Python34_light/python.exe $$PWD/pyc_res_generator.py -o $$PWD/generated/generated.cpp && echo "Generated!"
generated_file.depends  = FORCE

QMAKE_EXTRA_TARGETS            += generated_file

PRE_TARGETDEPS                 += my_custom_target

SOURCES                        += generated/generated.cpp

#DEPENDPATH = ./generated

With pyc_res_generator.py simply being:

#! /usr/bin/env python
# -*- coding: utf8 *-*

import argparse

parser = argparse.ArgumentParser(description="", formatter_class=argparse.RawTextHelpFormatter)
parser.add_argument("-o", type=str, help="Output file name")

args = parser.parse_args()

with open(args.o, 'w') as output_file:
    output_file.write( "// This is valid C++!" )

To test this, I write some invalid C++ in generated.cpp (like fjkfkfk). When I compile (target is Android), I see in the log:

echo Generating... && python.exe pyc_res_generator.py -o generated/generated.cpp && echo Generated!
Generating... 
B:\Android\android-ndk-r11b/toolchains/arm-linux-androideabi-4.9/prebuilt/windows-x86_64/bin/arm-linux-androideabi-g++ -c -Wno-psabi -march=armv7-a -mfloat-abi=softfp -mfpu=vfp -ffunction-sections -funwind-tables -fstack-protector -fno-short-enums -DANDROID -Wa,--noexecstack -fno-builtin-memmove -std=c++11 -g -g -marm -O0 -fno-omit-frame-pointer -Wall -Wno-psabi -W -D_REENTRANT -fPIC -DQT_QML_DEBUG -DQT_WIDGETS_LIB -DQT_GUI_LIB -DQT_CORE_LIB -I..\TestQt -I. -IB:\QtCreator5_6_1\5.6\android_armv7\include -IB:\QtCreator5_6_1\5.6\android_armv7\include\QtWidgets -IB:\QtCreator5_6_1\5.6\android_armv7\include\QtGui -IB:\QtCreator5_6_1\5.6\android_armv7\include\QtCore -I. -isystem B:\Android\android-ndk-r11b\sources\cxx-stl\gnu-libstdc++\4.9\include -isystem B:\Android\android-ndk-r11b\sources\cxx-stl\gnu-libstdc++\4.9\libs\armeabi-v7a\include -isystem B:\Android\android-ndk-r11b\platforms\android-9\arch-arm\usr\include -IB:\QtCreator5_6_1\5.6\android_armv7\mkspecs\android-g++ -o main.obj ..\TestQt\main.cpp
B:\Android\android-ndk-r11b/toolchains/arm-linux-androideabi-4.9/prebuilt/windows-x86_64/bin/arm-linux-androideabi-g++ -c -Wno-psabi -march=armv7-a -mfloat-abi=softfp -mfpu=vfp -ffunction-sections -funwind-tables -fstack-protector -fno-short-enums -DANDROID -Wa,--noexecstack -fno-builtin-memmove -std=c++11 -g -g -marm -O0 -fno-omit-frame-pointer -Wall -Wno-psabi -W -D_REENTRANT -fPIC -DQT_QML_DEBUG -DQT_WIDGETS_LIB -DQT_GUI_LIB -DQT_CORE_LIB -I..\TestQt -I. -IB:\QtCreator5_6_1\5.6\android_armv7\include -IB:\QtCreator5_6_1\5.6\android_armv7\include\QtWidgets -IB:\QtCreator5_6_1\5.6\android_armv7\include\QtGui -IB:\QtCreator5_6_1\5.6\android_armv7\include\QtCore -I. -isystem B:\Android\android-ndk-r11b\sources\cxx-stl\gnu-libstdc++\4.9\include -isystem B:\Android\android-ndk-r11b\sources\cxx-stl\gnu-libstdc++\4.9\libs\armeabi-v7a\include -isystem B:\Android\android-ndk-r11b\platforms\android-9\arch-arm\usr\include -IB:\QtCreator5_6_1\5.6\android_armv7\mkspecs\android-g++ -o dialog.obj ..\TestQt\dialog.cpp
B:\Android\android-ndk-r11b/toolchains/arm-linux-androideabi-4.9/prebuilt/windows-x86_64/bin/arm-linux-androideabi-g++ -c -Wno-psabi -march=armv7-a -mfloat-abi=softfp -mfpu=vfp -ffunction-sections -funwind-tables -fstack-protector -fno-short-enums -DANDROID -Wa,--noexecstack -fno-builtin-memmove -std=c++11 -g -g -marm -O0 -fno-omit-frame-pointer -Wall -Wno-psabi -W -D_REENTRANT -fPIC -DQT_QML_DEBUG -DQT_WIDGETS_LIB -DQT_GUI_LIB -DQT_CORE_LIB -I..\TestQt -I. -IB:\QtCreator5_6_1\5.6\android_armv7\include -IB:\QtCreator5_6_1\5.6\android_armv7\include\QtWidgets -IB:\QtCreator5_6_1\5.6\android_armv7\include\QtGui -IB:\QtCreator5_6_1\5.6\android_armv7\include\QtCore -I. -isystem B:\Android\android-ndk-r11b\sources\cxx-stl\gnu-libstdc++\4.9\include -isystem B:\Android\android-ndk-r11b\sources\cxx-stl\gnu-libstdc++\4.9\libs\armeabi-v7a\include -isystem B:\Android\android-ndk-r11b\platforms\android-9\arch-arm\usr\include -IB:\QtCreator5_6_1\5.6\android_armv7\mkspecs\android-g++ -o generated.obj ..\TestQt\generated\generated.cpp
B:\QtCreator5_6_1\5.6\android_armv7\bin\moc.exe -DQT_QML_DEBUG -DQT_WIDGETS_LIB -DQT_GUI_LIB -DQT_CORE_LIB -IB:/QtCreator5_6_1/5.6/android_armv7/mkspecs/android-g++ -IC:/Users/jp225611/Documents/TestQt -IB:/QtCreator5_6_1/5.6/android_armv7/include -IB:/QtCreator5_6_1/5.6/android_armv7/include/QtWidgets -IB:/QtCreator5_6_1/5.6/android_armv7/include/QtGui -IB:/QtCreator5_6_1/5.6/android_armv7/include/QtCore -I. -IB:\Android\android-ndk-r11b/sources/cxx-stl/gnu-libstdc++/4.9/include -IB:\Android\android-ndk-r11b/sources/cxx-stl/gnu-libstdc++/4.9/libs/armeabi-v7a/include -IB:\Android\android-ndk-r11b/platforms/android-9/arch-arm//usr/include ..\TestQt\dialog.h -o moc_dialog.cpp
..\TestQt\generated\generated.cpp:1:1: error: 'fjkfkfk' does not name a type
   // This is valid C++!
   ^
makefile:670: recipe for target 'generated.obj' failed
mingw32-make: *** [generated.obj] Error 1
mingw32-make: *** Waiting for unfinished jobs....
Generated!
13:59:08: Le processus "B:\QtCreator5_6_1\Tools\mingw492_32\bin\mingw32-make.exe" s'est terminé avec le code 2.
Erreur lors de la compilation/déploiement du projet TestQt (kit : android_armeabi-v7a)
When executing step "Make"
13:59:08: Temps écoulé : 00:03.

And I see that generated\generated.cpp was correctly generated by pyc_res_generator.py (it now contains // This is valid C++!)...but apparently too lately as the compiler saw fjkfkfk... As you can see, the output reports:

Generating...
B:\Android\android-ndk-r11b/toolchains/arm-linux-androideabi-4.9/prebuilt/windows-x86_64/bin/arm-linux-androideabi-g++ .... ..\TestQt\generated\generated.cpp
Generated...

While one would expect:

Generating...
Generated...
B:\Android\android-ndk-r11b/toolchains/arm-linux-androideabi-4.9/prebuilt/windows-x86_64/bin/arm-linux-androideabi-g++ .... ..\TestQt\generated\generated.cpp

This only occurs when parallel builds are enabled (I set MAKEFLAGS environment variable to '-j8`)

It looks like my custom command generated_file is started before generated\generated.cpp is compiled, but the system does not wait for it to end before actually compiling generated\generated.cpp.

Am I doing something wrong?

jpo38
  • 20,821
  • 10
  • 70
  • 151
  • https://stackoverflow.com/q/15864689/17034 – Hans Passant May 29 '18 at 12:55
  • Quote: "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...". As you found out. – Hans Passant May 29 '18 at 13:15
  • @HansPassant: Just tested that, but the difference is that the linked post was generating a header file, then `DEPENDPATH` apparently helps, in my case, I'm generating a source file, and I could not make it behave as expected even when using `DEPENDPATH`. – jpo38 May 29 '18 at 13:30
  • Note that I filled Qtbug https://bugreports.qt.io/browse/QTBUG-68553 – jpo38 May 30 '18 at 13:05

3 Answers3

3

PRE_TARGETDEPS (custom targets) is not the good approach here as generated_file.target must match exactly the Makefile target name which may be vary on different options:

  • shadow build enabled or not
  • host platform (using slashs or backslash as a folder separator)
  • probably targetted compiler/platform

However, custom compiler apparently works much better. That's how moccing files is done. A custom compiler can generate a new file to be compiled (and then the system waits for the file to be generated before compiling it, even when performing parallel builds).

# Set fuiles to be generated in a variable
TO_GENERATE = $$PWD/generated/generated.cpp

# Specify custom command output file:
custom_generator.output  = $$PWD/generated/generated.cpp
# Specify custom command:
custom_generator.commands = 'echo "Generating..." && python $$PWD/pyc_res_generator.py -o $$PWD/generated/generated.cpp && echo "Generated..."'
# dependency:
custom_generator.depends = FORCE
# link to input file variable
custom_generator.input = TO_GENERATE
# link to variable to store generated file to
custom_generator.variable_out = SOURCES
# add to qmake:
QMAKE_EXTRA_COMPILERS += custom_generator

Then, you don't even need to specify SOURCES += $$PWD/generated/generated.cpp. And also, $$PWD/generated/generated.cpp gets deleted upon clean!

This works perfectly as expected!

jpo38
  • 20,821
  • 10
  • 70
  • 151
  • OMG!!! This is wonderful! I've been banging my head on the wall ALL DAY trying to generate a RESOURCE in a pre-build step via the `PRE_TARGETDEPS` approach. My original QMake worked fine under other builds conditions, but then I ran the process in a new environment and started encountering race conditions. This approach solved it!! I wish I could upvote 10 times. – BuvinJ Aug 17 '23 at 00:35
  • @BuvinJ: Just create 10 stackoverflow acounts... ;-) Glad it helped you! – jpo38 Aug 17 '23 at 09:01
0

You need to set the name of the target to the name of the file that it generates. Here's a full project file that works for me (qmake 3.1, Qt 5.10.1):

# Filename relative to the *build* directory.
generated_file.target   = generated/generated.cpp
generated_file.commands = 'echo "Generating..." && mkdir -p generated && echo "int main() {}" > generated/generated.cpp'
generated_file.depends  = FORCE

QMAKE_EXTRA_TARGETS     += generated_file

# Filename relative to the *source* directory.
SOURCES                 += $$OUT_PWD/generated/generated.cpp

The reason is that generated_file.target becomes the name of the target in the Makefile, so when generated.cpp is needed, your custom rule will be executed to create the file.

You can also remove it from PRE_TARGETDEPS, because you're already adding the generated file to SOURCES instead, which creates the proper dependency.

Note that this approach generates the file in the build directory, not the source directory. This is why you need to specify $$OUT_PWD when adding it to SOURCES: by default, qmake seems to assume that SOURCES are relative to the source directory.

If you want to generate it in the source directory instead, you might think that you just prefix $$PWD everywhere, but then qmake sometimes uses absolute paths and sometimes relative, and everything fails. You can probably work around that with $$absolute_path($$PWD) but I haven't tried.

Relevant sections from the generated Makefile (comments are mine):

# How to create generated/generated.cpp.
# Depending on FORCE to make it always stale (fails if you create a file named FORCE :)).
generated/generated.cpp: FORCE
    echo "Generating..." && mkdir -p generated && echo "int main() {}" > generated/generated.cpp

# generated.o depends on generated/generated.cpp.
# Note that it's not in the same directory; we don't care.
generated.o: generated/generated.cpp 
    $(CXX) -c $(CXXFLAGS) $(INCPATH) -o generated.o generated/generated.cpp

# The final executable depends on the compiled generated.o.
OBJECTS = generated.o
$(TARGET):  $(OBJECTS)  
    $(LINK) $(LFLAGS) -o $(TARGET) $(OBJECTS) $(OBJCOMP) $(LIBS)

The qmake documentation is pretty vague about this, only stating that target should be "The name of the custom build target.". However, the example shows that they're setting it to the name of the output file. (As a bonus, it also shows how to not repeat that name in the commands.)

Thomas
  • 174,939
  • 50
  • 355
  • 478
  • Thanks for your help. But if I do `generated_file.target = $$PWD/generated/generated.cpp`, then the custom command does not get invoked anymore (with or without `PRE_TARGETDEPS` statement). The file content remains `fjkfkfk`.... – jpo38 May 29 '18 at 15:59
  • Try `SOURCES += $$PWD/generated/generated.cpp` just to make sure the file's location is consistent. Inspect the generated `Makefile` if in doubt. – Thomas May 29 '18 at 16:10
  • I tried that. Used `$$PWD/generated/generated.cpp` for `generated_file.target`, `PRE_TARGETDEPS` and `SOURCES`: same behaviour. Also tried to remove `PRE_TARGETDEPS`, but then Python is not invoked. Also tried to remove `SOURCES` but then `generated.cpp` does not get compiled...so this did not help, unfortunately. – jpo38 May 30 '18 at 12:46
  • In the Makefile, I see instructions to compile `generated/generated.cpp` and to call `python`, but I see no rule to establish a sort of dependency between them.... – jpo38 May 30 '18 at 12:49
  • Sorry, yes, it's a bit more complicated. Updated my answer. – Thomas May 30 '18 at 15:11
  • Your stuff works, but it relies on build options. It works when "shadow build" is unchecked (build done in $$PWD), but if shadow build is enabled (build done in a $$PWD subfolder), then the custom command is not executed anymore. Replacing `generated_file.target = generated/generated.cpp` by `generated_file.target = ../generated/generated.cpp`, then it's executed again (but won't if you disable shadow build back). Tried `generated_file.target = $$OUT_PWD/generated/generated.cpp` without success (custom command not ran). Any idea how we can make this be independent of build options? – jpo38 May 30 '18 at 18:31
  • As you mentioned, it looks like `generated_file.target` must be the exact string used in the Makefile (sometimes `../generated/generated.cpp`, sometimes `generated/generated.cpp`...or maybe fullfapth later...). So we need to find a way to get this exact string to setup `generated_file.target` correctly. – jpo38 May 30 '18 at 18:42
  • Looks like `generated_file.target = $$relative_path($$PWD/generated/generated.cpp,$$OUT_PWD)` works fine both with and without shadow build.....but I'm afraid it may start failing when akefile syntax will change in the future... – jpo38 May 30 '18 at 19:06
  • Unfortunately, I could not make that work. As I mentioned, the string specified as `generated_file.target` must match exactly the string used after `generated.obj:` in the Makefile. And it's really hard to guarantee that for all targets with or without shadow build (as path may have extra `..` but also one may use slashs while the other may use backslashes). Per Qt team, it looks like the good approach is to use extra compiler rather than extra target.... – jpo38 May 31 '18 at 06:56
  • Hmm, I'm pretty sure I tested this both with and without shadow build. Using a custom compiler is another viable alternative that I've used successfully for this kind of code generation. – Thomas May 31 '18 at 07:14
  • Could make it work by using QMAKE_EXTRA_COMPILERS, see my answer. Thanks for your time, it really helped to identify why `QMAKE_EXTRA_TARGETS` were not the good idea ;-) – jpo38 May 31 '18 at 08:34
0

An alternative relies on the TEMPLATE=subdirs, to honour libname.depends in a parallel build environment. You would need two .pro files in each application folder. And one in the shared library folder. Any other folders in your development area that use TEMPLATE=subdirs need to be made compatible.

eg TestApp using LibLarry.

Folder TestApp has bld.pro and TestApp.pro.

TestApp.pro:

#This is a TEMPLATE=subdirs, which gmake will interpret;  
# making sure the build dependencies are respected
TEMPLATE = subdirs
SUBDIRS += bld  mylib
bld.file = bld.pro
bld.depends = mylib # force library to be built first.
mylib.file = $${PATH_TO_LIB_LARRY}/liblarry.pro

bld.pro:

TARGET=TestApp
SOURCES+=TestApp.cpp
PRE_TARGETDEPS+=$${PATH_TO_LIB_LARRY}/liblarry.a

#boilerplate...
QT -= gui
CONFIG = ...
include ($${PATH_TO_LIB_LARRY}/liblarry.pri) # contains include file info
CONFIG += link_prl # allow lib larry to specify its position in the linker input command.

Folder LibLarry has LibLarry.pro containing

LibLarry.pro:

TARGET=larry
TEMPLATE=lib
CONFIG+=staticlib 
CONFIG+=create_prl # export information, so apps can link correctly
SOURCES+=my_shared_function.cpp
include (liblarry.pri)

liblarry.pri:

HEADERS += $$PWD/my_shared_function.h
INCLUDEPATH += $$PWD
Rumex
  • 1