| #!/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 creates xcspec files in the iOS Simulator 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 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 createXcodeSpecificationFilesForSDKIfNeeded($); |
| sub createXcodeSpecificationFromSpecificationAndId($$$); |
| sub sdkDirectory($); |
| sub sdkPlatformDirectory($); |
| sub readXcodeSpecificationById($$); |
| 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 " }; |
| |
| # 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)) { |
| createXcodeSpecificationFilesForSDKIfNeeded($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 createXcodeSpecificationFilesForSDKIfNeeded($) |
| { |
| 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"); |
| createXcodeSpecificationFromSpecificationAndId($packageTypesForWebKitDevelopmentPath, $packageTypesForMacOSXPath, COMMAND_LINE_PACKAGE_TYPE); |
| } |
| |
| if (!$hasProductTypeForCommandLineTool) { |
| my $productTypesForMacOSXPath = File::Spec->catfile($macosxSDKSpecificationsPath, "MacOSX Product Types.xcspec"); |
| my $productTypesForWebKitDevelopmentPath = File::Spec->catfile($sdkSpecificationsPath, "${fileNamePrefix}ProductTypes For WebKit Development.xcspec"); |
| createXcodeSpecificationFromSpecificationAndId($productTypesForWebKitDevelopmentPath, $productTypesForMacOSXPath, COMMAND_LINE_PRODUCT_TYPE); |
| } |
| } |
| |
| 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); |
| 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 createXcodeSpecificationFromSpecificationAndId($$$) |
| { |
| my ($targetXcodeSpecificationFile, $sourceXcodeSpecificationFile, $id) = @_; |
| my $specification = readXcodeSpecificationById($sourceXcodeSpecificationFile, $id); |
| if (!$specification) { |
| die "Failed to find '$id' in '$sourceXcodeSpecificationFile'.\n"; |
| } |
| writeXcodeSpecification($targetXcodeSpecificationFile, $specification); |
| print "Successfully created '$targetXcodeSpecificationFile'.\n"; |
| } |