Skip (re-)creation of cmake files during build passes.
[profile/ivi/qtbase.git] / bin / qtmodule-configtests
1 #!/usr/bin/perl
2 #############################################################################
3 ##
4 ## Copyright (C) 2012 Nokia Corporation and/or its subsidiary(-ies).
5 ## Contact: http://www.qt-project.org/
6 ##
7 ## This file is part of the build configuration tools of the Qt Toolkit.
8 ##
9 ## $QT_BEGIN_LICENSE:LGPL$
10 ## GNU Lesser General Public License Usage
11 ## This file may be used under the terms of the GNU Lesser General Public
12 ## License version 2.1 as published by the Free Software Foundation and
13 ## appearing in the file LICENSE.LGPL included in the packaging of this
14 ## file. Please review the following information to ensure the GNU Lesser
15 ## General Public License version 2.1 requirements will be met:
16 ## http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
17 ##
18 ## In addition, as a special exception, Nokia gives you certain additional
19 ## rights. These rights are described in the Nokia Qt LGPL Exception
20 ## version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
21 ##
22 ## GNU General Public License Usage
23 ## Alternatively, this file may be used under the terms of the GNU General
24 ## Public License version 3.0 as published by the Free Software Foundation
25 ## and appearing in the file LICENSE.GPL included in the packaging of this
26 ## file. Please review the following information to ensure the GNU General
27 ## Public License version 3.0 requirements will be met:
28 ## http://www.gnu.org/copyleft/gpl.html.
29 ##
30 ## Other Usage
31 ## Alternatively, this file may be used in accordance with the terms and
32 ## conditions contained in a signed written agreement between you and Nokia.
33 ##
34 ##
35 ##
36 ##
37 ##
38 ##
39 ## $QT_END_LICENSE$
40 ##
41 #############################################################################
42
43 #
44 # Runs any module configuration tests
45 #
46 # Called (currently) from syncqt, and expects a few arguments
47 #
48 # configtests $basedir $out_basedir $qtbasedir $quietmode
49 #
50
51 use strict;
52 use warnings;
53
54 # use packages -------------------------------------------------------
55 use File::Basename;
56 use File::Path 'mkpath';
57 use File::Spec::Functions qw/ :ALL /;
58 use File::Temp qw/ :POSIX /;
59 use Cwd;
60 use Config;
61
62 # Which file to look for the %configtests variable in
63 my $configTestSource = "sync.profile";
64
65 if ($#ARGV < 3) {
66     warn "Usage:\n";
67     warn "  $0 <module base directory> <module output directory> <QtBase directory> <generator spec>\n";
68     exit 1;
69 }
70
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];
76
77 our %configtests;
78
79 my $qmakeCachePath = catfile($out_basedir, '.qmake.cache');
80 my $configLogPath = catfile($out_basedir, 'config.log');
81
82 my $QMAKE = catfile($qtbasedir, "bin", ($^O =~ /win32/i) ? 'qmake.exe' : 'qmake');
83 if (!-x $QMAKE) {
84     # try the qmake from the path (e.g. this is a shadow build)
85     $QMAKE = 'qmake';
86 }
87
88 # Need to use the right make
89 # SYMBIAN_UNIX/MINGW should fall back to the non SYMBIAN ones
90 my $MAKE = 'make'; # default, only works on unix
91 if ($generator =~ /UNIX|XCODE/i) { # XCODE = make?
92     $MAKE = 'make';
93 } elsif ($generator =~ /MINGW/i) {
94     $MAKE = 'mingw32-make';
95 } elsif ($generator =~ /MSVC.NET|MSBUILD/i) {
96     $MAKE = 'nmake';
97 } else {
98     # Unhandled (at least): BMAKE, GBUILD, SYMBIAN_ABLD, SYMBIAN_SBSV2
99     warn "Unrecognized generator spec ($generator) - assuming '$MAKE'\n";
100 }
101
102 ######################################################################
103 # Syntax:  fileContents(filename)
104 # Params:  filename, string, filename of file to return contents
105 #
106 # Purpose: Get the contents of a file.
107 # Returns: String with contents of the file, or empty string if file
108 #          doens't exist.
109 # Warning: Dies if it does exist but script cannot get read access.
110 ######################################################################
111 sub fileContents {
112     my ($filename) = @_;
113     my $filecontents = "";
114     if (-e $filename) {
115         open(I, "< $filename") || die "Could not open $filename for reading, read block?";
116         local $/;
117         binmode I;
118         $filecontents = <I>;
119         close I;
120     }
121     return $filecontents;
122 }
123
124 ######################################################################
125 # Syntax:  loadConfigTests()
126 #
127 # Purpose: Loads the config tests from the source basedir into %configtests.
128 # Returns: Nothing
129 ######################################################################
130 sub loadConfigTests {
131     my $configprofile = catfile($basedir, $configTestSource);
132     my $result;
133     unless ($result = do $configprofile) {
134         die "configtests couldn't parse $configprofile: $@\n" if $@;
135         # We don't check for non null output, since that is valid
136     }
137 }
138
139 ######################################################################
140 # Syntax:  hashesAreDifferent
141 #
142 # Purpose: Compares two hashes. (must have same key=value for everything)
143 # Returns: 0 if they are the same, 1 otherwise
144 ######################################################################
145 sub hashesAreDifferent {
146     my %a = %{$_[0]};
147     my %b = %{$_[1]};
148
149     if (keys %a != keys %b) {
150         return 1;
151     }
152
153     my %cmp = map { $_ => 1 } keys %a;
154     for my $key (keys %b) {
155         last unless exists $cmp{$key};
156         last unless $a{$key} eq $b{$key};
157         delete $cmp{$key};
158     }
159     if (%cmp) {
160         return 1;
161     } else {
162         return 0;
163     }
164 }
165
166
167 ######################################################################
168 # Syntax:  executeLoggedCommand()
169 # Params:  path to executable, arguments
170 #
171 # This function is equivalent to system(), except that the command
172 # details and output is placed in the configure log (only).
173 #
174 # Purpose: run a command and log the output
175 # Returns: exit code and output.
176 ######################################################################
177 sub executeLoggedCommand {
178     my (@command_with_args) = @_;
179
180     # Redirect all stdout, stderr into the config.log
181     my ($save_stdout, $save_stderr);
182     open($save_stdout, '>&', STDOUT) || die "save STDOUT: $!";
183     open($save_stderr, '>&', STDERR) || die "save STDERR: $!";
184
185     my $tmpName = File::Temp::tempnam(File::Spec->tmpdir(), 'log');
186     open(STDOUT, '>', $tmpName) || die "open $tmpName: $!";
187     open(STDERR, '>&', STDOUT) || die "redirect STDERR to STDOUT: $!";
188
189     print "+ @command_with_args\n";
190     my $exitCode = system(@command_with_args) >> 8;
191
192     # Put them back.
193     close(STDOUT);
194     close(STDERR);
195     open(STDOUT, '>&', $save_stdout) || die "restoring STDOUT: $!";
196     open(STDERR, '>&', $save_stderr) || die "restoring STDERR: $!";
197
198     # Append output to config log and return it.
199     my ($tmpFile, $configLog);
200     my $out = '';
201     open($tmpFile, '<', $tmpName) || die "open $tmpName: $!";
202     open($configLog, '>>', $configLogPath) || die "open $configLogPath: $!";
203     while (my $line = <$tmpFile>) {
204         print $configLog $line;
205         $out .= $line;
206     }
207     close($tmpFile);
208     close($configLog);
209     unlink($tmpName);
210     return ($exitCode, $out);
211 }
212
213 ######################################################################
214 # Syntax:  executeTest()
215 # Params: testName
216 #
217 # The testName variable controls the actual config test run - the
218 # source is assumed to be in $basedir/config.tests/$testName, and
219 # when 'qmake; make clean; make' is run, is expected to produce a file
220 # $out_basedir/config.tests/$testName/$testName.  If this test passes,
221 # then 'config_test_$testName = yes' will be written to $out_basedir/.qmake.cache
222 #
223 # Purpose: Runs a configuration time test.
224 # Returns: 0 if the test fails, 1 if it passes, 2 if the test is skipped
225 #          (e.g. .pro file has requires(x) and x is not satisfied)
226 ######################################################################
227 sub executeTest {
228     my ($testName) = @_;
229
230     {
231         my $fh;
232         open($fh, '>>', $configLogPath) || die "open $configLogPath: $!";
233         print $fh 'executing config test "',$testName, "\":\n";
234         close($fh);
235     }
236
237     my $oldWorkingDir = getcwd();
238     my $ret = 0;
239
240     my @QMAKEARGS = ('CONFIG-=debug_and_release', 'CONFIG-=app_bundle');
241
242     my $testOutDir = catdir($out_basedir, 'config.tests', $testName);
243
244     # Since we might be cross compiling, look for barename (Linux) and .exe (Win32/Symbian)
245     my $testOutFile1 = catfile($testOutDir, "$testName.exe");
246     my $testOutFile2 = catfile($testOutDir, $testName);
247
248     if ($basedir eq $out_basedir) {
249         chdir $testOutDir or die "\nUnable to change to config test directory ($testOutDir): $!\n";
250     } else { # shadow build
251         if (! -e $testOutDir) {
252             mkpath $testOutDir or die "\nUnable to create shadow build config test directory ($testOutDir): $!\n";
253         }
254         chdir $testOutDir or die "\nUnable to change to config test directory ($testOutDir): $!\n";
255
256         push (@QMAKEARGS, catdir($basedir, 'config.tests', $testName));
257     }
258
259     # First remove existing stuff (XXX this probably needs generator specific code, but hopefully
260     # the target removal below will suffice)
261     if (-e "Makefile") {
262         executeLoggedCommand($MAKE, 'distclean');
263     }
264
265     # and any targets that we might find that weren't distcleaned
266     unlink $testOutFile1, $testOutFile2;
267
268     # Run qmake && make
269     my ($qmakeExitCode, $qmakeOutput) = executeLoggedCommand($QMAKE, @QMAKEARGS);
270     if ($qmakeExitCode == 0) {
271         my ($makeExitCode, $makeOutput) = executeLoggedCommand($MAKE);
272
273         # If make prints "blah blah blah\nSkipped." we consider this a skipped test
274         if ($makeOutput !~ qr(^Skipped\.$)ms) {
275             # Check the test exists (can't reliably execute, especially for cross compilation)
276             if ($makeExitCode == 0 and (-e $testOutFile1 or -e $testOutFile2)) {
277                 $ret = 1;
278             }
279         } else {
280             $ret = 2;
281         }
282     }
283
284     my $fh;
285     open($fh, '>>', $configLogPath) || die "open $configLogPath: $!";
286     print $fh 'config test "',$testName, '" completed with result ',$ret, "\n";
287     close($fh);
288
289     chdir $oldWorkingDir or die "\nUnable to restore working directory: $!\n";
290     return $ret;
291 }
292
293 # Remove existing config.log
294 if (-e $configLogPath) {
295     unlink($configLogPath) || die "unlink $configLogPath: $!";
296 }
297
298 # Now run configuration tests
299 # %configtests is a map from config test name to a map of parameters
300 # e.g:
301 #
302 # %configtests = (
303 #    "simple" => {fatal => 1, message => "Missing required 'simple' component\n"},
304 #    "failed" => {message => "You need to install the FAILED sdk for this to work\n"}
305 # );
306 #
307 # Parameters and their defaults:
308 #  - fatal [false] - whether failing this test should abort everything
309 #  - message [""] - A special message to display if this test fails
310 #
311 loadConfigTests();
312
313 # Only do this step for modules that have config tests
314 # (qtbase doesn't). We try to preserve existing contents (and furthermore
315 # only write to .qmake.cache if the tests change)
316 if ($out_basedir ne $qtbasedir) {
317     # Read any existing content
318     my $existingContents = fileContents($qmakeCachePath);
319     my %oldTestResults;
320     my %newTestResults;
321     my @fatalTestsEncountered;
322
323     # Parse the existing results so we can check if we change them
324     while ($existingContents =~ /^config_test_(.*) = (yes|no)$/gm) {
325         $oldTestResults{$1} = $2;
326     }
327
328     # Get the longest length test name so we can pretty print
329     use List::Util qw(max);
330     my $maxNameLength = max map { length $_ } keys %configtests;
331
332     # Turn off buffering
333     $| = 1;
334
335     # Remove existing config.log
336     if (-e $configLogPath) {
337         unlink($configLogPath) || die "unlink $configLogPath: $!";
338     }
339
340     # Now run the configuration tests
341     print "Configuration tests:\n" if (%configtests);
342
343     while ((my $testName, my $testParameters) = each %configtests) {
344         printf "  % *s: ", $maxNameLength, $testName; # right aligned, yes/no lines up
345
346         my $fatalTest = $testParameters->{"fatal"};
347         my $message = $testParameters->{"message"};
348
349         my $testResult = executeTest($testName);
350         my @testResultStrings = ("no\n","yes\n","skipped\n");
351
352         $newTestResults{$testName} = (($testResult == 1) ? "yes" : "no"); # skipped = no
353
354         if ($testResult == 0) {
355             # Failed test
356             if ($fatalTest) {
357                 print "no (fatal)\n";
358                 # Report the fatality at the end, too
359                 push (@fatalTestsEncountered, $testName);
360             } else {
361                 print "no\n";
362             }
363             if (defined($message)) {
364                 print $message;
365                 print "\n" unless chop $message eq "\n";
366             }
367         } else {
368             # yes or skipped
369             print $testResultStrings[$testResult];
370         }
371     }
372
373     # Check if the test results are different
374     if (hashesAreDifferent(\%oldTestResults, \%newTestResults)) {
375         # Generate the new contents
376         my $newContents = $existingContents;
377
378         # Strip out any existing config test results
379         $newContents =~ s/^config_test_[^\$]*$//gm;
380         $newContents =~ s/^# Compile time test results[^\$]*$//gm;
381
382         # Add any remaining content and make sure we start on a new line
383         if ($newContents and chop $newContents ne '\n') {
384             $newContents = $newContents . "\n";
385         }
386
387         # Results and header
388         if (%newTestResults) {
389             $newContents = $newContents . '# Compile time test results ('.(localtime).")\n";
390
391             # Results
392             while ((my $testName, my $testResult) = each %newTestResults) {
393                 $newContents = $newContents . "config_test_$testName = $testResult\n";
394             }
395         }
396
397         # Remove blank lines
398         $newContents =~ s/^[\s]*$//gms;
399
400         # and open the file
401         open my $cacheFileHandle, ">$qmakeCachePath" or die "Unable to open $qmakeCachePath for writing: $!\n";
402
403         print $cacheFileHandle $newContents;
404
405         close $cacheFileHandle or die "Unable to close $qmakeCachePath: $!\n";
406     }
407
408     # Now see if we have to die
409     if (@fatalTestsEncountered) {
410         if ($#fatalTestsEncountered == 0) {
411             warn "Mandatory configuration test (".$fatalTestsEncountered[0].") failed.\n\n";
412         } else {
413             warn "Mandatory configuration tests (". join (", ", @fatalTestsEncountered) . ") failed.\n\n";
414         }
415         exit -1;
416     }
417 }
418
419 exit 0;