2 #############################################################################
4 ## Copyright (C) 2012 Nokia Corporation and/or its subsidiary(-ies).
5 ## All rights reserved.
6 ## Contact: Nokia Corporation (qt-info@nokia.com)
8 ## This file is part of the build configuration tools of the Qt Toolkit.
10 ## $QT_BEGIN_LICENSE:LGPL$
11 ## GNU Lesser General Public License Usage
12 ## This file may be used under the terms of the GNU Lesser General Public
13 ## License version 2.1 as published by the Free Software Foundation and
14 ## appearing in the file LICENSE.LGPL included in the packaging of this
15 ## file. Please review the following information to ensure the GNU Lesser
16 ## General Public License version 2.1 requirements will be met:
17 ## http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
19 ## In addition, as a special exception, Nokia gives you certain additional
20 ## rights. These rights are described in the Nokia Qt LGPL Exception
21 ## version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
23 ## GNU General Public License Usage
24 ## Alternatively, this file may be used under the terms of the GNU General
25 ## Public License version 3.0 as published by the Free Software Foundation
26 ## and appearing in the file LICENSE.GPL included in the packaging of this
27 ## file. Please review the following information to ensure the GNU General
28 ## Public License version 3.0 requirements will be met:
29 ## http://www.gnu.org/copyleft/gpl.html.
32 ## Alternatively, this file may be used in accordance with the terms and
33 ## conditions contained in a signed written agreement between you and Nokia.
41 #############################################################################
44 # Runs any module configuration tests
46 # Called (currently) from syncqt, and expects a few arguments
48 # configtests $basedir $out_basedir $qtbasedir $quietmode
54 # use packages -------------------------------------------------------
56 use File::Path 'mkpath';
57 use File::Spec::Functions;
62 # Which file to look for the %configtests variable in
63 my $configTestSource = "sync.profile";
67 warn " $0 <module base directory> <module output directory> <QtBase directory> <generator spec>\n";
71 # These might be needed in sync.profile
72 our $basedir = $ARGV[0];
73 our $out_basedir = $ARGV[1];
74 our $qtbasedir = $ARGV[2];
75 my $generator = $ARGV[3];
79 my $absOutDir = abs_path($out_basedir);
80 my $qmakeCachePath = catfile($absOutDir, '.qmake.cache');
81 my $configLogPath = catfile($absOutDir, 'config.log');
83 my $QMAKE = catfile($qtbasedir, "bin", ($^O =~ /win32/i) ? 'qmake.exe' : 'qmake');
85 # try the qmake from the path (e.g. this is a shadow build)
89 # Need to use the right make
90 # SYMBIAN_UNIX/MINGW should fall back to the non SYMBIAN ones
91 my $MAKE = 'make'; # default, only works on unix
92 if ($generator =~ /UNIX|XCODE/i) { # XCODE = make?
94 } elsif ($generator =~ /MINGW/i) {
95 $MAKE = 'mingw32-make';
96 } elsif ($generator =~ /MSVC.NET|MSBUILD/i) {
99 # Unhandled (at least): BMAKE, GBUILD, SYMBIAN_ABLD, SYMBIAN_SBSV2
100 warn "Unrecognized generator spec ($generator) - assuming '$MAKE'\n";
103 ######################################################################
104 # Syntax: fileContents(filename)
105 # Params: filename, string, filename of file to return contents
107 # Purpose: Get the contents of a file.
108 # Returns: String with contents of the file, or empty string if file
110 # Warning: Dies if it does exist but script cannot get read access.
111 ######################################################################
114 my $filecontents = "";
116 open(I, "< $filename") || die "Could not open $filename for reading, read block?";
122 return $filecontents;
125 ######################################################################
126 # Syntax: loadConfigTests()
128 # Purpose: Loads the config tests from the source basedir into %configtests.
130 ######################################################################
131 sub loadConfigTests {
132 my $configprofile = catfile($basedir, $configTestSource);
134 unless ($result = do $configprofile) {
135 die "configtests couldn't parse $configprofile: $@\n" if $@;
136 # We don't check for non null output, since that is valid
140 ######################################################################
141 # Syntax: hashesAreDifferent
143 # Purpose: Compares two hashes. (must have same key=value for everything)
144 # Returns: 0 if they are the same, 1 otherwise
145 ######################################################################
146 sub hashesAreDifferent {
150 if (keys %a != keys %b) {
154 my %cmp = map { $_ => 1 } keys %a;
155 for my $key (keys %b) {
156 last unless exists $cmp{$key};
157 last unless $a{$key} eq $b{$key};
168 ######################################################################
169 # Syntax: executeLoggedCommand()
170 # Params: path to executable, arguments
172 # This function is equivalent to system(), except that the command
173 # details and output is placed in the configure log (only).
175 # Purpose: run a command and log the output
176 # Returns: exit status (as returned by system())
177 ######################################################################
178 sub executeLoggedCommand {
179 my (@command_with_args) = @_;
181 # Redirect all stdout, stderr into the config.log
182 my ($save_stdout, $save_stderr);
183 open($save_stdout, '>&', STDOUT) || die "save STDOUT: $!";
184 open($save_stderr, '>&', STDERR) || die "save STDERR: $!";
185 open(STDOUT, '>>', $configLogPath) || die "open $configLogPath: $!";
186 open(STDERR, '>&', STDOUT) || die "redirect STDERR to STDOUT: $!";
189 print "+ @command_with_args\n";
190 my $out = system(@command_with_args);
193 open(STDOUT, '>&', $save_stdout) || die "restoring STDOUT: $!";
194 open(STDERR, '>&', $save_stderr) || die "restoring STDERR: $!";
199 ######################################################################
200 # Syntax: executeTest()
203 # The testName variable controls the actual config test run - the
204 # source is assumed to be in $basedir/config.tests/$testName, and
205 # when 'qmake; make clean; make' is run, is expected to produce a file
206 # $out_basedir/config.tests/$testName/$testName. If this test passes,
207 # then 'config_test_$testName = yes' will be written to $out_basedir/.qmake.cache
209 # Purpose: Runs a configuration time test.
210 # Returns: 0 if the test fails, 1 if it passes, 2 if the test is skipped
211 # (e.g. .pro file has requires(x) and x is not satisfied)
212 ######################################################################
218 open($fh, '>>', $configLogPath) || die "open $configLogPath: $!";
219 print $fh "executing config test $testName:\n";
222 my $oldWorkingDir = getcwd();
225 my @QMAKEARGS = ('CONFIG-=debug_and_release', 'CONFIG-=app_bundle');
227 my $testOutDir = abs_path(catdir($out_basedir, 'config.tests', $testName));
229 # Since we might be cross compiling, look for barename (Linux) and .exe (Win32/Symbian)
230 my $testOutFile1 = catfile($testOutDir, "$testName.exe");
231 my $testOutFile2 = catfile($testOutDir, $testName);
233 if (abs_path($basedir) eq abs_path($out_basedir)) {
234 chdir $testOutDir or die "\nUnable to change to config test directory ($testOutDir): $!\n";
235 } else { # shadow build
236 if (! -e $testOutDir) {
237 mkpath $testOutDir or die "\nUnable to create shadow build config test directory ($testOutDir): $!\n";
239 chdir $testOutDir or die "\nUnable to change to config test directory ($testOutDir): $!\n";
241 push (@QMAKEARGS, catdir($basedir, 'config.tests', $testName));
244 # First remove existing stuff (XXX this probably needs generator specific code, but hopefully
245 # the target removal below will suffice)
247 executeLoggedCommand($MAKE, 'distclean');
250 # and any targets that we might find that weren't distcleaned
251 unlink $testOutFile1, $testOutFile2;
254 if (executeLoggedCommand($QMAKE, @QMAKEARGS)) {
255 # qmake failed -> config test failed
257 } elsif (executeLoggedCommand($MAKE)) {
258 # make failed -> config test failed
260 } elsif (-e $testOutFile1 or -e $testOutFile2) {
261 # qmake, make passed, output file exists -> success
264 # qmake, make passed, output file doesn't exist -> skipped
269 open($fh, '>>', $configLogPath) || die "open $configLogPath: $!";
270 print $fh "config test $testName completed with result $ret\n";
272 chdir $oldWorkingDir or die "\nUnable to restore working directory: $!\n";
276 # Now run configuration tests
277 # %configtests is a map from config test name to a map of parameters
281 # "simple" => {fatal => 1, message => "Missing required 'simple' component\n"},
282 # "failed" => {message => "You need to install the FAILED sdk for this to work\n"}
285 # Parameters and their defaults:
286 # - fatal [false] - whether failing this test should abort everything
287 # - message [""] - A special message to display if this test fails
291 # Only do this step for modules that have config tests
292 # (qtbase doesn't). We try to preserve existing contents (and furthermore
293 # only write to .qmake.cache if the tests change)
294 if (abs_path($out_basedir) ne abs_path($qtbasedir)) {
295 # Read any existing content
296 my $existingContents = fileContents($qmakeCachePath);
299 my @fatalTestsEncountered;
301 # Parse the existing results so we can check if we change them
302 while ($existingContents =~ /^config_test_(.*) = (yes|no)$/gm) {
303 $oldTestResults{$1} = $2;
306 # Get the longest length test name so we can pretty print
307 use List::Util qw(max);
308 my $maxNameLength = max map { length $_ } keys %configtests;
313 # Remove existing config.log
314 if (-e $configLogPath) {
315 unlink($configLogPath) || die "unlink $configLogPath: $!";
318 # Now run the configuration tests
319 print "Configuration tests:\n" if (%configtests);
321 while ((my $testName, my $testParameters) = each %configtests) {
322 printf " % *s: ", $maxNameLength, $testName; # right aligned, yes/no lines up
324 my $fatalTest = $testParameters->{"fatal"};
325 my $message = $testParameters->{"message"};
327 my $testResult = executeTest($testName);
328 my @testResultStrings = ("no\n","yes\n","skipped\n");
330 $newTestResults{$testName} = (($testResult == 1) ? "yes" : "no"); # skipped = no
332 if ($testResult == 0) {
335 print "no (fatal)\n";
336 # Report the fatality at the end, too
337 push (@fatalTestsEncountered, $testName);
341 if (defined($message)) {
343 print "\n" unless chop $message eq "\n";
347 print $testResultStrings[$testResult];
351 # Check if the test results are different
352 if (hashesAreDifferent(\%oldTestResults, \%newTestResults)) {
353 # Generate the new contents
354 my $newContents = $existingContents;
356 # Strip out any existing config test results
357 $newContents =~ s/^config_test_[^\$]*$//gm;
358 $newContents =~ s/^# Compile time test results[^\$]*$//gm;
360 # Add any remaining content and make sure we start on a new line
361 if ($newContents and chop $newContents ne '\n') {
362 $newContents = $newContents . "\n";
366 if (%newTestResults) {
367 $newContents = $newContents . '# Compile time test results ('.(localtime).")\n";
370 while ((my $testName, my $testResult) = each %newTestResults) {
371 $newContents = $newContents . "config_test_$testName = $testResult\n";
376 $newContents =~ s/^[\s]*$//gms;
379 open my $cacheFileHandle, ">$qmakeCachePath" or die "Unable to open $qmakeCachePath for writing: $!\n";
381 print $cacheFileHandle $newContents;
383 close $cacheFileHandle or die "Unable to close $qmakeCachePath: $!\n";
386 # Now see if we have to die
387 if (@fatalTestsEncountered) {
388 if ($#fatalTestsEncountered == 0) {
389 warn "Mandatory configuration test (".$fatalTestsEncountered[0].") failed.\n\n";
391 warn "Mandatory configuration tests (". join (", ", @fatalTestsEncountered) . ") failed.\n\n";