configure-xcode-for-ios-development does not work with Xcode 7
[WebKit-https.git] / Tools / Scripts / configure-xcode-for-ios-development
1 #!/usr/bin/perl -w
2
3 # Copyright (C) 2014, 2015 Apple Inc. All rights reserved.
4 #
5 # Redistribution and use in source and binary forms, with or without
6 # modification, are permitted provided that the following conditions
7 # are met:
8 # 1.  Redistributions of source code must retain the above copyright
9 #     notice, this list of conditions and the following disclaimer.
10 # 2.  Redistributions in binary form must reproduce the above copyright
11 #     notice, this list of conditions and the following disclaimer in the
12 #     documentation and/or other materials provided with the distribution.
13 #
14 # THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS'' AND ANY
15 # EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
16 # WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
17 # DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS BE LIABLE FOR ANY
18 # DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
19 # (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
20 # LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
21 # ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
22 # (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
23 # SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
24
25 # Checks if Xcode supports building a command line tool for the iOS Simulator.
26 # If not, then updates/creates xcspec files in the iOS SDK for a command line
27 # tool product- and package- type using the definitions in the OS X SDK for the
28 # same types.
29
30 use strict;
31 use warnings;
32
33 use Cwd qw(realpath);
34 use English;
35 use File::Basename;
36 use File::Find;
37 use File::Spec;
38 use File::Temp qw(tempfile);
39 use FindBin;
40 use lib $FindBin::Bin;
41 use webkitdirs;
42
43 sub copyMissingHeadersToIPhoneOSSDKIfNeeded();
44 sub createLegacyXcodeSpecificationFilesForSDKIfNeeded($);
45 sub mergeXcodeSpecificationWithSpecificationAndId($$$);
46 sub readXcodeSpecificationById($$);
47 sub sdkDirectory($);
48 sub sdkPlatformDirectory($);
49 sub updateXcode7SpecificationFile($);
50 sub updateXcodeSpecificationFilesForSDKIfNeeded($);
51 sub xcodeSDKSpecificationsPath($);
52
53 use constant COMMAND_LINE_PACKAGE_TYPE => "com.apple.package-type.mach-o-executable";
54 use constant COMMAND_LINE_PRODUCT_TYPE => "com.apple.product-type.tool";
55 use constant SDK_TO_XCSPEC_NAME_MAP => +{ "iphoneos" => "iPhoneOS", "iphonesimulator" => "iPhone Simulator " };
56 use constant SDK_TO_PLUGIN_XCSPEC_NAME_MAP => +{ "iphoneos" => "Embedded-Device.xcspec", "iphonesimulator" => "Embedded-Simulator.xcspec" };
57
58 # FIXME: We should only require running as root if needed. It's not necessary to run as root if
59 #        Xcode was installed by the user, say via a download from <http://developer.apple.com>.
60 if ($EFFECTIVE_USER_ID) {
61     print STDERR basename($0) . " must be run as root.\n";
62     exit 1;
63 }
64
65 for my $sdk (qw(iphoneos iphonesimulator)) {
66     updateXcodeSpecificationFilesForSDKIfNeeded($sdk);
67 }
68
69 copyMissingHeadersToIPhoneOSSDKIfNeeded();
70
71 exit 0;
72
73 sub copyMissingHeadersToIPhoneOSSDKIfNeeded()
74 {
75     my @missingHeaders = qw(
76         /usr/include/crt_externs.h
77         /usr/include/MacErrors.h
78         /usr/include/mach/mach_types.defs
79         /usr/include/mach/machine/machine_types.defs
80         /usr/include/mach/std_types.defs
81         /usr/include/objc/objc-class.h
82         /usr/include/objc/objc-runtime.h
83         /usr/include/objc/Protocol.h
84         /usr/include/readline/history.h
85         /usr/include/readline/readline.h
86         /usr/include/sqlite3_private.h
87     );
88
89     my $iphoneosSDKDirectory = sdkDirectory("iphoneos");
90     my $iphonesimulatorSDKDirectory = sdkDirectory("iphonesimulator");
91
92     for my $header (@missingHeaders) {
93         my $iphoneosSDKPath = File::Spec->canonpath(File::Spec->catfile($iphoneosSDKDirectory, $header));
94         next if (-f $iphoneosSDKPath);
95
96         my $iphonesimulatorSDKPath = File::Spec->canonpath(File::Spec->catfile($iphonesimulatorSDKDirectory, $header));
97         system("/usr/bin/ditto", $iphonesimulatorSDKPath, $iphoneosSDKPath);
98         die "Could not copy $iphonesimulatorSDKPath to $iphoneosSDKPath: $!" if exitStatus($?);
99         print "Successfully copied $iphonesimulatorSDKPath to $iphoneosSDKPath.\n";
100     }
101 }
102
103 sub updateXcodeSpecificationFilesForSDKIfNeeded($)
104 {
105     my ($sdkName) = @_;
106     my $xcode7SpecificationFile = realpath(File::Spec->catfile(sdkPlatformDirectory($sdkName), "..", "..", "..", "PlugIns", "IDEiOSSupportCore.ideplugin", "Contents", "Resources", SDK_TO_PLUGIN_XCSPEC_NAME_MAP->{$sdkName}));
107     if (-f $xcode7SpecificationFile) {
108         updateXcode7SpecificationFile($xcode7SpecificationFile);
109     } else {
110         createLegacyXcodeSpecificationFilesForSDKIfNeeded($sdkName);
111     }
112 }
113
114 sub updateXcode7SpecificationFile($)
115 {
116     my ($specificationFile) = @_;
117
118     my $hasPackageTypeForCommandLineTool = !!readXcodeSpecificationById($specificationFile, COMMAND_LINE_PACKAGE_TYPE);
119     my $hasProductTypeForCommandLineTool = !!readXcodeSpecificationById($specificationFile, COMMAND_LINE_PRODUCT_TYPE);
120     if ($hasPackageTypeForCommandLineTool && $hasProductTypeForCommandLineTool) {
121         return; # Xcode knows how to build a command line tool for $sdkName.
122     }
123
124     my $macosxSDKSpecificationsPath = xcodeSDKSpecificationsPath("macosx");
125     if (!$hasPackageTypeForCommandLineTool) {
126         my $packageTypesForMacOSXPath = File::Spec->catfile($macosxSDKSpecificationsPath, "MacOSX Package Types.xcspec");
127         mergeXcodeSpecificationWithSpecificationAndId($specificationFile, $packageTypesForMacOSXPath, COMMAND_LINE_PACKAGE_TYPE);
128     }
129
130     if (!$hasProductTypeForCommandLineTool) {
131         my $productTypesForMacOSXPath = File::Spec->catfile($macosxSDKSpecificationsPath, "MacOSX Product Types.xcspec");
132         mergeXcodeSpecificationWithSpecificationAndId($specificationFile, $productTypesForMacOSXPath, COMMAND_LINE_PRODUCT_TYPE);
133     }
134     print "Successfully updated '$specificationFile'.\n";
135 }
136
137 sub createLegacyXcodeSpecificationFilesForSDKIfNeeded($)
138 {
139     my ($sdk) = @_;
140     my $sdkSpecificationsPath = xcodeSDKSpecificationsPath($sdk);
141
142     local @::xcodeSpecificationFiles;
143     sub wanted
144     {
145         my $file = $_;
146
147         # Ignore hidden files/directories.
148         if ($file =~ /^\../) {
149             $File::Find::prune = 1;
150             return;
151         }
152
153         if (!-f $file || $file !~ /\.xcspec$/) {
154             return;
155         }
156
157         push @::xcodeSpecificationFiles, $File::Find::name;
158     }
159
160     find(\&wanted, $sdkSpecificationsPath);
161
162     my $hasPackageTypeForCommandLineTool;
163     my $hasProductTypeForCommandLineTool;
164     foreach my $specificationFile (@::xcodeSpecificationFiles) {
165         last if $hasPackageTypeForCommandLineTool && $hasProductTypeForCommandLineTool;
166         if (!$hasPackageTypeForCommandLineTool && readXcodeSpecificationById($specificationFile, COMMAND_LINE_PACKAGE_TYPE)) {
167             $hasPackageTypeForCommandLineTool = 1;
168             next;
169         }
170         if (!$hasProductTypeForCommandLineTool && readXcodeSpecificationById($specificationFile, COMMAND_LINE_PRODUCT_TYPE)) {
171             $hasProductTypeForCommandLineTool = 1;
172             next;
173         }
174     }
175
176     if ($hasPackageTypeForCommandLineTool && $hasProductTypeForCommandLineTool) {
177         return; # Xcode knows how to build a command line tool for $sdk.
178     }
179
180     my $fileNamePrefix = SDK_TO_XCSPEC_NAME_MAP->{$sdk};
181
182     my $macosxSDKSpecificationsPath = xcodeSDKSpecificationsPath("macosx");
183     if (!$hasPackageTypeForCommandLineTool) {
184         my $packageTypesForMacOSXPath = File::Spec->catfile($macosxSDKSpecificationsPath, "MacOSX Package Types.xcspec");
185         my $packageTypesForWebKitDevelopmentPath = File::Spec->catfile($sdkSpecificationsPath, "${fileNamePrefix}PackageTypes For WebKit Development.xcspec");
186         mergeXcodeSpecificationWithSpecificationAndId($packageTypesForWebKitDevelopmentPath, $packageTypesForMacOSXPath, COMMAND_LINE_PACKAGE_TYPE);
187         print "Successfully created '$packageTypesForWebKitDevelopmentPath'.\n";
188     }
189
190     if (!$hasProductTypeForCommandLineTool) {
191         my $productTypesForMacOSXPath = File::Spec->catfile($macosxSDKSpecificationsPath, "MacOSX Product Types.xcspec");
192         my $productTypesForWebKitDevelopmentPath = File::Spec->catfile($sdkSpecificationsPath, "${fileNamePrefix}ProductTypes For WebKit Development.xcspec");
193         mergeXcodeSpecificationWithSpecificationAndId($productTypesForWebKitDevelopmentPath, $productTypesForMacOSXPath, COMMAND_LINE_PRODUCT_TYPE);
194         print "Successfully created '$productTypesForWebKitDevelopmentPath'.\n";
195     }
196 }
197
198 sub sdkDirectory($)
199 {
200     my ($sdkName) = @_;
201     chomp(my $sdkDirectory = `xcrun --sdk '$sdkName' --show-sdk-path`);
202     die "Failed to get SDK path from xcrun: $!" if exitStatus($?);
203     return $sdkDirectory;
204 }
205
206 sub sdkPlatformDirectory($)
207 {
208     my ($sdkName) = @_;
209     chomp(my $sdkPlatformDirectory = `xcrun --sdk '$sdkName' --show-sdk-platform-path`);
210     die "Failed to get SDK platform path from xcrun: $!" if exitStatus($?);
211     return $sdkPlatformDirectory;
212 }
213
214 sub writeXcodeSpecification($$)
215 {
216     my ($xcodeSpecificationFile, $specification) = @_;
217     my ($tempFileHandle, $tempFilename) = tempfile("webkit-xcspecXXXXXXX", UNLINK => 1);
218     print $tempFileHandle $specification;
219     close($tempFileHandle);
220     if (!-f $xcodeSpecificationFile) {
221         system("/usr/libexec/PlistBuddy -x -c 'clear array' '$xcodeSpecificationFile' > /dev/null") == 0 or die "PlistBuddy exited with $?: $!";
222     }
223     system("/usr/libexec/PlistBuddy -x -c 'add 0 dict' '$xcodeSpecificationFile' > /dev/null") == 0 or die "PlistBuddy exited with $?: $!";
224     system("/usr/libexec/PlistBuddy -x -c 'merge $tempFilename 0' '$xcodeSpecificationFile' > /dev/null") == 0 or die "PlistBuddy exited with $?: $!";
225 }
226
227 sub readXcodeSpecificationById($$)
228 {
229     my ($xcodeSpecificationFile, $id) = @_;
230     open(PLIST_BUDDY, "-|", "/usr/libexec/PlistBuddy", "-x", "-c", "Print", $xcodeSpecificationFile) or die "Failed to run PlistBuddy: $!";
231     my $foundStartOfSpecificationsArray;
232     while (<PLIST_BUDDY>) {
233         if (/^<array>$/) {
234             $foundStartOfSpecificationsArray = 1;
235             last;
236         }
237     }
238     if (!$foundStartOfSpecificationsArray) {
239        return ""; # Not a Xcode specification file.
240     }
241     my $position = -1;
242     my $foundIdentfierKey = 0;
243     my $foundSpecification = 0;
244     while (<PLIST_BUDDY>) {
245         if (/^\s<dict>$/) {
246             ++$position;
247             next;
248         }
249         if (!$foundIdentfierKey && /^\s\s<key>Identifier<\/key>$/) {
250             $foundIdentfierKey = 1;
251             next;
252         }
253         if ($foundIdentfierKey && /^\s\s<string>([^<]+)<\/string>$/) {
254             if ($1 eq $id) {
255                 $foundSpecification = 1;
256                 last;
257             }
258             $foundIdentfierKey = 0;
259             next;
260         }
261     }
262     close(PLIST_BUDDY);
263     if ($foundSpecification && $position >= 0) {
264         chomp(my $result = `/usr/libexec/PlistBuddy -x -c 'Print $position' '$xcodeSpecificationFile'`);
265         die "Failed to run PlistBuddy" if $?;
266         return $result;
267     }
268     return ""; # Did not find id.
269 }
270
271 sub xcodeSDKSpecificationsPath($)
272 {
273     my ($sdkName) = @_;
274
275     return File::Spec->catdir(sdkPlatformDirectory($sdkName), "Developer", "Library", "Xcode", "Specifications");
276 }
277
278 sub mergeXcodeSpecificationWithSpecificationAndId($$$)
279 {
280     my ($targetXcodeSpecificationFile, $sourceXcodeSpecificationFile, $id) = @_;
281     my $specification = readXcodeSpecificationById($sourceXcodeSpecificationFile, $id);
282     if (!$specification) {
283         die "Failed to find '$id' in '$sourceXcodeSpecificationFile'.\n";
284     }
285     writeXcodeSpecification($targetXcodeSpecificationFile, $specification);
286 }