Using the file system path to identify the SDK platform, and determine which platforms the SDK supports, is unreliable. In particular, the SDK's file name prefix is usually significant, and dropping it usually gives incorrect results. Instead, use information from SDKinfo to positively identify its platform/environment, and to identify which triples are compatible.
268 lines
11 KiB
C++
268 lines
11 KiB
C++
//===--- 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 <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 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) {
|
|
// For older SDKs that don't have SupportedTargets, infer one from the SDK's
|
|
// OS/Environment.
|
|
StringRef PlatformPrefix;
|
|
if (SDKOS == llvm::Triple::DriverKit)
|
|
PlatformPrefix = "/System/DriverKit";
|
|
PlatformInfos.push_back({llvm::Triple::Apple, SDKOS, SDKEnvironment,
|
|
llvm::Triple::MachO, PlatformPrefix});
|
|
return PlatformInfos;
|
|
}
|
|
|
|
for (auto SupportedTargetPair : *SupportedTargets) {
|
|
llvm::json::Object *SupportedTarget =
|
|
SupportedTargetPair.getSecond().getAsObject();
|
|
auto Vendor = SupportedTarget->getString("LLVMTargetTripleVendor");
|
|
auto OS = SupportedTarget->getString("LLVMTargetTripleSys");
|
|
if (!Vendor || !OS)
|
|
continue;
|
|
|
|
StringRef Arch = llvm::Triple::getArchName(llvm::Triple::UnknownArch);
|
|
auto Environment =
|
|
SupportedTarget->getString("LLVMTargetTripleEnvironment");
|
|
llvm::Triple Triple;
|
|
if (Environment)
|
|
Triple = llvm::Triple(Arch, *Vendor, *OS, *Environment);
|
|
else
|
|
Triple = llvm::Triple(Arch, *Vendor, *OS);
|
|
|
|
// 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 ((Triple.getOS() == llvm::Triple::DriverKit) &&
|
|
(Version < VersionTuple(22, 1)))
|
|
EffectivePlatformPrefix = "/System/DriverKit";
|
|
}
|
|
}
|
|
|
|
DarwinSDKInfo::SDKPlatformInfo PlatformInfo(
|
|
Triple.getVendor(), Triple.getOS(), Triple.getEnvironment(),
|
|
Triple.getObjectFormat(), EffectivePlatformPrefix);
|
|
if (PlatformOrVariant == XcodePlatform)
|
|
PlatformInfos.insert(PlatformInfos.begin(), PlatformInfo);
|
|
else
|
|
PlatformInfos.push_back(PlatformInfo);
|
|
}
|
|
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);
|
|
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());
|
|
}
|