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