blob: 4e44da8febc3e970fecb311be306f5b3c8dcb49e [file] [edit]
//===--- DarwinSDKInfo.cpp - SDK Information parser for darwin - ----------===//
//
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
// See https://llvm.org/LICENSE.txt for license information.
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
//
//===----------------------------------------------------------------------===//
#include "clang/Basic/DarwinSDKInfo.h"
#include "llvm/ADT/StringSwitch.h"
#include "llvm/Support/ErrorOr.h"
#include "llvm/Support/JSON.h"
#include "llvm/Support/MemoryBuffer.h"
#include "llvm/Support/Path.h"
#include "llvm/TargetParser/ARMTargetParser.h"
#include <optional>
using namespace clang;
std::optional<VersionTuple> DarwinSDKInfo::RelatedTargetVersionMapping::map(
const VersionTuple &Key, const VersionTuple &MinimumValue,
std::optional<VersionTuple> MaximumValue) const {
if (Key < MinimumKeyVersion)
return MinimumValue;
if (Key > MaximumKeyVersion)
return MaximumValue;
auto KV = Mapping.find(Key.normalize());
if (KV != Mapping.end())
return KV->getSecond();
// If no exact entry found, try just the major key version. Only do so when
// a minor version number is present, to avoid recursing indefinitely into
// the major-only check.
if (Key.getMinor())
return map(VersionTuple(Key.getMajor()), MinimumValue, MaximumValue);
// If this a major only key, return std::nullopt for a missing entry.
return std::nullopt;
}
std::optional<DarwinSDKInfo::RelatedTargetVersionMapping>
DarwinSDKInfo::RelatedTargetVersionMapping::parseJSON(
const llvm::json::Object &Obj, VersionTuple MaximumDeploymentTarget) {
VersionTuple Min = VersionTuple(std::numeric_limits<unsigned>::max());
VersionTuple Max = VersionTuple(0);
VersionTuple MinValue = Min;
llvm::DenseMap<VersionTuple, VersionTuple> Mapping;
for (const auto &KV : Obj) {
if (auto Val = KV.getSecond().getAsString()) {
llvm::VersionTuple KeyVersion;
llvm::VersionTuple ValueVersion;
if (KeyVersion.tryParse(KV.getFirst()) || ValueVersion.tryParse(*Val))
return std::nullopt;
Mapping[KeyVersion.normalize()] = ValueVersion;
if (KeyVersion < Min)
Min = KeyVersion;
if (KeyVersion > Max)
Max = KeyVersion;
if (ValueVersion < MinValue)
MinValue = ValueVersion;
}
}
if (Mapping.empty())
return std::nullopt;
return RelatedTargetVersionMapping(
Min, Max, MinValue, MaximumDeploymentTarget, std::move(Mapping));
}
static std::optional<StringRef>
parseXcodePlatform(const llvm::json::Object &Obj) {
// The CanonicalName is the Xcode platform followed by a version, e.g.
// macosx15.0.
auto CanonicalName = Obj.getString("CanonicalName");
if (!CanonicalName)
return std::nullopt;
size_t VersionStart = CanonicalName->find_first_of("0123456789");
return CanonicalName->slice(0, VersionStart);
}
static std::pair<llvm::Triple::OSType, llvm::Triple::EnvironmentType>
parseOSAndEnvironment(std::optional<StringRef> XcodePlatform) {
if (!XcodePlatform)
return {llvm::Triple::UnknownOS, llvm::Triple::UnknownEnvironment};
llvm::Triple::OSType OS =
llvm::StringSwitch<llvm::Triple::OSType>(*XcodePlatform)
.Case("macosx", llvm::Triple::MacOSX)
.Cases({"iphoneos", "iphonesimulator"}, llvm::Triple::IOS)
.Cases({"appletvos", "appletvsimulator"}, llvm::Triple::TvOS)
.Cases({"watchos", "watchsimulator"}, llvm::Triple::WatchOS)
.Case("bridgeos", llvm::Triple::BridgeOS)
.Cases({"xros", "xrsimulator"}, llvm::Triple::XROS)
.Case("driverkit", llvm::Triple::DriverKit)
.Default(llvm::Triple::UnknownOS);
llvm::Triple::EnvironmentType Environment =
llvm::StringSwitch<llvm::Triple::EnvironmentType>(*XcodePlatform)
.Cases({"iphonesimulator", "appletvsimulator", "watchsimulator",
"xrsimulator"},
llvm::Triple::Simulator)
.Default(llvm::Triple::UnknownEnvironment);
return {OS, Environment};
}
static DarwinSDKInfo::PlatformInfoStorageType
legacyPlatformInfos(llvm::Triple::OSType SDKOS,
llvm::Triple::EnvironmentType SDKEnvironment) {
DarwinSDKInfo::PlatformInfoStorageType PlatformInfos;
// Synthesize platform infos for older SDKs from the first SDKs with
// SupportedTargets: macOS 10.15 (DriverKit 19.0), iOS 13.0, tvOS 13.0,
// watchOS 6.0. Older macOS SDKs supported ppc and i386 architectures, and
// maybe ppc64. Support for those is difficult to identify from the SDK, and
// so aren't listed here. Other older SDKs (especially iOS) most likely
// supported armv6 and armv7 architectures that aren't listed here either.
switch (SDKOS) {
case llvm::Triple::MacOSX:
PlatformInfos.push_back({{llvm::Triple("x86_64-apple-macosx")}, ""});
// macOS 10.15 also has a Mac Catalyst supported target, but Mac Catalyst
// was new in that version so omit it for older versions.
break;
case llvm::Triple::DriverKit:
// DriverKit 19.0 only had SDKSettings.plist, which isn't used. So this code
// path is used in 19.x as well, not just earlier versions.
PlatformInfos.push_back(
{{llvm::Triple("x86_64-apple-driverkit")}, "/System/DriverKit"});
break;
case llvm::Triple::IOS:
switch (SDKEnvironment) {
case llvm::Triple::UnknownEnvironment:
PlatformInfos.push_back(
{{llvm::Triple("armv7-apple-ios"), llvm::Triple("armv7s-apple-ios"),
llvm::Triple("arm64-apple-ios")},
""});
break;
case llvm::Triple::Simulator:
PlatformInfos.push_back(
{{llvm::Triple("x86_64-apple-ios-simulator")}, ""});
break;
default:
break;
}
break;
case llvm::Triple::TvOS:
switch (SDKEnvironment) {
case llvm::Triple::UnknownEnvironment:
PlatformInfos.push_back({{llvm::Triple("arm64-apple-tvos")}, ""});
break;
case llvm::Triple::Simulator:
PlatformInfos.push_back(
{{llvm::Triple("x86_64-apple-tvos-simulator")}, ""});
break;
default:
break;
}
break;
case llvm::Triple::WatchOS:
switch (SDKEnvironment) {
case llvm::Triple::UnknownEnvironment:
PlatformInfos.push_back({{llvm::Triple("armv7k-apple-watchos"),
llvm::Triple("arm64_32-apple-watchos")},
""});
break;
case llvm::Triple::Simulator:
PlatformInfos.push_back(
{{llvm::Triple("x86_64-apple-watchos-simulator")}, ""});
break;
default:
break;
}
break;
case llvm::Triple::BridgeOS:
PlatformInfos.push_back({{llvm::Triple("armv7-apple-bridgeos"),
llvm::Triple("armv7s-apple-bridgeos"),
llvm::Triple("arm64-apple-bridgeos")},
""});
break;
default:
break;
}
return PlatformInfos;
}
static DarwinSDKInfo::PlatformInfoStorageType parsePlatformInfos(
const llvm::json::Object &Obj, std::optional<StringRef> XcodePlatform,
llvm::Triple::OSType SDKOS, llvm::Triple::EnvironmentType SDKEnvironment,
VersionTuple Version) {
DarwinSDKInfo::PlatformInfoStorageType PlatformInfos;
auto SupportedTargets = Obj.getObject("SupportedTargets");
if (!SupportedTargets)
return legacyPlatformInfos(SDKOS, SDKEnvironment);
for (const auto &SupportedTargetPair : *SupportedTargets) {
const llvm::json::Object *SupportedTarget =
SupportedTargetPair.getSecond().getAsObject();
if (!SupportedTarget)
continue;
auto Archs = SupportedTarget->getArray("Archs");
auto Vendor = SupportedTarget->getString("LLVMTargetTripleVendor");
auto OS = SupportedTarget->getString("LLVMTargetTripleSys");
if (!Archs || !Vendor || !OS)
continue;
DarwinSDKInfo::SDKPlatformInfo::TripleStorageType Triples;
auto Environment =
SupportedTarget->getString("LLVMTargetTripleEnvironment");
for (const auto &ArchValue : *Archs) {
if (auto Arch = ArchValue.getAsString()) {
if (Environment && !Environment->empty())
Triples.emplace_back(*Arch, *Vendor, *OS, *Environment);
else
Triples.emplace_back(*Arch, *Vendor, *OS);
}
}
if (Triples.empty())
continue;
// The key is either the Xcode platform, or a variant. The platform must be
// the first entry in the returned PlatformInfoStorageType.
StringRef PlatformOrVariant = SupportedTargetPair.getFirst();
StringRef EffectivePlatformPrefix;
// Ignore iosmac value if it exists.
if ((PlatformOrVariant != "iosmac") || (Version >= VersionTuple(99))) {
auto PlatformPrefix = SupportedTarget->getString("SystemPrefix");
if (PlatformPrefix) {
EffectivePlatformPrefix = *PlatformPrefix;
} else {
// Older SDKs don't have SystemPrefix in SupportedTargets, manually add
// their prefixes.
if ((Triples[0].getOS() == llvm::Triple::DriverKit) &&
(Version < VersionTuple(22, 1)))
EffectivePlatformPrefix = "/System/DriverKit";
}
}
if (PlatformOrVariant == XcodePlatform)
PlatformInfos.insert(PlatformInfos.begin(),
{std::move(Triples), EffectivePlatformPrefix});
else
PlatformInfos.emplace_back(std::move(Triples), EffectivePlatformPrefix);
}
return PlatformInfos;
}
static std::optional<VersionTuple> getVersionKey(const llvm::json::Object &Obj,
StringRef Key) {
auto Value = Obj.getString(Key);
if (!Value)
return std::nullopt;
VersionTuple Version;
if (Version.tryParse(*Value))
return std::nullopt;
return Version;
}
std::optional<DarwinSDKInfo>
DarwinSDKInfo::parseDarwinSDKSettingsJSON(std::string FilePath,
const llvm::json::Object *Obj) {
auto Version = getVersionKey(*Obj, "Version");
if (!Version)
return std::nullopt;
auto MaximumDeploymentVersion =
getVersionKey(*Obj, "MaximumDeploymentTarget");
if (!MaximumDeploymentVersion)
return std::nullopt;
std::optional<StringRef> XcodePlatform = parseXcodePlatform(*Obj);
std::pair<llvm::Triple::OSType, llvm::Triple::EnvironmentType>
OSAndEnvironment = parseOSAndEnvironment(XcodePlatform);
// DisplayName should always be present, but don't require it.
StringRef DisplayName =
Obj->getString("DisplayName")
.value_or(Obj->getString("CanonicalName").value_or("<unknown>"));
PlatformInfoStorageType PlatformInfos =
parsePlatformInfos(*Obj, XcodePlatform, OSAndEnvironment.first,
OSAndEnvironment.second, *Version);
if (PlatformInfos.empty())
return std::nullopt;
llvm::DenseMap<OSEnvPair::StorageType,
std::optional<RelatedTargetVersionMapping>>
VersionMappings;
if (const auto *VM = Obj->getObject("VersionMap")) {
// FIXME: Generalize this out beyond iOS-deriving targets.
// Look for ios_<targetos> version mapping for targets that derive from ios.
for (const auto &KV : *VM) {
auto Pair = StringRef(KV.getFirst()).split("_");
if (Pair.first.compare_insensitive("ios") == 0) {
llvm::Triple TT(llvm::Twine("--") + Pair.second.lower());
if (TT.getOS() != llvm::Triple::UnknownOS) {
auto Mapping = RelatedTargetVersionMapping::parseJSON(
*KV.getSecond().getAsObject(), *MaximumDeploymentVersion);
if (Mapping)
VersionMappings[OSEnvPair(llvm::Triple::IOS,
llvm::Triple::UnknownEnvironment,
TT.getOS(),
llvm::Triple::UnknownEnvironment)
.Value] = std::move(Mapping);
}
}
}
if (const auto *Mapping = VM->getObject("macOS_iOSMac")) {
auto VersionMap = RelatedTargetVersionMapping::parseJSON(
*Mapping, *MaximumDeploymentVersion);
if (!VersionMap)
return std::nullopt;
VersionMappings[OSEnvPair::macOStoMacCatalystPair().Value] =
std::move(VersionMap);
}
if (const auto *Mapping = VM->getObject("iOSMac_macOS")) {
auto VersionMap = RelatedTargetVersionMapping::parseJSON(
*Mapping, *MaximumDeploymentVersion);
if (!VersionMap)
return std::nullopt;
VersionMappings[OSEnvPair::macCatalystToMacOSPair().Value] =
std::move(VersionMap);
}
}
return DarwinSDKInfo(std::move(FilePath), OSAndEnvironment.first,
OSAndEnvironment.second, std::move(*Version),
DisplayName, std::move(*MaximumDeploymentVersion),
std::move(PlatformInfos), std::move(VersionMappings));
}
Expected<std::optional<DarwinSDKInfo>>
clang::parseDarwinSDKInfo(llvm::vfs::FileSystem &VFS, StringRef SDKRootPath) {
llvm::SmallString<256> Filepath = SDKRootPath;
llvm::sys::path::append(Filepath, "SDKSettings.json");
llvm::ErrorOr<std::unique_ptr<llvm::MemoryBuffer>> File =
VFS.getBufferForFile(Filepath);
if (!File) {
// If the file couldn't be read, assume it just doesn't exist.
return std::nullopt;
}
Expected<llvm::json::Value> Result =
llvm::json::parse(File.get()->getBuffer());
if (!Result)
return Result.takeError();
if (const auto *Obj = Result->getAsObject()) {
if (auto SDKInfo = DarwinSDKInfo::parseDarwinSDKSettingsJSON(
Filepath.str().str(), Obj))
return std::move(SDKInfo);
}
return llvm::make_error<llvm::StringError>("invalid SDKSettings.json",
llvm::inconvertibleErrorCode());
}
DarwinSDKInfo::DarwinSDKInfo(llvm::Triple::OSType OS,
llvm::Triple::EnvironmentType Environment,
VersionTuple Version, StringRef DisplayName,
VersionTuple MaximumDeploymentTarget)
: DarwinSDKInfo("", OS, Environment, Version, DisplayName,
MaximumDeploymentTarget,
legacyPlatformInfos(OS, Environment)) {}
static DarwinSDKInfo::PlatformInfoStorageType::const_iterator
findPlatformInfo(const DarwinSDKInfo::PlatformInfoStorageType &PlatformInfos,
const llvm::Triple &Triple) {
auto PlatformInfoIt = llvm::find_if(
PlatformInfos,
[&Triple](const DarwinSDKInfo::SDKPlatformInfo &PlatformInfo) {
const auto &Triples = PlatformInfo.getTriples();
return llvm::find(Triples, Triple) != Triples.end();
});
// The SDK specifies values for Xcode to use for the -target argument. It's
// hard to perfectly match the triple passed to this function against those
// values though. The passed in triple might have been computed from just
// -arch, or it might have been modified by -march and several other arguments
// that can effect any of the triple components. It's not really possible to
// account for all of the triple variations, but one common modification is
// that "arm" gets changed to "thumb". If the passed in triple is "thumb", try
// mapping it back to an "arm" triple since that's what the SDK will specify.
if (PlatformInfoIt == PlatformInfos.end() &&
Triple.getArch() == llvm::Triple::thumb) {
StringRef ARMArch = llvm::Triple::getArchName(llvm::Triple::arm);
// Preserve the sub-arch from the triple.
llvm::ARM::ArchKind ArchKind = llvm::ARM::parseArch(Triple.getArchName());
StringRef SubArch = llvm::ARM::getSubArch(ArchKind);
llvm::Triple ARMTriple(Triple);
ARMTriple.setArchName((ARMArch + SubArch).str());
if (ARMTriple.getArch() != llvm::Triple::thumb) {
// Sometimes changing the architecture name in the triple doesn't change
// its parsed architecture. e.g. armv6m parses as thumb, so it won't help
// to search for armv6m if thumbv6m already failed.
PlatformInfoIt = findPlatformInfo(PlatformInfos, ARMTriple);
}
}
return PlatformInfoIt;
}
bool DarwinSDKInfo::supportsTriple(const llvm::Triple &Triple) const {
return findPlatformInfo(PlatformInfos, Triple) != PlatformInfos.end();
}
StringRef DarwinSDKInfo::getPlatformPrefix(const llvm::Triple &Triple) const {
auto PlatformInfoIt = findPlatformInfo(PlatformInfos, Triple);
if (PlatformInfoIt != PlatformInfos.end())
return PlatformInfoIt->getPlatformPrefix();
// This triple probably isn't supported by the SDK. However, almost every SDK
// just has a single prefix where all of its contents are, so return that.
StringRef PlatformPrefix = PlatformInfos[0].getPlatformPrefix();
for (PlatformInfoIt = std::next(PlatformInfos.begin());
PlatformInfoIt != PlatformInfos.end(); ++PlatformInfoIt) {
if (PlatformInfoIt->getPlatformPrefix() != PlatformPrefix) {
// This SDK has multiple prefixes, give up and return nothing.
return StringRef();
}
}
return PlatformPrefix;
}