Skip to check directories existence in build-webkit
[WebKit-https.git] / Tools / Scripts / webkitdirs.pm
1 # Copyright (C) 2005-2007, 2010-2016 Apple Inc. All rights reserved.
2 # Copyright (C) 2009 Google Inc. All rights reserved.
3 # Copyright (C) 2011 Research In Motion Limited. All rights reserved.
4 # Copyright (C) 2013 Nokia Corporation and/or its subsidiary(-ies).
5 #
6 # Redistribution and use in source and binary forms, with or without
7 # modification, are permitted provided that the following conditions
8 # are met:
9 #
10 # 1.  Redistributions of source code must retain the above copyright
11 #     notice, this list of conditions and the following disclaimer. 
12 # 2.  Redistributions in binary form must reproduce the above copyright
13 #     notice, this list of conditions and the following disclaimer in the
14 #     documentation and/or other materials provided with the distribution. 
15 # 3.  Neither the name of Apple Inc. ("Apple") nor the names of
16 #     its contributors may be used to endorse or promote products derived
17 #     from this software without specific prior written permission. 
18 #
19 # THIS SOFTWARE IS PROVIDED BY APPLE AND ITS CONTRIBUTORS "AS IS" AND ANY
20 # EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
21 # WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
22 # DISCLAIMED. IN NO EVENT SHALL APPLE OR ITS CONTRIBUTORS BE LIABLE FOR ANY
23 # DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
24 # (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
25 # LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
26 # ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
27 # (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
28 # THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
29
30 # Module to share code to get to WebKit directories.
31
32 use strict;
33 use version;
34 use warnings;
35 use Config;
36 use Cwd qw(realpath);
37 use Digest::MD5 qw(md5_hex);
38 use FindBin;
39 use File::Basename;
40 use File::Find;
41 use File::Path qw(make_path mkpath rmtree);
42 use File::Spec;
43 use File::stat;
44 use List::Util;
45 use POSIX;
46 use Time::HiRes qw(usleep);
47 use VCSUtils;
48
49 BEGIN {
50    use Exporter   ();
51    our ($VERSION, @ISA, @EXPORT, @EXPORT_OK, %EXPORT_TAGS);
52    $VERSION     = 1.00;
53    @ISA         = qw(Exporter);
54    @EXPORT      = qw(
55        &XcodeCoverageSupportOptions
56        &XcodeOptionString
57        &XcodeOptionStringNoConfig
58        &XcodeOptions
59        &XcodeStaticAnalyzerOption
60        &appDisplayNameFromBundle
61        &appendToEnvironmentVariableList
62        &baseProductDir
63        &chdirWebKit
64        &checkFrameworks
65        &cmakeBasedPortArguments
66        &currentSVNRevision
67        &debugSafari
68        &executableProductDir
69        &findOrCreateSimulatorForIOSDevice
70        &iosSimulatorDeviceByName
71        &nmPath
72        &passedConfiguration
73        &prependToEnvironmentVariableList
74        &printHelpAndExitForRunAndDebugWebKitAppIfNeeded
75        &productDir
76        &quitIOSSimulator
77        &relaunchIOSSimulator
78        &restartIOSSimulatorDevice
79        &runIOSWebKitApp
80        &runMacWebKitApp
81        &safariPath
82        &iosVersion
83        &setConfiguration
84        &setupMacWebKitEnvironment
85        &sharedCommandLineOptions
86        &sharedCommandLineOptionsUsage
87        &shutDownIOSSimulatorDevice
88        &willUseIOSDeviceSDK
89        &willUseIOSSimulatorSDK
90        SIMULATOR_DEVICE_SUFFIX_FOR_WEBKIT_DEVELOPMENT
91        USE_OPEN_COMMAND
92    );
93    %EXPORT_TAGS = ( );
94    @EXPORT_OK   = ();
95 }
96
97 # Ports
98 use constant {
99     AppleWin => "AppleWin",
100     GTK      => "GTK",
101     Efl      => "Efl",
102     iOS      => "iOS",
103     Mac      => "Mac",
104     JSCOnly  => "JSCOnly",
105     WinCairo => "WinCairo",
106     Unknown  => "Unknown"
107 };
108
109 use constant USE_OPEN_COMMAND => 1; # Used in runMacWebKitApp().
110 use constant INCLUDE_OPTIONS_FOR_DEBUGGING => 1;
111 use constant SIMULATOR_DEVICE_STATE_SHUTDOWN => "1";
112 use constant SIMULATOR_DEVICE_STATE_BOOTED => "3";
113 use constant SIMULATOR_DEVICE_SUFFIX_FOR_WEBKIT_DEVELOPMENT  => "For WebKit Development";
114
115 # See table "Certificate types and names" on <https://developer.apple.com/library/ios/documentation/IDEs/Conceptual/AppDistributionGuide/MaintainingCertificates/MaintainingCertificates.html#//apple_ref/doc/uid/TP40012582-CH31-SW41>.
116 use constant IOS_DEVELOPMENT_CERTIFICATE_NAME_PREFIX => "iPhone Developer: ";
117
118 our @EXPORT_OK;
119
120 my $architecture;
121 my $asanIsEnabled;
122 my $numberOfCPUs;
123 my $maxCPULoad;
124 my $baseProductDir;
125 my @baseProductDirOption;
126 my $configuration;
127 my $xcodeSDK;
128 my $configurationForVisualStudio;
129 my $configurationProductDir;
130 my $sourceDir;
131 my $currentSVNRevision;
132 my $didLoadIPhoneSimulatorNotification;
133 my $nmPath;
134 my $osXVersion;
135 my $iosVersion;
136 my $generateDsym;
137 my $isCMakeBuild;
138 my $isWin64;
139 my $isInspectorFrontend;
140 my $portName;
141 my $shouldUseGuardMalloc;
142 my $shouldNotUseNinja;
143 my $xcodeVersion;
144
145 my $unknownPortProhibited = 0;
146
147 # Variables for Win32 support
148 my $programFilesPath;
149 my $vcBuildPath;
150 my $vsInstallDir;
151 my $msBuildInstallDir;
152 my $vsVersion;
153 my $windowsSourceDir;
154 my $winVersion;
155 my $willUseVCExpressWhenBuilding = 0;
156
157 # Defined in VCSUtils.
158 sub exitStatus($);
159
160 sub findMatchingArguments($$);
161 sub hasArgument($$);
162
163 sub determineSourceDir
164 {
165     return if $sourceDir;
166     $sourceDir = $FindBin::Bin;
167     $sourceDir =~ s|/+$||; # Remove trailing '/' as we would die later
168
169     # walks up path checking each directory to see if it is the main WebKit project dir, 
170     # defined by containing Sources, WebCore, and JavaScriptCore.
171     until ((-d File::Spec->catdir($sourceDir, "Source") && -d File::Spec->catdir($sourceDir, "Source", "WebCore") && -d File::Spec->catdir($sourceDir, "Source", "JavaScriptCore")) || (-d File::Spec->catdir($sourceDir, "Internal") && -d File::Spec->catdir($sourceDir, "OpenSource")))
172     {
173         if ($sourceDir !~ s|/[^/]+$||) {
174             die "Could not find top level webkit directory above source directory using FindBin.\n";
175         }
176     }
177
178     $sourceDir = File::Spec->catdir($sourceDir, "OpenSource") if -d File::Spec->catdir($sourceDir, "OpenSource");
179 }
180
181 sub currentPerlPath()
182 {
183     my $thisPerl = $^X;
184     if ($^O ne 'VMS') {
185         $thisPerl .= $Config{_exe} unless $thisPerl =~ m/$Config{_exe}$/i;
186     }
187     return $thisPerl;
188 }
189
190 # used for scripts which are stored in a non-standard location
191 sub setSourceDir($)
192 {
193     ($sourceDir) = @_;
194 }
195
196 sub determineNinjaVersion
197 {
198     chomp(my $ninjaVersion = `ninja --version`);
199     return $ninjaVersion;
200 }
201
202 sub determineXcodeVersion
203 {
204     return if defined $xcodeVersion;
205     my $xcodebuildVersionOutput = `xcodebuild -version`;
206     $xcodeVersion = ($xcodebuildVersionOutput =~ /Xcode ([0-9](\.[0-9]+)*)/) ? $1 : "3.0";
207 }
208
209 sub readXcodeUserDefault($)
210 {
211     my ($key) = @_;
212
213     my $devnull = File::Spec->devnull();
214
215     my $value = `defaults read com.apple.dt.Xcode ${key} 2> ${devnull}`;
216     return if $?;
217
218     chomp $value;
219     return $value;
220 }
221
222 sub determineBaseProductDir
223 {
224     return if defined $baseProductDir;
225     determineSourceDir();
226
227     my $setSharedPrecompsDir;
228     $baseProductDir = $ENV{"WEBKIT_OUTPUTDIR"};
229
230     if (!defined($baseProductDir) and isAppleMacWebKit()) {
231         # Silently remove ~/Library/Preferences/xcodebuild.plist which can
232         # cause build failure. The presence of
233         # ~/Library/Preferences/xcodebuild.plist can prevent xcodebuild from
234         # respecting global settings such as a custom build products directory
235         # (<rdar://problem/5585899>).
236         my $personalPlistFile = $ENV{HOME} . "/Library/Preferences/xcodebuild.plist";
237         if (-e $personalPlistFile) {
238             unlink($personalPlistFile) || die "Could not delete $personalPlistFile: $!";
239         }
240
241         my $buildLocationStyle = join '', readXcodeUserDefault("IDEBuildLocationStyle");
242         if ($buildLocationStyle eq "Custom") {
243             my $buildLocationType = join '', readXcodeUserDefault("IDECustomBuildLocationType");
244             # FIXME: Read CustomBuildIntermediatesPath and set OBJROOT accordingly.
245             $baseProductDir = readXcodeUserDefault("IDECustomBuildProductsPath") if $buildLocationType eq "Absolute";
246         }
247
248         # DeterminedByTargets corresponds to a setting of "Legacy" in Xcode.
249         # It is the only build location style for which SHARED_PRECOMPS_DIR is not
250         # overridden when building from within Xcode.
251         $setSharedPrecompsDir = 1 if $buildLocationStyle ne "DeterminedByTargets";
252
253         if (!defined($baseProductDir)) {
254             $baseProductDir = join '', readXcodeUserDefault("IDEApplicationwideBuildSettings");
255             $baseProductDir = $1 if $baseProductDir =~ /SYMROOT\s*=\s*\"(.*?)\";/s;
256         }
257
258         undef $baseProductDir unless $baseProductDir =~ /^\//;
259     }
260
261     if (!defined($baseProductDir)) { # Port-specific checks failed, use default
262         $baseProductDir = File::Spec->catdir($sourceDir, "WebKitBuild");
263     }
264
265     if (isGit() && isGitBranchBuild()) {
266         my $branch = gitBranch();
267         $baseProductDir = "$baseProductDir/$branch";
268     }
269
270     if (isAppleMacWebKit()) {
271         $baseProductDir =~ s|^\Q$(SRCROOT)/..\E$|$sourceDir|;
272         $baseProductDir =~ s|^\Q$(SRCROOT)/../|$sourceDir/|;
273         $baseProductDir =~ s|^~/|$ENV{HOME}/|;
274         die "Can't handle Xcode product directory with a ~ in it.\n" if $baseProductDir =~ /~/;
275         die "Can't handle Xcode product directory with a variable in it.\n" if $baseProductDir =~ /\$/;
276         @baseProductDirOption = ("SYMROOT=$baseProductDir", "OBJROOT=$baseProductDir");
277         push(@baseProductDirOption, "SHARED_PRECOMPS_DIR=${baseProductDir}/PrecompiledHeaders") if $setSharedPrecompsDir;
278     }
279
280     if (isCygwin()) {
281         my $dosBuildPath = `cygpath --windows \"$baseProductDir\"`;
282         chomp $dosBuildPath;
283         $ENV{"WEBKIT_OUTPUTDIR"} = $dosBuildPath;
284         my $unixBuildPath = `cygpath --unix \"$baseProductDir\"`;
285         chomp $unixBuildPath;
286         $baseProductDir = $dosBuildPath;
287     }
288 }
289
290 sub setBaseProductDir($)
291 {
292     ($baseProductDir) = @_;
293 }
294
295 sub determineConfiguration
296 {
297     return if defined $configuration;
298     determineBaseProductDir();
299     if (open CONFIGURATION, "$baseProductDir/Configuration") {
300         $configuration = <CONFIGURATION>;
301         close CONFIGURATION;
302     }
303     if ($configuration) {
304         chomp $configuration;
305         # compatibility for people who have old Configuration files
306         $configuration = "Release" if $configuration eq "Deployment";
307         $configuration = "Debug" if $configuration eq "Development";
308     } else {
309         $configuration = "Release";
310     }
311 }
312
313 sub determineArchitecture
314 {
315     return if defined $architecture;
316     # make sure $architecture is defined in all cases
317     $architecture = "";
318
319     determineBaseProductDir();
320     determineXcodeSDK();
321
322     if (isAppleMacWebKit()) {
323         if (open ARCHITECTURE, "$baseProductDir/Architecture") {
324             $architecture = <ARCHITECTURE>;
325             close ARCHITECTURE;
326         }
327         if ($architecture) {
328             chomp $architecture;
329         } else {
330             if (not defined $xcodeSDK or $xcodeSDK =~ /^(\/$|macosx)/) {
331                 my $supports64Bit = `sysctl -n hw.optional.x86_64`;
332                 chomp $supports64Bit;
333                 $architecture = 'x86_64' if $supports64Bit;
334             } elsif ($xcodeSDK =~ /^iphonesimulator/) {
335                 $architecture = 'x86_64';
336             } elsif ($xcodeSDK =~ /^iphoneos/) {
337                 $architecture = 'arm64';
338             }
339         }
340     } elsif (isCMakeBuild()) {
341         my $host_processor = "";
342         if (open my $cmake_sysinfo, "cmake --system-information |") {
343             while (<$cmake_sysinfo>) {
344                 next unless index($_, 'CMAKE_SYSTEM_PROCESSOR') == 0;
345                 if (/^CMAKE_SYSTEM_PROCESSOR \"([^"]+)\"/) {
346                     $architecture = $1;
347                     $architecture = 'x86_64' if $architecture eq 'amd64';
348                     last;
349                 }
350             }
351             close $cmake_sysinfo;
352         }
353     }
354
355     if (!isAnyWindows()) {
356         if (!$architecture) {
357             # Fall back to output of `arch', if it is present.
358             $architecture = `arch`;
359             chomp $architecture;
360         }
361
362         if (!$architecture) {
363             # Fall back to output of `uname -m', if it is present.
364             $architecture = `uname -m`;
365             chomp $architecture;
366         }
367     }
368
369     $architecture = 'x86_64' if ($architecture =~ /amd64/ && isBSD());
370 }
371
372 sub determineASanIsEnabled
373 {
374     return if defined $asanIsEnabled;
375     determineBaseProductDir();
376
377     $asanIsEnabled = 0;
378     my $asanConfigurationValue;
379
380     if (open ASAN, "$baseProductDir/ASan") {
381         $asanConfigurationValue = <ASAN>;
382         close ASAN;
383         chomp $asanConfigurationValue;
384         $asanIsEnabled = 1 if $asanConfigurationValue eq "YES";
385     }
386 }
387
388 sub determineNumberOfCPUs
389 {
390     return if defined $numberOfCPUs;
391     if (defined($ENV{NUMBER_OF_PROCESSORS})) {
392         $numberOfCPUs = $ENV{NUMBER_OF_PROCESSORS};
393     } elsif (isLinux()) {
394         # First try the nproc utility, if it exists. If we get no
395         # results fall back to just interpretting /proc directly.
396         chomp($numberOfCPUs = `nproc --all 2> /dev/null`);
397         if ($numberOfCPUs eq "") {
398             $numberOfCPUs = (grep /processor/, `cat /proc/cpuinfo`);
399         }
400     } elsif (isAnyWindows()) {
401         # Assumes cygwin
402         $numberOfCPUs = `ls /proc/registry/HKEY_LOCAL_MACHINE/HARDWARE/DESCRIPTION/System/CentralProcessor | wc -w`;
403     } elsif (isDarwin() || isBSD()) {
404         chomp($numberOfCPUs = `sysctl -n hw.ncpu`);
405     }
406 }
407
408 sub determineMaxCPULoad
409 {
410     return if defined $maxCPULoad;
411     if (defined($ENV{MAX_CPU_LOAD})) {
412         $maxCPULoad = $ENV{MAX_CPU_LOAD};
413     }
414 }
415
416 sub jscPath($)
417 {
418     my ($productDir) = @_;
419     my $jscName = "jsc";
420     $jscName .= "_debug"  if configuration() eq "Debug_All";
421     $jscName .= ".exe" if (isAnyWindows());
422     return "$productDir/$jscName" if -e "$productDir/$jscName";
423     return "$productDir/JavaScriptCore.framework/Resources/$jscName";
424 }
425
426 sub argumentsForConfiguration()
427 {
428     determineConfiguration();
429     determineArchitecture();
430     determineXcodeSDK();
431
432     my @args = ();
433     # FIXME: Is it necessary to pass --debug, --release, --32-bit or --64-bit?
434     # These are determined automatically from stored configuration.
435     push(@args, '--debug') if ($configuration =~ "^Debug");
436     push(@args, '--release') if ($configuration =~ "^Release");
437     push(@args, '--device') if (defined $xcodeSDK && $xcodeSDK =~ /^iphoneos/);
438     push(@args, '--ios-simulator') if (defined $xcodeSDK && $xcodeSDK =~ /^iphonesimulator/);
439     push(@args, '--32-bit') if ($architecture ne "x86_64" and !isWin64());
440     push(@args, '--64-bit') if (isWin64());
441     push(@args, '--gtk') if isGtk();
442     push(@args, '--efl') if isEfl();
443     push(@args, '--jsc-only') if isJSCOnly();
444     push(@args, '--wincairo') if isWinCairo();
445     push(@args, '--inspector-frontend') if isInspectorFrontend();
446     return @args;
447 }
448
449 sub determineXcodeSDK
450 {
451     return if defined $xcodeSDK;
452     my $sdk;
453     if (checkForArgumentAndRemoveFromARGVGettingValue("--sdk", \$sdk)) {
454         $xcodeSDK = $sdk;
455     }
456     if (checkForArgumentAndRemoveFromARGV("--device")) {
457         my $hasInternalSDK = exitStatus(system("xcrun --sdk iphoneos.internal --show-sdk-version > /dev/null 2>&1")) == 0;
458         $xcodeSDK ||= $hasInternalSDK ? "iphoneos.internal" : "iphoneos";
459     }
460     if (checkForArgumentAndRemoveFromARGV("--ios-simulator")) {
461         $xcodeSDK ||= 'iphonesimulator';
462     }
463 }
464
465 sub xcodeSDK
466 {
467     determineXcodeSDK();
468     return $xcodeSDK;
469 }
470
471 sub setXcodeSDK($)
472 {
473     ($xcodeSDK) = @_;
474 }
475
476
477 sub xcodeSDKPlatformName()
478 {
479     determineXcodeSDK();
480     return "" if !defined $xcodeSDK;
481     return "iphoneos" if $xcodeSDK =~ /iphoneos/i;
482     return "iphonesimulator" if $xcodeSDK =~ /iphonesimulator/i;
483     return "macosx" if $xcodeSDK =~ /macosx/i;
484     die "Couldn't determine platform name from Xcode SDK";
485 }
486
487 sub XcodeSDKPath
488 {
489     determineXcodeSDK();
490
491     die "Can't find the SDK path because no Xcode SDK was specified" if not $xcodeSDK;
492
493     my $sdkPath = `xcrun --sdk $xcodeSDK --show-sdk-path` if $xcodeSDK;
494     die 'Failed to get SDK path from xcrun' if $?;
495     chomp $sdkPath;
496
497     return $sdkPath;
498 }
499
500 sub xcodeSDKVersion
501 {
502     determineXcodeSDK();
503
504     die "Can't find the SDK version because no Xcode SDK was specified" if !$xcodeSDK;
505
506     chomp(my $sdkVersion = `xcrun --sdk $xcodeSDK --show-sdk-version`);
507     die "Failed to get SDK version from xcrun" if exitStatus($?);
508
509     return $sdkVersion;
510 }
511
512 sub programFilesPath
513 {
514     return $programFilesPath if defined $programFilesPath;
515
516     $programFilesPath = $ENV{'PROGRAMFILES(X86)'} || $ENV{'PROGRAMFILES'} || "C:\\Program Files";
517
518     return $programFilesPath;
519 }
520
521 sub visualStudioInstallDir
522 {
523     return $vsInstallDir if defined $vsInstallDir;
524
525     if ($ENV{'VSINSTALLDIR'}) {
526         $vsInstallDir = $ENV{'VSINSTALLDIR'};
527         $vsInstallDir =~ s|[\\/]$||;
528     } else {
529         $vsInstallDir = File::Spec->catdir(programFilesPath(), "Microsoft Visual Studio 14.0");
530     }
531     chomp($vsInstallDir = `cygpath "$vsInstallDir"`) if isCygwin();
532
533     print "Using Visual Studio: $vsInstallDir\n";
534     return $vsInstallDir;
535 }
536
537 sub msBuildInstallDir
538 {
539     return $msBuildInstallDir if defined $msBuildInstallDir;
540
541     $msBuildInstallDir = File::Spec->catdir(programFilesPath(), "MSBuild", "14.0", "Bin");
542    
543     chomp($msBuildInstallDir = `cygpath "$msBuildInstallDir"`) if isCygwin();
544
545     print "Using MSBuild: $msBuildInstallDir\n";
546     return $msBuildInstallDir;
547 }
548
549 sub visualStudioVersion
550 {
551     return $vsVersion if defined $vsVersion;
552
553     my $installDir = visualStudioInstallDir();
554
555     $vsVersion = ($installDir =~ /Microsoft Visual Studio ([0-9]+\.[0-9]*)/) ? $1 : "14";
556
557     print "Using Visual Studio $vsVersion\n";
558     return $vsVersion;
559 }
560
561 sub determineConfigurationForVisualStudio
562 {
563     return if defined $configurationForVisualStudio;
564     determineConfiguration();
565     # FIXME: We should detect when Debug_All or Production has been chosen.
566     $configurationForVisualStudio = "/p:Configuration=" . $configuration;
567 }
568
569 sub usesPerConfigurationBuildDirectory
570 {
571     # [Gtk] We don't have Release/Debug configurations in straight
572     # autotool builds (non build-webkit). In this case and if
573     # WEBKIT_OUTPUTDIR exist, use that as our configuration dir. This will
574     # allows us to run run-webkit-tests without using build-webkit.
575     return ($ENV{"WEBKIT_OUTPUTDIR"} && isGtk()) || isAppleWinWebKit();
576 }
577
578 sub determineConfigurationProductDir
579 {
580     return if defined $configurationProductDir;
581     determineBaseProductDir();
582     determineConfiguration();
583     if (isAppleWinWebKit() || isWinCairo()) {
584         $configurationProductDir = File::Spec->catdir($baseProductDir, $configuration);
585     } else {
586         if (usesPerConfigurationBuildDirectory()) {
587             $configurationProductDir = "$baseProductDir";
588         } else {
589             $configurationProductDir = "$baseProductDir/$configuration";
590             $configurationProductDir .= "-" . xcodeSDKPlatformName() if isIOSWebKit();
591         }
592     }
593 }
594
595 sub setConfigurationProductDir($)
596 {
597     ($configurationProductDir) = @_;
598 }
599
600 sub determineCurrentSVNRevision
601 {
602     # We always update the current SVN revision here, and leave the caching
603     # to currentSVNRevision(), so that changes to the SVN revision while the
604     # script is running can be picked up by calling this function again.
605     determineSourceDir();
606     $currentSVNRevision = svnRevisionForDirectory($sourceDir);
607     return $currentSVNRevision;
608 }
609
610
611 sub chdirWebKit
612 {
613     determineSourceDir();
614     chdir $sourceDir or die;
615 }
616
617 sub baseProductDir
618 {
619     determineBaseProductDir();
620     return $baseProductDir;
621 }
622
623 sub sourceDir
624 {
625     determineSourceDir();
626     return $sourceDir;
627 }
628
629 sub productDir
630 {
631     determineConfigurationProductDir();
632     return $configurationProductDir;
633 }
634
635 sub executableProductDir
636 {
637     my $productDirectory = productDir();
638
639     my $binaryDirectory;
640     if (isEfl() || isGtk() || isJSCOnly()) {
641         $binaryDirectory = "bin";
642     } elsif (isAnyWindows()) {
643         $binaryDirectory = isWin64() ? "bin64" : "bin32";
644     } else {
645         return $productDirectory;
646     }
647
648     return File::Spec->catdir($productDirectory, $binaryDirectory);
649 }
650
651 sub jscProductDir
652 {
653     return executableProductDir();
654 }
655
656 sub configuration()
657 {
658     determineConfiguration();
659     return $configuration;
660 }
661
662 sub asanIsEnabled()
663 {
664     determineASanIsEnabled();
665     return $asanIsEnabled;
666 }
667
668 sub configurationForVisualStudio()
669 {
670     determineConfigurationForVisualStudio();
671     return $configurationForVisualStudio;
672 }
673
674 sub currentSVNRevision
675 {
676     determineCurrentSVNRevision() if not defined $currentSVNRevision;
677     return $currentSVNRevision;
678 }
679
680 sub generateDsym()
681 {
682     determineGenerateDsym();
683     return $generateDsym;
684 }
685
686 sub determineGenerateDsym()
687 {
688     return if defined($generateDsym);
689     $generateDsym = checkForArgumentAndRemoveFromARGV("--dsym");
690 }
691
692 sub hasIOSDevelopmentCertificate()
693 {
694     return !exitStatus(system("security find-identity -p codesigning | grep '" . IOS_DEVELOPMENT_CERTIFICATE_NAME_PREFIX . "' > /dev/null 2>&1"));
695 }
696
697 sub argumentsForXcode()
698 {
699     my @args = ();
700     push @args, "DEBUG_INFORMATION_FORMAT=dwarf-with-dsym" if generateDsym();
701     return @args;
702 }
703
704 sub XcodeOptions
705 {
706     determineBaseProductDir();
707     determineConfiguration();
708     determineArchitecture();
709     determineASanIsEnabled();
710     determineXcodeSDK();
711
712     my @options;
713     push @options, "-UseSanitizedBuildSystemEnvironment=YES";
714     push @options, ("-configuration", $configuration);
715     push @options, ("-xcconfig", sourceDir() . "/Tools/asan/asan.xcconfig", "ASAN_IGNORE=" . sourceDir() . "/Tools/asan/webkit-asan-ignore.txt") if $asanIsEnabled;
716     push @options, @baseProductDirOption;
717     push @options, "ARCHS=$architecture" if $architecture;
718     push @options, "SDKROOT=$xcodeSDK" if $xcodeSDK;
719     if (willUseIOSDeviceSDK()) {
720         push @options, "ENABLE_BITCODE=NO";
721         if (hasIOSDevelopmentCertificate()) {
722             # FIXME: May match more than one installed development certificate.
723             push @options, "CODE_SIGN_IDENTITY=" . IOS_DEVELOPMENT_CERTIFICATE_NAME_PREFIX;
724         } else {
725             push @options, "CODE_SIGN_IDENTITY="; # No identity
726             push @options, "CODE_SIGNING_REQUIRED=NO";
727         }
728     }
729     push @options, argumentsForXcode();
730     return @options;
731 }
732
733 sub XcodeOptionString
734 {
735     return join " ", XcodeOptions();
736 }
737
738 sub XcodeOptionStringNoConfig
739 {
740     return join " ", @baseProductDirOption;
741 }
742
743 sub XcodeCoverageSupportOptions()
744 {
745     my @coverageSupportOptions = ();
746     push @coverageSupportOptions, "GCC_GENERATE_TEST_COVERAGE_FILES=YES";
747     push @coverageSupportOptions, "GCC_INSTRUMENT_PROGRAM_FLOW_ARCS=YES";
748     return @coverageSupportOptions;
749 }
750
751 sub XcodeStaticAnalyzerOption()
752 {
753     return "RUN_CLANG_STATIC_ANALYZER=YES";
754 }
755
756 my $passedConfiguration;
757 my $searchedForPassedConfiguration;
758 sub determinePassedConfiguration
759 {
760     return if $searchedForPassedConfiguration;
761     $searchedForPassedConfiguration = 1;
762     $passedConfiguration = undef;
763
764     if (checkForArgumentAndRemoveFromARGV("--debug")) {
765         $passedConfiguration = "Debug";
766     } elsif(checkForArgumentAndRemoveFromARGV("--release")) {
767         $passedConfiguration = "Release";
768     } elsif (checkForArgumentAndRemoveFromARGV("--profile") || checkForArgumentAndRemoveFromARGV("--profiling")) {
769         $passedConfiguration = "Profiling";
770     }
771 }
772
773 sub passedConfiguration
774 {
775     determinePassedConfiguration();
776     return $passedConfiguration;
777 }
778
779 sub setConfiguration
780 {
781     setArchitecture();
782
783     if (my $config = shift @_) {
784         $configuration = $config;
785         return;
786     }
787
788     determinePassedConfiguration();
789     $configuration = $passedConfiguration if $passedConfiguration;
790 }
791
792
793 my $passedArchitecture;
794 my $searchedForPassedArchitecture;
795 sub determinePassedArchitecture
796 {
797     return if $searchedForPassedArchitecture;
798     $searchedForPassedArchitecture = 1;
799
800     $passedArchitecture = undef;
801     if (checkForArgumentAndRemoveFromARGV("--32-bit")) {
802         if (isAppleMacWebKit()) {
803             # PLATFORM_IOS: Don't run `arch` command inside Simulator environment
804             local %ENV = %ENV;
805             delete $ENV{DYLD_ROOT_PATH};
806             delete $ENV{DYLD_FRAMEWORK_PATH};
807
808             $passedArchitecture = `arch`;
809             chomp $passedArchitecture;
810         }
811     }
812 }
813
814 sub passedArchitecture
815 {
816     determinePassedArchitecture();
817     return $passedArchitecture;
818 }
819
820 sub architecture()
821 {
822     determineArchitecture();
823     return $architecture;
824 }
825
826 sub numberOfCPUs()
827 {
828     determineNumberOfCPUs();
829     return $numberOfCPUs;
830 }
831
832 sub maxCPULoad()
833 {
834     determineMaxCPULoad();
835     return $maxCPULoad;
836 }
837
838 sub setArchitecture
839 {
840     if (my $arch = shift @_) {
841         $architecture = $arch;
842         return;
843     }
844
845     determinePassedArchitecture();
846     $architecture = $passedArchitecture if $passedArchitecture;
847 }
848
849 sub skipSafariExecutableEntitlementChecks
850 {
851     return `defaults read /Library/Preferences/org.webkit.BuildConfiguration SkipSafariExecutableEntitlementChecks 2>/dev/null` eq "1\n";
852 }
853
854 sub executableHasEntitlements
855 {
856     my $executablePath = shift;
857     return (`codesign -d --entitlements - $executablePath 2>&1` =~ /<key>/);
858 }
859
860 sub safariPathFromSafariBundle
861 {
862     my ($safariBundle) = @_;
863
864     die "Safari path is only relevant on Apple Mac platform\n" unless isAppleMacWebKit();
865
866     my $safariPath = "$safariBundle/Contents/MacOS/Safari";
867     return $safariPath if skipSafariExecutableEntitlementChecks();
868
869     my $safariForWebKitDevelopmentPath = "$safariBundle/Contents/MacOS/SafariForWebKitDevelopment";
870     return $safariForWebKitDevelopmentPath if -f $safariForWebKitDevelopmentPath && executableHasEntitlements($safariPath);
871
872     return $safariPath;
873 }
874
875 sub installedSafariPath
876 {
877     return safariPathFromSafariBundle("/Applications/Safari.app");
878 }
879
880 # Locate Safari.
881 sub safariPath
882 {
883     die "Safari path is only relevant on Apple Mac platform\n" unless isAppleMacWebKit();
884
885     # Use WEBKIT_SAFARI environment variable if present.
886     my $safariBundle = $ENV{WEBKIT_SAFARI};
887     if (!$safariBundle) {
888         determineConfigurationProductDir();
889         # Use Safari.app in product directory if present (good for Safari development team).
890         if (-d "$configurationProductDir/Safari.app") {
891             $safariBundle = "$configurationProductDir/Safari.app";
892         }
893         if (!$safariBundle) {
894             return installedSafariPath();
895         }
896     }
897     my $safariPath = safariPathFromSafariBundle($safariBundle);
898     die "Can't find executable at $safariPath.\n" if !-x $safariPath;
899     return $safariPath;
900 }
901
902 sub builtDylibPathForName
903 {
904     my $libraryName = shift;
905     determineConfigurationProductDir();
906
907     if (isGtk()) {
908         my $extension = isDarwin() ? ".dylib" : ".so";
909         return "$configurationProductDir/lib/libwebkit2gtk-4.0" . $extension;
910     }
911     if (isEfl()) {
912         return "$configurationProductDir/lib/libewebkit2.so";
913     }
914     if (isIOSWebKit()) {
915         return "$configurationProductDir/$libraryName.framework/$libraryName";
916     }
917     if (isAppleMacWebKit()) {
918         return "$configurationProductDir/$libraryName.framework/Versions/A/$libraryName";
919     }
920     if (isAppleWinWebKit()) {
921         if ($libraryName eq "JavaScriptCore") {
922             return "$baseProductDir/lib/$libraryName.lib";
923         } else {
924             return "$baseProductDir/$libraryName.intermediate/$configuration/$libraryName.intermediate/$libraryName.lib";
925         }
926     }
927
928     die "Unsupported platform, can't determine built library locations.\nTry `build-webkit --help` for more information.\n";
929 }
930
931 # Check to see that all the frameworks are built.
932 sub checkFrameworks # FIXME: This is a poor name since only the Mac calls built WebCore a Framework.
933 {
934     return if isAnyWindows();
935     my @frameworks = ("JavaScriptCore", "WebCore");
936     push(@frameworks, "WebKit") if isAppleMacWebKit(); # FIXME: This seems wrong, all ports should have a WebKit these days.
937     for my $framework (@frameworks) {
938         my $path = builtDylibPathForName($framework);
939         die "Can't find built framework at \"$path\".\n" unless -e $path;
940     }
941 }
942
943 sub isInspectorFrontend()
944 {
945     determineIsInspectorFrontend();
946     return $isInspectorFrontend;
947 }
948
949 sub determineIsInspectorFrontend()
950 {
951     return if defined($isInspectorFrontend);
952     $isInspectorFrontend = checkForArgumentAndRemoveFromARGV("--inspector-frontend");
953 }
954
955 sub commandExists($)
956 {
957     my $command = shift;
958     my $devnull = File::Spec->devnull();
959
960     if (isAnyWindows()) {
961         return exitStatus(system("where /q $command >$devnull 2>&1")) == 0;
962     }
963     return exitStatus(system("which $command >$devnull 2>&1")) == 0;
964 }
965
966 sub checkForArgumentAndRemoveFromARGV($)
967 {
968     my $argToCheck = shift;
969     return checkForArgumentAndRemoveFromArrayRef($argToCheck, \@ARGV);
970 }
971
972 sub checkForArgumentAndRemoveFromArrayRefGettingValue($$$)
973 {
974     my ($argToCheck, $valueRef, $arrayRef) = @_;
975     my $argumentStartRegEx = qr#^$argToCheck(?:=\S|$)#;
976     my $i = 0;
977     for (; $i < @$arrayRef; ++$i) {
978         last if $arrayRef->[$i] =~ $argumentStartRegEx;
979     }
980     if ($i >= @$arrayRef) {
981         return $$valueRef = undef;
982     }
983     my ($key, $value) = split("=", $arrayRef->[$i]);
984     splice(@$arrayRef, $i, 1);
985     if (defined($value)) {
986         # e.g. --sdk=iphonesimulator
987         return $$valueRef = $value;
988     }
989     return $$valueRef = splice(@$arrayRef, $i, 1); # e.g. --sdk iphonesimulator
990 }
991
992 sub checkForArgumentAndRemoveFromARGVGettingValue($$)
993 {
994     my ($argToCheck, $valueRef) = @_;
995     return checkForArgumentAndRemoveFromArrayRefGettingValue($argToCheck, $valueRef, \@ARGV);
996 }
997
998 sub findMatchingArguments($$)
999 {
1000     my ($argToCheck, $arrayRef) = @_;
1001     my @matchingIndices;
1002     foreach my $index (0 .. $#$arrayRef) {
1003         my $opt = $$arrayRef[$index];
1004         if ($opt =~ /^$argToCheck$/i ) {
1005             push(@matchingIndices, $index);
1006         }
1007     }
1008     return @matchingIndices; 
1009 }
1010
1011 sub hasArgument($$)
1012 {
1013     my ($argToCheck, $arrayRef) = @_;
1014     my @matchingIndices = findMatchingArguments($argToCheck, $arrayRef);
1015     return scalar @matchingIndices > 0;
1016 }
1017
1018 sub checkForArgumentAndRemoveFromArrayRef
1019 {
1020     my ($argToCheck, $arrayRef) = @_;
1021     my @indicesToRemove = findMatchingArguments($argToCheck, $arrayRef);
1022     my $removeOffset = 0;
1023     foreach my $index (@indicesToRemove) {
1024         splice(@$arrayRef, $index - $removeOffset++, 1);
1025     }
1026     return scalar @indicesToRemove > 0;
1027 }
1028
1029 sub prohibitUnknownPort()
1030 {
1031     $unknownPortProhibited = 1;
1032 }
1033
1034 sub determinePortName()
1035 {
1036     return if defined $portName;
1037
1038     my %argToPortName = (
1039         efl => Efl,
1040         gtk => GTK,
1041         'jsc-only' => JSCOnly,
1042         wincairo => WinCairo
1043     );
1044
1045     for my $arg (sort keys %argToPortName) {
1046         if (checkForArgumentAndRemoveFromARGV("--$arg")) {
1047             die "Argument '--$arg' conflicts with selected port '$portName'\n"
1048                 if defined $portName;
1049
1050             $portName = $argToPortName{$arg};
1051         }
1052     }
1053
1054     return if defined $portName;
1055
1056     # Port was not selected via command line, use appropriate default value
1057
1058     if (isAnyWindows()) {
1059         $portName = AppleWin;
1060     } elsif (isDarwin()) {
1061         determineXcodeSDK();
1062         if (willUseIOSDeviceSDK() || willUseIOSSimulatorSDK()) {
1063             $portName = iOS;
1064         } else {
1065             $portName = Mac;
1066         }
1067     } else {
1068         if ($unknownPortProhibited) {
1069             my $portsChoice = join "\n\t", qw(
1070                 --efl
1071                 --gtk
1072                 --jsc-only
1073             );
1074             die "Please specify which WebKit port to build using one of the following options:"
1075                 . "\n\t$portsChoice\n";
1076         }
1077
1078         # If script is run without arguments we cannot determine port
1079         # TODO: This state should be outlawed
1080         $portName = Unknown;
1081     }
1082 }
1083
1084 sub portName()
1085 {
1086     determinePortName();
1087     return $portName;
1088 }
1089
1090 sub isEfl()
1091 {
1092     return portName() eq Efl;
1093 }
1094
1095 sub isGtk()
1096 {
1097     return portName() eq GTK;
1098 }
1099
1100 sub isJSCOnly()
1101 {
1102     return portName() eq JSCOnly;
1103 }
1104
1105 # Determine if this is debian, ubuntu, linspire, or something similar.
1106 sub isDebianBased()
1107 {
1108     return -e "/etc/debian_version";
1109 }
1110
1111 sub isFedoraBased()
1112 {
1113     return -e "/etc/fedora-release";
1114 }
1115
1116 sub isWinCairo()
1117 {
1118     return portName() eq WinCairo;
1119 }
1120
1121 sub isWin64()
1122 {
1123     determineIsWin64();
1124     return $isWin64;
1125 }
1126
1127 sub determineIsWin64()
1128 {
1129     return if defined($isWin64);
1130     $isWin64 = checkForArgumentAndRemoveFromARGV("--64-bit");
1131 }
1132
1133 sub determineIsWin64FromArchitecture($)
1134 {
1135     my $arch = shift;
1136     $isWin64 = ($arch eq "x86_64");
1137     return $isWin64;
1138 }
1139
1140 sub isCygwin()
1141 {
1142     return ($^O eq "cygwin") || 0;
1143 }
1144
1145 sub isAnyWindows()
1146 {
1147     return isWindows() || isCygwin();
1148 }
1149
1150 sub determineWinVersion()
1151 {
1152     return if $winVersion;
1153
1154     if (!isAnyWindows()) {
1155         $winVersion = -1;
1156         return;
1157     }
1158
1159     my $versionString = `cmd /c ver`;
1160     $versionString =~ /(\d)\.(\d)\.(\d+)/;
1161
1162     $winVersion = {
1163         major => $1,
1164         minor => $2,
1165         build => $3,
1166     };
1167 }
1168
1169 sub winVersion()
1170 {
1171     determineWinVersion();
1172     return $winVersion;
1173 }
1174
1175 sub isWindows7SP0()
1176 {
1177     return isAnyWindows() && winVersion()->{major} == 6 && winVersion()->{minor} == 1 && winVersion()->{build} == 7600;
1178 }
1179
1180 sub isWindowsVista()
1181 {
1182     return isAnyWindows() && winVersion()->{major} == 6 && winVersion()->{minor} == 0;
1183 }
1184
1185 sub isWindowsXP()
1186 {
1187     return isAnyWindows() && winVersion()->{major} == 5 && winVersion()->{minor} == 1;
1188 }
1189
1190 sub isDarwin()
1191 {
1192     return ($^O eq "darwin") || 0;
1193 }
1194
1195 sub isWindows()
1196 {
1197     return ($^O eq "MSWin32") || 0;
1198 }
1199
1200 sub isLinux()
1201 {
1202     return ($^O eq "linux") || 0;
1203 }
1204
1205 sub isBSD()
1206 {
1207     return ($^O eq "freebsd") || ($^O eq "openbsd") || ($^O eq "netbsd") || 0;
1208 }
1209
1210 sub isARM()
1211 {
1212     return ($Config{archname} =~ /^arm[v\-]/) || ($Config{archname} =~ /^aarch64[v\-]/);
1213 }
1214
1215 sub isX86_64()
1216 {
1217     return (architecture() eq "x86_64") || 0;
1218 }
1219
1220 sub isCrossCompilation()
1221 {
1222   my $compiler = "";
1223   $compiler = $ENV{'CC'} if (defined($ENV{'CC'}));
1224   if ($compiler =~ /gcc/) {
1225       my $compiler_options = `$compiler -v 2>&1`;
1226       my @host = $compiler_options =~ m/--host=(.*?)\s/;
1227       my @target = $compiler_options =~ m/--target=(.*?)\s/;
1228
1229       return ($host[0] ne "" && $target[0] ne "" && $host[0] ne $target[0]);
1230   }
1231   return 0;
1232 }
1233
1234 sub isAppleWebKit()
1235 {
1236     return isAppleMacWebKit() || isAppleWinWebKit();
1237 }
1238
1239 sub isAppleMacWebKit()
1240 {
1241     return (portName() eq Mac) || isIOSWebKit();
1242 }
1243
1244 sub isAppleWinWebKit()
1245 {
1246     return portName() eq AppleWin;
1247 }
1248
1249 sub iOSSimulatorDevicesPath
1250 {
1251     return "$ENV{HOME}/Library/Developer/CoreSimulator/Devices";
1252 }
1253
1254 sub iOSSimulatorDevices
1255 {
1256     eval "require Foundation";
1257     my $devicesPath = iOSSimulatorDevicesPath();
1258     opendir(DEVICES, $devicesPath);
1259     my @udids = grep {
1260         $_ =~ m/^[0-9A-F]{8}-([0-9A-F]{4}-){3}[0-9A-F]{12}$/;
1261     } readdir(DEVICES);
1262     close(DEVICES);
1263
1264     # FIXME: We should parse the device.plist file ourself and map the dictionary keys in it to known
1265     #        dictionary keys so as to decouple our representation of the plist from the actual structure
1266     #        of the plist, which may change.
1267     my @devices = map {
1268         Foundation::perlRefFromObjectRef(NSDictionary->dictionaryWithContentsOfFile_("$devicesPath/$_/device.plist"));
1269     } @udids;
1270
1271     return @devices;
1272 }
1273
1274 sub createiOSSimulatorDevice
1275 {
1276     my $name = shift;
1277     my $deviceTypeId = shift;
1278     my $runtimeId = shift;
1279
1280     my $created = system("xcrun", "--sdk", "iphonesimulator", "simctl", "create", $name, $deviceTypeId, $runtimeId) == 0;
1281     die "Couldn't create simulator device: $name $deviceTypeId $runtimeId" if not $created;
1282
1283     system("xcrun", "--sdk", "iphonesimulator", "simctl", "list");
1284
1285     print "Waiting for device to be created ...\n";
1286     sleep 5;
1287     for (my $tries = 0; $tries < 5; $tries++){
1288         my @devices = iOSSimulatorDevices();
1289         foreach my $device (@devices) {
1290             return $device if $device->{name} eq $name and $device->{deviceType} eq $deviceTypeId and $device->{runtime} eq $runtimeId;
1291         }
1292         sleep 5;
1293     }
1294     die "Device $name $deviceTypeId $runtimeId wasn't found in " . iOSSimulatorDevicesPath();
1295 }
1296
1297 sub willUseIOSDeviceSDK()
1298 {
1299     return xcodeSDKPlatformName() eq "iphoneos";
1300 }
1301
1302 sub willUseIOSSimulatorSDK()
1303 {
1304     return xcodeSDKPlatformName() eq "iphonesimulator";
1305 }
1306
1307 sub isIOSWebKit()
1308 {
1309     return portName() eq iOS;
1310 }
1311
1312 sub determineNmPath()
1313 {
1314     return if $nmPath;
1315
1316     if (isAppleMacWebKit()) {
1317         $nmPath = `xcrun -find nm`;
1318         chomp $nmPath;
1319     }
1320     $nmPath = "nm" if !$nmPath;
1321 }
1322
1323 sub nmPath()
1324 {
1325     determineNmPath();
1326     return $nmPath;
1327 }
1328
1329 sub splitVersionString
1330 {
1331     my $versionString = shift;
1332     my @splitVersion = split(/\./, $versionString);
1333     @splitVersion >= 2 or die "Invalid version $versionString";
1334     $osXVersion = {
1335             "major" => $splitVersion[0],
1336             "minor" => $splitVersion[1],
1337             "subminor" => (defined($splitVersion[2]) ? $splitVersion[2] : 0),
1338     };
1339 }
1340
1341 sub determineOSXVersion()
1342 {
1343     return if $osXVersion;
1344
1345     if (!isDarwin()) {
1346         $osXVersion = -1;
1347         return;
1348     }
1349
1350     my $versionString = `sw_vers -productVersion`;
1351     $osXVersion = splitVersionString($versionString);
1352 }
1353
1354 sub osXVersion()
1355 {
1356     determineOSXVersion();
1357     return $osXVersion;
1358 }
1359
1360 sub determineIOSVersion()
1361 {
1362     return if $iosVersion;
1363
1364     if (!isIOSWebKit()) {
1365         $iosVersion = -1;
1366         return;
1367     }
1368
1369     my $versionString = xcodeSDKVersion();
1370     $iosVersion = splitVersionString($versionString);
1371 }
1372
1373 sub iosVersion()
1374 {
1375     determineIOSVersion();
1376     return $iosVersion;
1377 }
1378
1379 sub isWindowsNT()
1380 {
1381     return $ENV{'OS'} eq 'Windows_NT';
1382 }
1383
1384 sub appendToEnvironmentVariableList($$)
1385 {
1386     my ($name, $value) = @_;
1387
1388     if (defined($ENV{$name})) {
1389         $ENV{$name} .= $Config{path_sep} . $value;
1390     } else {
1391         $ENV{$name} = $value;
1392     }
1393 }
1394
1395 sub prependToEnvironmentVariableList($$)
1396 {
1397     my ($name, $value) = @_;
1398
1399     if (defined($ENV{$name})) {
1400         $ENV{$name} = $value . $Config{path_sep} . $ENV{$name};
1401     } else {
1402         $ENV{$name} = $value;
1403     }
1404 }
1405
1406 sub sharedCommandLineOptions()
1407 {
1408     return (
1409         "g|guard-malloc" => \$shouldUseGuardMalloc,
1410     );
1411 }
1412
1413 sub sharedCommandLineOptionsUsage
1414 {
1415     my %opts = @_;
1416
1417     my %switches = (
1418         '-g|--guard-malloc' => 'Use guardmalloc when running executable',
1419     );
1420
1421     my $indent = " " x ($opts{indent} || 2);
1422     my $switchWidth = List::Util::max(int($opts{switchWidth}), List::Util::max(map { length($_) } keys %switches) + ($opts{brackets} ? 2 : 0));
1423
1424     my $result = "Common switches:\n";
1425
1426     for my $switch (keys %switches) {
1427         my $switchName = $opts{brackets} ? "[" . $switch . "]" : $switch;
1428         $result .= sprintf("%s%-" . $switchWidth . "s %s\n", $indent, $switchName, $switches{$switch});
1429     }
1430
1431     return $result;
1432 }
1433
1434 sub setUpGuardMallocIfNeeded
1435 {
1436     if (!isDarwin()) {
1437         return;
1438     }
1439
1440     if (!defined($shouldUseGuardMalloc)) {
1441         $shouldUseGuardMalloc = checkForArgumentAndRemoveFromARGV("-g") || checkForArgumentAndRemoveFromARGV("--guard-malloc");
1442     }
1443
1444     if ($shouldUseGuardMalloc) {
1445         appendToEnvironmentVariableList("DYLD_INSERT_LIBRARIES", "/usr/lib/libgmalloc.dylib");
1446         appendToEnvironmentVariableList("__XPC_DYLD_INSERT_LIBRARIES", "/usr/lib/libgmalloc.dylib");
1447     }
1448 }
1449
1450 sub relativeScriptsDir()
1451 {
1452     my $scriptDir = File::Spec->catpath("", File::Spec->abs2rel($FindBin::Bin, getcwd()), "");
1453     if ($scriptDir eq "") {
1454         $scriptDir = ".";
1455     }
1456     return $scriptDir;
1457 }
1458
1459 sub launcherPath()
1460 {
1461     my $relativeScriptsPath = relativeScriptsDir();
1462     if (isGtk() || isEfl()) {
1463         return "$relativeScriptsPath/run-minibrowser";
1464     } elsif (isAppleWebKit()) {
1465         return "$relativeScriptsPath/run-safari";
1466     }
1467 }
1468
1469 sub launcherName()
1470 {
1471     if (isGtk() || isEfl()) {
1472         return "MiniBrowser";
1473     } elsif (isAppleMacWebKit()) {
1474         return "Safari";
1475     } elsif (isAppleWinWebKit()) {
1476         return "MiniBrowser";
1477     }
1478 }
1479
1480 sub checkRequiredSystemConfig
1481 {
1482     if (isDarwin()) {
1483         chomp(my $productVersion = `sw_vers -productVersion`);
1484         if (eval "v$productVersion" lt v10.10.5) {
1485             print "*************************************************************\n";
1486             print "OS X Yosemite v10.10.5 or later is required to build WebKit.\n";
1487             print "You have " . $productVersion . ", thus the build will most likely fail.\n";
1488             print "*************************************************************\n";
1489         }
1490         determineXcodeVersion();
1491         if (eval "v$xcodeVersion" lt v7.0) {
1492             print "*************************************************************\n";
1493             print "Xcode 7.0 or later is required to build WebKit.\n";
1494             print "You have an earlier version of Xcode, thus the build will\n";
1495             print "most likely fail. The latest Xcode is available from the App Store.\n";
1496             print "*************************************************************\n";
1497         }
1498     }
1499 }
1500
1501 sub determineWindowsSourceDir()
1502 {
1503     return if $windowsSourceDir;
1504     $windowsSourceDir = sourceDir();
1505     chomp($windowsSourceDir = `cygpath -w '$windowsSourceDir'`) if isCygwin();
1506 }
1507
1508 sub windowsSourceDir()
1509 {
1510     determineWindowsSourceDir();
1511     return $windowsSourceDir;
1512 }
1513
1514 sub windowsSourceSourceDir()
1515 {
1516     return File::Spec->catdir(windowsSourceDir(), "Source");
1517 }
1518
1519 sub windowsLibrariesDir()
1520 {
1521     return File::Spec->catdir(windowsSourceDir(), "WebKitLibraries", "win");
1522 }
1523
1524 sub windowsOutputDir()
1525 {
1526     return File::Spec->catdir(windowsSourceDir(), "WebKitBuild");
1527 }
1528
1529 sub fontExists($)
1530 {
1531     my $font = shift;
1532     my $cmd = "reg query \"HKLM\\SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion\\Fonts\" /v \"$font\" 2>&1";
1533     my $val = `$cmd`;
1534     return $? == 0;
1535 }
1536
1537 sub checkInstalledTools()
1538 {
1539     # environment variables. Avoid until this is corrected.
1540     my $pythonVer = `python --version 2>&1`;
1541     die "You must have Python installed to build WebKit.\n" if ($?);
1542
1543     # cURL 7.34.0 has a bug that prevents authentication with opensource.apple.com (and other things using SSL3).
1544     my $curlVer = `curl --version 2> NUL`;
1545     if (!$? and $curlVer =~ "(.*curl.*)") {
1546         $curlVer = $1;
1547         if ($curlVer =~ /libcurl\/7\.34\.0/) {
1548             print "cURL version 7.34.0 has a bug that prevents authentication with SSL v2 or v3.\n";
1549             print "cURL 7.33.0 is known to work. The cURL projects is preparing an update to\n";
1550             print "correct this problem.\n\n";
1551             die "Please install a working cURL and try again.\n";
1552         }
1553     }
1554
1555     # MathML requires fonts that may not ship with Windows.
1556     # Warn the user if they are missing.
1557     my @fonts = ('Cambria & Cambria Math (TrueType)', 'LatinModernMath-Regular (TrueType)', 'STIXMath-Regular (TrueType)');
1558     my @missing = ();
1559     foreach my $font (@fonts) {
1560         push @missing, $font if not fontExists($font);
1561     }
1562
1563     if (scalar @missing > 0) {
1564         print "*************************************************************\n";
1565         print "Mathematical fonts, such as Latin Modern Math are needed to\n";
1566         print "use the MathML feature.  You do not appear to have these fonts\n";
1567         print "on your system.\n\n";
1568         print "You can download a suitable set of fonts from the following URL:\n";
1569         print "https://trac.webkit.org/wiki/MathML/Fonts\n";
1570         print "*************************************************************\n";
1571     }
1572
1573     print "Installed tools are correct for the WebKit build.\n";
1574 }
1575
1576 sub setupAppleWinEnv()
1577 {
1578     return unless isAppleWinWebKit();
1579
1580     checkInstalledTools();
1581
1582     if (isWindowsNT()) {
1583         my $restartNeeded = 0;
1584         my %variablesToSet = ();
1585
1586         # FIXME: We should remove this explicit version check for cygwin once we stop supporting Cygwin 1.7.9 or older versions. 
1587         # https://bugs.webkit.org/show_bug.cgi?id=85791
1588         my $uname_version = (POSIX::uname())[2];
1589         $uname_version =~ s/\(.*\)//;  # Remove the trailing cygwin version, if any.
1590         $uname_version =~ s/\-.*$//; # Remove trailing dash-version content, if any
1591         if (version->parse($uname_version) < version->parse("1.7.10")) {
1592             # Setting the environment variable 'CYGWIN' to 'tty' makes cygwin enable extra support (i.e., termios)
1593             # for UNIX-like ttys in the Windows console
1594             $variablesToSet{CYGWIN} = "tty" unless $ENV{CYGWIN};
1595         }
1596         
1597         # Those environment variables must be set to be able to build inside Visual Studio.
1598         $variablesToSet{WEBKIT_LIBRARIES} = windowsLibrariesDir() unless $ENV{WEBKIT_LIBRARIES};
1599         $variablesToSet{WEBKIT_OUTPUTDIR} = windowsOutputDir() unless $ENV{WEBKIT_OUTPUTDIR};
1600         $variablesToSet{MSBUILDDISABLENODEREUSE} = "1" unless $ENV{MSBUILDDISABLENODEREUSE};
1601         $variablesToSet{_IsNativeEnvironment} = "true" unless $ENV{_IsNativeEnvironment};
1602         $variablesToSet{PreferredToolArchitecture} = "x64" unless $ENV{PreferredToolArchitecture};
1603
1604         foreach my $variable (keys %variablesToSet) {
1605             print "Setting the Environment Variable '" . $variable . "' to '" . $variablesToSet{$variable} . "'\n\n";
1606             my $ret = system "setx", $variable, $variablesToSet{$variable};
1607             if ($ret != 0) {
1608                 system qw(regtool -s set), '\\HKEY_CURRENT_USER\\Environment\\' . $variable, $variablesToSet{$variable};
1609             }
1610             $restartNeeded ||=  $variable eq "WEBKIT_LIBRARIES" || $variable eq "WEBKIT_OUTPUTDIR";
1611         }
1612
1613         if ($restartNeeded) {
1614             print "Please restart your computer before attempting to build inside Visual Studio.\n\n";
1615         }
1616     } else {
1617         if (!defined $ENV{'WEBKIT_LIBRARIES'} || !$ENV{'WEBKIT_LIBRARIES'}) {
1618             print "Warning: You must set the 'WebKit_Libraries' environment variable\n";
1619             print "         to be able build WebKit from within Visual Studio 2013 and newer.\n";
1620             print "         Make sure that 'WebKit_Libraries' points to the\n";
1621             print "         'WebKitLibraries/win' directory, not the 'WebKitLibraries/' directory.\n\n";
1622         }
1623         if (!defined $ENV{'WEBKIT_OUTPUTDIR'} || !$ENV{'WEBKIT_OUTPUTDIR'}) {
1624             print "Warning: You must set the 'WebKit_OutputDir' environment variable\n";
1625             print "         to be able build WebKit from within Visual Studio 2013 and newer.\n\n";
1626         }
1627         if (!defined $ENV{'MSBUILDDISABLENODEREUSE'} || !$ENV{'MSBUILDDISABLENODEREUSE'}) {
1628             print "Warning: You should set the 'MSBUILDDISABLENODEREUSE' environment variable to '1'\n";
1629             print "         to avoid periodic locked log files when building.\n\n";
1630         }
1631     }
1632     # FIXME (125180): Remove the following temporary 64-bit support once official support is available.
1633     if (isWin64() and !$ENV{'WEBKIT_64_SUPPORT'}) {
1634         print "Warning: You must set the 'WEBKIT_64_SUPPORT' environment variable\n";
1635         print "         to be able run WebKit or JavaScriptCore tests.\n\n";
1636     }
1637 }
1638
1639 sub setupCygwinEnv()
1640 {
1641     return if !isAnyWindows();
1642     return if $vcBuildPath;
1643
1644     my $programFilesPath = programFilesPath();
1645     my $visualStudioPath = File::Spec->catfile(visualStudioInstallDir(), qw(Common7 IDE devenv.com));
1646     if (-e $visualStudioPath) {
1647         # Visual Studio is installed;
1648         if (visualStudioVersion() eq "12") {
1649             $visualStudioPath = File::Spec->catfile(visualStudioInstallDir(), qw(Common7 IDE devenv.exe));
1650         }
1651     } else {
1652         # Visual Studio not found, try VC++ Express
1653         $visualStudioPath = File::Spec->catfile(visualStudioInstallDir(), qw(Common7 IDE WDExpress.exe));
1654         if (! -e $visualStudioPath) {
1655             print "*************************************************************\n";
1656             print "Cannot find '$visualStudioPath'\n";
1657             print "Please execute the file 'vcvars32.bat' from\n";
1658             print "'$programFilesPath\\Microsoft Visual Studio 14.0\\VC\\bin\\'\n";
1659             print "to setup the necessary environment variables.\n";
1660             print "*************************************************************\n";
1661             die;
1662         }
1663         $willUseVCExpressWhenBuilding = 1;
1664     }
1665
1666     print "Building results into: ", baseProductDir(), "\n";
1667     print "WEBKIT_OUTPUTDIR is set to: ", $ENV{"WEBKIT_OUTPUTDIR"}, "\n";
1668     print "WEBKIT_LIBRARIES is set to: ", $ENV{"WEBKIT_LIBRARIES"}, "\n";
1669     # FIXME (125180): Remove the following temporary 64-bit support once official support is available.
1670     print "WEBKIT_64_SUPPORT is set to: ", $ENV{"WEBKIT_64_SUPPORT"}, "\n" if isWin64();
1671
1672     # We will actually use MSBuild to build WebKit, but we need to find the Visual Studio install (above) to make
1673     # sure we use the right options.
1674     $vcBuildPath = File::Spec->catfile(msBuildInstallDir(), qw(MSBuild.exe));
1675     if (! -e $vcBuildPath) {
1676         print "*************************************************************\n";
1677         print "Cannot find '$vcBuildPath'\n";
1678         print "Please make sure execute that the Microsoft .NET Framework SDK\n";
1679         print "is installed on this machine.\n";
1680         print "*************************************************************\n";
1681         die;
1682     }
1683 }
1684
1685 sub dieIfWindowsPlatformSDKNotInstalled
1686 {
1687     my $registry32Path = "/proc/registry/";
1688     my $registry64Path = "/proc/registry64/";
1689     my @windowsPlatformSDKRegistryEntries = (
1690         "HKEY_LOCAL_MACHINE/SOFTWARE/Microsoft/Microsoft SDKs/Windows/v8.0A",
1691         "HKEY_LOCAL_MACHINE/SOFTWARE/Microsoft/Microsoft SDKs/Windows/v8.0",
1692         "HKEY_LOCAL_MACHINE/SOFTWARE/Microsoft/Microsoft SDKs/Windows/v7.1A",
1693         "HKEY_LOCAL_MACHINE/SOFTWARE/Microsoft/Microsoft SDKs/Windows/v7.0A",
1694         "HKEY_LOCAL_MACHINE/SOFTWARE/Microsoft/MicrosoftSDK/InstalledSDKs/D2FF9F89-8AA2-4373-8A31-C838BF4DBBE1",
1695     );
1696
1697     # FIXME: It would be better to detect whether we are using 32- or 64-bit Windows
1698     # and only check the appropriate entry. But for now we just blindly check both.
1699     my $recommendedPlatformSDK = $windowsPlatformSDKRegistryEntries[0];
1700
1701     while (@windowsPlatformSDKRegistryEntries) {
1702         my $windowsPlatformSDKRegistryEntry = shift @windowsPlatformSDKRegistryEntries;
1703         return if (-e $registry32Path . $windowsPlatformSDKRegistryEntry) || (-e $registry64Path . $windowsPlatformSDKRegistryEntry);
1704     }
1705
1706     print "*************************************************************\n";
1707     print "Cannot find registry entry '$recommendedPlatformSDK'.\n";
1708     print "Please download and install the Microsoft Windows SDK\n";
1709     print "from <http://www.microsoft.com/en-us/download/details.aspx?id=8279>.\n\n";
1710     print "Then follow step 2 in the Windows section of the \"Installing Developer\n";
1711     print "Tools\" instructions at <http://www.webkit.org/building/tools.html>.\n";
1712     print "*************************************************************\n";
1713     die;
1714 }
1715
1716 sub buildXCodeProject($$@)
1717 {
1718     my ($project, $clean, @extraOptions) = @_;
1719
1720     if ($clean) {
1721         push(@extraOptions, "-alltargets");
1722         push(@extraOptions, "clean");
1723     }
1724
1725     chomp($ENV{DSYMUTIL_NUM_THREADS} = `sysctl -n hw.activecpu`);
1726     return system "xcodebuild", "-project", "$project.xcodeproj", @extraOptions;
1727 }
1728
1729 sub usingVisualStudioExpress()
1730 {
1731     setupCygwinEnv();
1732     return $willUseVCExpressWhenBuilding;
1733 }
1734
1735 sub buildVisualStudioProject
1736 {
1737     my ($project, $clean) = @_;
1738     setupCygwinEnv();
1739
1740     my $config = configurationForVisualStudio();
1741
1742     dieIfWindowsPlatformSDKNotInstalled() if $willUseVCExpressWhenBuilding;
1743
1744     chomp($project = `cygpath -w "$project"`) if isCygwin();
1745
1746     my $action = "/t:build";
1747     if ($clean) {
1748         $action = "/t:clean";
1749     }
1750
1751     my $platform = "/p:Platform=" . (isWin64() ? "x64" : "Win32");
1752     my $logPath = File::Spec->catdir($baseProductDir, $configuration);
1753     make_path($logPath) unless -d $logPath or $logPath eq ".";
1754
1755     my $errorLogFile = File::Spec->catfile($logPath, "webkit_errors.log");
1756     chomp($errorLogFile = `cygpath -w "$errorLogFile"`) if isCygwin();
1757     my $errorLogging = "/flp:LogFile=" . $errorLogFile . ";ErrorsOnly";
1758
1759     my $warningLogFile = File::Spec->catfile($logPath, "webkit_warnings.log");
1760     chomp($warningLogFile = `cygpath -w "$warningLogFile"`) if isCygwin();
1761     my $warningLogging = "/flp1:LogFile=" . $warningLogFile . ";WarningsOnly";
1762
1763     my @command = ($vcBuildPath, "/verbosity:minimal", $project, $action, $config, $platform, "/fl", $errorLogging, "/fl1", $warningLogging);
1764     print join(" ", @command), "\n";
1765     return system @command;
1766 }
1767
1768 sub getJhbuildPath()
1769 {
1770     my @jhbuildPath = File::Spec->splitdir(baseProductDir());
1771     if (isGit() && isGitBranchBuild() && gitBranch()) {
1772         pop(@jhbuildPath);
1773     }
1774     if (isEfl()) {
1775         push(@jhbuildPath, "DependenciesEFL");
1776     } elsif (isGtk()) {
1777         push(@jhbuildPath, "DependenciesGTK");
1778     } else {
1779         die "Cannot get JHBuild path for platform that isn't GTK+ or EFL.\n";
1780     }
1781     return File::Spec->catdir(@jhbuildPath);
1782 }
1783
1784 sub isCachedArgumentfileOutOfDate($@)
1785 {
1786     my ($filename, $currentContents) = @_;
1787
1788     if (! -e $filename) {
1789         return 1;
1790     }
1791
1792     open(CONTENTS_FILE, $filename);
1793     chomp(my $previousContents = <CONTENTS_FILE>);
1794     close(CONTENTS_FILE);
1795
1796     if ($previousContents ne $currentContents) {
1797         print "Contents for file $filename have changed.\n";
1798         print "Previous contents were: $previousContents\n\n";
1799         print "New contents are: $currentContents\n";
1800         return 1;
1801     }
1802
1803     return 0;
1804 }
1805
1806 sub wrapperPrefixIfNeeded()
1807 {
1808     if (isAnyWindows() || isJSCOnly()) {
1809         return ();
1810     }
1811     if (isAppleMacWebKit()) {
1812         return ("xcrun");
1813     }
1814     if (-e getJhbuildPath()) {
1815         my @prefix = (File::Spec->catfile(sourceDir(), "Tools", "jhbuild", "jhbuild-wrapper"));
1816         if (isEfl()) {
1817             push(@prefix, "--efl");
1818         } elsif (isGtk()) {
1819             push(@prefix, "--gtk");
1820         }
1821         push(@prefix, "run");
1822
1823         return @prefix;
1824     }
1825
1826     return ();
1827 }
1828
1829 sub cmakeCachePath()
1830 {
1831     return File::Spec->catdir(baseProductDir(), configuration(), "CMakeCache.txt");
1832 }
1833
1834 sub shouldRemoveCMakeCache(@)
1835 {
1836     my ($cacheFilePath, @buildArgs) = @_;
1837
1838     # We check this first, because we always want to create this file for a fresh build.
1839     my $productDir = File::Spec->catdir(baseProductDir(), configuration());
1840     my $optionsCache = File::Spec->catdir($productDir, "build-webkit-options.txt");
1841     my $joinedBuildArgs = join(" ", @buildArgs);
1842     if (isCachedArgumentfileOutOfDate($optionsCache, $joinedBuildArgs)) {
1843         File::Path::mkpath($productDir) unless -d $productDir;
1844         open(CACHED_ARGUMENTS, ">", $optionsCache);
1845         print CACHED_ARGUMENTS $joinedBuildArgs;
1846         close(CACHED_ARGUMENTS);
1847
1848         return 1;
1849     }
1850
1851     my $cmakeCache = cmakeCachePath();
1852     unless (-e $cmakeCache) {
1853         return 0;
1854     }
1855
1856     my $cacheFileModifiedTime = stat($cmakeCache)->mtime;
1857     my $platformConfiguration = File::Spec->catdir(sourceDir(), "Source", "cmake", "Options" . cmakeBasedPortName() . ".cmake");
1858     if ($cacheFileModifiedTime < stat($platformConfiguration)->mtime) {
1859         return 1;
1860     }
1861
1862     my $globalConfiguration = File::Spec->catdir(sourceDir(), "Source", "cmake", "OptionsCommon.cmake");
1863     if ($cacheFileModifiedTime < stat($globalConfiguration)->mtime) {
1864         return 1;
1865     }
1866
1867     my $inspectorUserInterfaceDircetory = File::Spec->catdir(sourceDir(), "Source", "WebInspectorUI", "UserInterface");
1868     if ($cacheFileModifiedTime < stat($inspectorUserInterfaceDircetory)->mtime) {
1869         return 1;
1870     }
1871
1872     if(isAnyWindows()) {
1873         my $winConfiguration = File::Spec->catdir(sourceDir(), "Source", "cmake", "OptionsWin.cmake");
1874         if ($cacheFileModifiedTime < stat($winConfiguration)->mtime) {
1875             return 1;
1876         }
1877     }
1878
1879     return 0;
1880 }
1881
1882 sub removeCMakeCache(@)
1883 {
1884     my (@buildArgs) = @_;
1885     if (shouldRemoveCMakeCache(@buildArgs)) {
1886         my $cmakeCache = cmakeCachePath();
1887         unlink($cmakeCache) if -e $cmakeCache;
1888     }
1889 }
1890
1891 sub canUseNinja(@)
1892 {
1893     if (!defined($shouldNotUseNinja)) {
1894         $shouldNotUseNinja = checkForArgumentAndRemoveFromARGV("--no-ninja");
1895     }
1896
1897     if ($shouldNotUseNinja) {
1898         return 0;
1899     }
1900
1901     # Test both ninja and ninja-build. Fedora uses ninja-build and has patched CMake to also call ninja-build.
1902     return commandExists("ninja") || commandExists("ninja-build");
1903 }
1904
1905 sub canUseNinjaGenerator(@)
1906 {
1907     # Check that a Ninja generator is installed
1908     my $devnull = File::Spec->devnull();
1909     return exitStatus(system("cmake -N -G Ninja >$devnull 2>&1")) == 0;
1910 }
1911
1912 sub canUseEclipseNinjaGenerator(@)
1913 {
1914     # Check that eclipse and eclipse Ninja generator is installed
1915     my $devnull = File::Spec->devnull();
1916     return commandExists("eclipse") && exitStatus(system("cmake -N -G 'Eclipse CDT4 - Ninja' >$devnull 2>&1")) == 0;
1917 }
1918
1919 sub cmakeGeneratedBuildfile(@)
1920 {
1921     my ($willUseNinja) = @_;
1922     if ($willUseNinja) {
1923         return File::Spec->catfile(baseProductDir(), configuration(), "build.ninja")
1924     } elsif (isAnyWindows()) {
1925         return File::Spec->catfile(baseProductDir(), configuration(), "WebKit.sln")
1926     } else {
1927         return File::Spec->catfile(baseProductDir(), configuration(), "Makefile")
1928     }
1929 }
1930
1931 sub generateBuildSystemFromCMakeProject
1932 {
1933     my ($prefixPath, @cmakeArgs) = @_;
1934     my $config = configuration();
1935     my $port = cmakeBasedPortName();
1936     my $buildPath = File::Spec->catdir(baseProductDir(), $config);
1937     File::Path::mkpath($buildPath) unless -d $buildPath;
1938     my $originalWorkingDirectory = getcwd();
1939     chdir($buildPath) or die;
1940
1941     # We try to be smart about when to rerun cmake, so that we can have faster incremental builds.
1942     my $willUseNinja = canUseNinja() && canUseNinjaGenerator();
1943     if (-e cmakeCachePath() && -e cmakeGeneratedBuildfile($willUseNinja)) {
1944         return 0;
1945     }
1946
1947     my @args;
1948     push @args, "-DPORT=\"$port\"";
1949     push @args, "-DCMAKE_INSTALL_PREFIX=\"$prefixPath\"" if $prefixPath;
1950     push @args, "-DCMAKE_EXPORT_COMPILE_COMMANDS=ON";
1951     if ($config =~ /release/i) {
1952         push @args, "-DCMAKE_BUILD_TYPE=Release";
1953     } elsif ($config =~ /debug/i) {
1954         push @args, "-DCMAKE_BUILD_TYPE=Debug";
1955     }
1956
1957     if ($willUseNinja) {
1958         push @args, "-G";
1959         if (canUseEclipseNinjaGenerator()) {
1960             push @args, "'Eclipse CDT4 - Ninja'";
1961         } else {
1962             push @args, "Ninja";
1963         }
1964     } elsif (isAnyWindows() && isWin64()) {
1965         push @args, '-G "Visual Studio 14 2015 Win64"';
1966     }
1967
1968     # Some ports have production mode, but build-webkit should always use developer mode.
1969     push @args, "-DDEVELOPER_MODE=ON" if isEfl() || isGtk() || isJSCOnly();
1970
1971     # Don't warn variables which aren't used by cmake ports.
1972     push @args, "--no-warn-unused-cli";
1973     push @args, @cmakeArgs if @cmakeArgs;
1974
1975     my $cmakeSourceDir = isCygwin() ? windowsSourceDir() : sourceDir();
1976     push @args, '"' . $cmakeSourceDir . '"';
1977
1978     # Compiler options to keep floating point values consistent
1979     # between 32-bit and 64-bit architectures.
1980     determineArchitecture();
1981     if ($architecture eq "i686" && !isCrossCompilation() && !isAnyWindows()) {
1982         $ENV{'CXXFLAGS'} = "-march=pentium4 -msse2 -mfpmath=sse " . ($ENV{'CXXFLAGS'} || "");
1983     }
1984
1985     # We call system("cmake @args") instead of system("cmake", @args) so that @args is
1986     # parsed for shell metacharacters.
1987     my $wrapper = join(" ", wrapperPrefixIfNeeded()) . " ";
1988     my $returnCode = system($wrapper . "cmake @args");
1989
1990     chdir($originalWorkingDirectory);
1991     return $returnCode;
1992 }
1993
1994 sub buildCMakeGeneratedProject($)
1995 {
1996     my ($makeArgs) = @_;
1997     my $config = configuration();
1998     my $buildPath = File::Spec->catdir(baseProductDir(), $config);
1999     if (! -d $buildPath) {
2000         die "Must call generateBuildSystemFromCMakeProject() before building CMake project.";
2001     }
2002
2003     my $command = "cmake";
2004     my @args = ("--build", $buildPath, "--config", $config);
2005     push @args, ("--", $makeArgs) if $makeArgs;
2006
2007     # GTK and JSCOnly can use a build script to preserve colors and pretty-printing.
2008     if ((isGtk() || isJSCOnly()) && -e "$buildPath/build.sh") {
2009         chdir "$buildPath" or die;
2010         $command = "$buildPath/build.sh";
2011         @args = ($makeArgs);
2012     }
2013
2014     if ($ENV{VERBOSE} && canUseNinja()) {
2015         push @args, "-v";
2016         push @args, "-d keeprsp" if (version->parse(determineNinjaVersion()) >= version->parse("1.4.0"));
2017     }
2018
2019     # We call system("cmake @args") instead of system("cmake", @args) so that @args is
2020     # parsed for shell metacharacters. In particular, $makeArgs may contain such metacharacters.
2021     my $wrapper = join(" ", wrapperPrefixIfNeeded()) . " ";
2022     return system($wrapper . "$command @args");
2023 }
2024
2025 sub cleanCMakeGeneratedProject()
2026 {
2027     my $config = configuration();
2028     my $buildPath = File::Spec->catdir(baseProductDir(), $config);
2029     if (-d $buildPath) {
2030         return system("cmake", "--build", $buildPath, "--config", $config, "--target", "clean");
2031     }
2032     return 0;
2033 }
2034
2035 sub buildCMakeProjectOrExit($$$@)
2036 {
2037     my ($clean, $prefixPath, $makeArgs, @cmakeArgs) = @_;
2038     my $returnCode;
2039
2040     exit(exitStatus(cleanCMakeGeneratedProject())) if $clean;
2041
2042     if (isEfl() && checkForArgumentAndRemoveFromARGV("--update-efl")) {
2043         system("perl", "$sourceDir/Tools/Scripts/update-webkitefl-libs") == 0 or die $!;
2044     }
2045
2046     if (isGtk() && checkForArgumentAndRemoveFromARGV("--update-gtk")) {
2047         system("perl", "$sourceDir/Tools/Scripts/update-webkitgtk-libs") == 0 or die $!;
2048     }
2049
2050     $returnCode = exitStatus(generateBuildSystemFromCMakeProject($prefixPath, @cmakeArgs));
2051     exit($returnCode) if $returnCode;
2052
2053     $returnCode = exitStatus(buildCMakeGeneratedProject($makeArgs));
2054     exit($returnCode) if $returnCode;
2055     return 0;
2056 }
2057
2058 sub cmakeBasedPortArguments()
2059 {
2060     return ();
2061 }
2062
2063 sub cmakeBasedPortName()
2064 {
2065     return ucfirst portName();
2066 }
2067
2068 sub determineIsCMakeBuild()
2069 {
2070     return if defined($isCMakeBuild);
2071     $isCMakeBuild = checkForArgumentAndRemoveFromARGV("--cmake");
2072 }
2073
2074 sub isCMakeBuild()
2075 {
2076     return 1 unless isAppleMacWebKit();
2077     determineIsCMakeBuild();
2078     return $isCMakeBuild;
2079 }
2080
2081 sub promptUser
2082 {
2083     my ($prompt, $default) = @_;
2084     my $defaultValue = $default ? "[$default]" : "";
2085     print "$prompt $defaultValue: ";
2086     chomp(my $input = <STDIN>);
2087     return $input ? $input : $default;
2088 }
2089
2090 sub appleApplicationSupportPath
2091 {
2092     open INSTALL_DIR, "</proc/registry/HKEY_LOCAL_MACHINE/SOFTWARE/Apple\ Inc./Apple\ Application\ Support/InstallDir";
2093     my $path = <INSTALL_DIR>;
2094     $path =~ s/[\r\n\x00].*//;
2095     close INSTALL_DIR;
2096
2097     my $unixPath = `cygpath -u '$path'`;
2098     chomp $unixPath;
2099     return $unixPath;
2100 }
2101
2102 sub setPathForRunningWebKitApp
2103 {
2104     my ($env) = @_;
2105
2106     if (isAnyWindows()) {
2107         my $productBinaryDir = executableProductDir();
2108         if (isAppleWinWebKit()) {
2109             $env->{PATH} = join(':', $productBinaryDir, appleApplicationSupportPath(), $env->{PATH} || "");
2110         } elsif (isWinCairo()) {
2111             my $winCairoBin = sourceDir() . "/WebKitLibraries/win/" . (isWin64() ? "bin64/" : "bin32/");
2112             my $gstreamerBin = isWin64() ? $ENV{"GSTREAMER_1_0_ROOT_X86_64"} . "bin" : $ENV{"GSTREAMER_1_0_ROOT_X86"} . "bin";
2113             $env->{PATH} = join(':', $productBinaryDir, $winCairoBin, $gstreamerBin, $env->{PATH} || "");
2114         }
2115     }
2116 }
2117
2118 sub printHelpAndExitForRunAndDebugWebKitAppIfNeeded
2119 {
2120     return unless checkForArgumentAndRemoveFromARGV("--help");
2121
2122     my ($includeOptionsForDebugging) = @_;
2123
2124     print STDERR <<EOF;
2125 Usage: @{[basename($0)]} [options] [args ...]
2126   --help                            Show this help message
2127   --no-saved-state                  Launch the application without state restoration
2128   -g|--guard-malloc                 Enable Guard Malloc (OS X only)
2129 EOF
2130
2131     exit(1);
2132 }
2133
2134 sub argumentsForRunAndDebugMacWebKitApp()
2135 {
2136     my @args = ();
2137     if (checkForArgumentAndRemoveFromARGV("--no-saved-state")) {
2138         push @args, ("-ApplePersistenceIgnoreStateQuietly", "YES");
2139         # FIXME: Don't set ApplePersistenceIgnoreState once all supported OS versions respect ApplePersistenceIgnoreStateQuietly (rdar://15032886).
2140         push @args, ("-ApplePersistenceIgnoreState", "YES");
2141     }
2142     unshift @args, @ARGV;
2143
2144     return @args;
2145 }
2146
2147 sub setupMacWebKitEnvironment($)
2148 {
2149     my ($dyldFrameworkPath) = @_;
2150
2151     $dyldFrameworkPath = File::Spec->rel2abs($dyldFrameworkPath);
2152
2153     prependToEnvironmentVariableList("DYLD_FRAMEWORK_PATH", $dyldFrameworkPath);
2154     prependToEnvironmentVariableList("__XPC_DYLD_FRAMEWORK_PATH", $dyldFrameworkPath);
2155     $ENV{WEBKIT_UNSET_DYLD_FRAMEWORK_PATH} = "YES";
2156
2157     setUpGuardMallocIfNeeded();
2158 }
2159
2160 sub setupIOSWebKitEnvironment($)
2161 {
2162     my ($dyldFrameworkPath) = @_;
2163     $dyldFrameworkPath = File::Spec->rel2abs($dyldFrameworkPath);
2164
2165     prependToEnvironmentVariableList("DYLD_FRAMEWORK_PATH", $dyldFrameworkPath);
2166     prependToEnvironmentVariableList("DYLD_LIBRARY_PATH", $dyldFrameworkPath);
2167
2168     setUpGuardMallocIfNeeded();
2169 }
2170
2171 sub iosSimulatorApplicationsPath()
2172 {
2173     return File::Spec->catdir(XcodeSDKPath(), "Applications");
2174 }
2175
2176 sub installedMobileSafariBundle()
2177 {
2178     return File::Spec->catfile(iosSimulatorApplicationsPath(), "MobileSafari.app");
2179 }
2180
2181 sub mobileSafariBundle()
2182 {
2183     determineConfigurationProductDir();
2184
2185     # Use MobileSafari.app in product directory if present.
2186     if (isAppleMacWebKit() && -d "$configurationProductDir/MobileSafari.app") {
2187         return "$configurationProductDir/MobileSafari.app";
2188     }
2189     return installedMobileSafariBundle();
2190 }
2191
2192 sub plistPathFromBundle($)
2193 {
2194     my ($appBundle) = @_;
2195     return "$appBundle/Info.plist" if -f "$appBundle/Info.plist"; # iOS app bundle
2196     return "$appBundle/Contents/Info.plist" if -f "$appBundle/Contents/Info.plist"; # Mac app bundle
2197     return "";
2198 }
2199
2200 sub appIdentifierFromBundle($)
2201 {
2202     my ($appBundle) = @_;
2203     my $plistPath = File::Spec->rel2abs(plistPathFromBundle($appBundle)); # defaults(1) will complain if the specified path is not absolute.
2204     chomp(my $bundleIdentifier = `defaults read '$plistPath' CFBundleIdentifier 2> /dev/null`);
2205     return $bundleIdentifier;
2206 }
2207
2208 sub appDisplayNameFromBundle($)
2209 {
2210     my ($appBundle) = @_;
2211     my $plistPath = File::Spec->rel2abs(plistPathFromBundle($appBundle)); # defaults(1) will complain if the specified path is not absolute.
2212     chomp(my $bundleDisplayName = `defaults read '$plistPath' CFBundleDisplayName 2> /dev/null`);
2213     return $bundleDisplayName;
2214 }
2215
2216 sub waitUntilIOSSimulatorDeviceIsInState($$)
2217 {
2218     my ($deviceUDID, $waitUntilState) = @_;
2219     my $device = iosSimulatorDeviceByUDID($deviceUDID);
2220     # FIXME: We should add a maximum time limit to wait here.
2221     while ($device->{state} ne $waitUntilState) {
2222         usleep(500 * 1000); # Waiting 500ms between file system polls does not make script run-safari feel sluggish.
2223         $device = iosSimulatorDeviceByUDID($deviceUDID);
2224     }
2225 }
2226
2227 sub shutDownIOSSimulatorDevice($)
2228 {
2229     my ($simulatorDevice) = @_;
2230     system("xcrun --sdk iphonesimulator simctl shutdown $simulatorDevice->{UDID} > /dev/null 2>&1");
2231 }
2232
2233 sub restartIOSSimulatorDevice($)
2234 {
2235     my ($simulatorDevice) = @_;
2236     shutDownIOSSimulatorDevice($simulatorDevice);
2237
2238     exitStatus(system("xcrun", "--sdk", "iphonesimulator", "simctl", "boot", $simulatorDevice->{UDID})) == 0 or die "Failed to boot simulator device $simulatorDevice->{UDID}";
2239 }
2240
2241 sub relaunchIOSSimulator($)
2242 {
2243     my ($simulatedDevice) = @_;
2244     quitIOSSimulator($simulatedDevice->{UDID});
2245
2246     # FIXME: <rdar://problem/20916140> Switch to using CoreSimulator.framework for launching and quitting iOS Simulator
2247     chomp(my $developerDirectory = $ENV{DEVELOPER_DIR} || `xcode-select --print-path`); 
2248     my $iosSimulatorPath = File::Spec->catfile($developerDirectory, "Applications", "Simulator.app"); 
2249     system("open", "-a", $iosSimulatorPath, "--args", "-CurrentDeviceUDID", $simulatedDevice->{UDID}) == 0 or die "Failed to open $iosSimulatorPath: $!"; 
2250
2251     waitUntilIOSSimulatorDeviceIsInState($simulatedDevice->{UDID}, SIMULATOR_DEVICE_STATE_BOOTED);
2252 }
2253
2254 sub quitIOSSimulator(;$)
2255 {
2256     my ($waitForShutdownOfSimulatedDeviceUDID) = @_;
2257     # FIXME: <rdar://problem/20916140> Switch to using CoreSimulator.framework for launching and quitting iOS Simulator
2258     if (exitStatus(system {"osascript"} "osascript", "-e", 'tell application id "com.apple.iphonesimulator" to quit')) {
2259         # osascript returns a non-zero exit status if Simulator.app is not registered in LaunchServices.
2260         return;
2261     }
2262
2263     if (!defined($waitForShutdownOfSimulatedDeviceUDID)) {
2264         return;
2265     }
2266     # FIXME: We assume that $waitForShutdownOfSimulatedDeviceUDID was not booted using the simctl command line tool.
2267     #        Otherwise we will spin indefinitely since quiting the iOS Simulator will not shutdown this device. We
2268     #        should add a maximum time limit to wait for a device to shutdown and either return an error or die()
2269     #        on expiration of the time limit.
2270     waitUntilIOSSimulatorDeviceIsInState($waitForShutdownOfSimulatedDeviceUDID, SIMULATOR_DEVICE_STATE_SHUTDOWN);
2271 }
2272
2273 sub iosSimulatorDeviceByName($)
2274 {
2275     my ($simulatorName) = @_;
2276     my $simulatorRuntime = iosSimulatorRuntime();
2277     my @devices = iOSSimulatorDevices();
2278     for my $device (@devices) {
2279         if ($device->{name} eq $simulatorName && $device->{runtime} eq $simulatorRuntime) {
2280             return $device;
2281         }
2282     }
2283     return undef;
2284 }
2285
2286 sub iosSimulatorDeviceByUDID($)
2287 {
2288     my ($simulatedDeviceUDID) = @_;
2289     my $devicePlistPath = File::Spec->catfile(iOSSimulatorDevicesPath(), $simulatedDeviceUDID, "device.plist");
2290     if (!-f $devicePlistPath) {
2291         return;
2292     }
2293     # FIXME: We should parse the device.plist file ourself and map the dictionary keys in it to known
2294     #        dictionary keys so as to decouple our representation of the plist from the actual structure
2295     #        of the plist, which may change.
2296     eval "require Foundation";
2297     return Foundation::perlRefFromObjectRef(NSDictionary->dictionaryWithContentsOfFile_($devicePlistPath));
2298 }
2299
2300 sub iosSimulatorRuntime()
2301 {
2302     my $xcodeSDKVersion = xcodeSDKVersion();
2303     $xcodeSDKVersion =~ s/\./-/;
2304     return "com.apple.CoreSimulator.SimRuntime.iOS-$xcodeSDKVersion";
2305 }
2306
2307 sub findOrCreateSimulatorForIOSDevice($)
2308 {
2309     my ($simulatorNameSuffix) = @_;
2310     my $simulatorName;
2311     my $simulatorDeviceType;
2312     if (architecture() eq "x86_64") {
2313         $simulatorName = "iPhone 5s " . $simulatorNameSuffix;
2314         $simulatorDeviceType = "com.apple.CoreSimulator.SimDeviceType.iPhone-5s";
2315     } else {
2316         $simulatorName = "iPhone 5 " . $simulatorNameSuffix;
2317         $simulatorDeviceType = "com.apple.CoreSimulator.SimDeviceType.iPhone-5";
2318     }
2319     my $simulatedDevice = iosSimulatorDeviceByName($simulatorName);
2320     return $simulatedDevice if $simulatedDevice;
2321     return createiOSSimulatorDevice($simulatorName, $simulatorDeviceType, iosSimulatorRuntime());
2322 }
2323
2324 sub isIOSSimulatorSystemInstalledApp($)
2325 {
2326     my ($appBundle) = @_;
2327     my $simulatorApplicationsPath = realpath(iosSimulatorApplicationsPath());
2328     return substr(realpath($appBundle), 0, length($simulatorApplicationsPath)) eq $simulatorApplicationsPath;
2329 }
2330
2331 sub hasUserInstalledAppInSimulatorDevice($$)
2332 {
2333     my ($appIdentifier, $simulatedDeviceUDID) = @_;
2334     my $userInstalledAppPath = File::Spec->catfile($ENV{HOME}, "Library", "Developer", "CoreSimulator", "Devices", $simulatedDeviceUDID, "data", "Containers", "Bundle", "Application");
2335     if (!-d $userInstalledAppPath) {
2336         return 0; # No user installed apps.
2337     }
2338     local @::userInstalledAppBundles;
2339     my $wantedFunction = sub {
2340         my $file = $_;
2341
2342         # Ignore hidden files and directories.
2343         if ($file =~ /^\../) {
2344             $File::Find::prune = 1;
2345             return;
2346         }
2347
2348         return if !-d $file || $file !~ /\.app$/;
2349         push @::userInstalledAppBundles, $File::Find::name;
2350         $File::Find::prune = 1; # Do not traverse contents of app bundle.
2351     };
2352     find($wantedFunction, $userInstalledAppPath);
2353     for my $userInstalledAppBundle (@::userInstalledAppBundles) {
2354         if (appIdentifierFromBundle($userInstalledAppBundle) eq $appIdentifier) {
2355             return 1; # Has user installed app.
2356         }
2357     }
2358     return 0; # Does not have user installed app.
2359 }
2360
2361 sub isSimulatorDeviceBooted($)
2362 {
2363     my ($simulatedDeviceUDID) = @_;
2364     my $device = iosSimulatorDeviceByUDID($simulatedDeviceUDID);
2365     return $device && $device->{state} eq SIMULATOR_DEVICE_STATE_BOOTED;
2366 }
2367
2368 sub runIOSWebKitAppInSimulator($;$)
2369 {
2370     my ($appBundle, $simulatorOptions) = @_;
2371     my $productDir = productDir();
2372     my $appDisplayName = appDisplayNameFromBundle($appBundle);
2373     my $appIdentifier = appIdentifierFromBundle($appBundle);
2374     my $simulatedDevice = findOrCreateSimulatorForIOSDevice(SIMULATOR_DEVICE_SUFFIX_FOR_WEBKIT_DEVELOPMENT);
2375     my $simulatedDeviceUDID = $simulatedDevice->{UDID};
2376
2377     my $willUseSystemInstalledApp = isIOSSimulatorSystemInstalledApp($appBundle);
2378     if ($willUseSystemInstalledApp) {
2379         if (hasUserInstalledAppInSimulatorDevice($appIdentifier, $simulatedDeviceUDID)) {
2380             # Restore the system-installed app in the simulator device corresponding to $appBundle as it
2381             # was previously overwritten with a custom built version of the app.
2382             # FIXME: Only restore the system-installed version of the app instead of erasing all contents and settings.
2383             print "Quitting iOS Simulator...\n";
2384             quitIOSSimulator($simulatedDeviceUDID);
2385             print "Erasing contents and settings for simulator device \"$simulatedDevice->{name}\".\n";
2386             exitStatus(system("xcrun", "--sdk", "iphonesimulator", "simctl", "erase", $simulatedDeviceUDID)) == 0 or die;
2387         }
2388         # FIXME: We assume that if $simulatedDeviceUDID is not booted then iOS Simulator is not open. However
2389         #        $simulatedDeviceUDID may have been booted using the simctl command line tool. If $simulatedDeviceUDID
2390         #        was booted using simctl then we should shutdown the device and launch iOS Simulator to boot it again.
2391         if (!isSimulatorDeviceBooted($simulatedDeviceUDID)) {
2392             print "Launching iOS Simulator...\n";
2393             relaunchIOSSimulator($simulatedDevice);
2394         }
2395     } else {
2396         # FIXME: We should killall(1) any running instances of $appBundle before installing it to ensure
2397         #        that simctl launch opens the latest installed version of the app. For now we quit and
2398         #        launch the iOS Simulator again to ensure there are no running instances of $appBundle.
2399         print "Quitting and launching iOS Simulator...\n";
2400         relaunchIOSSimulator($simulatedDevice);
2401
2402         print "Installing $appBundle.\n";
2403         # Install custom built app, overwriting an app with the same app identifier if one exists.
2404         exitStatus(system("xcrun", "--sdk", "iphonesimulator", "simctl", "install", $simulatedDeviceUDID, $appBundle)) == 0 or die;
2405
2406     }
2407
2408     $simulatorOptions = {} unless $simulatorOptions;
2409
2410     my %simulatorENV;
2411     %simulatorENV = %{$simulatorOptions->{applicationEnvironment}} if $simulatorOptions->{applicationEnvironment};
2412     {
2413         local %ENV; # Shadow global-scope %ENV so that changes to it will not be seen outside of this scope.
2414         setupIOSWebKitEnvironment($productDir);
2415         %simulatorENV = %ENV;
2416     }
2417     my $applicationArguments = \@ARGV;
2418     $applicationArguments = $simulatorOptions->{applicationArguments} if $simulatorOptions && $simulatorOptions->{applicationArguments};
2419
2420     # Prefix the environment variables with SIMCTL_CHILD_ per `xcrun simctl help launch`.
2421     foreach my $key (keys %simulatorENV) {
2422         $ENV{"SIMCTL_CHILD_$key"} = $simulatorENV{$key};
2423     }
2424
2425     print "Starting $appDisplayName with DYLD_FRAMEWORK_PATH set to point to built WebKit in $productDir.\n";
2426     return exitStatus(system("xcrun", "--sdk", "iphonesimulator", "simctl", "launch", $simulatedDeviceUDID, $appIdentifier, @$applicationArguments));
2427 }
2428
2429 sub runIOSWebKitApp($)
2430 {
2431     my ($appBundle) = @_;
2432     if (willUseIOSDeviceSDK()) {
2433         die "Only running Safari in iOS Simulator is supported now.";
2434     }
2435     if (willUseIOSSimulatorSDK()) {
2436         return runIOSWebKitAppInSimulator($appBundle);
2437     }
2438     die "Not using an iOS SDK."
2439 }
2440
2441 sub runMacWebKitApp($;$)
2442 {
2443     my ($appPath, $useOpenCommand) = @_;
2444     my $productDir = productDir();
2445     print "Starting @{[basename($appPath)]} with DYLD_FRAMEWORK_PATH set to point to built WebKit in $productDir.\n";
2446
2447     local %ENV = %ENV;
2448     setupMacWebKitEnvironment($productDir);
2449
2450     if (defined($useOpenCommand) && $useOpenCommand == USE_OPEN_COMMAND) {
2451         return system("open", "-W", "-a", $appPath, "--args", argumentsForRunAndDebugMacWebKitApp());
2452     }
2453     if (architecture()) {
2454         return system "arch", "-" . architecture(), $appPath, argumentsForRunAndDebugMacWebKitApp();
2455     }
2456     return system { $appPath } $appPath, argumentsForRunAndDebugMacWebKitApp();
2457 }
2458
2459 sub execMacWebKitAppForDebugging($)
2460 {
2461     my ($appPath) = @_;
2462     my $architectureSwitch = "--arch";
2463     my $argumentsSeparator = "--";
2464
2465     my $debuggerPath = `xcrun -find lldb`;
2466     chomp $debuggerPath;
2467     die "Can't find the lldb executable.\n" unless -x $debuggerPath;
2468
2469     my $productDir = productDir();
2470     setupMacWebKitEnvironment($productDir);
2471
2472     my @architectureFlags = ($architectureSwitch, architecture());
2473     print "Starting @{[basename($appPath)]} under lldb with DYLD_FRAMEWORK_PATH set to point to built WebKit in $productDir.\n";
2474     exec { $debuggerPath } $debuggerPath, @architectureFlags, $argumentsSeparator, $appPath, argumentsForRunAndDebugMacWebKitApp() or die;
2475 }
2476
2477 sub debugSafari
2478 {
2479     if (isAppleMacWebKit()) {
2480         checkFrameworks();
2481         execMacWebKitAppForDebugging(safariPath());
2482     }
2483
2484     return 1; # Unsupported platform; can't debug Safari on this platform.
2485 }
2486
2487 sub runSafari
2488 {
2489     if (isIOSWebKit()) {
2490         return runIOSWebKitApp(mobileSafariBundle());
2491     }
2492
2493     if (isAppleMacWebKit()) {
2494         return runMacWebKitApp(safariPath());
2495     }
2496
2497     if (isAppleWinWebKit()) {
2498         my $result;
2499         my $webKitLauncherPath = File::Spec->catfile(executableProductDir(), "MiniBrowser.exe");
2500         return system { $webKitLauncherPath } $webKitLauncherPath, @ARGV;
2501     }
2502
2503     return 1; # Unsupported platform; can't run Safari on this platform.
2504 }
2505
2506 sub runMiniBrowser
2507 {
2508     if (isAppleMacWebKit()) {
2509         return runMacWebKitApp(File::Spec->catfile(productDir(), "MiniBrowser.app", "Contents", "MacOS", "MiniBrowser"));
2510     } elsif (isAppleWinWebKit()) {
2511         my $result;
2512         my $webKitLauncherPath = File::Spec->catfile(executableProductDir(), "MiniBrowser.exe");
2513         return system { $webKitLauncherPath } $webKitLauncherPath, @ARGV;
2514     }
2515
2516     return 1;
2517 }
2518
2519 sub debugMiniBrowser
2520 {
2521     if (isAppleMacWebKit()) {
2522         execMacWebKitAppForDebugging(File::Spec->catfile(productDir(), "MiniBrowser.app", "Contents", "MacOS", "MiniBrowser"));
2523     }
2524     
2525     return 1;
2526 }
2527
2528 sub runWebKitTestRunner
2529 {
2530     if (isAppleMacWebKit()) {
2531         return runMacWebKitApp(File::Spec->catfile(productDir(), "WebKitTestRunner"));
2532     }
2533
2534     return 1;
2535 }
2536
2537 sub debugWebKitTestRunner
2538 {
2539     if (isAppleMacWebKit()) {
2540         execMacWebKitAppForDebugging(File::Spec->catfile(productDir(), "WebKitTestRunner"));
2541     }
2542
2543     return 1;
2544 }
2545
2546 sub readRegistryString
2547 {
2548     my ($valueName) = @_;
2549     chomp(my $string = `regtool --wow32 get "$valueName"`);
2550     return $string;
2551 }
2552
2553 sub writeRegistryString
2554 {
2555     my ($valueName, $string) = @_;
2556
2557     my $error = system "regtool", "--wow32", "set", "-s", $valueName, $string;
2558
2559     # On Windows Vista/7 with UAC enabled, regtool will fail to modify the registry, but will still
2560     # return a successful exit code. So we double-check here that the value we tried to write to the
2561     # registry was really written.
2562     return !$error && readRegistryString($valueName) eq $string;
2563 }
2564
2565 sub formatBuildTime($)
2566 {
2567     my ($buildTime) = @_;
2568
2569     my $buildHours = int($buildTime / 3600);
2570     my $buildMins = int(($buildTime - $buildHours * 3600) / 60);
2571     my $buildSecs = $buildTime - $buildHours * 3600 - $buildMins * 60;
2572
2573     if ($buildHours) {
2574         return sprintf("%dh:%02dm:%02ds", $buildHours, $buildMins, $buildSecs);
2575     }
2576     return sprintf("%02dm:%02ds", $buildMins, $buildSecs);
2577 }
2578
2579 sub runSvnUpdateAndResolveChangeLogs(@)
2580 {
2581     my @svnOptions = @_;
2582     my $openCommand = "svn update " . join(" ", @svnOptions);
2583     open my $update, "$openCommand |" or die "cannot execute command $openCommand";
2584     my @conflictedChangeLogs;
2585     while (my $line = <$update>) {
2586         print $line;
2587         $line =~ m/^C\s+(.+?)[\r\n]*$/;
2588         if ($1) {
2589           my $filename = normalizePath($1);
2590           push @conflictedChangeLogs, $filename if basename($filename) eq "ChangeLog";
2591         }
2592     }
2593     close $update or die;
2594
2595     if (@conflictedChangeLogs) {
2596         print "Attempting to merge conflicted ChangeLogs.\n";
2597         my $resolveChangeLogsPath = File::Spec->catfile(sourceDir(), "Tools", "Scripts", "resolve-ChangeLogs");
2598         (system($resolveChangeLogsPath, "--no-warnings", @conflictedChangeLogs) == 0)
2599             or die "Could not open resolve-ChangeLogs script: $!.\n";
2600     }
2601 }
2602
2603 sub runGitUpdate()
2604 {
2605     # Doing a git fetch first allows setups with svn-remote.svn.fetch = trunk:refs/remotes/origin/master
2606     # to perform the rebase much much faster.
2607     system("git", "fetch");
2608     if (isGitSVNDirectory(".")) {
2609         system("git", "svn", "rebase") == 0 or die;
2610     } else {
2611         # This will die if branch.$BRANCHNAME.merge isn't set, which is
2612         # almost certainly what we want.
2613         system("git", "pull") == 0 or die;
2614     }
2615 }
2616
2617 1;