Adding custom commands to existing targets in qmake

Jason C picture Jason C · Apr 24, 2015 · Viewed 7.5k times · Source

Is there a way to specify, in a .pro file, extra commands to be added to a standard target in the Makefile that qmake generates? For example, consider distclean, extra commands might be desired to:

  • Remove *~ files.
  • Clean out runtime-generated output files from the source tree.
  • Etc.

I want to use the normal target and not a custom target because I want this to be completely transparent in my workflow. That is (again using distclean as an example), I don't want to...

  • ... require knowledge in a multi-project setup that certain Makefiles use a custom rule instead of distclean.
  • ... document custom rules, even for stand-alone projects, as distclean is already well-known and intuitive.

I found How to add custom targets in a qmake generated Makefile?, but this describes adding custom targets (which is already documented, even back in 4.6) rather than adding rules to existing targets. While it does contain some hints, all of them require adding new custom targets, as specifying the same target more than once in a Makefile replaces (not adds) commands from the previous target.

The only thing I could really think of to try was to add target.commands += new commands to the .pro file as a wild guess (e.g distclean.commands += rm \"*~\"). This has no effect.

How can I transparently add custom commands to existing targets with qmake?


For the distclean example: While maintainer-clean is also on that "standard target" list, in practice I have found it to be rarely used, and in any case qmake doesn't generate it by default; I consider it to be unsuitable.

Answer

Jason C picture Jason C · Apr 24, 2015

There are two straightforward ways to accomplish this, depending on how self-contained / portable you want your solution to be and how lenient you want to be with the order of command execution.


Option 1

The first option is to create a custom target in the .pro file for the new commands, then add that target as a prerequisite to the standard target that you are modifying. Going back to the distclean example, let's say you want to add a command to remove all *~ files:

  1. Create a custom target in your .pro file. Note that you have to escape quotes and slashes in .pro files. For example, add:

    extraclean.commands = find . -name \"*~\" -exec rm -v {} \\;
    
  2. Add this target as a dependency of the target you are modifying:

    distclean.depends = extraclean
    

    This won't actually modify the distclean rule just yet, as this method can't be used to modify existing rules. However...

  3. Add both your new target and the target you are modifying as extra targets:

    QMAKE_EXTRA_TARGETS += distclean extraclean
    

    This will add a second specification of distclean to the Makefile, but this works because you can add dependencies to existing targets in make in separate rules, even though you can't add commands that way. If you were to also specify distclean.commands in your .pro file, you would break the existing distclean by replacing its default recipe.

So, putting that all together, in the .pro file:

extraclean.commands = find . -name \"*~\" -exec rm -v {} \\;
distclean.depends = extraclean
QMAKE_EXTRA_TARGETS += distclean extraclean

Where extraclean is some custom target with the commands you want to add, and distclean is the existing target that you wish to modify.

Pros:

  • Completely self-contained in a .pro file.
  • As portable as you can get, leaves the actual Makefile syntax and generation up to qmake.

Cons:

  • Your new commands aren't appended to the existing recipe. Rather, they happen after all prerequisite targets are satisfied but before the existing recipe. In the distclean example, with the version of qmake that I'm using, this places the commands after the source tree clean but before Makefile itself is deleted (which is the only action the default recipe takes). This is not an issue for this example, but may be an issue for you.

Option 2

The second option is to change the name of the Makefile that qmake generates, and create your own custom Makefile that defers to the generated one, rather than includes + overrides it. This is also a straightforward option; while not as self-contained as option 1, it gives you the ability to execute commands both before and after the default generated recipe.

You don't want to include + override the existing Makefile, because you don't want to replace the default recipes. If you do, you have to re-implement the default, but this can be an issue as that default may change (and you have to keep up with the changes). It's best to let qmake do as much work as possible, and not repeat its work.

To do this:

  1. First, change the name of the file that qmake generates. This can be accomplished by adding a line such as this to the .pro file:

    MAKEFILE = RealMakefile
    

    That will cause qmake to output RealMakefile instead of Makefile.

  2. The next step is to create your own Makefile with your custom commands. However, there are some caveats here. First, a full example, again using distclean. In a file named Makefile:

    .DEFAULT_GOAL := all
    
    %:
        @$(MAKE) -f RealMakefile $@
    
    distclean:
        @$(MAKE) -f RealMakefile $@ 
        @find . -name "*~" -exec rm -v {} \;
    

    Some notes about this:

    • We set .DEFAULT_GOAL because otherwise distclean would be the default. An alternative to this, if you're not comfortable with .DEFAULT_GOAL, is to specify an all rule using @$(MAKE) -f RealMakefile $@ as the recipe.
    • The % target matches any target that isn't otherwise defined in this Makefile. It simply delegates processing to RealMakefile.
    • The distclean target is where we add our commands. We still need to delegate to RealMakefile, but additional commands can be added both before and after that happens.

Pros:

  • More control over command order. Commands can be added both before and after the default recipe.

Cons:

  • Not self-contained in a .pro.
  • Not as portable: It doesn't leave all Makefile generation up to qmake, and also I'm not actually sure what parts are specific to GNU make here (comments welcome).

So, while this answer may be a little long, both of these methods are very straightforward. I would prefer option 1 unless the command execution order is an issue.