Fix config tests for modules on Mac OS X
[profile/ivi/qtbase.git] / bin / qtmodule-configtests
1 #!/usr/bin/perl
2 #############################################################################
3 ##
4 ## Copyright (C) 2011 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 $qmakeCachePath = catfile($out_basedir, ".qmake.cache");
80
81 my $QMAKE = catfile($qtbasedir, "bin", ($^O =~ /win32/i) ? 'qmake.exe' : 'qmake');
82 if (!-x $QMAKE) {
83     # try the qmake from the path (e.g. this is a shadow build)
84     $QMAKE = 'qmake';
85 }
86
87 # Need to use the right make
88 # SYMBIAN_UNIX/MINGW should fall back to the non SYMBIAN ones
89 my $MAKE = 'make'; # default, only works on unix
90 if ($generator =~ /UNIX|XCODE/i) { # XCODE = make?
91     $MAKE = 'make';
92 } elsif ($generator =~ /MINGW/i) {
93     $MAKE = 'mingw32-make';
94 } elsif ($generator =~ /MSVC.NET|MSBUILD/i) {
95     $MAKE = 'nmake';
96 } else {
97     # Unhandled (at least): BMAKE, GBUILD, SYMBIAN_ABLD, SYMBIAN_SBSV2
98     warn "Unrecognized generator spec ($generator) - assuming '$MAKE'\n";
99 }
100
101 ######################################################################
102 # Syntax:  fileContents(filename)
103 # Params:  filename, string, filename of file to return contents
104 #
105 # Purpose: Get the contents of a file.
106 # Returns: String with contents of the file, or empty string if file
107 #          doens't exist.
108 # Warning: Dies if it does exist but script cannot get read access.
109 ######################################################################
110 sub fileContents {
111     my ($filename) = @_;
112     my $filecontents = "";
113     if (-e $filename) {
114         open(I, "< $filename") || die "Could not open $filename for reading, read block?";
115         local $/;
116         binmode I;
117         $filecontents = <I>;
118         close I;
119     }
120     return $filecontents;
121 }
122
123 ######################################################################
124 # Syntax:  loadConfigTests()
125 #
126 # Purpose: Loads the config tests from the source basedir into %configtests.
127 # Returns: Nothing
128 ######################################################################
129 sub loadConfigTests {
130     my $configprofile = catfile($basedir, $configTestSource);
131     my $result;
132     unless ($result = do $configprofile) {
133         die "configtests couldn't parse $configprofile: $@\n" if $@;
134         # We don't check for non null output, since that is valid
135     }
136 }
137
138 ######################################################################
139 # Syntax:  hashesAreDifferent
140 #
141 # Purpose: Compares two hashes. (must have same key=value for everything)
142 # Returns: 0 if they are the same, 1 otherwise
143 ######################################################################
144 sub hashesAreDifferent {
145     my %a = %{$_[0]};
146     my %b = %{$_[1]};
147
148     if (keys %a != keys %b) {
149         return 1;
150     }
151
152     my %cmp = map { $_ => 1 } keys %a;
153     for my $key (keys %b) {
154         last unless exists $cmp{$key};
155         last unless $a{$key} eq $b{$key};
156         delete $cmp{$key};
157     }
158     if (%cmp) {
159         return 1;
160     } else {
161         return 0;
162     }
163 }
164
165 ######################################################################
166 # Syntax:  executeSomething
167 # Params:  A list of things.
168 #
169 # Purpose: Executes the first arg, passing the list.
170 #          stderr is redirected to stdout, and the output is captured.
171 # Returns: The output.
172 ######################################################################
173 sub executeSomething {
174     my ($program, @args) = @_;
175
176     my $pid = open(KID_TO_READ, "-|");
177
178     my $output;
179
180     if ($pid) {   # parent
181         while (<KID_TO_READ>) {
182             $output = $output . $_;
183         }
184         close(KID_TO_READ) || $! == 0 || warn "\nFailed to execute $program: exited $?";
185     } else {
186         # redirect STDERR to STDOUT
187         open STDERR, ">&STDOUT";
188
189         # Exec something
190         exec ($program, @args) || die "\nCan't exec $program: $!\n";
191         # NOTREACHED
192     }
193
194     return $output;
195 }
196
197 ######################################################################
198 # Syntax:  executeTest()
199 # Params: testName
200 #
201 # The testName variable controls the actual config test run - the
202 # source is assumed to be in $basedir/config.tests/$testName, and
203 # when 'qmake; make clean; make' is run, is expected to produce a file
204 # $out_basedir/config.tests/$testName/$testName.  If this test passes,
205 # then 'config_test_$testName = yes' will be written to $out_basedir/.qmake.cache
206 #
207 # Purpose: Runs a configuration time test.
208 # Returns: 0 if the test fails, 1 if it passes, 2 if the test is skipped
209 #          (e.g. .pro file has requires(x) and x is not satisfied)
210 ######################################################################
211 sub executeTest {
212     my ($testName) = @_;
213
214     my $oldWorkingDir = getcwd();
215     my $ret = 0;
216
217     my @QMAKEARGS = ('CONFIG-=debug_and_release', 'CONFIG-=app_bundle');
218
219     my $testOutDir = catdir($out_basedir, 'config.tests', $testName);
220
221     # Since we might be cross compiling, look for barename (Linux) and .exe (Win32/Symbian)
222     my $testOutFile1 = catfile($testOutDir, "$testName.exe");
223     my $testOutFile2 = catfile($testOutDir, $testName);
224
225     if (abs_path($basedir) eq abs_path($out_basedir)) {
226         chdir $testOutDir or die "\nUnable to change to config test directory ($testOutDir): $!\n";
227     } else { # shadow build
228         if (! -e $testOutDir) {
229             mkpath $testOutDir or die "\nUnable to create shadow build config test directory ($testOutDir): $!\n";
230         }
231         chdir $testOutDir or die "\nUnable to change to config test directory ($testOutDir): $!\n";
232
233         push (@QMAKEARGS, catdir($basedir, 'config.tests', $testName));
234     }
235
236     # First remove existing stuff (XXX this probably needs generator specific code, but hopefully
237     # the target removal below will suffice)
238     if (-e "Makefile") {
239         executeSomething($MAKE, 'distclean');
240     }
241
242     # and any targets that we might find that weren't distcleaned
243     unlink $testOutFile1, $testOutFile2;
244
245     # Run qmake && make
246     executeSomething($QMAKE, @QMAKEARGS);
247     my $makeOutput = executeSomething(($MAKE));
248
249     # If make prints "blah blah blah\nSkipped." we consider this a skipped test
250     if ($makeOutput !~ qr(^Skipped\.$)ms) {
251         # Check the test exists (can't reliably execute, especially for cross compilation)
252         if (-e $testOutFile1 or -e $testOutFile2) {
253             $ret = 1;
254         }
255     } else {
256         $ret = 2;
257     }
258
259     chdir $oldWorkingDir or die "\nUnable to restore working directory: $!\n";
260     return $ret;
261 }
262
263 # Now run configuration tests
264 # %configtests is a map from config test name to a map of parameters
265 # e.g:
266 #
267 # %configtests = (
268 #    "simple" => {fatal => 1, message => "Missing required 'simple' component\n"},
269 #    "failed" => {message => "You need to install the FAILED sdk for this to work\n"}
270 # );
271 #
272 # Parameters and their defaults:
273 #  - fatal [false] - whether failing this test should abort everything
274 #  - message [""] - A special message to display if this test fails
275 #
276 loadConfigTests();
277
278 # Only do this step for modules that have config tests
279 # (qtbase doesn't). We try to preserve existing contents (and furthermore
280 # only write to .qmake.cache if the tests change)
281 if (abs_path($out_basedir) ne abs_path($qtbasedir)) {
282     # Read any existing content
283     my $existingContents = fileContents($qmakeCachePath);
284     my %oldTestResults;
285     my %newTestResults;
286     my @fatalTestsEncountered;
287
288     # Parse the existing results so we can check if we change them
289     while ($existingContents =~ /^config_test_(.*) = (yes|no)$/gm) {
290         $oldTestResults{$1} = $2;
291     }
292
293     # Get the longest length test name so we can pretty print
294     use List::Util qw(max);
295     my $maxNameLength = max map { length $_ } keys %configtests;
296
297     # Turn off buffering
298     $| = 1;
299
300     # Now run the configuration tests
301     print "Configuration tests:\n" if (%configtests);
302
303     while ((my $testName, my $testParameters) = each %configtests) {
304         printf "  % *s: ", $maxNameLength, $testName; # right aligned, yes/no lines up
305
306         my $fatalTest = $testParameters->{"fatal"};
307         my $message = $testParameters->{"message"};
308
309         my $testResult = executeTest($testName);
310         my @testResultStrings = ("no\n","yes\n","skipped\n");
311
312         $newTestResults{$testName} = (($testResult == 1) ? "yes" : "no"); # skipped = no
313
314         if ($testResult == 0) {
315             # Failed test
316             if ($fatalTest) {
317                 print "no (fatal)\n";
318                 # Report the fatality at the end, too
319                 push (@fatalTestsEncountered, $testName);
320             } else {
321                 print "no\n";
322             }
323             if (defined($message)) {
324                 print $message;
325                 print "\n" unless chop $message eq "\n";
326             }
327         } else {
328             # yes or skipped
329             print $testResultStrings[$testResult];
330         }
331     }
332
333     # Check if the test results are different
334     if (hashesAreDifferent(\%oldTestResults, \%newTestResults)) {
335         # Generate the new contents
336         my $newContents = $existingContents;
337
338         # Strip out any existing config test results
339         $newContents =~ s/^config_test_[^\$]*$//gm;
340         $newContents =~ s/^# Compile time test results[^\$]*$//gm;
341
342         # Add any remaining content and make sure we start on a new line
343         if ($newContents and chop $newContents ne '\n') {
344             $newContents = $newContents . "\n";
345         }
346
347         # Results and header
348         if (%newTestResults) {
349             $newContents = $newContents . '# Compile time test results ('.(localtime).")\n";
350
351             # Results
352             while ((my $testName, my $testResult) = each %newTestResults) {
353                 $newContents = $newContents . "config_test_$testName = $testResult\n";
354             }
355         }
356
357         # Remove blank lines
358         $newContents =~ s/^[\s]*$//gms;
359
360         # and open the file
361         open my $cacheFileHandle, ">$qmakeCachePath" or die "Unable to open $qmakeCachePath for writing: $!\n";
362
363         print $cacheFileHandle $newContents;
364
365         close $cacheFileHandle or die "Unable to close $qmakeCachePath: $!\n";
366     }
367
368     # Now see if we have to die
369     if (@fatalTestsEncountered) {
370         if ($#fatalTestsEncountered == 0) {
371             warn "Mandatory configuration test (".$fatalTestsEncountered[0].") failed.\n\n";
372         } else {
373             warn "Mandatory configuration tests (". join (", ", @fatalTestsEncountered) . ") failed.\n\n";
374         }
375         exit -1;
376     }
377 }
378
379 exit 0;