Fixed qtmodule-configtests on Windows
[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 ## All rights reserved.
6 ## Contact: Nokia Corporation (qt-info@nokia.com)
7 ##
8 ## This file is part of the build configuration tools of the Qt Toolkit.
9 ##
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.
18 ##
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.
22 ##
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.
30 ##
31 ## Other Usage
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.
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;
58 use Cwd;
59 use Cwd 'abs_path';
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 $absOutDir = abs_path($out_basedir);
80 my $qmakeCachePath = catfile($absOutDir, '.qmake.cache');
81 my $configLogPath = catfile($absOutDir, 'config.log');
82
83 my $QMAKE = catfile($qtbasedir, "bin", ($^O =~ /win32/i) ? 'qmake.exe' : 'qmake');
84 if (!-x $QMAKE) {
85     # try the qmake from the path (e.g. this is a shadow build)
86     $QMAKE = 'qmake';
87 }
88
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?
93     $MAKE = 'make';
94 } elsif ($generator =~ /MINGW/i) {
95     $MAKE = 'mingw32-make';
96 } elsif ($generator =~ /MSVC.NET|MSBUILD/i) {
97     $MAKE = 'nmake';
98 } else {
99     # Unhandled (at least): BMAKE, GBUILD, SYMBIAN_ABLD, SYMBIAN_SBSV2
100     warn "Unrecognized generator spec ($generator) - assuming '$MAKE'\n";
101 }
102
103 ######################################################################
104 # Syntax:  fileContents(filename)
105 # Params:  filename, string, filename of file to return contents
106 #
107 # Purpose: Get the contents of a file.
108 # Returns: String with contents of the file, or empty string if file
109 #          doens't exist.
110 # Warning: Dies if it does exist but script cannot get read access.
111 ######################################################################
112 sub fileContents {
113     my ($filename) = @_;
114     my $filecontents = "";
115     if (-e $filename) {
116         open(I, "< $filename") || die "Could not open $filename for reading, read block?";
117         local $/;
118         binmode I;
119         $filecontents = <I>;
120         close I;
121     }
122     return $filecontents;
123 }
124
125 ######################################################################
126 # Syntax:  loadConfigTests()
127 #
128 # Purpose: Loads the config tests from the source basedir into %configtests.
129 # Returns: Nothing
130 ######################################################################
131 sub loadConfigTests {
132     my $configprofile = catfile($basedir, $configTestSource);
133     my $result;
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
137     }
138 }
139
140 ######################################################################
141 # Syntax:  hashesAreDifferent
142 #
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 {
147     my %a = %{$_[0]};
148     my %b = %{$_[1]};
149
150     if (keys %a != keys %b) {
151         return 1;
152     }
153
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};
158         delete $cmp{$key};
159     }
160     if (%cmp) {
161         return 1;
162     } else {
163         return 0;
164     }
165 }
166
167
168 ######################################################################
169 # Syntax:  executeLoggedCommand()
170 # Params:  path to executable, arguments
171 #
172 # This function is equivalent to system(), except that the command
173 # details and output is placed in the configure log (only).
174 #
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) = @_;
180
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: $!";
187
188
189     print "+ @command_with_args\n";
190     my $out = system(@command_with_args);
191
192     # Put them back.
193     open(STDOUT, '>&', $save_stdout) || die "restoring STDOUT: $!";
194     open(STDERR, '>&', $save_stderr) || die "restoring STDERR: $!";
195
196     return $out;
197 }
198
199 ######################################################################
200 # Syntax:  executeTest()
201 # Params: testName
202 #
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
208 #
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 ######################################################################
213 sub executeTest {
214     my ($testName) = @_;
215
216     {
217         my $fh;
218         open($fh, '>>', $configLogPath) || die "open $configLogPath: $!";
219         print $fh "executing config test $testName:\n";
220     }
221
222     my $oldWorkingDir = getcwd();
223     my $ret;
224
225     my @QMAKEARGS = ('CONFIG-=debug_and_release', 'CONFIG-=app_bundle');
226
227     my $testOutDir = abs_path(catdir($out_basedir, 'config.tests', $testName));
228
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);
232
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";
238         }
239         chdir $testOutDir or die "\nUnable to change to config test directory ($testOutDir): $!\n";
240
241         push (@QMAKEARGS, catdir($basedir, 'config.tests', $testName));
242     }
243
244     # First remove existing stuff (XXX this probably needs generator specific code, but hopefully
245     # the target removal below will suffice)
246     if (-e "Makefile") {
247         executeLoggedCommand($MAKE, 'distclean');
248     }
249
250     # and any targets that we might find that weren't distcleaned
251     unlink $testOutFile1, $testOutFile2;
252
253     # Run qmake && make
254     if (executeLoggedCommand($QMAKE, @QMAKEARGS)) {
255         # qmake failed -> config test failed
256         $ret = 0;
257     } elsif (executeLoggedCommand($MAKE)) {
258         # make failed -> config test failed
259         $ret = 0;
260     } elsif (-e $testOutFile1 or -e $testOutFile2) {
261         # qmake, make passed, output file exists -> success
262         $ret = 1;
263     } else {
264         # qmake, make passed, output file doesn't exist -> skipped
265         $ret = 2;
266     }
267
268     my $fh;
269     open($fh, '>>', $configLogPath) || die "open $configLogPath: $!";
270     print $fh "config test $testName completed with result $ret\n";
271
272     chdir $oldWorkingDir or die "\nUnable to restore working directory: $!\n";
273     return $ret;
274 }
275
276 # Now run configuration tests
277 # %configtests is a map from config test name to a map of parameters
278 # e.g:
279 #
280 # %configtests = (
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"}
283 # );
284 #
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
288 #
289 loadConfigTests();
290
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);
297     my %oldTestResults;
298     my %newTestResults;
299     my @fatalTestsEncountered;
300
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;
304     }
305
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;
309
310     # Turn off buffering
311     $| = 1;
312
313     # Remove existing config.log
314     if (-e $configLogPath) {
315         unlink($configLogPath) || die "unlink $configLogPath: $!";
316     }
317
318     # Now run the configuration tests
319     print "Configuration tests:\n" if (%configtests);
320
321     while ((my $testName, my $testParameters) = each %configtests) {
322         printf "  % *s: ", $maxNameLength, $testName; # right aligned, yes/no lines up
323
324         my $fatalTest = $testParameters->{"fatal"};
325         my $message = $testParameters->{"message"};
326
327         my $testResult = executeTest($testName);
328         my @testResultStrings = ("no\n","yes\n","skipped\n");
329
330         $newTestResults{$testName} = (($testResult == 1) ? "yes" : "no"); # skipped = no
331
332         if ($testResult == 0) {
333             # Failed test
334             if ($fatalTest) {
335                 print "no (fatal)\n";
336                 # Report the fatality at the end, too
337                 push (@fatalTestsEncountered, $testName);
338             } else {
339                 print "no\n";
340             }
341             if (defined($message)) {
342                 print $message;
343                 print "\n" unless chop $message eq "\n";
344             }
345         } else {
346             # yes or skipped
347             print $testResultStrings[$testResult];
348         }
349     }
350
351     # Check if the test results are different
352     if (hashesAreDifferent(\%oldTestResults, \%newTestResults)) {
353         # Generate the new contents
354         my $newContents = $existingContents;
355
356         # Strip out any existing config test results
357         $newContents =~ s/^config_test_[^\$]*$//gm;
358         $newContents =~ s/^# Compile time test results[^\$]*$//gm;
359
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";
363         }
364
365         # Results and header
366         if (%newTestResults) {
367             $newContents = $newContents . '# Compile time test results ('.(localtime).")\n";
368
369             # Results
370             while ((my $testName, my $testResult) = each %newTestResults) {
371                 $newContents = $newContents . "config_test_$testName = $testResult\n";
372             }
373         }
374
375         # Remove blank lines
376         $newContents =~ s/^[\s]*$//gms;
377
378         # and open the file
379         open my $cacheFileHandle, ">$qmakeCachePath" or die "Unable to open $qmakeCachePath for writing: $!\n";
380
381         print $cacheFileHandle $newContents;
382
383         close $cacheFileHandle or die "Unable to close $qmakeCachePath: $!\n";
384     }
385
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";
390         } else {
391             warn "Mandatory configuration tests (". join (", ", @fatalTestsEncountered) . ") failed.\n\n";
392         }
393         exit -1;
394     }
395 }
396
397 exit 0;