Search code examples
qmake

qmake extra compiler with multiple outputs per file


As described in Undocumented qmake, I declared an extra compiler in my qmake project file:

TEST = somefile.h

test_c.output = ${QMAKE_VAR_OBJECTS_DIR}${QMAKE_FILE_BASE}_1.cpp
test_c.input = TEST
test_c.commands = C:/somedir/some.exe /i ${QMAKE_FILE_IN} /o ${QMAKE_VAR_OBJECTS_DIR}${QMAKE_FILE_BASE}_1.cpp
test_c.variable_out = SOURCES
test_c.name = MyTestC

QMAKE_EXTRA_COMPILERS += test_c

And this works fine. But I also want to generate a header file. I can easily make a second custom tool for parsing this file (or files, if >1 will be in TEST), but I don't want to parse each file twice. I tried:

test_c.output = ${QMAKE_VAR_OBJECTS_DIR}${QMAKE_FILE_BASE}_1.cpp \
    ${QMAKE_VAR_OBJECTS_DIR}${QMAKE_FILE_BASE}_2.cpp

Just to test that the extra compiler can make two files per run. I expected some error like "file somefile_2.cpp doesn't exist", but project compiles without errors and second output file is ignored. In Makefile somefile_2.cpp is not present.

Now I'm thinking about two variants:

  1. Make an extra compiler that produces an archive, where all needed output files will be saved at once. Set tool1.variable_out = TOOL_1_OUT, and add two more extra compilers with toolN.input = TOOL_1_OUT to just "unzip" the archived files (one per tool) and append them to some variables.

    In this case three executes will be called per one input file. This is not optimal, but at least the parser will run only once per file.

  2. Experiment with the .output_function option. Make a qmake function that returns the same name as .output now does, but also append second filename to HEADERS.

P.S. I am using MinGW x32 4.7, QtCreator 2.7.1, Qt 5.1.0, C++11.


Solution

  • This works ok (variant #1):

    MY_COMP = src/precompiled.h \
        src/file2.h
    
    GENERATE_FOLDER = generated/
    
    # build package file
    my_build.clean = $${GENERATE_FOLDER}gen_${QMAKE_FILE_BASE}.pack
    my_build.depends = [somepath]/my_precompiler.exe
    my_build.output = $${GENERATE_FOLDER}gen_${QMAKE_FILE_BASE}.pack
    my_build.input = MY_COMP
    my_build.commands = [somepath]/my_precompiler.exe /i ${QMAKE_FILE_IN} /o $${GENERATE_FOLDER}gen_${QMAKE_FILE_BASE}.pack
    my_build.variable_out = MY_PACKAGES
    my_build.name = "package build"
    
    # unpack cpp
    my_unpack_cpp.clean = $${GENERATE_FOLDER}${QMAKE_FILE_BASE}.cpp
    my_unpack_cpp.depends = $${GENERATE_FOLDER}${QMAKE_FILE_BASE}.h
    my_unpack_cpp.output = $${GENERATE_FOLDER}${QMAKE_FILE_BASE}.cpp
    my_unpack_cpp.input = MY_PACKAGES
    my_unpack_cpp.commands = [somepath]/my_precompiler.exe /unpack cpp /i ${QMAKE_FILE_IN} /o $${GENERATE_FOLDER}${QMAKE_FILE_BASE}.cpp
    my_unpack_cpp.variable_out = GENERATED_SOURCES
    my_unpack_cpp.dependency_type = TYPE_C
    my_unpack_cpp.name = "unpack code"
    my_unpack_cpp.CONFIG = no_link
    
    # unpack header
    my_unpack_h.clean = $${GENERATE_FOLDER}${QMAKE_FILE_BASE}.h
    my_unpack_h.output = $${GENERATE_FOLDER}${QMAKE_FILE_BASE}.h
    my_unpack_h.input = MY_PACKAGES
    my_unpack_h.commands = [somepath]/my_precompiler.exe /unpack h /i ${QMAKE_FILE_IN} /o $${GENERATE_FOLDER}${QMAKE_FILE_BASE}.h
    my_unpack_h.variable_out = HEADERS
    my_unpack_h.name = "unpack header"
    my_unpack_h.CONFIG = no_link
    
    QMAKE_EXTRA_COMPILERS += my_build my_unpack_h my_unpack_cpp
    

    With this technique number of output files per one parse may vary, but may be constant for all files in project, of course.

    In my_precompiler I parse file if unpack option isn't preserved and build two files (cpp + h) into two QBuffers. After that I simply write builded data to QFile:

    QDataStream ds(&file);
    ds.setVersion(QDataStream::Qt_5_1);
    
    ds << qCompress(output_cpp.data(), 9);
    ds << qCompress(output_h.data(), 9);
    
    file.close();
    

    In fact, now qCompress isn't profitable, because generated files too small to compression size exceeded the size of the headers zlib - sizeof(.pack) > size(.h + .h).

    Unpacking:

    QByteArray ba;
    
    QDataStream ds(&file);
    ds.setVersion(QDataStream::Qt_5_1);
    
    ds >> ba;
    
    if(unpack != "cpp")
    {
     ds >> ba;
    }
    file.close();  
    
    ba = qUncompress(ba);
    
    file.setFileName(output);
    if(!file.open(QFile::WriteOnly | QFile::Truncate)) return 1;
    
    file.write(ba);
    file.close();
    

    When generating:

    1. Write #include "original header" in begin of generated header
    2. Write #include "generated header" in begin of generated code

    Therefore I set this:

    my_unpack_cpp.depends = $${GENERATE_FOLDER}${QMAKE_FILE_BASE}.h
    

    So /unpack cpp (and, therefore, building) performs after building needed header file. And this:

    my_build.depends = [somepath]/my_precompiler.exe
    

    Sets builded pack (and, therefore, generated cpp+h) depends on my_precompiler, so all will be rebuilded if I modify and rebuild precompiler.

    P.S. IMHO these lines must works as cleaners before rebuilding:

    my_build.clean = $${GENERATE_FOLDER}gen_${QMAKE_FILE_BASE}.pack
    my_unpack_cpp.clean = $${GENERATE_FOLDER}${QMAKE_FILE_BASE}.cpp
    my_unpack_h.clean = $${GENERATE_FOLDER}${QMAKE_FILE_BASE}.h
    

    But they don't :( At present I ignore that, but now if building .pack is failed than previously builded pack-file is used