NAME

QMake::Project - evaluate qmake project files

SYNOPSIS

use QMake::Project;

# Load a project from a .pro file
my $prj = QMake::Project->new( 'test.pro' );

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

# May also load from a qmake-generated Makefile
$prj->set_makefile( 'path/to/Makefile' );

# 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 project, 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. This module facilitates the extraction of this metadata.

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 parse any qmake .pro files itself. qmake does all the parsing.

Values are resolved using a process like the following:

  • If a qmake-generated makefile is given, it 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.

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 will not result in any qmake runs, until one of the values returned by these functions is used. This is accomplished by returning deferred values via Scalar::Defer.

For example, consider this code:

my $project = QMake::Project->new( 'test.pro' );
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 would potentially have much worse performance:

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

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( 'test.pro' );
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 )
new( PROJECTFILE )
new( DIRECTORY )

Returns a new QMake::Project representing the qmake project for the given MAKEFILE, PROJECTFILE or DIRECTORY.

If passed a makefile, the makefile must be generated from a qmake project and contain a valid 'qmake' target.

If passed a directory, the project file will be resolved according to the same rules used by qmake when invoked on a directory.

If no argument is provided, one of set_makefile or set_project_file must be called before attempting to retrieve any values from the project.

This function will handle a filename matching /\.pr.$/ as a project file and any other filename as a makefile. If this is not appropriate, call one of the set_makefile or set_project_file functions.

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, and unsets the project file.

project_file()
set_project_file( PROJECTFILE )
set_project_file( DIRECTORY )

Get or set the project file (.pro file) referred to by this project.

Note that changing the project file invalidates any values resolved via the old project file, and unsets the 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.

qmake()
set_qmake( QMAKE )

Get or set the "qmake" binary (with no arguments). If unset (the default), the first existing 'qmake', 'qmake-qt5' or 'qmake-qt4' command in PATH will be used.

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 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 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.