blob: c292ded030175c844b13d75be1f71aca41abdfa9 [file] [log] [blame]
/*
* Copyright 2014-present Facebook, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may
* not use this file except in compliance with the License. You may obtain
* a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*/
package com.facebook.buck.apple;
import com.dd.plist.NSArray;
import com.dd.plist.NSDictionary;
import com.dd.plist.NSObject;
import com.dd.plist.NSString;
import com.dd.plist.PropertyListParser;
import com.facebook.buck.log.Logger;
import com.facebook.buck.util.VersionStringComparator;
import com.google.common.base.Function;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Ordering;
import com.google.common.collect.TreeMultimap;
import java.io.BufferedInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.nio.file.DirectoryStream;
import java.nio.file.Files;
import java.nio.file.NoSuchFileException;
import java.nio.file.Path;
import java.util.Locale;
/**
* Utility class to discover the location of SDKs contained inside an Xcode
* installation.
*/
public class AppleSdkDiscovery {
private static final Logger LOG = Logger.get(AppleSdkDiscovery.class);
private static final Ordering<AppleSdk> APPLE_SDK_VERSION_ORDERING =
Ordering
.from(new VersionStringComparator())
.onResultOf(new Function<AppleSdk, String>() {
@Override
public String apply(AppleSdk appleSdk) {
return appleSdk.getVersion();
}
});
private static final String DEFAULT_TOOLCHAIN_ID = "com.apple.dt.toolchain.XcodeDefault";
// Utility class; do not instantiate.
private AppleSdkDiscovery() { }
/**
* Given a path to an Xcode developer directory and a map of
* (xctoolchain ID: path) pairs as returned by
* {@link AppleToolchainDiscovery}, walks through the platforms
* and builds a map of ({@link AppleSdk}: {@link AppleSdkPaths})
* objects describing the paths to the SDKs inside.
*
* The {@link AppleSdk#getName()} strings match the ones displayed by {@code xcodebuild -showsdks}
* and look like {@code macosx10.9}, {@code iphoneos8.0}, {@code iphonesimulator8.0},
* etc.
*/
public static ImmutableMap<AppleSdk, AppleSdkPaths> discoverAppleSdkPaths(
Path xcodeDir,
ImmutableMap<String, Path> xcodeToolchainPaths)
throws IOException {
Path defaultToolchainPath = xcodeToolchainPaths.get(DEFAULT_TOOLCHAIN_ID);
if (defaultToolchainPath == null) {
LOG.debug("Could not find default toolchain %s; skipping discovery.", DEFAULT_TOOLCHAIN_ID);
return ImmutableMap.of();
}
LOG.debug("Searching for Xcode platforms under %s", xcodeDir);
ImmutableMap.Builder<AppleSdk, AppleSdkPaths> appleSdkPathsBuilder = ImmutableMap.builder();
Path platforms = xcodeDir.resolve("Platforms");
if (!Files.exists(platforms)) {
return appleSdkPathsBuilder.build();
}
// We need to find the most recent SDK for each platform so we can
// make the fall-back SDKs with no version number in their name
// ("macosx", "iphonesimulator", "iphoneos").
//
// To do this, we store a map of (platform: [sdk1, sdk2, ...])
// pairs where the SDKs for each platform are ordered by version.
TreeMultimap<ApplePlatform, ImmutableAppleSdk> orderedSdksForPlatform =
TreeMultimap.create(
Ordering.natural(),
APPLE_SDK_VERSION_ORDERING);
try (DirectoryStream<Path> platformStream = Files.newDirectoryStream(
platforms,
"*.platform")) {
for (Path platformDir : platformStream) {
LOG.debug("Searching for Xcode SDKs under %s", platformDir);
Path developerSdksPath = platformDir.resolve("Developer/SDKs");
try (DirectoryStream<Path> sdkStream = Files.newDirectoryStream(
developerSdksPath,
"*.sdk")) {
for (Path sdkDir : sdkStream) {
LOG.debug("Fetching SDK name for %s", sdkDir);
if (Files.isSymbolicLink(sdkDir)) {
continue;
}
ImmutableAppleSdk.Builder sdkBuilder = ImmutableAppleSdk.builder();
if (buildSdkFromPath(sdkDir, sdkBuilder)) {
ImmutableAppleSdk sdk = sdkBuilder.build();
LOG.debug("Found SDK %s", sdk);
ImmutableSet.Builder<Path> toolchainPathsBuilder = ImmutableSet.builder();
for (String toolchain : sdk.getToolchains()) {
Path toolchainPath = xcodeToolchainPaths.get(toolchain);
if (toolchainPath == null) {
LOG.debug("Could not find toolchain with ID %s, ignoring", toolchain);
} else {
toolchainPathsBuilder.add(toolchainPath);
}
}
ImmutableSet<Path> toolchainPaths = toolchainPathsBuilder.build();
ImmutableAppleSdkPaths.Builder xcodePathsBuilder = ImmutableAppleSdkPaths.builder();
if (toolchainPaths.isEmpty()) {
LOG.debug(
"No toolchains found for SDK %s, falling back to default %s",
sdk,
defaultToolchainPath);
xcodePathsBuilder.addToolchainPaths(defaultToolchainPath);
} else {
xcodePathsBuilder.addAllToolchainPaths(toolchainPaths);
}
ImmutableAppleSdkPaths xcodePaths = xcodePathsBuilder
.setDeveloperPath(xcodeDir)
.setPlatformDeveloperPath(platformDir.resolve("Developer"))
.setSdkPath(sdkDir)
.build();
appleSdkPathsBuilder.put(sdk, xcodePaths);
orderedSdksForPlatform.put(sdk.getApplePlatform(), sdk);
}
}
} catch (NoSuchFileException e) {
LOG.warn(
e,
"Couldn't discover SDKs at path %s, ignoring platform %s",
developerSdksPath,
platformDir);
}
}
}
// Get a snapshot of what's in appleSdkPathsBuilder, then for each
// ApplePlatform, add to appleSdkPathsBuilder the most recent
// SDK with an unversioned name.
ImmutableMap<AppleSdk, AppleSdkPaths> discoveredSdkPaths = appleSdkPathsBuilder.build();
for (ApplePlatform platform : orderedSdksForPlatform.keySet()) {
ImmutableAppleSdk mostRecentSdkForPlatform = orderedSdksForPlatform.get(platform).last();
appleSdkPathsBuilder.put(
mostRecentSdkForPlatform.withName(platform.toString()),
discoveredSdkPaths.get(mostRecentSdkForPlatform));
}
// This includes both the discovered SDKs with versions in their names, as well as
// the unversioned aliases added just above.
return appleSdkPathsBuilder.build();
}
private static void addArchitecturesForPlatform(
ImmutableAppleSdk.Builder sdkBuilder,
ApplePlatform applePlatform) {
// TODO(user): These need to be read from the SDK, not hard-coded.
switch (applePlatform) {
case MACOSX:
// Fall through.
case IPHONESIMULATOR:
sdkBuilder.addArchitectures("i386", "x86_64");
break;
case IPHONEOS:
sdkBuilder.addArchitectures("armv7", "arm64");
break;
}
}
private static boolean buildSdkFromPath(
Path sdkDir,
ImmutableAppleSdk.Builder sdkBuilder) throws IOException {
try (InputStream sdkSettingsPlist = Files.newInputStream(sdkDir.resolve("SDKSettings.plist"));
BufferedInputStream bufferedSdkSettingsPlist = new BufferedInputStream(sdkSettingsPlist)) {
NSDictionary sdkSettings;
try {
sdkSettings = (NSDictionary) PropertyListParser.parse(bufferedSdkSettingsPlist);
} catch (Exception e) {
throw new IOException(e);
}
String name = sdkSettings.objectForKey("CanonicalName").toString();
String version = sdkSettings.objectForKey("Version").toString();
NSDictionary defaultProperties = (NSDictionary) sdkSettings.objectForKey("DefaultProperties");
NSArray toolchains = (NSArray) sdkSettings.objectForKey("Toolchains");
if (toolchains != null) {
for (NSObject toolchain : toolchains.getArray()) {
String toolchainId = toolchain.toString();
sdkBuilder.addToolchains(toolchainId);
}
}
NSString platformName = (NSString) defaultProperties.objectForKey("PLATFORM_NAME");
// TODO(grp): Generalize this to handle new platforms as they are added.
ApplePlatform applePlatform;
try {
applePlatform = ApplePlatform.valueOf(platformName.toString().toUpperCase(Locale.US));
sdkBuilder.setName(name).setVersion(version).setApplePlatform(applePlatform);
addArchitecturesForPlatform(sdkBuilder, applePlatform);
return true;
} catch (IllegalArgumentException e) {
LOG.debug(e, "Ignoring SDK at %s with unrecognized platform %s", sdkDir, platformName);
return false;
}
} catch (FileNotFoundException e) {
LOG.error(e, "No SDKSettings.plist found under SDK path %s", sdkDir);
return false;
}
}
}