/* * 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.NSDictionary; import com.dd.plist.NSObject; import com.dd.plist.PropertyListFormatException; import com.dd.plist.PropertyListParser; import com.facebook.buck.log.Logger; import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableMap; 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.text.ParseException; import java.util.Arrays; import java.util.HashSet; import java.util.Optional; import javax.xml.parsers.ParserConfigurationException; import org.xml.sax.SAXException; /** Utility class to discover the location of toolchains contained inside an Xcode installation. */ public class AppleToolchainDiscovery { private static final Logger LOG = Logger.get(AppleToolchainDiscovery.class); // Utility class; do not instantiate. private AppleToolchainDiscovery() {} /** * Given a path to an Xcode developer directory, walks through the toolchains and builds a map of * (identifier: path) pairs of the toolchains inside. */ public static ImmutableMap<String, AppleToolchain> discoverAppleToolchains( Optional<Path> developerDir, ImmutableList<Path> extraDirs) throws IOException { ImmutableMap.Builder<String, AppleToolchain> toolchainIdentifiersToToolchainsBuilder = ImmutableMap.builder(); HashSet<Path> toolchainPaths = new HashSet<Path>(extraDirs); if (developerDir.isPresent()) { Path toolchainsDir = developerDir.get().resolve("Toolchains"); LOG.debug("Searching for Xcode toolchains under %s", toolchainsDir); toolchainPaths.add(toolchainsDir); } for (Path toolchains : toolchainPaths) { if (!Files.exists(toolchains)) { LOG.debug("Skipping toolchain search path %s that does not exist", toolchains); continue; } LOG.debug("Searching for Xcode toolchains in %s", toolchains); try (DirectoryStream<Path> toolchainStream = Files.newDirectoryStream(toolchains, "*.xctoolchain")) { for (Path toolchainPath : toolchainStream) { LOG.debug("Getting identifier for for Xcode toolchain under %s", toolchainPath); addIdentifierForToolchain(toolchainPath, toolchainIdentifiersToToolchainsBuilder); } } } return toolchainIdentifiersToToolchainsBuilder.build(); } private static void addIdentifierForToolchain( Path toolchainDir, ImmutableMap.Builder<String, AppleToolchain> identifierToToolchainBuilder) throws IOException { boolean addedToolchain = false; String[] potentialPlistNames = {"ToolchainInfo.plist", "Info.plist"}; for (String plistName : potentialPlistNames) { try { Optional<AppleToolchain> toolchain = toolchainFromPlist(toolchainDir, plistName); if (toolchain.isPresent()) { identifierToToolchainBuilder.put(toolchain.get().getIdentifier(), toolchain.get()); addedToolchain = true; break; } } catch (FileNotFoundException | NoSuchFileException e) { LOG.debug("Loading toolchain from plist %s for %s failed", plistName, toolchainDir); } } if (!addedToolchain) { LOG.debug( "Failed to resolve info about toolchain %s from plist files %s", toolchainDir.toString(), Arrays.toString(potentialPlistNames)); } } private static Optional<AppleToolchain> toolchainFromPlist(Path toolchainDir, String plistName) throws IOException { Path toolchainInfoPlistPath = toolchainDir.resolve(plistName); InputStream toolchainInfoPlist = Files.newInputStream(toolchainInfoPlistPath); BufferedInputStream bufferedToolchainInfoPlist = new BufferedInputStream(toolchainInfoPlist); NSDictionary parsedToolchainInfoPlist; try { parsedToolchainInfoPlist = (NSDictionary) PropertyListParser.parse(bufferedToolchainInfoPlist); } catch (PropertyListFormatException | ParseException | ParserConfigurationException | SAXException e) { LOG.error(e, "Failed to parse %s: %s, ignoring", plistName, toolchainInfoPlistPath); return Optional.empty(); } NSObject identifierObject = parsedToolchainInfoPlist.objectForKey("Identifier"); if (identifierObject == null) { LOG.error("Identifier not found for toolchain path %s, ignoring", toolchainDir); return Optional.empty(); } String identifier = identifierObject.toString(); NSObject versionObject = parsedToolchainInfoPlist.objectForKey("DTSDKBuild"); Optional<String> version = versionObject == null ? Optional.empty() : Optional.of(versionObject.toString()); LOG.debug("Mapped SDK identifier %s to path %s", identifier, toolchainDir); AppleToolchain.Builder toolchainBuilder = AppleToolchain.builder(); toolchainBuilder.setIdentifier(identifier); toolchainBuilder.setVersion(version); toolchainBuilder.setPath(toolchainDir); return Optional.of(toolchainBuilder.build()); } }