#!/usr/bin/perl -w # 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 copyMissingHeadersToIPhoneOSSDKIfNeeded(); sub copyMissingXSLTHeadersToSDKIfNeeded($); sub createLegacyXcodeSpecificationFilesForSDKIfNeeded($); sub mergeXcodeSpecificationWithSpecificationAndId($$$); sub readXcodeSpecificationById($$); sub sdkDirectory($); sub sdkPlatformDirectory($); 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 . 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); } copyMissingHeadersToIPhoneOSSDKIfNeeded(); exit 0; sub copyMissingHeadersToIPhoneOSSDKIfNeeded() { 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 $iphoneosSDKDirectory = sdkDirectory("iphoneos"); my $iphonesimulatorSDKDirectory = sdkDirectory("iphonesimulator"); for my $header (@missingHeaders) { my $iphoneosSDKPath = File::Spec->canonpath(File::Spec->catfile($iphoneosSDKDirectory, $header)); next if (-f $iphoneosSDKPath); my $iphonesimulatorSDKPath = File::Spec->canonpath(File::Spec->catfile($iphonesimulatorSDKDirectory, $header)); system("/usr/bin/ditto", $iphonesimulatorSDKPath, $iphoneosSDKPath); die "Could not copy $iphonesimulatorSDKPath to $iphoneosSDKPath: $!" if exitStatus($?); print "Successfully copied $iphonesimulatorSDKPath to $iphoneosSDKPath.\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 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 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 () { if (/^$/) { $foundStartOfSpecificationsArray = 1; last; } } if (!$foundStartOfSpecificationsArray) { return ""; # Not a Xcode specification file. } my $position = -1; my $foundIdentfierKey = 0; my $foundSpecification = 0; while () { if (/^\s$/) { ++$position; next; } if (!$foundIdentfierKey && /^\s\sIdentifier<\/key>$/) { $foundIdentfierKey = 1; next; } if ($foundIdentfierKey && /^\s\s([^<]+)<\/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); }