//===--- 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 using namespace clang; std::optional DarwinSDKInfo::RelatedTargetVersionMapping::map( const VersionTuple &Key, const VersionTuple &MinimumValue, std::optional 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::parseJSON( const llvm::json::Object &Obj, VersionTuple MaximumDeploymentTarget) { VersionTuple Min = VersionTuple(std::numeric_limits::max()); VersionTuple Max = VersionTuple(0); VersionTuple MinValue = Min; llvm::DenseMap 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 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 parseOSAndEnvironment(std::optional XcodePlatform) { if (!XcodePlatform) return {llvm::Triple::UnknownOS, llvm::Triple::UnknownEnvironment}; llvm::Triple::OSType OS = llvm::StringSwitch(*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(*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 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 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::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 XcodePlatform = parseXcodePlatform(*Obj); std::pair 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("")); PlatformInfoStorageType PlatformInfos = parsePlatformInfos(*Obj, XcodePlatform, OSAndEnvironment.first, OSAndEnvironment.second, *Version); llvm::DenseMap> VersionMappings; if (const auto *VM = Obj->getObject("VersionMap")) { // FIXME: Generalize this out beyond iOS-deriving targets. // Look for ios_ 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> clang::parseDarwinSDKInfo(llvm::vfs::FileSystem &VFS, StringRef SDKRootPath) { llvm::SmallString<256> Filepath = SDKRootPath; llvm::sys::path::append(Filepath, "SDKSettings.json"); llvm::ErrorOr> File = VFS.getBufferForFile(Filepath); if (!File) { // If the file couldn't be read, assume it just doesn't exist. return std::nullopt; } Expected 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("invalid SDKSettings.json", llvm::inconvertibleErrorCode()); }