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