| #!/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 <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); | |
| } | |
| 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 (<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); | |
| } |