The London Perl and Raku Workshop takes place on 26th Oct 2024. If your company depends on Perl, please consider sponsoring and/or attending.

NAME

QMake::Project - perl interface to qmake .pro file(s)

SYNOPSIS

use QMake::Project;

# Load a project from a qmake-generated Makefile
my $prj = QMake::Project->new( 'Makefile' );

# Perform arbitrary tests; may be anything usable from a qmake scope
my $testcase = $prj->test( 'testcase' );
my $insignificant = $prj->test( 'insignificant_test' );

# Retrieve arbitrary values (scalars or lists)
my $target = $prj->values( 'TARGET' );

return unless $testcase;

my $status = system( $target, '-silent' );
return unless $status;
if ($insignificant) {
    warn "Test $target failed; ignoring, since it is insignificant";
    return;
}
die "Test $target failed with exit status $status";

Given a qmake-generated Makefile, provides an API for accessing any qmake variables or tests (scopes).

DESCRIPTION

For projects using qmake, .pro files are a convenient place to include all sorts of metadata. However, making that metadata robustly readable by tools other than qmake has been rather challenging. Typically the data is only able to flow in one direction: if some tool outside of the build system wants to access build system metadata, then qmake or some .prf files must be modified to explicitly export that data. General programmatic access has not been possible.

This module aims to solve this problem, allowing robust and correct reading of metadata from qmake project files without requiring any changes to qmake.

HOW IT WORKS

The qmake language is undefined, and there is no library form of qmake. This means that only qmake (the binary) can parse qmake (the language). Therefore, this module does not actually parse any qmake .pro files itself. qmake does all the parsing.

Values are resolved roughly using a process like the following:

  • The given makefile is used to determine the correct qmake command, arguments and .pro file for this test.

  • A temporary .pro file is created containing the content of the real .pro file, as well as some additional code which outputs all of the requested variables / tests.

  • qmake is run over the temporary .pro file. The Makefile generated by this qmake run is discarded. The standard output of qmake is parsed to determine the values of the evaluated variables/tests.

At a glance, it may seem odd that this package operates on Makefiles (qmake's output) rather than .pro files (qmake's input). In fact, there is a good reason for this.

Various context affects the behavior of qmake, including the directory containing the .pro file, the directory containing the Makefile, the arguments passed by the user, the presence of a .qmake.cache file, etc. The Makefile encapsulates all of this context.

DELAYED EVALUATION

Running qmake can be relatively slow (e.g. a few seconds for a cold run), and therefore the amount of qmake runs should be minimized. This is accomplished by delayed evaluation.

Essentially, repeated calls to the test or values functions may not result in any qmake runs, until one of the values returned by these functions is actually used. This is accomplished by returning blessed values with overloaded conversions.

For example, consider this code:

my $project = QMake::Project->new( 'Makefile' );
my $target = $project->values( 'TARGET' );
my $target_path = $project->values( 'target.path' );

say "$target will be installed to $target_path";  # QMAKE EXECUTED HERE!

There is a single qmake execution, occurring only when the values are used by the caller.

This means that writing the code a bit differently potentially would have much worse performance:

#### BAD EXAMPLE ####
my $project = QMake::Project->new( 'Makefile' );

my $target = $project->values( 'TARGET' );
say "Processing $target";                            # QMAKE EXECUTED HERE!

my $target_path = $project->values( 'target.path' );
say "  -> will be installed to $target_path";        # QMAKE EXECUTED HERE!

Therefore it is good to keep the delayed evaluation in mind, to avoid writing poorly performing code.

As a caveat to all of the above, a list evaluation is never delayed. This is because the size of the list must always be known when a list is returned.

my $project = QMake::Project->new( 'Makefile' );
my $target = $project->values( 'TARGET' );
my @config = $project->values( 'CONFIG' ); # QMAKE EXECUTED HERE!

say "configuration of $target: ".join(' ', @CONFIG);

ERROR HANDLING

By default, all errors are considered fatal, and raised as exceptions. This includes errors encountered during delayed evaluation.

Errors can be made into non-fatal warnings by calling set_die_on_error( 0 ).

All exceptions and warnings match the pattern qr/^QMake::Project:/.

FUNCTIONS

The following functions are provided:

new()
new( MAKEFILE )

Returns a new QMake::Project representing the qmake project for the given MAKEFILE. If MAKEFILE is not provided, a makefile must be set via set_makefile before attempting to retrieve any values from the project.

test( EXPRESSION )

Returns a true value if the given qmake EXPRESSION evaluated to true, a false value otherwise.

EXPRESSION must be a valid qmake "test" expression, as in the following construct:

EXPRESSION:message("The expression is true!")

Compound expressions are fine. For example:

if ($project->test( 'testcase:CONFIG(debug, debug|release)' )) {
  say "Found testcase in debug mode.  Running test in debugger.";
  ...
}

The actual evaluation of the expression might be delayed until the returned value is used in a boolean context. See "DELAYED EVALUATION" for more details.

values( VARIABLE )

Returns the value(s) of the given qmake VARIABLE.

VARIABLE may be any valid qmake variable name, without any leading $$.

Note that (almost) all qmake variables are inherently lists. A variable with a single value, such as TARGET, is a list with one element. A variable such as CONFIG contains many elements.

In scalar context, this function will return only the variable's first value. In list context, it will return all values.

Example:

my $target = $project->values( 'TARGET' );
my @testdata = $project->values( 'TESTDATA' );

if (@testdata) {
  say "Deploying testdata for $target";
  ...
}

In scalar context, the actual evaluation of the variable might be delayed until the returned value is used in a string, integer or boolean context. See "DELAYED EVALUATION" for more details. In list context, evaluation is never delayed, due to implementation difficulties.

makefile()
set_makefile( MAKEFILE )

Get or set the makefile referred to by this project. Note that changing the makefile invalidates any values resolved via the old makefile.

make()
set_make( MAKE )

Get or set the "make" binary (with no arguments) to be used for parsing the makefile. It should rarely be required to use these functions, as there is a reasonable default.

die_on_error()
set_die_on_error( BOOL )

Get or set whether to raise exceptions when an error occurs. By default, exceptions are raised.

Calling set_die_on_error( 0 ) will cause errors to be reported as warnings only. When errors occur, undefined values will be returned by test and values.

COMPATIBILITY

This module should work with qmake from Qt 3, Qt 4 and Qt 5.

CAVEATS

jom <= 1.0.11 should not be used as the make command with this module, due to a bug in those versions of jom (QTCREATORBUG-7170).

Write permissions are required to both the directory containing the .pro file and the directory containing the Makefile.

The module tries to ensure that all evaluations are performed after qmake has processed default_post.prf and CONFIG - so, for example, if a .pro file contains CONFIG+=debug, QMAKE_CXXFLAGS would contain (e.g.) -g, as expected. However, certain strange code could break this (such as some .prf files loaded via CONFIG themselves re-ordering the CONFIG variable).

It is possible to use this module to run arbitrary qmake code. It goes without saying that users are discouraged from abusing this :)

Various exotic constructs may cause this code to fail; for example, .pro files with side effects. The rule of thumb is: if make qmake works for your project, then this package should also work.

This module is (somewhat obviously) using qmake in a way it was not designed to be used. Although it appears to work well in practice, it's fair to call this module one a big hack.

LICENSE AND COPYRIGHT

Copyright 2012 Nokia Corporation and/or its subsidiary(-ies).

Copyright 2012 Rohan McGovern.

This program is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License version 2.1 as published by the Free Software Foundation.

This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details.

You should have received a copy of the GNU Lesser General Public License along with this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA.