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