blob: 08d498cc319398d99566f88e05921d438670a0bc [file] [log] [blame] [edit]
# Copyright (C) 2005-2023 Apple Inc. All rights reserved.
# Copyright (C) 2009 Google Inc. All rights reserved.
# Copyright (C) 2011 Research In Motion Limited. All rights reserved.
# Copyright (C) 2013 Nokia Corporation and/or its subsidiary(-ies).
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions
# are met:
#
# 1. Redistributions of source code must retain the above copyright
# notice, this list of conditions and the following disclaimer.
# 2. Redistributions in binary form must reproduce the above copyright
# notice, this list of conditions and the following disclaimer in the
# documentation and/or other materials provided with the distribution.
# 3. Neither the name of Apple Inc. ("Apple") nor the names of
# its contributors may be used to endorse or promote products derived
# from this software without specific prior written permission.
#
# THIS SOFTWARE IS PROVIDED BY APPLE AND ITS CONTRIBUTORS "AS IS" AND ANY
# EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
# DISCLAIMED. IN NO EVENT SHALL APPLE OR ITS CONTRIBUTORS BE LIABLE FOR ANY
# DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
# (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
# LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
# ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
# THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
# Module to share code to get to WebKit directories.
package webkitdirs;
use strict;
use version;
use warnings;
use Config;
use Cwd qw(realpath);
use Digest::MD5 qw(md5_hex);
use FindBin;
use File::Basename;
use File::Find;
use File::Glob qw(bsd_glob GLOB_TILDE);
use File::Path qw(make_path mkpath rmtree);
use File::Spec;
use File::Temp qw(tempdir);
use File::stat;
use List::Util qw(first);
use POSIX;
use Time::HiRes qw(usleep);
use Text::ParseWords;
use VCSUtils;
use webkitperl::FeatureList qw(getFeatureOptionList);
unless (defined(&decode_json)) {
eval "use JSON::XS;";
eval "use JSON::PP;" if $@;
}
BEGIN {
use Exporter ();
our ($VERSION, @ISA, @EXPORT, @EXPORT_OK, %EXPORT_TAGS);
$VERSION = 1.00;
@ISA = qw(Exporter);
@EXPORT = qw(
&XcodeCoverageSupportOptions
&XcodeExportCompileCommandsOptions
&XcodeOptionString
&XcodeOptionStringNoConfig
&XcodeOptions
&XcodeSDKPath
&XcodeStaticAnalyzerOption
&appDisplayNameFromBundle
&appendToEnvironmentVariableList
&architecture
&architecturesForProducts
&argumentsForConfiguration
&asanIsEnabled
&availableXcodeSDKs
&baseProductDir
&buildCMakeProjectOrExit
&buildVisualStudioProject
&buildXCodeProject
&buildXcodeScheme
&builtDylibPathForName
&canUseNinja
&chdirWebKit
&checkBuild
&checkForArgumentAndRemoveFromARGV
&checkForArgumentAndRemoveFromARGVGettingValue
&checkForArgumentAndRemoveFromArrayRef
&checkForArgumentAndRemoveFromArrayRefGettingValue
&checkRequiredSystemConfig
&cmakeArgsFromFeatures
&configuration
&configuredXcodeWorkspace
&coverageIsEnabled
&currentPerlPath
&currentSVNRevision
&debugMiniBrowser
&debugSwiftBrowser
&debugSafari
&debugWebKitTestRunner
&determineCurrentSVNRevision
&determineCrossTarget
&determineXcodeSDK
&executableProductDir
&exitStatus
&extractNonMacOSHostConfiguration
&forceOptimizationLevel
&formatBuildTime
&fuzzilliIsEnabled
&generateBuildSystemFromCMakeProject
&getCrossTargetName
&getJhbuildPath
&getJhbuildModulesetName
&inCrossTargetEnvironment
&inFlatpakSandbox
&iosVersion
&isARM64
&isAnyWindows
&isAppleCocoaWebKit
&isAppleMacWebKit
&isAppleWebKit
&isCMakeBuild
&isCygwin
&isDebianBased
&isFedoraBased
&isEmbeddedWebKit
&isGenerateProjectOnly
&isGtk
&isIOSWebKit
&isInspectorFrontend
&isJSCOnly
&isLinux
&isMacCatalystWebKit
&isPlayStation
&isWPE
&isWin
&isWindows
&isX86_64
&jscPath
&jscProductDir
&launcherName
&launcherPath
&libFuzzerIsEnabled
&ltoMode
&markBaseProductDirectoryAsCreatedByXcodeBuildSystem
&maybeUseContainerSDKRootDir
&maxCPULoad
&nativeArchitecture
&nmPath
&numberOfCPUs
&osXVersion
&overrideConfiguredXcodeWorkspace
&parseAvailableXcodeSDKs
&passedConfiguration
&plistPathFromBundle
&portName
&prependToEnvironmentVariableList
&printHelpAndExitForRunAndDebugWebKitAppIfNeeded
&productDir
&prohibitUnknownPort
&relativeScriptsDir
&removeCMakeCache
&runGitUpdate
&runIOSWebKitApp
&runInCrossTargetEnvironment
&runInFlatpak
&runMacWebKitApp
&runMiniBrowser
&runSwiftBrowser
&runSafari
&runWebKitTestRunner
&safariPath
&scriptPathForName
&sdkDirectory
&sdkPlatformDirectory
&setBaseProductDir
&setConfiguration
&setConfigurationProductDir
&setPathForRunningWebKitApp
&setUpGuardMallocIfNeeded
&setXcodeSDK
&setupMacWebKitEnvironment
&setupUnixWebKitEnvironment
&sharedCommandLineOptions
&sharedCommandLineOptionsUsage
&shouldBuild32Bit
&shouldBuildForCrossTarget
&shouldUseFlatpak
&shouldUseVcpkg
&sourceDir
&splitVersionString
&tsanIsEnabled
&ubsanIsEnabled
&usesCryptexPath
&vcpkgArgsFromFeatures
&willUseAppleTVDeviceSDK
&willUseAppleTVSimulatorSDK
&willUseIOSDeviceSDK
&willUseIOSSimulatorSDK
&willUseWatchDeviceSDK
&willUseWatchSimulatorSDK
&willUseVisionDeviceSDK
&willUseVisionSimulatorSDK
&winVersion
&wrapperPrefixIfNeeded
&xcodeSDK
&xcodeSDKPlatformName
&xcodeVersion
DO_NOT_USE_OPEN_COMMAND
Mac
USE_OPEN_COMMAND
iOS
);
%EXPORT_TAGS = ( );
@EXPORT_OK = ();
}
# Ports
use constant {
GTK => "GTK",
Haiku => "Haiku",
iOS => "iOS",
tvOS => "tvOS",
watchOS => "watchOS",
visionOS => "visionOS",
Mac => "Mac",
MacCatalyst => "MacCatalyst",
JSCOnly => "JSCOnly",
PlayStation => "PlayStation",
Win => "Win",
WPE => "WPE",
Unknown => "Unknown"
};
use constant USE_OPEN_COMMAND => 1; # Used in runMacWebKitApp().
use constant DO_NOT_USE_OPEN_COMMAND => 2;
use constant SIMULATOR_DEVICE_STATE_SHUTDOWN => "Shutdown";
use constant SIMULATOR_DEVICE_STATE_BOOTED => "Booted";
use constant SIMULATOR_DEVICE_SUFFIX_FOR_WEBKIT_DEVELOPMENT => "For WebKit Development";
# 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>.
use constant IOS_DEVELOPMENT_CERTIFICATE_NAME_PREFIX => "iPhone Developer: ";
our @EXPORT_OK;
my $architecture;
my $destination;
my $didUserSpecifyArchitecture = 0;
my %nativeArchitectureMap = ();
my $asanIsEnabled;
my $tsanIsEnabled;
my $ubsanIsEnabled;
my $libFuzzerIsEnabled;
my $coverageIsEnabled;
my $fuzzilliIsEnabled;
my $forceOptimizationLevel;
my $ltoMode;
my $numberOfCPUs;
my $maxCPULoad;
my $baseProductDir;
my @baseProductDirOption;
my $configuration;
my $configurationExplanation;
my $xcodeSDK;
my $xcodeSDKPlatformName;
my $xcodeSDKPlatformNameExplanation;
my $simulatorIdiom;
my $configurationForVisualStudio;
my $configurationProductDir;
my $sourceDir;
my $currentSVNRevision;
my $didLoadIPhoneSimulatorNotification;
my $nmPath;
my $osXVersion;
my $iosVersion;
my $generateDsym;
my $isCMakeBuild;
my $isGenerateProjectOnly;
my $shouldBuild32Bit;
my $isInspectorFrontend;
my $portName;
my $shouldUseGuardMalloc;
my $shouldNotUseNinja;
my $xcodeVersion;
my $configuredXcodeWorkspace;
my $crossTarget;
my $unknownPortProhibited = 0;
my @originalArgv = @ARGV;
# Variables for Win32 support
my $programFilesPath;
my $msBuildPath;
my $vsInstallDir;
my $vsVersion;
my $windowsSourceDir;
my $winVersion;
# Defined in VCSUtils.
sub exitStatus($);
sub findMatchingArguments($$);
sub hasArgument($$);
sub sdkDirectory($)
{
my ($sdkName) = @_;
chomp(my $sdkDirectory = `xcrun --sdk '$sdkName' --show-sdk-path`);
die "Failed to get SDK path from xcrun: $!" if exitStatus($?);
return $sdkDirectory;
}
sub sdkPlatformDirectory($)
{
my ($sdkName) = @_;
chomp(my $sdkPlatformDirectory = `xcrun --sdk '$sdkName' --show-sdk-platform-path`);
die "Failed to get SDK platform path from xcrun: $!" if exitStatus($?);
return $sdkPlatformDirectory;
}
sub determineSourceDir
{
return if $sourceDir;
$sourceDir = $FindBin::Bin;
$sourceDir =~ s|/+$||; # Remove trailing '/' as we would die later
# walks up path checking each directory to see if it is the main WebKit project dir,
# defined by containing Sources, WebCore, and JavaScriptCore.
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")))
{
if ($sourceDir !~ s|/[^/]+$||) {
die "Could not find top level webkit directory above source directory using FindBin.\n";
}
}
$sourceDir = File::Spec->catdir($sourceDir, "OpenSource") if -d File::Spec->catdir($sourceDir, "OpenSource");
}
sub currentPerlPath()
{
my $thisPerl = $^X;
if ($^O ne 'VMS') {
$thisPerl .= $Config{_exe} unless $thisPerl =~ m/$Config{_exe}$/i;
}
return $thisPerl;
}
# used for scripts which are stored in a non-standard location
sub setSourceDir($)
{
($sourceDir) = @_;
}
sub determineNinjaVersion
{
chomp(my $ninjaVersion = `ninja --version`);
return $ninjaVersion;
}
sub determineXcodeVersion
{
return if defined $xcodeVersion;
my $xcodebuildVersionOutput = `xcodebuild -version`;
$xcodeVersion = ($xcodebuildVersionOutput =~ /Xcode ([0-9]+(\.[0-9]+)*)/) ? $1 : "3.0";
}
sub xcodeVersion
{
determineXcodeVersion();
return $xcodeVersion;
}
sub readXcodeUserDefault($)
{
my ($key) = @_;
my $devnull = File::Spec->devnull();
my $value = `defaults read com.apple.dt.Xcode ${key} 2> ${devnull}`;
return if $?;
chomp $value;
return $value;
}
sub determineBaseProductDir
{
return if defined $baseProductDir;
determineSourceDir();
my $setSharedPrecompsDir;
my $indexDataStoreDir;
$baseProductDir = $ENV{"WEBKIT_OUTPUTDIR"};
if (!defined($baseProductDir) and isAppleCocoaWebKit()) {
# Silently remove ~/Library/Preferences/xcodebuild.plist which can
# cause build failure. The presence of
# ~/Library/Preferences/xcodebuild.plist can prevent xcodebuild from
# respecting global settings such as a custom build products directory
# (<rdar://problem/5585899>).
my $personalPlistFile = $ENV{HOME} . "/Library/Preferences/xcodebuild.plist";
if (-e $personalPlistFile) {
unlink($personalPlistFile) || die "Could not delete $personalPlistFile: $!";
}
my $buildLocationStyle = join '', readXcodeUserDefault("IDEBuildLocationStyle");
if ($buildLocationStyle eq "Custom") {
my $buildLocationType = join '', readXcodeUserDefault("IDECustomBuildLocationType");
# FIXME: Read CustomBuildIntermediatesPath and set OBJROOT accordingly.
if ($buildLocationType eq "Absolute") {
$baseProductDir = readXcodeUserDefault("IDECustomBuildProductsPath");
$indexDataStoreDir = readXcodeUserDefault("IDECustomIndexStorePath");
}
}
# DeterminedByTargets corresponds to a setting of "Legacy" in Xcode.
# It is the only build location style for which SHARED_PRECOMPS_DIR is not
# overridden when building from within Xcode.
$setSharedPrecompsDir = 1 if $buildLocationStyle ne "DeterminedByTargets";
if (!defined($baseProductDir)) {
my $customPaths = join '', readXcodeUserDefault("IDEApplicationwideBuildSettings");
$baseProductDir = $1 if $customPaths =~ /SYMROOT\s*=\s*\"(.*?)\";/s;
}
# Glob to expand tilde in pathnames
# (https://bugs.webkit.org/show_bug.cgi?id=249442).
($baseProductDir) = bsd_glob($baseProductDir) if defined($baseProductDir);
if (defined($baseProductDir) && $baseProductDir !~ /^\//) {
# webkitdirs can be run from arbitrary directories, so any
# user-specified build directory must be absolute.
print STDERR "Ignoring Xcode application-wide build directory \"$baseProductDir\" because it's not an absolute path.\n";
undef $baseProductDir;
}
}
if (!defined($baseProductDir)) { # Port-specific checks failed, use default
$baseProductDir = File::Spec->catdir($sourceDir, "WebKitBuild");
}
if (isGit() && isGitBranchBuild()) {
my $branch = gitBranch();
$baseProductDir = "$baseProductDir/$branch";
}
if (isAppleCocoaWebKit()) {
$baseProductDir =~ s|^\Q$(SRCROOT)/..\E$|$sourceDir|;
$baseProductDir =~ s|^\Q$(SRCROOT)/../|$sourceDir/|;
$baseProductDir =~ s|^~/|$ENV{HOME}/|;
die "Can't handle Xcode product directory with a ~ in it.\n" if $baseProductDir =~ /~/;
die "Can't handle Xcode product directory with a variable in it.\n" if $baseProductDir =~ /\$/;
$baseProductDir = realpath($baseProductDir) if -e $baseProductDir;
@baseProductDirOption = ("SYMROOT=$baseProductDir", "OBJROOT=$baseProductDir");
push(@baseProductDirOption, "SHARED_PRECOMPS_DIR=${baseProductDir}/PrecompiledHeaders") if $setSharedPrecompsDir;
push(@baseProductDirOption, "INDEX_ENABLE_DATA_STORE=YES", "INDEX_DATA_STORE_DIR=${indexDataStoreDir}") if $indexDataStoreDir;
}
if (isCygwin()) {
my $dosBuildPath = `cygpath --windows \"$baseProductDir\"`;
chomp $dosBuildPath;
$ENV{"WEBKIT_OUTPUTDIR"} = $dosBuildPath;
my $unixBuildPath = `cygpath --unix \"$baseProductDir\"`;
chomp $unixBuildPath;
$baseProductDir = $dosBuildPath;
}
}
sub systemVerbose {
print "+ @_\n";
return system(@_);
}
sub setBaseProductDir($)
{
($baseProductDir) = @_;
}
sub markBaseProductDirectoryAsCreatedByXcodeBuildSystem
{
determineBaseProductDir();
make_path($baseProductDir);
# This attribute is needed to support VALIDATE_DEPENDENCIES and other diagnostics.
my @xattr = ("xattr", "-w", "com.apple.xcode.CreatedByBuildSystem", "true", $baseProductDir);
system(@xattr) == 0 or die "xattr failed: $?";
}
sub determineConfiguration
{
return if defined $configuration;
determineBaseProductDir();
if (open CONFIGURATION, "$baseProductDir/Configuration") {
$configuration = <CONFIGURATION>;
$configurationExplanation = "via `set-webkit-configuration`";
close CONFIGURATION;
}
if ($configuration) {
chomp $configuration;
# compatibility for people who have old Configuration files
$configuration = "Release" if $configuration eq "Deployment";
$configuration = "Debug" if $configuration eq "Development";
} else {
$configuration = "Release";
$configurationExplanation = "via default";
}
}
sub determineNativeArchitecture($)
{
my ($remotes) = @_;
return if defined $nativeArchitectureMap{@{$remotes}};
my $output;
if (@{$remotes} == 0) {
$output = `uname -m` unless isWindows();
} else {
foreach my $remote (@{$remotes}) {
my @split = split(':', $remote->{"address"});
my $target = $split[0];
my $port = 22;
$port = $split[1] if scalar(@split) > 1;
my $cmd = 'ssh -o NoHostAuthenticationForLocalhost=yes '. (exists $remote->{'idFilePath'} ? ('-i '.$remote->{'idFilePath'}) : '') ." -p $port $target 'uname -m'";
$output = readpipe($cmd);
last if ($? == 0);
}
if (length($output) == 0) {
die "Could not determineNativeArchitecture";
}
}
chomp $output if defined $output;
$output = "x86_64" if (not defined $output);
# FIXME: Remove this when <rdar://problem/64208532> is resolved
if (isAppleCocoaWebKit() && $output ne "x86_64") {
$output = "arm64";
}
$output = "arm64" if $output =~ m/^aarch64$/;
$output = "arm" if $output =~ m/^armv[78]l$/;
$nativeArchitectureMap{@{$remotes}} = $output;
}
sub determineArchitecture
{
return if defined $architecture;
determineBaseProductDir();
# The user explicitly specified the architecture, don't assume anything
if (checkForArgumentAndRemoveFromARGVGettingValue("--architecture", \$architecture)) {
$didUserSpecifyArchitecture = 1;
return;
}
$architecture = nativeArchitecture([]);
if (isAppleCocoaWebKit() && $architecture eq "arm64") {
determineXcodeSDK();
if ($xcodeSDK =~ /\.internal$/ && $xcodeSDK !~ /simulator/) {
# Device SDKs (macosx.internal, iphoneos.internal, etc.) default to arm64e.
$architecture = "arm64e";
}
}
if (isCMakeBuild()) {
if (isCrossCompilation()) {
my $compiler = "gcc";
$compiler = $ENV{'CC'} if (defined($ENV{'CC'}));
my @compiler_machine = split('-', `$compiler -dumpmachine`);
$architecture = $compiler_machine[0];
} elsif (open my $cmake_sysinfo, "cmake --system-information |") {
while (<$cmake_sysinfo>) {
next unless index($_, 'CMAKE_SYSTEM_PROCESSOR') == 0;
if (/^CMAKE_SYSTEM_PROCESSOR \"([^"]+)\"/) {
$architecture = $1;
last;
}
}
close $cmake_sysinfo;
}
}
$architecture = 'x86_64' if $architecture =~ /amd64/i;
$architecture = 'x86' if $architecture =~ /BePC/i && isHaiku();
$architecture = 'arm64' if $architecture =~ /aarch64/i;
}
sub xcodeBuildRequestsInRecencyOrder
{
determineBaseProductDir();
my @buildRequests = sort { -M $a <=> -M $b } <"$baseProductDir/XCBuildData/*.xcbuilddata/build-request.json">;
return map {
open(my $fh, $_) or warn "Can't open previous build request: $!";
return decode_json(join '', <$fh>) if $fh;
} @buildRequests;
}
sub determineXcodeDestination
{
return if defined $destination;
return if !isAppleCocoaWebKit();
determineXcodeSDKPlatformName();
determineArchitecture();
# Use a generic destination ("Any Mac", etc.) when there are multiple architectures, or when building to
# a device.
my @architectures = split(' ', $architecture);
my $generic = $xcodeSDKPlatformName =~ /os$/ || (scalar @architectures) > 1;
if (willUseIOSDeviceSDK()) {
$destination .= 'platform=iOS';
} elsif (willUseIOSSimulatorSDK()) {
$destination .= 'platform=iOS Simulator';
} elsif (willUseAppleTVDeviceSDK()) {
$destination .= 'platform=tvOS';
} elsif (willUseAppleTVSimulatorSDK()) {
$destination .= 'platform=tvOS Simulator';
} elsif (willUseWatchDeviceSDK()) {
$destination .= 'platform=watchOS';
} elsif (willUseWatchSimulatorSDK()) {
$destination .= 'platform=watchOS Simulator';
} elsif (willUseVisionDeviceSDK()) {
$destination .= 'platform=visionOS';
} elsif (willUseVisionSimulatorSDK()) {
$destination .= 'platform=visionOS Simulator';
} else {
$destination .= 'platform=macOS';
$destination .= ',devicetype=' . ($generic ? 'Any Mac' : 'Mac');
$destination .= ',arch=' . $architectures[0] unless $generic;
$destination .= ',variant=Mac Catalyst' if willUseMacCatalystSDK();
}
if (!$generic && $xcodeSDKPlatformName =~ /simulator$/) {
# Two goals:
# 1. Find a simulator device to build for, to avoid building multiple architectures.
# 2. Try to pick a simulator that's been used before (either by command-line or IDE builds) to avoid
# unnecessary recompilation.
#
# Changing the targeted simulator between builds breaks incremental building, because it changes
# build settings like TARGET_DEVICE_IDENTIFIER.
my $prevBuildRequest = first {
$_->{parameters}->{activeRunDestination}->{platform} eq $xcodeSDKPlatformName
} xcodeBuildRequestsInRecencyOrder();
my $prevUDID = $prevBuildRequest->{parameters}->{overrides}->{synthesized}->{table}->{TARGET_DEVICE_IDENTIFIER} if $prevBuildRequest;
if ($prevBuildRequest && !$prevUDID) {
warn "Can't find UDID in previous $xcodeSDKPlatformName build request, builds may not be incremental.\n";
}
# Sort the list of devices to match the ordering in Xcode's UI.
my @devices = sort { $a->{name} cmp $b->{name} } iOSSimulatorDevices();
my $prevDevice = first { $prevUDID && $_->{UDID} eq $prevUDID } @devices;
if ($prevUDID && !$prevDevice) {
warn "Simulator with UDID '$prevUDID' not found, falling back to another available simulator. " .
"This build may not be incremental.\n";
}
# If we found the previous device, check that the runtime being built has not changed (e.g. due to a
# major SDK update). If it has changed, or if no previous device is available, fall back to the first
# eligible device in the list.
my $runtime = simulatorRuntime($portName);
my $device;
if ($prevDevice && $prevDevice->{runtime} eq $runtime) {
$device = $prevDevice;
} else {
$device = first { $_->{runtime} eq $runtime } @devices;
}
if ($device) {
$destination .= ',id=' . $device->{UDID};
} else {
warn "Unable to find a simulator target for $xcodeSDKPlatformName. " .
"Building for a generic device, which may build unwanted additional architectures";
$generic = 1;
}
}
$destination = 'generic/' . $destination if $generic;
}
sub readSanitizerConfiguration($)
{
my ($fileName) = @_;
if (open FILE, File::Spec->catfile($baseProductDir, $fileName)) {
my $value = <FILE>;
close FILE;
chomp $value;
return ($value eq "YES");
}
return 0;
}
sub determineASanIsEnabled
{
return if defined $asanIsEnabled;
determineBaseProductDir();
$asanIsEnabled = readSanitizerConfiguration("ASan");
}
sub determineTSanIsEnabled
{
return if defined $tsanIsEnabled;
determineBaseProductDir();
$tsanIsEnabled = readSanitizerConfiguration("TSan");
}
sub determineUBSanIsEnabled
{
return if defined $ubsanIsEnabled;
determineBaseProductDir();
$ubsanIsEnabled = readSanitizerConfiguration("UBSan");
}
sub determineLibFuzzerIsEnabled
{
return if defined $libFuzzerIsEnabled;
determineBaseProductDir();
$libFuzzerIsEnabled = readSanitizerConfiguration("LibFuzzer");
}
sub determineForceOptimizationLevel
{
return if defined $forceOptimizationLevel;
determineBaseProductDir();
if (open ForceOptimizationLevel, "$baseProductDir/ForceOptimizationLevel") {
$forceOptimizationLevel = <ForceOptimizationLevel>;
close ForceOptimizationLevel;
chomp $forceOptimizationLevel;
}
}
sub determineCoverageIsEnabled
{
return if defined $coverageIsEnabled;
determineBaseProductDir();
if (open Coverage, "$baseProductDir/Coverage") {
$coverageIsEnabled = <Coverage>;
close Coverage;
chomp $coverageIsEnabled;
}
}
sub determineFuzzilliIsEnabled
{
return if defined $fuzzilliIsEnabled;
determineBaseProductDir();
if (open Fuzzilli, "$baseProductDir/Fuzzilli") {
$fuzzilliIsEnabled = <Fuzzilli>;
close Fuzzilli;
chomp $fuzzilliIsEnabled;
}
}
sub determineLTOMode
{
return if defined $ltoMode;
determineBaseProductDir();
if (open LTO, File::Spec->catfile($baseProductDir, "LTO")) {
$ltoMode = <LTO>;
close LTO;
chomp $ltoMode;
}
}
sub determineNumberOfCPUs
{
return if defined $numberOfCPUs;
if (defined($ENV{NUMBER_OF_PROCESSORS})) {
$numberOfCPUs = $ENV{NUMBER_OF_PROCESSORS};
} elsif (isHaiku()) {
$numberOfCPUs = `sysinfo -cpu | grep "CPU #" | wc -l`
} elsif (isLinux()) {
use POSIX;
$numberOfCPUs = POSIX::sysconf(83); # _SC_NPROCESSORS_ONLN = 83
if ($numberOfCPUs eq "") {
$numberOfCPUs = 0;
open CPUINFO, "/proc/cpuinfo";
while (<CPUINFO>) {
if (/[Pp]rocessor\s/) { $numberOfCPUs++; }
}
close CPUINFO;
}
if ($numberOfCPUs == 0) {
$numberOfCPUs = 1;
}
} elsif (isAnyWindows()) {
# Assumes cygwin
$numberOfCPUs = `ls /proc/registry/HKEY_LOCAL_MACHINE/HARDWARE/DESCRIPTION/System/CentralProcessor | wc -w`;
} elsif (isDarwin() || isBSD()) {
chomp($numberOfCPUs = `sysctl -n hw.ncpu`);
} else {
$numberOfCPUs = 1;
}
}
sub determineMaxCPULoad
{
return if defined $maxCPULoad;
if (defined($ENV{MAX_CPU_LOAD})) {
$maxCPULoad = $ENV{MAX_CPU_LOAD};
}
}
sub jscPath($)
{
my ($productDir) = @_;
my $jscName = "jsc";
$jscName .= "_debug" if configuration() eq "Debug_All";
if (isPlayStation()) {
$jscName .= ".self";
} elsif (isAnyWindows()) {
$jscName .= ".exe";
}
return "$productDir/$jscName" if -e "$productDir/$jscName";
return "$productDir/JavaScriptCore.framework/Helpers/$jscName";
}
sub argumentsForConfiguration()
{
determineConfiguration();
determineArchitecture();
if (isAppleCocoaWebKit()) {
determineXcodeSDKPlatformName();
}
my @args = ();
# FIXME: Is it necessary to pass --debug, --release, or --32-bit?
# These are determined automatically from stored configuration.
push(@args, '--debug') if ($configuration =~ "^Debug");
push(@args, '--release') if ($configuration =~ "^Release");
push(@args, '--ios-device') if (defined $xcodeSDKPlatformName && $xcodeSDKPlatformName eq 'iphoneos');
push(@args, '--ios-simulator') if (defined $xcodeSDKPlatformName && $xcodeSDKPlatformName eq 'iphonesimulator' && $simulatorIdiom eq "iPhone");
push(@args, '--ipad-simulator') if (defined $xcodeSDKPlatformName && $xcodeSDKPlatformName eq 'iphonesimulator' && $simulatorIdiom eq "iPad");
push(@args, '--tvos-device') if (defined $xcodeSDKPlatformName && $xcodeSDKPlatformName eq 'appletvos');
push(@args, '--tvos-simulator') if (defined $xcodeSDKPlatformName && $xcodeSDKPlatformName eq 'appletvsimulator');
push(@args, '--watchos-device') if (defined $xcodeSDKPlatformName && $xcodeSDKPlatformName eq 'watchos');
push(@args, '--watchos-simulator') if (defined $xcodeSDKPlatformName && $xcodeSDKPlatformName eq 'watchsimulator');
push(@args, '--visionos-device') if (defined $xcodeSDKPlatformName && $xcodeSDKPlatformName eq 'xros');
push(@args, '--visionos-simulator') if (defined $xcodeSDKPlatformName && $xcodeSDKPlatformName eq 'xrsimulator');
push(@args, '--maccatalyst') if (defined $xcodeSDKPlatformName && $xcodeSDKPlatformName eq 'maccatalyst');
push(@args, '--32-bit') if ($architecture eq "x86");
push(@args, '--gtk') if isGtk();
push(@args, '--wpe') if isWPE();
push(@args, '--jsc-only') if isJSCOnly();
push(@args, '--win') if isWin();
push(@args, '--playstation') if isPlayStation();
push(@args, '--haiku') if isHaiku();
return @args;
}
sub extractNonMacOSHostConfiguration
{
my @args = ();
my @extract = ('--device', '--gtk', '--ios', '--platform', '--sdk', '--simulator', '--win', '--tvos', '--visionos', '--watchos', 'SDKROOT', 'ARCHS');
foreach (@{$_[0]}) {
my $line = $_;
my $flag = 0;
foreach (@extract) {
if (length($line) >= length($_) && substr($line, 0, length($_)) eq $_ && index($line, 'x86_64') == -1) {
$flag = 1;
}
}
if (!$flag) {
push @args, $_;
}
}
return @args;
}
# FIXME: Convert to json <rdar://problem/21594308>
sub parseAvailableXcodeSDKs($)
{
my @outputToParse = @{$_[0]};
my @result = ();
foreach my $line (@outputToParse) {
# Examples:
# iOS 12.0 -sdk iphoneos12.0
# Simulator - iOS 12.0 -sdk iphonesimulator12.0
# macOS 10.14 -sdk macosx10.14
if ($line =~ /-sdk (\D+)([\d\.]+)(\D*)\n/) {
if ($3) {
push @result, "$1.$3";
} else {
push @result, "$1";
}
}
}
return @result;
}
sub unversionedSDKNameFromSDK($)
{
my $basename = shift;
if ($basename =~ /(\D+)(\d+\.[\d\.]+)(\D*)\.sdk/) {
if ($3) {
return lc "$1.$3";
} else {
return lc "$1";
}
}
}
sub availableXcodeSDKs
{
# Looking for SDKs in known locations is much faster than calling through to xcodebuild.
chomp(my $developerDir = `xcode-select -p`);
my @availableSDKDirectories = bsd_glob("$developerDir/Platforms/*.platform/Developer/SDKs/*");
if (@availableSDKDirectories) {
return map { unversionedSDKNameFromSDK(basename $_) || () } @availableSDKDirectories;
}
# As a fallback, parse the SDK list provided by xcodebuild.
my @output = `xcodebuild -showsdks`;
return parseAvailableXcodeSDKs(\@output);
}
sub isValidXcodeSDKPlatformName($) {
my $name = shift;
my @platforms = qw(
appletvos
appletvsimulator
iphoneos
iphonesimulator
macosx
watchos
watchsimulator
xros
xrsimulator
maccatalyst
);
return grep { $_ eq $name } @platforms;
}
sub determineCrossTarget {
return if defined $crossTarget;
return if not isLinux();
my $crossTargetCandidate;
if (checkForArgumentAndRemoveFromARGVGettingValue("--cross-target", \$crossTargetCandidate)) {
my $crossToolchainHelperPath = File::Spec->catfile(sourceDir(), "Tools", "Scripts", "cross-toolchain-helper");
my $availableTargets = `'$crossToolchainHelperPath' --print-available-targets`;
die "Failed to get available cross-targets from cross-toolchain-helper: $!" if exitStatus($?);
my @targets = split /\n/, $availableTargets;
foreach my $target (@targets) {
if ($target eq $crossTargetCandidate) {
$crossTarget = $crossTargetCandidate;
last;
}
}
return if defined $crossTarget;
die ("cross-target '" . $crossTargetCandidate . "' not valid. Available cross-targets are: '" . join("', '", @targets) . "'\n");
}
}
sub determineXcodeSDKPlatformName {
return if defined $xcodeSDKPlatformName;
my $sdk;
# Mac Catalyst is a platform but not an sdk, so it preempts an
# explicitly-provided sdk, unlike other platform flags.
if (checkForArgumentAndRemoveFromARGV("--maccatalyst")) {
$xcodeSDKPlatformName = "maccatalyst";
$xcodeSDKPlatformNameExplanation = "via argument, `--maccatalyst`";
}
if (checkForArgumentAndRemoveFromARGVGettingValue("--sdk", \$sdk)) {
$xcodeSDK = lc $sdk;
$xcodeSDKPlatformName ||= $sdk;
$xcodeSDKPlatformName =~ s/(\d+\.[\d\.]+)?(\.internal)?$//;
$xcodeSDKPlatformNameExplanation ||= "via argument, `--sdk $sdk`";
die "Couldn't determine platform name from Xcode SDK" unless isValidXcodeSDKPlatformName($xcodeSDKPlatformName);
return;
}
if (checkForArgumentAndRemoveFromARGV("--device")) {
$xcodeSDKPlatformName ||= "iphoneos";
$xcodeSDKPlatformNameExplanation ||= "via argument, `--device`";
}
if (checkForArgumentAndRemoveFromARGV("--ios-device")) {
$xcodeSDKPlatformName ||= "iphoneos";
$xcodeSDKPlatformNameExplanation ||= "via argument, `--ios-device`";
}
if (checkForArgumentAndRemoveFromARGV("--simulator")) {
$xcodeSDKPlatformName ||= 'iphonesimulator';
$xcodeSDKPlatformNameExplanation ||= "via argument, `--simulator`";
$simulatorIdiom = 'iPhone';
}
if (checkForArgumentAndRemoveFromARGV("--iphone-simulator") ||
checkForArgumentAndRemoveFromARGV("--ios-simulator")) {
# `--ios-simulator` option checking is for compatible here.
$xcodeSDKPlatformName ||= 'iphonesimulator';
$xcodeSDKPlatformNameExplanation ||= "via argument, `--iphone-simulator`";
$simulatorIdiom = 'iPhone';
}
if (checkForArgumentAndRemoveFromARGV("--ipad-simulator")) {
$xcodeSDKPlatformName ||= 'iphonesimulator';
$xcodeSDKPlatformNameExplanation ||= "via argument, `--ipad-simulator`";
$simulatorIdiom = 'iPad';
}
if (checkForArgumentAndRemoveFromARGV("--tvos-device")) {
$xcodeSDKPlatformName ||= "appletvos";
$xcodeSDKPlatformNameExplanation ||= "via argument, `--tvos-device`";
}
if (checkForArgumentAndRemoveFromARGV("--tvos-simulator")) {
$xcodeSDKPlatformName ||= "appletvsimulator";
$xcodeSDKPlatformNameExplanation ||= "via argument, `--tvos-simulator`";
}
if (checkForArgumentAndRemoveFromARGV("--watchos-device")) {
$xcodeSDKPlatformName ||= "watchos";
$xcodeSDKPlatformNameExplanation ||= "via argument, `--watchos-device`";
}
if (checkForArgumentAndRemoveFromARGV("--watchos-simulator")) {
$xcodeSDKPlatformName ||= "watchsimulator";
$xcodeSDKPlatformNameExplanation ||= "via argument, `--watchos-simulator`";
}
if (checkForArgumentAndRemoveFromARGV("--visionos-device")) {
$xcodeSDKPlatformName ||= "xros";
$xcodeSDKPlatformNameExplanation ||= "via argument, `--visionos-device`";
}
if (checkForArgumentAndRemoveFromARGV("--visionos-simulator")) {
$xcodeSDKPlatformName ||= "xrsimulator";
$xcodeSDKPlatformNameExplanation ||= "via argument, `--visionos-simulator`";
}
# Finally, fall back to macOS if no platform is specified.
$xcodeSDKPlatformName ||= "macosx";
$xcodeSDKPlatformNameExplanation ||= "via default";
}
sub determineXcodeSDK
{
determineXcodeSDKPlatformName(); # This can set $xcodeSDK if --sdk was used.
return if defined $xcodeSDK;
$xcodeSDK = willUseMacCatalystSDK() ? "macosx" : $xcodeSDKPlatformName;
# Prefer the internal version of an sdk, if it exists.
my @availableSDKs = availableXcodeSDKs();
foreach my $sdk (@availableSDKs) {
next if $sdk ne (willUseMacCatalystSDK() ? "macosx.internal" : "$xcodeSDKPlatformName.internal");
$xcodeSDK = $sdk;
last;
}
}
sub xcodeSDK
{
determineXcodeSDK();
return $xcodeSDK;
}
sub setXcodeSDK($)
{
($xcodeSDK) = @_;
$xcodeSDKPlatformName = $xcodeSDK;
$xcodeSDKPlatformName =~ s/\.internal$//;
}
sub xcodeSDKPlatformName
{
determineXcodeSDKPlatformName();
return $xcodeSDKPlatformName;
}
sub XcodeSDKPath
{
determineXcodeSDK();
die "Can't find the SDK path because no Xcode SDK was specified" if not $xcodeSDK;
return sdkDirectory($xcodeSDK);
}
sub xcodeSDKVersion
{
determineXcodeSDK();
die "Can't find the SDK version because no Xcode SDK was specified" if !$xcodeSDK;
chomp(my $sdkVersion = `xcrun --sdk $xcodeSDK --show-sdk-version`);
die "Failed to get SDK version from xcrun" if exitStatus($?);
return $sdkVersion;
}
sub programFilesPath
{
return $programFilesPath if defined $programFilesPath;
$programFilesPath = $ENV{'PROGRAMFILES(X86)'} || $ENV{'PROGRAMFILES'} || "C:\\Program Files";
return $programFilesPath;
}
sub programFilesPathX86
{
my $programFilesPathX86 = $ENV{'PROGRAMFILES(X86)'} || "C:\\Program Files (x86)";
return $programFilesPathX86;
}
sub visualStudioInstallDirVSWhere
{
my $vswhere = File::Spec->catdir(programFilesPathX86(), "Microsoft Visual Studio", "Installer", "vswhere.exe");
return unless -e $vswhere;
open(my $handle, "-|", $vswhere, qw(-nologo -latest -requires Microsoft.Component.MSBuild -property installationPath -products *)) || return;
my $vsWhereOut = <$handle>;
$vsWhereOut =~ s/\r?\n//;
return $vsWhereOut;
}
sub visualStudioInstallDir
{
return $vsInstallDir if defined $vsInstallDir;
if ($ENV{'VSINSTALLDIR'}) {
$vsInstallDir = $ENV{'VSINSTALLDIR'};
$vsInstallDir =~ s|[\\/]$||;
} else {
$vsInstallDir = visualStudioInstallDirVSWhere();
return unless defined $vsInstallDir;
}
chomp($vsInstallDir = `cygpath "$vsInstallDir"`) if isCygwin();
print "Using Visual Studio: $vsInstallDir\n";
return $vsInstallDir;
}
sub msBuildPath
{
my $installDir = visualStudioInstallDir();
# FIXME: vswhere.exe should be used to find msbuild.exe after AppleWin will get vswhere with -find switch.
# <https://github.com/Microsoft/vswhere/wiki/Find-MSBuild>
# <https://github.com/Microsoft/vswhere/releases/tag/2.6.6%2Bd9dbe79db3>
my $path = File::Spec->catdir($installDir, "MSBuild", "Current", "bin", "MSBuild.exe");
$path = File::Spec->catdir($installDir, "MSBuild", "15.0", "bin", "MSBuild.exe") unless -e $path;
chomp($path = `cygpath "$path"`) if isCygwin();
print "Using MSBuild: $path\n";
return $path;
}
sub determineConfigurationForVisualStudio
{
return if defined $configurationForVisualStudio;
determineConfiguration();
# FIXME: We should detect when Debug_All or Production has been chosen.
$configurationForVisualStudio = "/p:Configuration=" . $configuration;
}
sub usesPerConfigurationBuildDirectory
{
return (defined $ENV{"WEBKIT_OUTPUTDIR"});
}
sub determineConfigurationProductDir
{
return if defined $configurationProductDir;
determineBaseProductDir();
determineConfiguration();
if (isWin() || isPlayStation() || (isJSCOnly() && isWindows())) {
$configurationProductDir = File::Spec->catdir($baseProductDir, $configuration);
} else {
if (usesPerConfigurationBuildDirectory()) {
$configurationProductDir = "$baseProductDir";
} else {
if (isGtk() or isWPE() or isJSCOnly() or shouldUseFlatpak() or shouldBuildForCrossTarget() or inCrossTargetEnvironment()) {
$configurationProductDir = "$baseProductDir/$portName/$configuration";
} else {
$configurationProductDir = "$baseProductDir/$configuration";
}
$configurationProductDir .= "-" . xcodeSDKPlatformName() if isEmbeddedWebKit() || isMacCatalystWebKit();
}
}
}
sub setConfigurationProductDir($)
{
($configurationProductDir) = @_;
}
sub determineCurrentSVNRevision
{
# We always update the current SVN revision here, and leave the caching
# to currentSVNRevision(), so that changes to the SVN revision while the
# script is running can be picked up by calling this function again.
determineSourceDir();
$currentSVNRevision = svnRevisionForDirectory($sourceDir);
return $currentSVNRevision;
}
sub chdirWebKit
{
determineSourceDir();
chdir $sourceDir or die;
}
sub baseProductDir
{
determineBaseProductDir();
return $baseProductDir;
}
sub sourceDir
{
determineSourceDir();
return $sourceDir;
}
sub productDir
{
determineConfigurationProductDir();
return $configurationProductDir;
}
sub executableProductDir
{
return productDir() if isAppleCocoaWebKit();
return File::Spec->catdir(productDir(), "bin");
}
sub jscProductDir
{
return executableProductDir();
}
sub architecturesForProducts
{
# Most ports don't have emulation, assume that the user gave us an accurate architecture
if (!isAppleCocoaWebKit()) {
return architecture();
}
my $webkitBinary = File::Spec->catdir(executableProductDir(), "JavaScriptCore.framework", "JavaScriptCore");
my $architectures = `/usr/bin/lipo -archs $webkitBinary`;
chomp($architectures);
return $architectures;
}
sub configuration()
{
determineConfiguration();
return $configuration;
}
sub asanIsEnabled()
{
determineASanIsEnabled();
return $asanIsEnabled;
}
sub tsanIsEnabled()
{
determineTSanIsEnabled();
return $tsanIsEnabled;
}
sub ubsanIsEnabled()
{
determineUBSanIsEnabled();
return $ubsanIsEnabled;
}
sub libFuzzerIsEnabled()
{
determineLibFuzzerIsEnabled();
return $libFuzzerIsEnabled;
}
sub coverageIsEnabled()
{
determineCoverageIsEnabled();
return $coverageIsEnabled;
}
sub fuzzilliIsEnabled()
{
determineFuzzilliIsEnabled();
return $fuzzilliIsEnabled;
}
sub forceOptimizationLevel()
{
determineForceOptimizationLevel();
return $forceOptimizationLevel;
}
sub ltoMode()
{
determineLTOMode();
return $ltoMode;
}
sub configurationForVisualStudio()
{
determineConfigurationForVisualStudio();
return $configurationForVisualStudio;
}
sub currentSVNRevision
{
determineCurrentSVNRevision() if not defined $currentSVNRevision;
return $currentSVNRevision;
}
sub generateDsym()
{
determineGenerateDsym();
return $generateDsym;
}
sub determineGenerateDsym()
{
return if defined($generateDsym);
$generateDsym = checkForArgumentAndRemoveFromARGV("--dsym");
}
sub argumentsForXcode()
{
my @args = ();
push @args, "DEBUG_INFORMATION_FORMAT=dwarf-with-dsym" if generateDsym();
return @args;
}
sub determineConfiguredXcodeWorkspaceOrDefault()
{
return if defined $configuredXcodeWorkspace;
determineBaseProductDir();
if (open WORKSPACE, "$baseProductDir/Workspace") {
$configuredXcodeWorkspace = <WORKSPACE>;
close WORKSPACE;
chomp $configuredXcodeWorkspace;
return;
}
# No configured workspace, time to find the default one.
# If we're using an internal SDK use the internal workspace.
if (xcodeSDK() =~ /\.internal$/) {
my $path = sourceDir() . "/../Internal/Safari.xcworkspace";
$configuredXcodeWorkspace = Cwd::realpath($path);
die "Using internal SDK but unable to find adjacent Internal directory: $! at $path. SDK: $xcodeSDK" unless (defined $configuredXcodeWorkspace && -e $configuredXcodeWorkspace);
return;
}
$configuredXcodeWorkspace = sourceDir() . "/WebKit.xcworkspace";
}
sub configuredXcodeWorkspace()
{
determineConfiguredXcodeWorkspaceOrDefault();
return $configuredXcodeWorkspace;
}
sub overrideConfiguredXcodeWorkspace($)
{
$configuredXcodeWorkspace = shift;
}
sub XcodeOptions
{
determineBaseProductDir();
determineConfiguration();
determineArchitecture();
determineXcodeDestination();
determineASanIsEnabled();
determineTSanIsEnabled();
determineUBSanIsEnabled();
determineLibFuzzerIsEnabled();
determineForceOptimizationLevel();
determineCoverageIsEnabled();
determineFuzzilliIsEnabled();
determineLTOMode();
if (isAppleCocoaWebKit()) {
determineXcodeSDK();
determineConfiguredXcodeWorkspaceOrDefault();
}
my @options;
push @options, "-UseSanitizedBuildSystemEnvironment=YES";
push @options, "-ShowBuildOperationDuration=YES";
if (!checkForArgumentAndRemoveFromARGV("--no-use-workspace")) {
push @options, ("-workspace", $configuredXcodeWorkspace) if $configuredXcodeWorkspace;
}
push @options, ("-configuration", $configuration);
push @options, ("-destination", $destination) if $destination;
# Be mindful of adding build settings here. Any settings which don't
# *exactly* line up with build settings already provided by Xcode will
# invalidate command line <=> IDE incremental builds.
push @options, ("ENABLE_ADDRESS_SANITIZER=YES") if $asanIsEnabled;
push @options, ("ENABLE_THREAD_SANITIZER=YES") if $tsanIsEnabled;
push @options, ("ENABLE_UNDEFINED_BEHAVIOR_SANITIZER=YES") if $ubsanIsEnabled;
push @options, ("ENABLE_FUZZILLI=YES") if $fuzzilliIsEnabled;
push @options, ("ENABLE_LIBFUZZER=YES") if $libFuzzerIsEnabled;
push @options, XcodeCoverageSupportOptions() if $coverageIsEnabled;
push @options, "GCC_OPTIMIZATION_LEVEL=$forceOptimizationLevel" if $forceOptimizationLevel;
push @options, "WK_LTO_MODE=$ltoMode" if $ltoMode;
push @options, @baseProductDirOption;
push @options, "ARCHS=$architecture" if $didUserSpecifyArchitecture;
push @options, "SDKROOT=$xcodeSDK" if $xcodeSDK;
if (xcodeVersion() lt "15.0") {
push @options, "TAPI_USE_SRCROOT=YES" if $ENV{UseSRCROOTSupportForTAPI};
}
my @features = webkitperl::FeatureList::getFeatureOptionList();
foreach (@features) {
if (checkForArgumentAndRemoveFromARGV("--no-$_->{option}")) {
push @options, "$_->{define}=";
}
if (checkForArgumentAndRemoveFromARGV("--$_->{option}")) {
push @options, "$_->{define}=$_->{define}";
}
}
# When this environment variable is set Tools/Scripts/check-for-weak-vtables-and-externals
# treats errors as non-fatal when it encounters missing symbols related to coverage.
appendToEnvironmentVariableList("WEBKIT_COVERAGE_BUILD", "1") if $coverageIsEnabled;
die "Cannot enable both ASAN and TSAN at the same time\n" if $asanIsEnabled && $tsanIsEnabled;
die "Cannot enable both (ASAN or TSAN) and Coverage at this time\n" if $coverageIsEnabled && ($asanIsEnabled || $tsanIsEnabled);
push @options, argumentsForXcode();
return @options;
}
sub XcodeOptionString
{
return join " ", map { /\s/ ? "\"$_\"" : $_ } XcodeOptions();
}
sub XcodeOptionStringNoConfig
{
return join " ", @baseProductDirOption;
}
sub XcodeCoverageSupportOptions()
{
return ("CLANG_COVERAGE_MAPPING=YES");
}
sub XcodeExportCompileCommandsOptions()
{
return ("OTHER_CFLAGS=\$(inherited) -gen-cdb-fragment-path \$(BUILT_PRODUCTS_DIR)/compile_commands", "GCC_PRECOMPILE_PREFIX_HEADER=NO", "CLANG_ENABLE_MODULE_DEBUGGING=NO", "COMPILER_INDEX_STORE_ENABLE=NO");
}
sub XcodeStaticAnalyzerOption()
{
return "RUN_CLANG_STATIC_ANALYZER=YES";
}
my $passedConfiguration;
my $passedConfigurationArgument;
my $searchedForPassedConfiguration;
sub determinePassedConfiguration
{
return if $searchedForPassedConfiguration;
$searchedForPassedConfiguration = 1;
$passedConfiguration = undef;
if (checkForArgumentAndRemoveFromARGV("--debug")) {
$passedConfiguration = "Debug";
$passedConfigurationArgument = "--debug";
} elsif(checkForArgumentAndRemoveFromARGV("--release")) {
$passedConfiguration = "Release";
$passedConfigurationArgument = "--release";
} elsif (checkForArgumentAndRemoveFromARGV("--profile")) {
$passedConfiguration = "Profiling";
$passedConfigurationArgument = "--profile";
} elsif (checkForArgumentAndRemoveFromARGV("--profiling")) {
$passedConfiguration = "Profiling";
$passedConfigurationArgument = "--profiling";
} elsif(checkForArgumentAndRemoveFromARGV("--testing")) {
$passedConfiguration = "Testing";
$passedConfigurationArgument = "--testing";
} elsif(checkForArgumentAndRemoveFromARGV("--release-and-assert")) {
$passedConfiguration = "Release+Assert";
$passedConfigurationArgument = "--release-and-assert";
} elsif(checkForArgumentAndRemoveFromARGV("--ra")) {
$passedConfiguration = "Release+Assert";
$passedConfigurationArgument = "--ra";
}
if (shouldBuildForCrossTarget() or inCrossTargetEnvironment()) {
$passedConfiguration = "Release" if not $passedConfiguration;
$passedConfiguration .= "_" . getCrossTargetName();
}
}
sub passedConfiguration
{
determinePassedConfiguration();
return $passedConfiguration;
}
sub setConfiguration
{
if (my $config = shift @_) {
$configuration = $config;
$configurationExplanation = "via explicit call by script";
return;
}
determinePassedConfiguration();
if ($passedConfiguration) {
$configuration = $passedConfiguration;
if ($passedConfigurationArgument) {
$configurationExplanation = "via argument, `$passedConfigurationArgument`";
} else {
# In a few cross-target circumstances, `determinePassedConfiguration` will set `$passedConfiguration` even if no argument was specified. In those cases, it always some variation of "Release". For the purposes of explanitory text, this can be treated like the default case.
$configurationExplanation = "via default";
}
}
}
sub nativeArchitecture($)
{
my ($remotes) = @_;
determineNativeArchitecture($remotes);
return $nativeArchitectureMap{@{$remotes}};
}
sub architecture()
{
determineArchitecture();
return $architecture;
}
sub xcodeDestination()
{
determineXcodeDestination();
return $destination;
}
sub numberOfCPUs()
{
determineNumberOfCPUs();
return $numberOfCPUs;
}
sub maxCPULoad()
{
determineMaxCPULoad();
return $maxCPULoad;
}
# Locate Safari.
sub safariPath
{
die "Safari path is only relevant on Apple Mac platform\n" unless isAppleMacWebKit();
my $safariPath;
# Use WEBKIT_SAFARI environment variable if present.
my $safariBundle = $ENV{WEBKIT_SAFARI};
if (!$safariBundle) {
determineConfigurationProductDir();
# Use Safari.app in product directory if present (good for Safari development team).
if (-d "$configurationProductDir/Safari.app") {
$safariBundle = "$configurationProductDir/Safari.app";
}
}
if ($safariBundle) {
$safariPath = "$safariBundle/Contents/MacOS/Safari";
} else {
$safariPath = "/Applications/Safari.app/Contents/MacOS/SafariForWebKitDevelopment";
}
die "Can't find executable at $safariPath.\n" if !-x $safariPath;
return $safariPath;
}
sub builtDylibPathForName
{
my $libraryName = shift;
determineConfigurationProductDir();
if (isGtk()) {
my $extension = isDarwin() ? ".dylib" : ".so";
my @apiVersions = ("4.0", "5.0");
for my $apiVersion (@apiVersions) {
my $libraryPath;
if ($libraryName eq "JavaScriptCore") {
$libraryPath = "$configurationProductDir/lib/libjavascriptcoregtk-$apiVersion$extension";
} else {
$libraryPath = "$configurationProductDir/lib/libwebkit2gtk-$apiVersion$extension";
}
if (-e $libraryPath) {
return $libraryPath;
}
}
return "";
}
if (isIOSWebKit()) {
return "$configurationProductDir/$libraryName.framework/$libraryName";
}
if (isAppleCocoaWebKit()) {
return "$configurationProductDir/$libraryName.framework/Versions/A/$libraryName";
}
if (isWPE()) {
return "$configurationProductDir/lib/libWPEWebKit-2.0.so";
}
my $buildWebKitPath = scriptPathForName("build-webkit");
die "Unsupported platform, can't determine built library locations.\nTry `$buildWebKitPath --help` for more information.\n";
}
# Check to see that all the expected build products are available.
sub checkBuild
{
return if isAnyWindows();
# First check if the directory where the expected build products should be exists.
my $productDir = productDir();
if (!-d $productDir) {
print "No build products could be found for specified build:\n";
print " configuration: \"$configuration\" [$configurationExplanation]\n";
print " platform: \"$xcodeSDKPlatformName\" [$xcodeSDKPlatformNameExplanation]" if isEmbeddedWebKit() || isMacCatalystWebKit();
print " products: $productDir\n\n";
my $buildWebKitCommand = scriptPathForName("build-webkit") . ' ' . join(' ', argumentsForConfiguration());
die "To build this configuration, use the command `$buildWebKitCommand`.\n\nOnce that completes, re-run this command.\n";
}
# Then, do a more fine grain check that the expected frameworks/libraries/dylibs exist within that directory.
# FIXME: This set of frameworks is a subset of the frameworks built. We should probably check them all.
my @frameworks = ("JavaScriptCore", "WebCore");
push(@frameworks, "WebKit") if isAppleCocoaWebKit(); # FIXME: This seems wrong, all ports should have a WebKit these days.
for my $framework (@frameworks) {
my $dylibPath = builtDylibPathForName($framework);
if (!-e $dylibPath) {
print "A dylib, \"$framework\", needed to run this command is missing for specified build:\n";
print " configuration: \"$configuration\" [$configurationExplanation]\n";
print " platform: \"$xcodeSDKPlatformName\" [$xcodeSDKPlatformNameExplanation]" if isEmbeddedWebKit() || isMacCatalystWebKit();
print " products: $productDir\n\n";
print " dylib: $dylibPath\n\n";
my $buildWebKitCommand = scriptPathForName("build-webkit") . ' ' . join(' ', argumentsForConfiguration());
die "To build this configuration, use the command `$buildWebKitCommand`.\n\nOnce that completes, re-run this command.\n";
}
}
}
sub isInspectorFrontend()
{
determineIsInspectorFrontend();
return $isInspectorFrontend;
}
sub determineIsInspectorFrontend()
{
return if defined($isInspectorFrontend);
$isInspectorFrontend = checkForArgumentAndRemoveFromARGV("--inspector-frontend");
}
sub commandExists($)
{
my $command = shift;
my $devnull = File::Spec->devnull();
if (isAnyWindows()) {
return exitStatus(system("where /q $command >$devnull 2>&1")) == 0;
}
return exitStatus(system("which $command >$devnull 2>&1")) == 0;
}
sub checkForArgumentAndRemoveFromARGV($)
{
my $argToCheck = shift;
return checkForArgumentAndRemoveFromArrayRef($argToCheck, \@ARGV);
}
sub checkForArgumentAndRemoveFromArrayRefGettingValue($$$)
{
my ($argToCheck, $valueRef, $arrayRef) = @_;
my $argumentStartRegEx = qr#^$argToCheck(?:=\S|$)#;
my $i = 0;
for (; $i < @$arrayRef; ++$i) {
last if $arrayRef->[$i] =~ $argumentStartRegEx;
}
if ($i >= @$arrayRef) {
return $$valueRef = undef;
}
my ($key, $value) = split("=", $arrayRef->[$i]);
splice(@$arrayRef, $i, 1);
if (defined($value)) {
# e.g. --sdk=iphonesimulator
return $$valueRef = $value;
}
return $$valueRef = splice(@$arrayRef, $i, 1); # e.g. --sdk iphonesimulator
}
sub checkForArgumentAndRemoveFromARGVGettingValue($$)
{
my ($argToCheck, $valueRef) = @_;
return checkForArgumentAndRemoveFromArrayRefGettingValue($argToCheck, $valueRef, \@ARGV);
}
sub findMatchingArguments($$)
{
my ($argToCheck, $arrayRef) = @_;
my @matchingIndices;
foreach my $index (0 .. $#$arrayRef) {
my $opt = $$arrayRef[$index];
if ($opt =~ /^$argToCheck$/i ) {
push(@matchingIndices, $index);
}
}
return @matchingIndices;
}
sub hasArgument($$)
{
my ($argToCheck, $arrayRef) = @_;
my @matchingIndices = findMatchingArguments($argToCheck, $arrayRef);
return scalar @matchingIndices > 0;
}
sub checkForArgumentAndRemoveFromArrayRef
{
my ($argToCheck, $arrayRef) = @_;
my @indicesToRemove = findMatchingArguments($argToCheck, $arrayRef);
my $removeOffset = 0;
foreach my $index (@indicesToRemove) {
splice(@$arrayRef, $index - $removeOffset++, 1);
}
return scalar @indicesToRemove > 0;
}
sub prohibitUnknownPort()
{
$unknownPortProhibited = 1;
}
sub determinePortName()
{
return if defined $portName;
my %argToPortName = (
gtk => GTK,
haiku => Haiku,
'jsc-only' => JSCOnly,
playstation => PlayStation,
win => Win,
wpe => WPE
);
for my $arg (sort keys %argToPortName) {
if (checkForArgumentAndRemoveFromARGV("--$arg")) {
die "Argument '--$arg' conflicts with selected port '$portName'\n"
if defined $portName;
$portName = $argToPortName{$arg};
}
}
return if defined $portName;
# Port was not selected via command line, use appropriate default value
if (isAnyWindows()) {
$portName = Win;
} elsif (isDarwin()) {
determineXcodeSDKPlatformName();
if (willUseIOSDeviceSDK() || willUseIOSSimulatorSDK()) {
$portName = iOS;
} elsif (willUseAppleTVDeviceSDK() || willUseAppleTVSimulatorSDK()) {
$portName = tvOS;
} elsif (willUseWatchDeviceSDK() || willUseWatchSimulatorSDK()) {
$portName = watchOS;
} elsif (willUseVisionDeviceSDK() || willUseVisionSimulatorSDK()) {
$portName = visionOS;
} elsif (willUseMacCatalystSDK()) {
$portName = MacCatalyst;
} else {
$portName = Mac;
}
} else {
if ($unknownPortProhibited) {
my $portsChoice = join "\n\t", qw(
--gtk
--jsc-only
--wpe
);
die "Please specify which WebKit port to build using one of the following options:"
. "\n\t$portsChoice\n";
}
# If script is run without arguments we cannot determine port
# TODO: This state should be outlawed
$portName = Unknown;
}
}
sub portName()
{
determinePortName();
return $portName;
}
sub isGtk()
{
return portName() eq GTK;
}
sub isJSCOnly()
{
return portName() eq JSCOnly;
}
sub isWPE()
{
return portName() eq WPE;
}
sub isPlayStation()
{
return portName() eq PlayStation;
}
# Determine if this is debian, ubuntu, linspire, or something similar.
sub isDebianBased()
{
return -e "/etc/debian_version";
}
sub isFedoraBased()
{
return -e "/etc/fedora-release";
}
sub isWin()
{
return portName() eq Win;
}
sub shouldBuild32Bit()
{
determineShouldBuild32Bit();
return $shouldBuild32Bit;
}
sub determineShouldBuild32Bit()
{
return if defined($shouldBuild32Bit);
$shouldBuild32Bit = checkForArgumentAndRemoveFromARGV("--32-bit");
}
sub isCygwin()
{
return ($^O eq "cygwin") || 0;
}
sub isAnyWindows()
{
return isWindows() || isCygwin();
}
sub determineWinVersion()
{
return if $winVersion;
if (!isAnyWindows()) {
$winVersion = -1;
return;
}
my $versionString = `cmd /c ver`;
$versionString =~ /(\d+)\.(\d+)\.(\d+)/;
$winVersion = {
major => $1,
minor => $2,
subminor => $3,
};
}
sub winVersion()
{
determineWinVersion();
return $winVersion;
}
sub isDarwin()
{
return ($^O eq "darwin") || 0;
}
sub isWindows()
{
return ($^O eq "MSWin32") || 0;
}
sub isLinux()
{
return ($^O eq "linux") || 0;
}
sub isBSD()
{
return ($^O eq "freebsd") || ($^O eq "openbsd") || ($^O eq "netbsd") || 0;
}
sub isHaiku()
{
return ($^O eq "haiku") || 0;
}
sub isX86_64()
{
return (architecture() eq "x86_64") || 0;
}
sub isARM64()
{
return (architecture() eq "arm64") || 0;
}
sub isCrossCompilation()
{
if (isPlayStation()) {
return 1;
}
my $compiler = "";
$compiler = $ENV{'CC'} if (defined($ENV{'CC'}));
if ($compiler =~ /gcc/) {
my $compilerOptions = `$compiler -v 2>&1`;
my @host = $compilerOptions =~ m/--host=(.*?)\s/;
my @target = $compilerOptions =~ m/--target=(.*?)\s/;
if (!@target || !@host) {
# Sometimes a compiler does not report the host of target it was compiled for,
# in which case, lacking better information we assume we are not cross-compiling.
return 0;
}
elsif ($target[0] ne "" && $host[0] ne "") {
return ($host[0] ne $target[0]);
} else {
# $tempDir gets automatically deleted when goes out of scope
my $tempDir = File::Temp->newdir();
my $testProgramSourcePath = File::Spec->catfile($tempDir, "testcross.c");
my $testProgramBinaryPath = File::Spec->catfile($tempDir, "testcross");
open(my $testProgramSourceHandler, ">", $testProgramSourcePath);
print $testProgramSourceHandler "int main() { return 0; }\n";
system("$compiler $testProgramSourcePath -o $testProgramBinaryPath > /dev/null 2>&1") == 0 or return 0;
# Crosscompiling if the program fails to run (because it was built for other arch)
system("$testProgramBinaryPath > /dev/null 2>&1") == 0 or return 1;
return 0;
}
}
return 0;
}
sub isIOSWebKit()
{
return portName() eq iOS;
}
sub isTVOSWebKit()
{
return portName() eq tvOS;
}
sub isWatchOSWebKit()
{
return portName() eq watchOS;
}
sub isVisionOSWebKit()
{
return portName() eq visionOS;
}
sub isEmbeddedWebKit()
{
return isIOSWebKit() || isTVOSWebKit() || isWatchOSWebKit() || isVisionOSWebKit;
}
sub isAppleWebKit()
{
return isAppleCocoaWebKit();
}
sub isAppleMacWebKit()
{
return portName() eq Mac;
}
sub isMacCatalystWebKit()
{
return portName() eq MacCatalyst;
}
sub isAppleCocoaWebKit()
{
return isAppleMacWebKit() || isEmbeddedWebKit() || isMacCatalystWebKit();
}
sub usesCryptexPath
{
return isAppleMacWebKit() || isMacCatalystWebKit() || isIOSWebKit();
}
sub simulatorDeviceFromJSON
{
my $runtime = shift;
my $jsonDevice = shift;
return {
"UDID" => $jsonDevice->{udid},
"name" => $jsonDevice->{name},
"runtime" => $runtime,
"state" => $jsonDevice->{state},
"deviceType" => $jsonDevice->{deviceTypeIdentifier}
};
}
sub iOSSimulatorDevices
{
my $output = `xcrun simctl list devices --json`;
my $runtimes = decode_json($output)->{devices};
if (!$runtimes) {
die "No simulator devices found";
}
my @devices = ();
while ((my $runtime, my $devicesForRuntime) = each %$runtimes) {
foreach my $jsonDevice (@$devicesForRuntime) {
next if $jsonDevice->{availabilityError};
push @devices, simulatorDeviceFromJSON($runtime, $jsonDevice);
}
}
return @devices;
}
sub createiOSSimulatorDevice
{
my $name = shift;
my $deviceTypeId = shift;
my $runtimeId = shift;
my $created = system("xcrun", "--sdk", "iphonesimulator", "simctl", "create", $name, $deviceTypeId, $runtimeId) == 0;
die "Couldn't create simulator device: $name $deviceTypeId $runtimeId" if not $created;
my @devices = iOSSimulatorDevices();
foreach my $device (@devices) {
return $device if $device->{name} eq $name and $device->{deviceType} eq $deviceTypeId and $device->{runtime} eq $runtimeId;
}
die "Device $name $deviceTypeId $runtimeId wasn't found";
}
sub willUseIOSDeviceSDK()
{
return xcodeSDKPlatformName() eq "iphoneos";
}
sub willUseIOSSimulatorSDK()
{
return xcodeSDKPlatformName() eq "iphonesimulator";
}
sub willUseAppleTVDeviceSDK()
{
return xcodeSDKPlatformName() eq "appletvos";
}
sub willUseAppleTVSimulatorSDK()
{
return xcodeSDKPlatformName() eq "appletvsimulator";
}
sub willUseWatchDeviceSDK()
{
return xcodeSDKPlatformName() eq "watchos";
}
sub willUseWatchSimulatorSDK()
{
return xcodeSDKPlatformName() eq "watchsimulator";
}
sub willUseVisionDeviceSDK()
{
return xcodeSDKPlatformName() eq "xros";
}
sub willUseVisionSimulatorSDK()
{
return xcodeSDKPlatformName() eq "xrsimulator";
}
sub willUseMacCatalystSDK()
{
return xcodeSDKPlatformName() eq "maccatalyst";
}
sub determineNmPath()
{
return if $nmPath;
if (isAppleCocoaWebKit()) {
$nmPath = `xcrun -find nm`;
chomp $nmPath;
}
$nmPath = "nm" if !$nmPath;
}
sub nmPath()
{
determineNmPath();
return $nmPath;
}
sub splitVersionString
{
my $versionString = shift;
my @splitVersion = split(/\./, $versionString);
@splitVersion >= 2 or die "Invalid version $versionString";
my @subMinorVersion = defined($splitVersion[2]) ? split(/-/, $splitVersion[2]) : (0);
return {
"major" => $splitVersion[0],
"minor" => $splitVersion[1],
"subminor" => $subMinorVersion[0],
};
}
sub determineOSXVersion()
{
return if $osXVersion;
if (!isDarwin()) {
$osXVersion = -1;
return;
}
chomp(my $versionString = `sw_vers -productVersion`);
$osXVersion = splitVersionString($versionString);
}
sub osXVersion()
{
determineOSXVersion();
return $osXVersion;
}
sub determineIOSVersion()
{
return if $iosVersion;
if (!isIOSWebKit()) {
$iosVersion = -1;
return;
}
my $versionString = xcodeSDKVersion();
$iosVersion = splitVersionString($versionString);
}
sub iosVersion()
{
determineIOSVersion();
return $iosVersion;
}
sub appendToEnvironmentVariableList($$)
{
my ($name, $value) = @_;
if (defined($ENV{$name})) {
$ENV{$name} .= $Config{path_sep} . $value;
} else {
$ENV{$name} = $value;
}
}
sub prependToEnvironmentVariableList($$)
{
my ($name, $value) = @_;
if (defined($ENV{$name})) {
$ENV{$name} = $value . $Config{path_sep} . $ENV{$name};
} else {
$ENV{$name} = $value;
}
}
sub sharedCommandLineOptions()
{
return (
"g|guard-malloc" => \$shouldUseGuardMalloc,
);
}
sub sharedCommandLineOptionsUsage
{
my %opts = @_;
my %switches = (
'-g|--guard-malloc' => 'Use guardmalloc when running executable',
);
my $indent = " " x ($opts{indent} || 2);
my $switchWidth = List::Util::max(int($opts{switchWidth}), List::Util::max(map { length($_) } keys %switches) + ($opts{brackets} ? 2 : 0));
my $result = "Common switches:\n";
for my $switch (keys %switches) {
my $switchName = $opts{brackets} ? "[" . $switch . "]" : $switch;
$result .= sprintf("%s%-" . $switchWidth . "s %s\n", $indent, $switchName, $switches{$switch});
}
return $result;
}
sub setUpGuardMallocIfNeeded
{
if (!isDarwin()) {
return;
}
if (!defined($shouldUseGuardMalloc)) {
$shouldUseGuardMalloc = checkForArgumentAndRemoveFromARGV("-g") || checkForArgumentAndRemoveFromARGV("--guard-malloc");
}
if ($shouldUseGuardMalloc) {
appendToEnvironmentVariableList("DYLD_INSERT_LIBRARIES", "/usr/lib/libgmalloc.dylib");
appendToEnvironmentVariableList("__XPC_DYLD_INSERT_LIBRARIES", "/usr/lib/libgmalloc.dylib");
}
}
sub relativeScriptsDir()
{
my $scriptDir = File::Spec->catpath("", File::Spec->abs2rel($FindBin::Bin, getcwd()), "");
if ($scriptDir eq "") {
$scriptDir = ".";
}
return $scriptDir;
}
sub scriptPathForName($)
{
my $scriptName = shift;
if ((isGtk() || isWPE()) && inFlatpakSandbox()) {
return "Tools/Scripts/$scriptName";
} else {
return relativeScriptsDir() . "/$scriptName";
}
}
sub launcherPath()
{
if (isGtk() || isWPE() || isHaiku()) {
return scriptPathForName("run-minibrowser");
} elsif (isAppleWebKit()) {
return scriptPathForName("run-safari");
}
}
sub launcherName()
{
if (isGtk() || isWPE() || isHaiku()) {
return "MiniBrowser";
} elsif (isAppleMacWebKit()) {
return "Safari";
}
}
sub checkRequiredSystemConfig
{
if (isDarwin()) {
chomp(my $productVersion = `sw_vers -productVersion`);
if (eval "v$productVersion" lt v10.10.5) {
print "*************************************************************\n";
print "OS X Yosemite v10.10.5 or later is required to build WebKit.\n";
print "You have " . $productVersion . ", thus the build will most likely fail.\n";
print "*************************************************************\n";
}
determineXcodeVersion();
if (eval "v$xcodeVersion" lt v7.0) {
print "*************************************************************\n";
print "Xcode 7.0 or later is required to build WebKit.\n";
print "You have an earlier version of Xcode, thus the build will\n";
print "most likely fail. The latest Xcode is available from the App Store.\n";
print "*************************************************************\n";
}
}
}
sub determineWindowsSourceDir()
{
return if $windowsSourceDir;
$windowsSourceDir = sourceDir();
chomp($windowsSourceDir = `cygpath -w '$windowsSourceDir'`) if isCygwin();
}
sub windowsSourceDir()
{
determineWindowsSourceDir();
return $windowsSourceDir;
}
sub windowsSourceSourceDir()
{
return File::Spec->catdir(windowsSourceDir(), "Source");
}
sub windowsOutputDir()
{
return File::Spec->catdir(windowsSourceDir(), "WebKitBuild");
}
sub fontExists($)
{
my $font = shift;
my $cmd = "reg query \"HKLM\\SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion\\Fonts\" /v \"$font\" 2>&1";
my $val = `$cmd`;
return $? == 0;
}
sub setupCygwinEnv()
{
return if !isAnyWindows();
return if $msBuildPath;
my $programFilesPath = programFilesPath();
print "Building results into: ", baseProductDir(), "\n";
print "WEBKIT_OUTPUTDIR is set to: ", $ENV{"WEBKIT_OUTPUTDIR"}, "\n";
# We will actually use MSBuild to build WebKit, but we need to find the Visual Studio install (above) to make
# sure we use the right options.
$msBuildPath = msBuildPath();
if (! -e $msBuildPath) {
print "*************************************************************\n";
print "Cannot find '$msBuildPath'\n";
print "Please make sure execute that the Microsoft .NET Framework SDK\n";
print "is installed on this machine.\n";
print "*************************************************************\n";
die;
}
}
sub buildXCodeProject($$@)
{
my ($project, $clean, @extraOptions) = @_;
if ($clean) {
push(@extraOptions, "-alltargets");
push(@extraOptions, "clean");
}
chomp($ENV{DSYMUTIL_NUM_THREADS} = `sysctl -n hw.activecpu`);
return system "xcodebuild", "-project", "$project.xcodeproj", @extraOptions;
}
sub buildXcodeScheme($$@)
{
my ($scheme, $clean, @extraOptions) = @_;
if ($clean) {
push @extraOptions, "clean";
}
return system "xcodebuild", "-scheme", $scheme, @extraOptions;
}
sub getVisualStudioToolset()
{
if (isPlayStation()) {
return "";
} else {
return "x64";
}
}
sub getMSBuildPlatformArgument()
{
my $toolset = getVisualStudioToolset();
if (defined($toolset) && length($toolset)) {
return "/p:Platform=$toolset";
}
return "";
}
sub getCMakeWindowsToolsetArgument()
{
my @args;
my $toolset = getVisualStudioToolset();
push(@args, '-A', $toolset) if defined($toolset) && length($toolset);
push(@args, '-T', 'ClangCL') if defined($ENV{CC}) && $ENV{CC} =~ 'clang-cl';
return @args;
}
sub buildVisualStudioProject
{
my ($project, $clean) = @_;
setupCygwinEnv();
my $config = configurationForVisualStudio();
chomp($project = `cygpath -w "$project"`) if isCygwin();
my $action = "/t:build";
if ($clean) {
$action = "/t:clean";
}
my $platform = getMSBuildPlatformArgument();
my $logPath = File::Spec->catdir($baseProductDir, $configuration);
make_path($logPath) unless -d $logPath or $logPath eq ".";
my $errorLogFile = File::Spec->catfile($logPath, "webkit_errors.log");
chomp($errorLogFile = `cygpath -w "$errorLogFile"`) if isCygwin();
my $errorLogging = "/flp:LogFile=" . $errorLogFile . ";ErrorsOnly";
my $warningLogFile = File::Spec->catfile($logPath, "webkit_warnings.log");
chomp($warningLogFile = `cygpath -w "$warningLogFile"`) if isCygwin();
my $warningLogging = "/flp1:LogFile=" . $warningLogFile . ";WarningsOnly";
my $maxCPUCount = '/maxcpucount:' . numberOfCPUs();
my @command = ($msBuildPath, "/verbosity:minimal", $project, $action, $config, $platform, "/fl", $errorLogging, "/fl1", $warningLogging, $maxCPUCount);
print join(" ", @command), "\n";
return system @command;
}
sub getJhbuildPath()
{
my @jhbuildPath = File::Spec->splitdir(baseProductDir());
if (isGit() && isGitBranchBuild() && gitBranch()) {
pop(@jhbuildPath);
}
if (isGtk()) {
push(@jhbuildPath, "DependenciesGTK");
} elsif (isWPE()) {
push(@jhbuildPath, "DependenciesWPE");
} else {
die "Cannot get JHBuild path for platform that isn't GTK+ or WPE.\n";
}
return File::Spec->catdir(@jhbuildPath);
}
sub getJhbuildModulesetName()
{
if (defined($ENV{'WEBKIT_JHBUILD_MODULESET'})) {
return 'jhbuild-' . $ENV{'WEBKIT_JHBUILD_MODULESET'} . '.modules';
}
return 'jhbuild.modules';
}
sub getUserFlatpakPath()
{
if (defined($ENV{'WEBKIT_FLATPAK_USER_DIR'})) {
return $ENV{'WEBKIT_FLATPAK_USER_DIR'};
}
my $productDir = baseProductDir();
if (isGit() && isGitBranchBuild() && gitBranch()) {
my $branch = gitBranch();
$productDir =~ s/$branch//;
}
my @flatpakPath = File::Spec->splitdir($productDir);
push(@flatpakPath, "UserFlatpak");
return File::Spec->catdir(@flatpakPath);
}
sub isCachedArgumentfileOutOfDate($@)
{
my ($filename, $currentContents) = @_;
if (! -e $filename) {
return 1;
}
open(CONTENTS_FILE, $filename);
local $/; # Slurp whole input file at once.
chomp(my $previousContents = <CONTENTS_FILE> || "");
close(CONTENTS_FILE);
my %old_lines = map { $_ => 1 } split /\n/, $previousContents;
my %new_lines = map { $_ => 1 } split /\n/, $currentContents;
my @removed = sort grep { !exists $new_lines{$_} } keys %old_lines;
my @added = sort grep { !exists $old_lines{$_} } keys %new_lines;
if (@removed or @added) {
print "Contents for file $filename have changed:\n";
print "- $_\n" for @removed;
print "+ $_\n" for @added;
return 1;
}
return 0;
}
sub inFlatpakSandbox()
{
return (-f "/.flatpak-info");
}
sub runInCrossTargetEnvironment(@)
{
return if not shouldBuildForCrossTarget();
my @prefix = (File::Spec->catfile(sourceDir(), "Tools", "Scripts", "cross-toolchain-helper"),
"--cross-target", getCrossTargetName(), "--cross-toolchain-run-cmd");
my @command = @_;
exec @prefix, @command, argumentsForConfiguration(), @ARGV or die;
}
sub maybeUseContainerSDKRootDir()
{
return if not isLinux();
return if (shouldUseFlatpak() or shouldBuildForCrossTarget() or inCrossTargetEnvironment());
return if ($ENV{'WEBKIT_CONTAINER_SDK'} // '') ne '1';
return if ($ENV{'WEBKIT_CONTAINER_SDK_INSIDE_MOUNT_NAMESPACE'} // '') eq '1';
my $sourceDir = sourceDir();
my @wrapperScript = (File::Spec->catfile($sourceDir, "Tools", "Scripts", "container-sdk-rootdir-wrapper"));
if (system(@wrapperScript, "--create-symlink") != 0) {
print STDERR "WARNING: Unable to create symlink at /sdk/webkit. Skipping setting up SDK common root dir feature\n";
return 1;
}
my @checkCommand = ('test', '-f', '/sdk/webkit/Tools/Scripts/build-webkit');
my $command = $0;
if (system(@wrapperScript, @checkCommand) == 0) {
if (index($command, $sourceDir) == 0) {
$command = '/sdk/webkit' . substr($command, length($sourceDir));
}
print "Running in private mount namespace at /sdk/webkit\n";
exec @wrapperScript, $command, argumentsForConfiguration(), @ARGV or die;
}
print STDERR "WARNING: Unable to create /sdk/webkit private mount namespace. Continuing only with symlink support.\n";
if ($command =~ /\/build-webkit$/) {
# This can allow remote ccache to hit even when the bind-mount was not possible, however it won't work for sccache.
$ENV{"CFLAGS"} = "-ffile-prefix-map=$sourceDir=/sdk/webkit" . ($ENV{"CFLAGS"} || "");
$ENV{"CXXFLAGS"} = "-ffile-prefix-map=$sourceDir=/sdk/webkit" . ($ENV{"CXXFLAGS"} || "");
}
}
sub runInFlatpak(@)
{
if (isGtk() && checkForArgumentAndRemoveFromARGV("--update-gtk")) {
system("perl", File::Spec->catfile(sourceDir(), "Tools", "Scripts", "update-webkitgtk-libs"), argumentsForConfiguration()) == 0 or die $!;
}
if (isWPE() && checkForArgumentAndRemoveFromARGV("--update-wpe")) {
system("perl", File::Spec->catfile(sourceDir(), "Tools", "Scripts", "update-webkitwpe-libs"), argumentsForConfiguration()) == 0 or die $!;
}
my @arg = @_;
my @command = (File::Spec->catfile(sourceDir(), "Tools", "Scripts", "webkit-flatpak"));
my @flatpakArgs;
my @filteredArgv;
# Filter-out Flatpak SDK-specific arguments to a separate array, passed to webkit-flatpak.
my $prefix = "--flatpak-";
foreach my $opt (@ARGV) {
if (substr($opt, 0, length($prefix)) eq $prefix) {
my ($name, $value) = split("--flatpak-", $opt);
push(@flatpakArgs, "--$value");
} else {
push(@filteredArgv, $opt);
}
}
exec @command, argumentsForConfiguration(), @flatpakArgs, "--command", @_, argumentsForConfiguration(), @filteredArgv or die;
}
sub jhbuildWrapperPrefix()
{
my @prefix = (File::Spec->catfile(sourceDir(), "Tools", "jhbuild", "jhbuild-wrapper"));
if (isGtk()) {
push(@prefix, "--gtk");
} elsif (isWPE()) {
push(@prefix, "--wpe");
}
push(@prefix, "run");
return @prefix;
}
sub inCrossTargetEnvironment()
{
return defined $ENV{'WEBKIT_CROSS_TARGET'};
}
sub getCrossTargetName()
{
return $crossTarget if shouldBuildForCrossTarget();
return $ENV{'WEBKIT_CROSS_TARGET'} if inCrossTargetEnvironment();
return;
}
sub shouldBuildForCrossTarget()
{
determineCrossTarget();
return defined $crossTarget;
}
sub wrapperPrefixIfNeeded()
{
if (isAnyWindows() || isJSCOnly() || isPlayStation() || isHaiku()) {
return ();
}
if (isAppleCocoaWebKit()) {
return ("xcrun");
}
if (shouldBuildForCrossTarget() or inCrossTargetEnvironment()) {
return ();
}
# Returning () here means either Flatpak or no wrapper will be used.
if (isGtk() or isWPE()) {
# Respect user's choice.
if (defined $ENV{'WEBKIT_JHBUILD'}) {
if ($ENV{'WEBKIT_JHBUILD'} and -e getJhbuildPath()) {
return jhbuildWrapperPrefix();
} else {
return ();
}
# or let Flatpak take precedence over JHBuild.
} elsif (-e getUserFlatpakPath()) {
return ();
} elsif (-e getJhbuildPath()) {
return jhbuildWrapperPrefix();
}
}
return ();
}
sub shouldUseFlatpak()
{
# TODO: Use flatpak for JSCOnly on Linux? Could be useful when the SDK
# supports cross-compilation for ARMv7 and Aarch64 for instance.
if (!isGtk() and !isWPE()) {
return 0;
}
if ((defined $ENV{'WEBKIT_JHBUILD'} && $ENV{'WEBKIT_JHBUILD'}) or (defined $ENV{'WEBKIT_BUILD_USE_SYSTEM_LIBRARIES'} && $ENV{'WEBKIT_BUILD_USE_SYSTEM_LIBRARIES'})) {
return 0;
}
if (shouldBuildForCrossTarget() or inCrossTargetEnvironment()) {
return 0;
}
my @prefix = wrapperPrefixIfNeeded();
return ((! inFlatpakSandbox()) and (@prefix == 0) and -e getUserFlatpakPath());
}
sub shouldUseVcpkg()
{
return isWin() || (isJSCOnly() && isWindows());
}
sub cmakeCachePath()
{
return File::Spec->catdir(productDir(), "CMakeCache.txt");
}
sub shouldRemoveCMakeCache(@)
{
# For this check, ignore all arguments that do not begin with a dash. These
# are probably arguments specifying build targets. Changing those should
# not trigger a reconfiguration of the build.
my (@buildArgs) = grep(/^-/, sort(@_, @originalArgv));
push @buildArgs, parse_line('\s+', 0, $ENV{'BUILD_WEBKIT_ARGS'}) if ($ENV{'BUILD_WEBKIT_ARGS'});
# We check this first, because we always want to create this file for a fresh build.
my $productDir = productDir();
my $optionsCache = File::Spec->catdir($productDir, "build-webkit-options.txt");
my $buildArgsEnv = "ARGS=" . join(" ", @buildArgs);
my @relevantEnvFlags = ( "AR", "AS", "CC", "CXX", "LD", "NM", "RANLIB", "STRIP", # Toolchain
"CFLAGS", "CXXFLAGS", "CPPFLAGS", "LDFLAGS", # Compiler and linker flags
"PKG_CONFIG_LIBDIR", "PKG_CONFIG_PATH", # pkg-config
"CPATH", "LIBRARY_PATH", # GCC/Clang include/lib helpers
"CMAKE_MODULE_PATH", "CMAKE_PREFIX_PATH"); # CMake-specific
for my $envFlag (@relevantEnvFlags) {
my $flagValue = $ENV{$envFlag} || "";
$buildArgsEnv .= "\n" . $envFlag . "=" . $flagValue;
}
if (isCachedArgumentfileOutOfDate($optionsCache, $buildArgsEnv)) {
File::Path::mkpath($productDir) unless -d $productDir;
open(CACHED_ARGUMENTS, ">", $optionsCache);
print CACHED_ARGUMENTS $buildArgsEnv;
close(CACHED_ARGUMENTS);
return 1;
}
my $cmakeCache = cmakeCachePath();
unless (-e $cmakeCache) {
return 0;
}
my $cacheFileModifiedTime = stat($cmakeCache)->mtime;
my $platformConfiguration = File::Spec->catdir(sourceDir(), "Source", "cmake", "Options" . cmakeBasedPortName() . ".cmake");
if ($cacheFileModifiedTime < stat($platformConfiguration)->mtime) {
return 1;
}
my $globalConfiguration = File::Spec->catdir(sourceDir(), "Source", "cmake", "OptionsCommon.cmake");
if ($cacheFileModifiedTime < stat($globalConfiguration)->mtime) {
return 1;
}
my $compilerFlagsCMake = File::Spec->catdir(sourceDir(), "Source", "cmake", "WebKitCompilerFlags.cmake");
if ($cacheFileModifiedTime < stat($compilerFlagsCMake)->mtime) {
return 1;
}
# FIXME: This probably does not work as expected, or the next block to
# delete the images subdirectory would not be here. Directory mtime does not
# percolate upwards when files are added or removed from subdirectories.
my $inspectorUserInterfaceDirectory = File::Spec->catdir(sourceDir(), "Source", "WebInspectorUI", "UserInterface");
if ($cacheFileModifiedTime < stat($inspectorUserInterfaceDirectory)->mtime) {
return 1;
}
my $inspectorImageDirectory = File::Spec->catdir(sourceDir(), "Source", "WebInspectorUI", "UserInterface", "Images");
if ($cacheFileModifiedTime < stat($inspectorImageDirectory)->mtime) {
return 1;
}
if(isAnyWindows()) {
my $winConfiguration = File::Spec->catdir(sourceDir(), "Source", "cmake", "OptionsWin.cmake");
if ($cacheFileModifiedTime < stat($winConfiguration)->mtime) {
return 1;
}
}
# If a change on the JHBuild moduleset has been done, we need to clean the cache as well.
if (! shouldUseFlatpak() and (isGtk() || isWPE())) {
my $jhbuildRootDirectory = File::Spec->catdir(getJhbuildPath(), "Root");
# The script update-webkit-libs-jhbuild shall re-generate $jhbuildRootDirectory if the moduleset changed.
if (-d $jhbuildRootDirectory && $cacheFileModifiedTime < stat($jhbuildRootDirectory)->mtime) {
return 1;
}
}
return 0;
}
sub removeCMakeCache(@)
{
my (@buildArgs) = @_;
if (shouldRemoveCMakeCache(@buildArgs)) {
my $cmakeCache = cmakeCachePath();
if (-e $cmakeCache) {
print "Removing CMake Cache at " . $cmakeCache . "\n";
unlink($cmakeCache);
}
}
}
sub canUseNinja(@)
{
if (!defined($shouldNotUseNinja)) {
$shouldNotUseNinja = checkForArgumentAndRemoveFromARGV("--no-ninja");
}
if ($shouldNotUseNinja) {
return 0;
}
if (isAppleCocoaWebKit()) {
my $devnull = File::Spec->devnull();
if (exitStatus(system("xcrun -find ninja >$devnull 2>&1")) == 0) {
return 1;
}
}
# Test both ninja and ninja-build. Fedora uses ninja-build and has patched CMake to also call ninja-build.
return commandExists("ninja") || commandExists("ninja-build");
}
sub canUseEclipseNinjaGenerator(@)
{
# Check that eclipse and eclipse Ninja generator is installed
my $devnull = File::Spec->devnull();
return commandExists("eclipse") && exitStatus(system("cmake -N -G 'Eclipse CDT4 - Ninja' >$devnull 2>&1")) == 0;
}
sub cmakeGeneratedBuildfile(@)
{
my ($willUseNinja) = @_;
if ($willUseNinja) {
return File::Spec->catfile(productDir(), "build.ninja")
} elsif (isAnyWindows()) {
return File::Spec->catfile(productDir(), "WebKit.sln")
} else {
return File::Spec->catfile(productDir(), "Makefile")
}
}
sub generateBuildSystemFromCMakeProject
{
my ($prefixPath, @cmakeArgs) = @_;
my $config = configuration();
my $port = cmakeBasedPortName();
my $buildPath = productDir();
File::Path::mkpath($buildPath) unless -d $buildPath;
my $originalWorkingDirectory = getcwd();
chdir($buildPath) or die;
# We try to be smart about when to rerun cmake, so that we can have faster incremental builds.
my $willUseNinja = canUseNinja();
if (-e cmakeCachePath() && -e cmakeGeneratedBuildfile($willUseNinja)) {
return 0;
}
my @args;
push @args, "-DPORT=\"$port\"";
push @args, "-DCMAKE_INSTALL_PREFIX=\"$prefixPath\"" if $prefixPath;
if ($config =~ /release/i) {
push @args, "-DCMAKE_BUILD_TYPE=Release";
} elsif ($config =~ /debug/i) {
push @args, "-DCMAKE_BUILD_TYPE=Debug";
}
push @args, "-DENABLE_SANITIZERS=address" if asanIsEnabled();
push @args, "-DENABLE_SANITIZERS=thread" if tsanIsEnabled();
push @args, "-DENABLE_SANITIZERS=undefined" if ubsanIsEnabled();
push @args, "-DENABLE_SANITIZERS=fuzzer" if libFuzzerIsEnabled();
push @args, "-DLTO_MODE=$ltoMode" if ltoMode();
if (shouldUseVcpkg()) {
push @args, '-DCMAKE_TOOLCHAIN_FILE="' . $ENV{VCPKG_ROOT} . '\\scripts\\buildsystems\\vcpkg.cmake"';
push @args, '-DVCPKG_TARGET_TRIPLET=x64-windows-webkit'
} elsif (isPlayStation()) {
my $toolChainFile = $ENV{'CMAKE_TOOLCHAIN_FILE'} || "Platform/PlayStation5";
push @args, '-DCMAKE_TOOLCHAIN_FILE=' . $toolChainFile;
}
if ($willUseNinja) {
push @args, "-G";
if (canUseEclipseNinjaGenerator()) {
push @args, "'Eclipse CDT4 - Ninja'";
} else {
push @args, "Ninja";
}
push @args, "-DUSE_THIN_ARCHIVES=OFF" if isPlayStation();
} else {
if (isAnyWindows()) {
push @args, getCMakeWindowsToolsetArgument();
}
if ((isAnyWindows() || isPlayStation()) && defined $ENV{VisualStudioVersion}) {
my $var = int($ENV{VisualStudioVersion});
push @args, qq(-G "Visual Studio $var");
}
}
# Do not show progress of generating bindings in interactive Ninja build not to leave noisy lines on tty
push @args, '-DSHOW_BINDINGS_GENERATION_PROGRESS=1' unless ($willUseNinja && -t STDOUT);
# Some ports have production mode, but build-webkit should always use developer mode.
push @args, "-DDEVELOPER_MODE=ON" unless isAppleWebKit();
if (architecture() eq "x86_64" && shouldBuild32Bit()) {
# CMAKE_LIBRARY_ARCHITECTURE is needed to get the right .pc
# files in Debian-based systems, for the others
# CMAKE_PREFIX_PATH will get us /usr/lib, which should be the
# right path for 32bit. See FindPkgConfig.cmake.
push @cmakeArgs, '-DFORCE_32BIT=ON -DCMAKE_PREFIX_PATH="/usr" -DCMAKE_LIBRARY_ARCHITECTURE=x86';
$ENV{"CFLAGS"} = "-m32" . ($ENV{"CFLAGS"} || "");
$ENV{"CXXFLAGS"} = "-m32" . ($ENV{"CXXFLAGS"} || "");
$ENV{"LDFLAGS"} = "-m32" . ($ENV{"LDFLAGS"} || "");
}
if (architecture() eq "arm64" && shouldBuild32Bit()) {
my $compiler = "";
$compiler = $ENV{'CC'} if (defined($ENV{'CC'}));
# CMAKE_LIBRARY_ARCHITECTURE is needed to get the right .pc
# files in Debian-based systems, for the others
# CMAKE_PREFIX_PATH will get us /usr/lib, which should be the
# right path for 32bit. See FindPkgConfig.cmake.
push @cmakeArgs, '-DFORCE_32BIT=ON -DCMAKE_PREFIX_PATH="/usr" -DCMAKE_LIBRARY_ARCHITECTURE=armv7-a+fp ';
$ENV{"CFLAGS"} = " -march=armv7-a+fp " . ($ENV{"CFLAGS"} || "");
$ENV{"CXXFLAGS"} = " -march=armv7-a+fp " . ($ENV{"CXXFLAGS"} || "");
$ENV{"LDFLAGS"} = " -march=armv7-a+fp " . ($ENV{"LDFLAGS"} || "");
if ($compiler =~ /clang/) {
$ENV{"CFLAGS"} = " -m32 " . ($ENV{"CFLAGS"} || "");
$ENV{"CXXFLAGS"} = " -m32 " . ($ENV{"CXXFLAGS"} || "");
$ENV{"LDFLAGS"} = " -m32 " . ($ENV{"LDFLAGS"} || "");
}
}
push @args, @cmakeArgs if @cmakeArgs;
my $cmakeSourceDir = isCygwin() ? windowsSourceDir() : sourceDir();
push @args, '"' . $cmakeSourceDir . '"';
# We call system("cmake @args") instead of system("cmake", @args) so that @args is
# parsed for shell metacharacters.
my $wrapper = join(" ", wrapperPrefixIfNeeded()) . " ";
my $returnCode = systemVerbose($wrapper . "cmake @args");
chdir($originalWorkingDirectory);
return $returnCode;
}
sub buildCMakeGeneratedProject($)
{
my (@makeArgs) = @_;
my $config = configuration();
my $buildPath = productDir();
if (! -d $buildPath) {
die "Must call generateBuildSystemFromCMakeProject() before building CMake project.";
}
if ($ENV{VERBOSE} && canUseNinja()) {
push @makeArgs, "-v";
push @makeArgs, "-d keeprsp" if (version->parse(determineNinjaVersion()) >= version->parse("1.4.0"));
}
my $command = "cmake";
my @args = ("--build", $buildPath, "--config", $config);
push @args, ("--", @makeArgs) if @makeArgs;
# GTK and JSCOnly can use a build script to preserve colors and pretty-printing.
if ((isGtk() || isJSCOnly()) && -e "$buildPath/build.sh") {
chdir "$buildPath" or die;
$command = "$buildPath/build.sh";
@args = (@makeArgs);
}
# We call system("cmake @args") instead of system("cmake", @args) so that @args is
# parsed for shell metacharacters. In particular, @makeArgs may contain such metacharacters.
my $wrapper = join(" ", wrapperPrefixIfNeeded()) . " ";
return systemVerbose($wrapper . "$command @args");
}
sub cleanCMakeGeneratedProject()
{
my $config = configuration();
my $buildPath = productDir();
if (-d $buildPath) {
return systemVerbose("cmake", "--build", $buildPath, "--config", $config, "--target", "clean");
}
return 0;
}
sub buildCMakeProjectOrExit($$$@)
{
my ($clean, $prefixPath, $makeArgs, @cmakeArgs) = @_;
my $returnCode;
exit(exitStatus(cleanCMakeGeneratedProject())) if $clean;
my $wrapper = wrapperPrefixIfNeeded();
my $jhbuildPrefix = jhbuildWrapperPrefix();
if (defined($wrapper) && defined($jhbuildPrefix) && $wrapper == $jhbuildPrefix) {
if (isGtk() && checkForArgumentAndRemoveFromARGV("--update-gtk")) {
system("perl", File::Spec->catfile(sourceDir(), "Tools", "Scripts", "update-webkitgtk-libs")) == 0 or die $!;
}
if (isWPE() && checkForArgumentAndRemoveFromARGV("--update-wpe")) {
system("perl", File::Spec->catfile(sourceDir(), "Tools", "Scripts", "update-webkitwpe-libs")) == 0 or die $!;
}
}
$returnCode = exitStatus(generateBuildSystemFromCMakeProject($prefixPath, @cmakeArgs));
exit($returnCode) if $returnCode;
exit 0 if isGenerateProjectOnly();
$returnCode = exitStatus(buildCMakeGeneratedProject($makeArgs));
exit($returnCode) if $returnCode;
return 0;
}
sub cmakeArgsFromFeatures(\@;$)
{
my ($featuresArrayRef, $enableExperimentalFeatures) = @_;
my @args;
push @args, "-DENABLE_EXPERIMENTAL_FEATURES=ON" if $enableExperimentalFeatures;
foreach (@$featuresArrayRef) {
my $featureName = $_->{define};
if ($featureName) {
my $featureValue = ${$_->{value}}; # Undef to let the build system use its default.
if (defined($featureValue)) {
my $featureEnabled = $featureValue ? "ON" : "OFF";
push @args, "-D$featureName=$featureEnabled";
}
}
}
return @args;
}
sub vcpkgArgsFromFeatures(\@;$)
{
my ($featuresArrayRef, $enableExperimentalFeatures) = @_;
my @args;
my $avif = 0;
my $jpegxl = 1;
my $lcms = 1;
my $skia = 1;
my $woff2 = 1;
if (isJSCOnly()) {
return;
}
foreach (@$featuresArrayRef) {
my $featureName = $_->{define};
if ($featureName) {
my $featureValue = ${$_->{value}}; # Undef to let the build system use its default.
if (defined($featureValue)) {
if ($featureName eq "USE_AVIF") {
$avif = $featureValue;
} elsif ($featureName eq "USE_JPEGXL") {
$jpegxl = $featureValue;
} elsif ($featureName eq "USE_LCMS") {
$lcms = $featureValue;
} elsif ($featureName eq "USE_SKIA") {
$skia = $featureValue;
} elsif ($featureName eq "USE_WOFF2") {
$woff2 = $featureValue;
}
}
}
}
push @args, "web";
push @args, "avif" if $avif;
push @args, "jpeg-xl" if $jpegxl;
push @args, "lcms" if $lcms;
push @args, $skia ? "skia" : "cairo";
push @args, "woff2" if $woff2;
return "-DVCPKG_MANIFEST_FEATURES=" . join(";", @args);
}
sub cmakeBasedPortName()
{
return ucfirst portName();
}
sub determineIsCMakeBuild()
{
return if defined($isCMakeBuild);
$isCMakeBuild = checkForArgumentAndRemoveFromARGV("--cmake");
}
sub isCMakeBuild()
{
return 1 unless isAppleCocoaWebKit();
determineIsCMakeBuild();
return $isCMakeBuild;
}
sub determineIsGenerateProjectOnly()
{
return if defined($isGenerateProjectOnly);
$isGenerateProjectOnly = checkForArgumentAndRemoveFromARGV("--generate-project-only");
}
sub isGenerateProjectOnly()
{
determineIsGenerateProjectOnly();
return $isGenerateProjectOnly;
}
sub promptUser
{
my ($prompt, $default) = @_;
my $defaultValue = $default ? "[$default]" : "";
print "$prompt $defaultValue: ";
chomp(my $input = <STDIN>);
return $input ? $input : $default;
}
sub setPathForRunningWebKitApp
{
my ($env) = @_;
if (isAnyWindows()) {
my $productBinaryDir = executableProductDir();
my $winBin = sourceDir() . "/WebKitLibraries/win/bin/";
$env->{PATH} = join(':', $productBinaryDir, $winBin, $env->{PATH} || "");
}
}
sub printHelpAndExitForRunAndDebugWebKitAppIfNeeded
{
return unless checkForArgumentAndRemoveFromARGV("--help");
print STDERR <<EOF;
Usage: @{[basename($0)]} [options] <application-path>
--help Show this help message
--no-saved-state Launch the application without state restoration
--debug|release build configuration
Options specific to macOS:
-g|--guard-malloc Enable Guard Malloc
--lang=LANGUAGE Use a specific language instead of system language.
This accepts a language name (German) or a language code (de, ar, pt_BR, etc).
--locale=LOCALE Use a specific locale instead of the system region.
Options specific to iOS:
--iphone-simulator Run the app in an iPhone Simulator
--ipad-simulator Run the app in an iPad Simulator
EOF
exit(1);
}
sub argumentsForRunAndDebugMacWebKitApp()
{
my @args = ();
if (checkForArgumentAndRemoveFromARGV("--no-saved-state")) {
push @args, ("-ApplePersistenceIgnoreStateQuietly", "YES");
}
my $lang;
if (checkForArgumentAndRemoveFromARGVGettingValue("--lang", \$lang)) {
push @args, ("-AppleLanguages", "(" . $lang . ")");
}
my $locale;
if (checkForArgumentAndRemoveFromARGVGettingValue("--locale", \$locale)) {
push @args, ("-AppleLocale", $locale);
}
unshift @args, @ARGV;
return @args;
}
sub setupMacWebKitEnvironment($)
{
my ($dyldFrameworkPath) = @_;
$dyldFrameworkPath = File::Spec->rel2abs($dyldFrameworkPath);
prependToEnvironmentVariableList("DYLD_FRAMEWORK_PATH", $dyldFrameworkPath);
prependToEnvironmentVariableList("__XPC_DYLD_FRAMEWORK_PATH", $dyldFrameworkPath);
prependToEnvironmentVariableList("DYLD_LIBRARY_PATH", $dyldFrameworkPath);
prependToEnvironmentVariableList("__XPC_DYLD_LIBRARY_PATH", $dyldFrameworkPath);
$ENV{WEBKIT_UNSET_DYLD_FRAMEWORK_PATH} = "YES";
setUpGuardMallocIfNeeded();
}
sub setupUnixWebKitEnvironment($)
{
my ($productDir) = @_;
prependToEnvironmentVariableList("LD_LIBRARY_PATH", File::Spec->catfile($productDir, "lib"));
$ENV{TEST_RUNNER_INJECTED_BUNDLE_FILENAME} = File::Spec->catfile($productDir, "lib", "libTestRunnerInjectedBundle.so");
}
sub setupIOSWebKitEnvironment($)
{
my ($dyldFrameworkPath) = @_;
$dyldFrameworkPath = File::Spec->rel2abs($dyldFrameworkPath);
prependToEnvironmentVariableList("DYLD_FRAMEWORK_PATH", $dyldFrameworkPath);
prependToEnvironmentVariableList("DYLD_LIBRARY_PATH", $dyldFrameworkPath);
prependToEnvironmentVariableList("__XPC_DYLD_FRAMEWORK_PATH", $dyldFrameworkPath);
prependToEnvironmentVariableList("__XPC_DYLD_LIBRARY_PATH", $dyldFrameworkPath);
prependToEnvironmentVariableList("METAL_DEVICE_WRAPPER_TYPE", "1");
setUpGuardMallocIfNeeded();
}
sub iosSimulatorApplicationsPath()
{
my $output = `xcrun simctl list runtimes iOS --json`;
my $runtimes = decode_json($output)->{runtimes};
if (!$runtimes) {
die "No iOS simulator runtimes found";
}
my $runtimePath = @$runtimes[0]->{runtimeRoot};
return File::Spec->catdir($runtimePath, "Applications");
}
sub installedMobileSafariBundle()
{
return File::Spec->catfile(iosSimulatorApplicationsPath(), "MobileSafari.app");
}
sub installedMobileMiniBrowserBundle()
{
return File::Spec->catfile(iosSimulatorApplicationsPath(), "MobileMiniBrowser.app");
}
sub mobileSafariBundle()
{
determineConfigurationProductDir();
# Use MobileSafari.app in product directory if present.
if (isIOSWebKit() && -d "$configurationProductDir/MobileSafari.app") {
return "$configurationProductDir/MobileSafari.app";
}
return installedMobileSafariBundle();
}
sub mobileMiniBrowserBundle()
{
determineConfigurationProductDir();
if (isIOSWebKit() && -d "$configurationProductDir/MobileMiniBrowser.app") {
return "$configurationProductDir/MobileMiniBrowser.app";
}
return installedMobileMiniBrowserBundle();
}
sub installedSwiftBrowserBundle()
{
return File::Spec->catfile(iosSimulatorApplicationsPath(), "SwiftBrowser.app");
}
sub swiftBrowserBundle()
{
determineConfigurationProductDir();
if (isIOSWebKit() && -d "$configurationProductDir/SwiftBrowser.app") {
return "$configurationProductDir/SwiftBrowser.app";
}
return installedSwiftBrowserBundle();
}
sub plistPathFromBundle($)
{
my ($appBundle) = @_;
return "$appBundle/Info.plist" if -f "$appBundle/Info.plist"; # iOS app bundle
return "$appBundle/Contents/Info.plist" if -f "$appBundle/Contents/Info.plist"; # Mac app bundle
return "";
}
sub appIdentifierFromBundle($)
{
my ($appBundle) = @_;
my $plistPath = File::Spec->rel2abs(plistPathFromBundle($appBundle)); # defaults(1) will complain if the specified path is not absolute.
chomp(my $bundleIdentifier = `defaults read '$plistPath' CFBundleIdentifier 2> /dev/null`);
return $bundleIdentifier;
}
sub appDisplayNameFromBundle($)
{
my ($appBundle) = @_;
my $plistPath = File::Spec->rel2abs(plistPathFromBundle($appBundle)); # defaults(1) will complain if the specified path is not absolute.
chomp(my $bundleDisplayName = `defaults read '$plistPath' CFBundleDisplayName 2> /dev/null`);
return $bundleDisplayName;
}
sub waitUntilIOSSimulatorDeviceIsInState($$)
{
my ($deviceUDID, $waitUntilState) = @_;
my $device = iosSimulatorDeviceByUDID($deviceUDID);
# FIXME: We should add a maximum time limit to wait here.
while ($device->{state} ne $waitUntilState) {
usleep(500 * 1000); # Waiting 500ms between file system polls does not make script run-safari feel sluggish.
$device = iosSimulatorDeviceByUDID($deviceUDID);
}
}
sub waitUntilProcessNotRunning($)
{
my ($process) = @_;
while (system("/bin/ps -eo pid,comm | /usr/bin/grep '$process\$'") == 0) {
usleep(500 * 1000);
}
}
sub shutDownIOSSimulatorDevice($)
{
my ($simulatorDevice) = @_;
return if $simulatorDevice->{state} eq SIMULATOR_DEVICE_STATE_SHUTDOWN;
system("xcrun --sdk iphonesimulator simctl shutdown $simulatorDevice->{UDID} > /dev/null 2>&1");
}
sub restartIOSSimulatorDevice($)
{
my ($simulatorDevice) = @_;
shutDownIOSSimulatorDevice($simulatorDevice);
exitStatus(system("xcrun", "--sdk", "iphonesimulator", "simctl", "boot", $simulatorDevice->{UDID})) == 0 or die "Failed to boot simulator device $simulatorDevice->{UDID}";
}
sub relaunchIOSSimulator($)
{
my ($simulatedDevice) = @_;
shutDownIOSSimulatorDevice($simulatedDevice);
chomp(my $developerDirectory = $ENV{DEVELOPER_DIR} || `xcode-select --print-path`);
my $iosSimulatorPath = File::Spec->catfile($developerDirectory, "Applications", "Simulator.app");
# Simulator.app needs to be running before the simulator is booted to have it visible.
system("open", "-a", $iosSimulatorPath, "--args", "-CurrentDeviceUDID", $simulatedDevice->{UDID}) == 0 or die "Failed to open $iosSimulatorPath: $!";
system("xcrun", "simctl", "boot", $simulatedDevice->{UDID}) == 0 or die "Failed to boot simulator $simulatedDevice->{UDID}: $!";
waitUntilIOSSimulatorDeviceIsInState($simulatedDevice->{UDID}, SIMULATOR_DEVICE_STATE_BOOTED);
waitUntilProcessNotRunning("com.apple.datamigrator");
}
sub iosSimulatorDeviceByName($)
{
my ($simulatorName) = @_;
my $simulatorRuntime = iosSimulatorRuntime();
my @devices = iOSSimulatorDevices();
for my $device (@devices) {
if ($device->{name} eq $simulatorName && $device->{runtime} eq $simulatorRuntime) {
return $device;
}
}
return undef;
}
sub iosSimulatorDeviceByUDID($)
{
my ($simulatedDeviceUDID) = @_;
my $output = `xcrun simctl list devices $simulatedDeviceUDID --json`;
my $runtimes = decode_json($output)->{devices};
while ((my $runtime, my $devicesForRuntime) = each %$runtimes) {
next if not @$devicesForRuntime;
die "Multiple devices found for UDID $simulatedDeviceUDID: $output" if scalar(@$devicesForRuntime) > 1;
return simulatorDeviceFromJSON($runtime, @$devicesForRuntime[0]);
}
return undef;
}
sub iosSimulatorRuntime
{
return simulatorRuntime(iOS);
}
sub simulatorRuntime($)
{
my $platformName = shift;
my $xcodeSDKVersion = xcodeSDKVersion();
my $output = `xcrun --sdk $xcodeSDK simctl list runtimes $platformName --json` or die "Failed to run find simulator runtime";
for my $runtime (@{decode_json($output)->{runtimes}}) {
if ($runtime->{version} eq $xcodeSDKVersion) {
return $runtime->{identifier};
}
if ($runtime->{version} =~ /^$xcodeSDKVersion/) {
my $runtime_version = $runtime->{version};
my $runtime_id = $runtime->{identifier};
warn "WARNING: Fuzzy-matched $platformName SDK version $xcodeSDKVersion to runtime $runtime_id with version $runtime_version.";
return $runtime_id;
}
}
}
sub findOrCreateSimulatorForIOSDevice($)
{
my ($simulatorNameSuffix) = @_;
my $simulatorName;
my $simulatorDeviceType;
# These should match the DEFAULT_DEVICE_TYPES in webkitpy/port/ios_simulator.py.
if ($simulatorIdiom eq "iPad") {
$simulatorName = "iPad (9th generation) " . $simulatorNameSuffix;
$simulatorDeviceType = "com.apple.CoreSimulator.SimDeviceType.iPad-9th-generation";
} else {
$simulatorName = "iPhone 12 " . $simulatorNameSuffix;
$simulatorDeviceType = "com.apple.CoreSimulator.SimDeviceType.iPhone-12";
}
my $simulatedDevice = iosSimulatorDeviceByName($simulatorName);
return $simulatedDevice if $simulatedDevice;
return createiOSSimulatorDevice($simulatorName, $simulatorDeviceType, iosSimulatorRuntime());
}
sub isIOSSimulatorSystemInstalledApp($)
{
my ($appBundle) = @_;
my $simulatorApplicationsPath = realpath(iosSimulatorApplicationsPath());
return substr(realpath($appBundle), 0, length($simulatorApplicationsPath)) eq $simulatorApplicationsPath;
}
sub hasUserInstalledAppInSimulatorDevice($$)
{
my ($appIdentifier, $simulatedDeviceUDID) = @_;
my $userInstalledAppPath = File::Spec->catfile($ENV{HOME}, "Library", "Developer", "CoreSimulator", "Devices", $simulatedDeviceUDID, "data", "Containers", "Bundle", "Application");
if (!-d $userInstalledAppPath) {
return 0; # No user installed apps.
}
local @::userInstalledAppBundles;
my $wantedFunction = sub {
my $file = $_;
# Ignore hidden files and directories.
if ($file =~ /^\../) {
$File::Find::prune = 1;
return;
}
return if !-d $file || $file !~ /\.app$/;
push @::userInstalledAppBundles, $File::Find::name;
$File::Find::prune = 1; # Do not traverse contents of app bundle.
};
find($wantedFunction, $userInstalledAppPath);
for my $userInstalledAppBundle (@::userInstalledAppBundles) {
if (appIdentifierFromBundle($userInstalledAppBundle) eq $appIdentifier) {
return 1; # Has user installed app.
}
}
return 0; # Does not have user installed app.
}
sub isSimulatorDeviceBooted($)
{
my ($simulatedDeviceUDID) = @_;
my $device = iosSimulatorDeviceByUDID($simulatedDeviceUDID);
return $device && $device->{state} eq SIMULATOR_DEVICE_STATE_BOOTED;
}
sub runIOSWebKitAppInSimulator($;$)
{
my ($appBundle, $simulatorOptions) = @_;
my $productDir = productDir();
my $appDisplayName = appDisplayNameFromBundle($appBundle);
my $appIdentifier = appIdentifierFromBundle($appBundle);
my $simulatedDevice = findOrCreateSimulatorForIOSDevice(SIMULATOR_DEVICE_SUFFIX_FOR_WEBKIT_DEVELOPMENT);
my $simulatedDeviceUDID = $simulatedDevice->{UDID};
my $willUseSystemInstalledApp = isIOSSimulatorSystemInstalledApp($appBundle);
if ($willUseSystemInstalledApp) {
if (hasUserInstalledAppInSimulatorDevice($appIdentifier, $simulatedDeviceUDID)) {
# Restore the system-installed app in the simulator device corresponding to $appBundle as it
# was previously overwritten with a custom built version of the app.
# FIXME: Only restore the system-installed version of the app instead of erasing all contents and settings.
print "Quitting iOS Simulator...\n";
shutDownIOSSimulatorDevice($simulatedDevice);
print "Erasing contents and settings for simulator device \"$simulatedDevice->{name}\".\n";
exitStatus(system("xcrun", "--sdk", "iphonesimulator", "simctl", "erase", $simulatedDeviceUDID)) == 0 or die;
}
# FIXME: We assume that if $simulatedDeviceUDID is not booted then iOS Simulator is not open. However
# $simulatedDeviceUDID may have been booted using the simctl command line tool. If $simulatedDeviceUDID
# was booted using simctl then we should shutdown the device and launch iOS Simulator to boot it again.
if (!isSimulatorDeviceBooted($simulatedDeviceUDID)) {
print "Launching iOS Simulator...\n";
relaunchIOSSimulator($simulatedDevice);
}
} else {
# FIXME: We should killall(1) any running instances of $appBundle before installing it to ensure
# that simctl launch opens the latest installed version of the app. For now we quit and
# launch the iOS Simulator again to ensure there are no running instances of $appBundle.
print "Quitting and launching iOS Simulator...\n";
relaunchIOSSimulator($simulatedDevice);
print "Installing $appBundle.\n";
# Install custom built app, overwriting an app with the same app identifier if one exists.
exitStatus(system("xcrun", "--sdk", "iphonesimulator", "simctl", "install", $simulatedDeviceUDID, $appBundle)) == 0 or die;
}
$simulatorOptions = {} unless $simulatorOptions;
my %simulatorENV;
%simulatorENV = %{$simulatorOptions->{applicationEnvironment}} if $simulatorOptions->{applicationEnvironment};
{
local %ENV; # Shadow global-scope %ENV so that changes to it will not be seen outside of this scope.
setupIOSWebKitEnvironment($productDir);
%simulatorENV = %ENV;
}
my $applicationArguments = \@ARGV;
$applicationArguments = $simulatorOptions->{applicationArguments} if $simulatorOptions && $simulatorOptions->{applicationArguments};
# Prefix the environment variables with SIMCTL_CHILD_ per `xcrun simctl help launch`.
foreach my $key (keys %simulatorENV) {
$ENV{"SIMCTL_CHILD_$key"} = $simulatorENV{$key};
}
print "Starting $appDisplayName with DYLD_FRAMEWORK_PATH set to point to built WebKit in $productDir.\n";
return exitStatus(system("xcrun", "--sdk", "iphonesimulator", "simctl", "launch", $simulatedDeviceUDID, $appIdentifier, @$applicationArguments));
}
sub runIOSWebKitApp($)
{
my ($appBundle) = @_;
if (willUseIOSDeviceSDK()) {
die "Only running Safari in iOS Simulator is supported now.";
}
if (willUseIOSSimulatorSDK()) {
return runIOSWebKitAppInSimulator($appBundle);
}
die "Not using an iOS SDK."
}
sub commandLineArgumentsForRestrictedEnvironmentVariables($)
{
my $prefix = shift;
return map { ($prefix, "$_=$ENV{$_}") } grep { /^DYLD_/ } keys %ENV;
}
sub runMacWebKitApp($;$)
{
my ($appPath, $useOpenCommand) = @_;
my $productDir = productDir();
print "Starting @{[basename($appPath)]} with DYLD_FRAMEWORK_PATH set to point to built WebKit in $productDir.\n";
local %ENV = %ENV;
setupMacWebKitEnvironment($productDir);
if (defined($useOpenCommand) && $useOpenCommand == USE_OPEN_COMMAND) {
return system("open", "-W", "-a", $appPath, commandLineArgumentsForRestrictedEnvironmentVariables("--env"), "--args", argumentsForRunAndDebugMacWebKitApp());
}
if (architecture()) {
return system "arch", "-" . architecture(), commandLineArgumentsForRestrictedEnvironmentVariables("-e"), $appPath, argumentsForRunAndDebugMacWebKitApp();
}
return system { $appPath } $appPath, argumentsForRunAndDebugMacWebKitApp();
}
sub runUnixWebKitApp($)
{
my ($appPath) = @_;
my $productDir = productDir();
print "Starting @{[basename($appPath)]} with built WebKit in $productDir.\n";
local %ENV = %ENV;
setupUnixWebKitEnvironment($productDir);
return system { $appPath } $appPath, @ARGV;
}
sub execMacWebKitAppForDebugging($)
{
my ($appPath) = @_;
my $architectureSwitch = "--arch";
my $argumentsSeparator = "--";
my $debuggerPath = `xcrun -find lldb`;
chomp $debuggerPath;
die "Can't find the lldb executable.\n" unless -x $debuggerPath;
my $productDir = productDir();
setupMacWebKitEnvironment($productDir);
my @architectureFlags = ($architectureSwitch, architecture());
print "Starting @{[basename($appPath)]} under lldb with DYLD_FRAMEWORK_PATH set to point to built WebKit in $productDir.\n";
exec { $debuggerPath } $debuggerPath, @architectureFlags, $argumentsSeparator, $appPath, argumentsForRunAndDebugMacWebKitApp() or die;
}
sub execUnixAppForDebugging($)
{
my ($appPath) = @_;
my $debuggerPath = `which gdb | head -1`;
chomp $debuggerPath;
die "Can't find the gdb executable.\n" unless -x $debuggerPath;
my $productDir = productDir();
setupUnixWebKitEnvironment($productDir);
my @cmdline = wrapperPrefixIfNeeded();
push @cmdline, $debuggerPath, "--args", $appPath;
print "Starting @{[basename($appPath)]} under gdb with built WebKit in $productDir.\n";
exec @cmdline, @ARGV or die;
}
sub debugSafari
{
if (isAppleMacWebKit()) {
checkBuild();
execMacWebKitAppForDebugging(safariPath());
}
return 1; # Unsupported platform; can't debug Safari on this platform.
}
sub runSafari
{
if (isIOSWebKit()) {
return runIOSWebKitApp(mobileSafariBundle());
}
if (isAppleMacWebKit()) {
return runMacWebKitApp(safariPath());
}
return 1; # Unsupported platform; can't run Safari on this platform.
}
sub runMiniBrowser
{
if (isAppleMacWebKit()) {
return runMacWebKitApp(File::Spec->catfile(productDir(), "MiniBrowser.app", "Contents", "MacOS", "MiniBrowser"));
}
if (isIOSWebKit()) {
return runIOSWebKitApp(mobileMiniBrowserBundle());
}
return 1;
}
sub debugMiniBrowser
{
if (isAppleMacWebKit()) {
execMacWebKitAppForDebugging(File::Spec->catfile(productDir(), "MiniBrowser.app", "Contents", "MacOS", "MiniBrowser"));
}
return 1;
}
sub runSwiftBrowser
{
if (isAppleMacWebKit()) {
return runMacWebKitApp(File::Spec->catfile(productDir(), "SwiftBrowser.app", "Contents", "MacOS", "SwiftBrowser"));
}
if (isIOSWebKit()) {
return runIOSWebKitApp(swiftBrowserBundle());
}
return 1;
}
sub debugSwiftBrowser
{
if (isAppleMacWebKit()) {
execMacWebKitAppForDebugging(File::Spec->catfile(productDir(), "SwiftBrowser.app", "Contents", "MacOS", "SwiftBrowser"));
}
return 1;
}
sub runWebKitTestRunner
{
if (isAppleMacWebKit()) {
return runMacWebKitApp(File::Spec->catfile(productDir(), "WebKitTestRunner"));
} elsif (isGtk() or isWPE()) {
return runUnixWebKitApp(File::Spec->catfile(productDir(), "bin", "WebKitTestRunner"));
}
return 1;
}
sub debugWebKitTestRunner
{
if (isAppleMacWebKit()) {
execMacWebKitAppForDebugging(File::Spec->catfile(productDir(), "WebKitTestRunner"));
} elsif (isGtk() or isWPE()) {
execUnixAppForDebugging(File::Spec->catfile(productDir(), "bin", "WebKitTestRunner"));
}
return 1;
}
sub readRegistryString
{
my ($valueName) = @_;
chomp(my $string = `regtool --wow32 get "$valueName"`);
return $string;
}
sub writeRegistryString
{
my ($valueName, $string) = @_;
my $error = system "regtool", "--wow32", "set", "-s", $valueName, $string;
# On Windows Vista/7 with UAC enabled, regtool will fail to modify the registry, but will still
# return a successful exit code. So we double-check here that the value we tried to write to the
# registry was really written.
return !$error && readRegistryString($valueName) eq $string;
}
sub formatBuildTime($)
{
my ($buildTime) = @_;
my $buildHours = int($buildTime / 3600);
my $buildMins = int(($buildTime - $buildHours * 3600) / 60);
my $buildSecs = $buildTime - $buildHours * 3600 - $buildMins * 60;
if ($buildHours) {
return sprintf("%dh:%02dm:%02ds", $buildHours, $buildMins, $buildSecs);
}
return sprintf("%02dm:%02ds", $buildMins, $buildSecs);
}
sub runGitUpdate()
{
# This will die if branch.$BRANCHNAME.merge isn't set, which is
# almost certainly what we want.
system("git", "pull", "--autostash") == 0 or die;
}
1;