blob: 14acdaaff1847925172dd5dfcf68572c411bbcd7 [file] [log] [blame]
#!/usr/bin/env perl
# Copyright (C) 2014, 2015 Apple Inc. All rights reserved.
#
# 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.
#
# THIS SOFTWARE IS PROVIDED BY APPLE INC. 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 INC. 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.
# Checks if Xcode supports building a command line tool for the iOS Simulator.
# If not, then updates/creates xcspec files in the iOS SDK for a command line
# tool product- and package- type using the definitions in the OS X SDK for the
# same types.
use strict;
use warnings;
use Cwd qw(realpath);
use English;
use File::Basename;
use File::Find;
use File::Spec;
use File::Temp qw(tempfile);
use FindBin;
use lib $FindBin::Bin;
use webkitdirs;
sub copyMissingHeadersFromSDKToSDKIfNeeded($$);
sub copyMissingXSLTHeadersToSDKIfNeeded($);
sub createLegacyXcodeSpecificationFilesForSDKIfNeeded($);
sub mergeXcodeSpecificationWithSpecificationAndId($$$);
sub readXcodeSpecificationById($$);
sub updateXcode7SpecificationFile($);
sub updateXcodeSpecificationFilesForSDKIfNeeded($);
sub xcodeSDKSpecificationsPath($);
use constant COMMAND_LINE_PACKAGE_TYPE => "com.apple.package-type.mach-o-executable";
use constant COMMAND_LINE_PRODUCT_TYPE => "com.apple.product-type.tool";
use constant SDK_TO_XCSPEC_NAME_MAP => +{ "iphoneos" => "iPhoneOS", "iphonesimulator" => "iPhone Simulator " };
use constant SDK_TO_PLUGIN_XCSPEC_NAME_MAP => +{ "iphoneos" => "Embedded-Device.xcspec", "iphonesimulator" => "Embedded-Simulator.xcspec" };
# FIXME: We should only require running as root if needed. It's not necessary to run as root if
# Xcode was installed by the user, say via a download from <http://developer.apple.com>.
if ($EFFECTIVE_USER_ID) {
print STDERR basename($0) . " must be run as root.\n";
exit 1;
}
for my $sdk (qw(iphoneos iphonesimulator)) {
updateXcodeSpecificationFilesForSDKIfNeeded($sdk);
copyMissingXSLTHeadersToSDKIfNeeded($sdk);
}
copyMissingHeadersFromSDKToSDKIfNeeded("macosx", "iphonesimulator");
copyMissingHeadersFromSDKToSDKIfNeeded("iphonesimulator", "iphoneos");
exit 0;
sub copyMissingHeadersFromSDKToSDKIfNeeded($$)
{
my @missingHeaders = qw(
/usr/include/crt_externs.h
/usr/include/MacErrors.h
/usr/include/mach/mach_types.defs
/usr/include/mach/machine/machine_types.defs
/usr/include/mach/std_types.defs
/usr/include/objc/objc-class.h
/usr/include/objc/objc-runtime.h
/usr/include/objc/Protocol.h
/usr/include/readline/history.h
/usr/include/readline/readline.h
/usr/include/sqlite3_private.h
);
my ($sourceSDK, $destinationSDK) = @_;
my $sourceDirectory = sdkDirectory($sourceSDK);
my $destinationDirectory = sdkDirectory($destinationSDK);
for my $header (@missingHeaders) {
my $sourcePath = File::Spec->canonpath(File::Spec->catfile($sourceDirectory, $header));
my $destinationPath = File::Spec->canonpath(File::Spec->catfile($destinationDirectory, $header));
next if (-f $destinationPath);
next if (!-f $sourcePath);
system("/usr/bin/ditto", $sourcePath, $destinationPath);
die "Could not copy $sourcePath to $destinationPath: $!" if exitStatus($?);
print "Successfully copied $sourcePath to $destinationPath.\n";
}
}
sub copyMissingXSLTHeadersToSDKIfNeeded($)
{
my ($sdkName) = @_;
my $sdkXSLTHeaderDirectory = File::Spec->canonpath(File::Spec->catdir(sdkDirectory($sdkName), "usr", "include", "libxslt"));
return if -d $sdkXSLTHeaderDirectory;
my $macosxXSLTHeaderDirectory = File::Spec->canonpath(File::Spec->catdir(sdkDirectory("macosx"), "usr", "include", "libxslt"));
system("/usr/bin/ditto", $macosxXSLTHeaderDirectory, $sdkXSLTHeaderDirectory);
die "Could not copy $macosxXSLTHeaderDirectory to $sdkXSLTHeaderDirectory: $!" if exitStatus($?);
print "Successfully copied $macosxXSLTHeaderDirectory to $sdkXSLTHeaderDirectory.\n";
}
sub updateXcodeSpecificationFilesForSDKIfNeeded($)
{
my ($sdkName) = @_;
my $xcode7SpecificationFile = realpath(File::Spec->catfile(sdkPlatformDirectory($sdkName), "..", "..", "..", "PlugIns", "IDEiOSSupportCore.ideplugin", "Contents", "Resources", SDK_TO_PLUGIN_XCSPEC_NAME_MAP->{$sdkName}));
if (-f $xcode7SpecificationFile) {
updateXcode7SpecificationFile($xcode7SpecificationFile);
} else {
createLegacyXcodeSpecificationFilesForSDKIfNeeded($sdkName);
}
}
sub updateXcode7SpecificationFile($)
{
my ($specificationFile) = @_;
my $hasPackageTypeForCommandLineTool = !!readXcodeSpecificationById($specificationFile, COMMAND_LINE_PACKAGE_TYPE);
my $hasProductTypeForCommandLineTool = !!readXcodeSpecificationById($specificationFile, COMMAND_LINE_PRODUCT_TYPE);
if ($hasPackageTypeForCommandLineTool && $hasProductTypeForCommandLineTool) {
return; # Xcode knows how to build a command line tool for $sdkName.
}
my $macosxSDKSpecificationsPath = xcodeSDKSpecificationsPath("macosx");
if (!$hasPackageTypeForCommandLineTool) {
my $packageTypesForMacOSXPath = File::Spec->catfile($macosxSDKSpecificationsPath, "MacOSX Package Types.xcspec");
mergeXcodeSpecificationWithSpecificationAndId($specificationFile, $packageTypesForMacOSXPath, COMMAND_LINE_PACKAGE_TYPE);
}
if (!$hasProductTypeForCommandLineTool) {
my $productTypesForMacOSXPath = File::Spec->catfile($macosxSDKSpecificationsPath, "MacOSX Product Types.xcspec");
mergeXcodeSpecificationWithSpecificationAndId($specificationFile, $productTypesForMacOSXPath, COMMAND_LINE_PRODUCT_TYPE);
}
print "Successfully updated '$specificationFile'.\n";
}
sub createLegacyXcodeSpecificationFilesForSDKIfNeeded($)
{
my ($sdk) = @_;
my $sdkSpecificationsPath = xcodeSDKSpecificationsPath($sdk);
local @::xcodeSpecificationFiles;
sub wanted
{
my $file = $_;
# Ignore hidden files/directories.
if ($file =~ /^\../) {
$File::Find::prune = 1;
return;
}
if (!-f $file || $file !~ /\.xcspec$/) {
return;
}
push @::xcodeSpecificationFiles, $File::Find::name;
}
find(\&wanted, $sdkSpecificationsPath);
my $hasPackageTypeForCommandLineTool;
my $hasProductTypeForCommandLineTool;
foreach my $specificationFile (@::xcodeSpecificationFiles) {
last if $hasPackageTypeForCommandLineTool && $hasProductTypeForCommandLineTool;
if (!$hasPackageTypeForCommandLineTool && readXcodeSpecificationById($specificationFile, COMMAND_LINE_PACKAGE_TYPE)) {
$hasPackageTypeForCommandLineTool = 1;
next;
}
if (!$hasProductTypeForCommandLineTool && readXcodeSpecificationById($specificationFile, COMMAND_LINE_PRODUCT_TYPE)) {
$hasProductTypeForCommandLineTool = 1;
next;
}
}
if ($hasPackageTypeForCommandLineTool && $hasProductTypeForCommandLineTool) {
return; # Xcode knows how to build a command line tool for $sdk.
}
my $fileNamePrefix = SDK_TO_XCSPEC_NAME_MAP->{$sdk};
my $macosxSDKSpecificationsPath = xcodeSDKSpecificationsPath("macosx");
if (!$hasPackageTypeForCommandLineTool) {
my $packageTypesForMacOSXPath = File::Spec->catfile($macosxSDKSpecificationsPath, "MacOSX Package Types.xcspec");
my $packageTypesForWebKitDevelopmentPath = File::Spec->catfile($sdkSpecificationsPath, "${fileNamePrefix}PackageTypes For WebKit Development.xcspec");
mergeXcodeSpecificationWithSpecificationAndId($packageTypesForWebKitDevelopmentPath, $packageTypesForMacOSXPath, COMMAND_LINE_PACKAGE_TYPE);
print "Successfully created '$packageTypesForWebKitDevelopmentPath'.\n";
}
if (!$hasProductTypeForCommandLineTool) {
my $productTypesForMacOSXPath = File::Spec->catfile($macosxSDKSpecificationsPath, "MacOSX Product Types.xcspec");
my $productTypesForWebKitDevelopmentPath = File::Spec->catfile($sdkSpecificationsPath, "${fileNamePrefix}ProductTypes For WebKit Development.xcspec");
mergeXcodeSpecificationWithSpecificationAndId($productTypesForWebKitDevelopmentPath, $productTypesForMacOSXPath, COMMAND_LINE_PRODUCT_TYPE);
print "Successfully created '$productTypesForWebKitDevelopmentPath'.\n";
}
}
sub writeXcodeSpecification($$)
{
my ($xcodeSpecificationFile, $specification) = @_;
my ($tempFileHandle, $tempFilename) = tempfile("webkit-xcspecXXXXXXX", UNLINK => 1);
print $tempFileHandle $specification;
close($tempFileHandle);
if (!-f $xcodeSpecificationFile) {
system("/usr/libexec/PlistBuddy -x -c 'clear array' '$xcodeSpecificationFile' > /dev/null") == 0 or die "PlistBuddy exited with $?: $!";
}
system("/usr/libexec/PlistBuddy -x -c 'add 0 dict' '$xcodeSpecificationFile' > /dev/null") == 0 or die "PlistBuddy exited with $?: $!";
system("/usr/libexec/PlistBuddy -x -c 'merge $tempFilename 0' '$xcodeSpecificationFile' > /dev/null") == 0 or die "PlistBuddy exited with $?: $!";
}
sub readXcodeSpecificationById($$)
{
my ($xcodeSpecificationFile, $id) = @_;
open(PLIST_BUDDY, "-|", "/usr/libexec/PlistBuddy", "-x", "-c", "Print", $xcodeSpecificationFile) or die "Failed to run PlistBuddy: $!";
my $foundStartOfSpecificationsArray;
while (<PLIST_BUDDY>) {
if (/^<array>$/) {
$foundStartOfSpecificationsArray = 1;
last;
}
}
if (!$foundStartOfSpecificationsArray) {
return ""; # Not a Xcode specification file.
}
my $position = -1;
my $foundIdentfierKey = 0;
my $foundSpecification = 0;
while (<PLIST_BUDDY>) {
if (/^\s<dict>$/) {
++$position;
next;
}
if (!$foundIdentfierKey && /^\s\s<key>Identifier<\/key>$/) {
$foundIdentfierKey = 1;
next;
}
if ($foundIdentfierKey && /^\s\s<string>([^<]+)<\/string>$/) {
if ($1 eq $id) {
$foundSpecification = 1;
last;
}
$foundIdentfierKey = 0;
next;
}
}
close(PLIST_BUDDY);
if ($foundSpecification && $position >= 0) {
chomp(my $result = `/usr/libexec/PlistBuddy -x -c 'Print $position' '$xcodeSpecificationFile'`);
die "Failed to run PlistBuddy" if $?;
return $result;
}
return ""; # Did not find id.
}
sub xcodeSDKSpecificationsPath($)
{
my ($sdkName) = @_;
return File::Spec->catdir(sdkPlatformDirectory($sdkName), "Developer", "Library", "Xcode", "Specifications");
}
sub mergeXcodeSpecificationWithSpecificationAndId($$$)
{
my ($targetXcodeSpecificationFile, $sourceXcodeSpecificationFile, $id) = @_;
my $specification = readXcodeSpecificationById($sourceXcodeSpecificationFile, $id);
if (!$specification) {
die "Failed to find '$id' in '$sourceXcodeSpecificationFile'.\n";
}
writeXcodeSpecification($targetXcodeSpecificationFile, $specification);
}