1//===--- DarwinSDKInfo.cpp - SDK Information parser for darwin - ----------===//
2//
3// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
4// See https://llvm.org/LICENSE.txt for license information.
5// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
6//
7//===----------------------------------------------------------------------===//
8
9#include "clang/Basic/DarwinSDKInfo.h"
10#include "llvm/ADT/StringSwitch.h"
11#include "llvm/Support/ErrorOr.h"
12#include "llvm/Support/JSON.h"
13#include "llvm/Support/MemoryBuffer.h"
14#include "llvm/Support/Path.h"
15#include <optional>
16
17using namespace clang;
18
19std::optional<VersionTuple> DarwinSDKInfo::RelatedTargetVersionMapping::map(
20 const VersionTuple &Key, const VersionTuple &MinimumValue,
21 std::optional<VersionTuple> MaximumValue) const {
22 if (Key < MinimumKeyVersion)
23 return MinimumValue;
24 if (Key > MaximumKeyVersion)
25 return MaximumValue;
26 auto KV = Mapping.find(Val: Key.normalize());
27 if (KV != Mapping.end())
28 return KV->getSecond();
29 // If no exact entry found, try just the major key version. Only do so when
30 // a minor version number is present, to avoid recursing indefinitely into
31 // the major-only check.
32 if (Key.getMinor())
33 return map(Key: VersionTuple(Key.getMajor()), MinimumValue, MaximumValue);
34 // If this a major only key, return std::nullopt for a missing entry.
35 return std::nullopt;
36}
37
38std::optional<DarwinSDKInfo::RelatedTargetVersionMapping>
39DarwinSDKInfo::RelatedTargetVersionMapping::parseJSON(
40 const llvm::json::Object &Obj, VersionTuple MaximumDeploymentTarget) {
41 VersionTuple Min = VersionTuple(std::numeric_limits<unsigned>::max());
42 VersionTuple Max = VersionTuple(0);
43 VersionTuple MinValue = Min;
44 llvm::DenseMap<VersionTuple, VersionTuple> Mapping;
45 for (const auto &KV : Obj) {
46 if (auto Val = KV.getSecond().getAsString()) {
47 llvm::VersionTuple KeyVersion;
48 llvm::VersionTuple ValueVersion;
49 if (KeyVersion.tryParse(string: KV.getFirst()) || ValueVersion.tryParse(string: *Val))
50 return std::nullopt;
51 Mapping[KeyVersion.normalize()] = ValueVersion;
52 if (KeyVersion < Min)
53 Min = KeyVersion;
54 if (KeyVersion > Max)
55 Max = KeyVersion;
56 if (ValueVersion < MinValue)
57 MinValue = ValueVersion;
58 }
59 }
60 if (Mapping.empty())
61 return std::nullopt;
62 return RelatedTargetVersionMapping(
63 Min, Max, MinValue, MaximumDeploymentTarget, std::move(Mapping));
64}
65
66static std::optional<StringRef>
67parseXcodePlatform(const llvm::json::Object &Obj) {
68 // The CanonicalName is the Xcode platform followed by a version, e.g.
69 // macosx15.0.
70 auto CanonicalName = Obj.getString(K: "CanonicalName");
71 if (!CanonicalName)
72 return std::nullopt;
73 size_t VersionStart = CanonicalName->find_first_of(Chars: "0123456789");
74 return CanonicalName->slice(Start: 0, End: VersionStart);
75}
76
77static std::pair<llvm::Triple::OSType, llvm::Triple::EnvironmentType>
78parseOSAndEnvironment(std::optional<StringRef> XcodePlatform) {
79 if (!XcodePlatform)
80 return {llvm::Triple::UnknownOS, llvm::Triple::UnknownEnvironment};
81
82 llvm::Triple::OSType OS =
83 llvm::StringSwitch<llvm::Triple::OSType>(*XcodePlatform)
84 .Case(S: "macosx", Value: llvm::Triple::MacOSX)
85 .Cases(CaseStrings: {"iphoneos", "iphonesimulator"}, Value: llvm::Triple::IOS)
86 .Cases(CaseStrings: {"appletvos", "appletvsimulator"}, Value: llvm::Triple::TvOS)
87 .Cases(CaseStrings: {"watchos", "watchsimulator"}, Value: llvm::Triple::WatchOS)
88 .Case(S: "bridgeos", Value: llvm::Triple::BridgeOS)
89 .Cases(CaseStrings: {"xros", "xrsimulator"}, Value: llvm::Triple::XROS)
90 .Case(S: "driverkit", Value: llvm::Triple::DriverKit)
91 .Default(Value: llvm::Triple::UnknownOS);
92
93 llvm::Triple::EnvironmentType Environment =
94 llvm::StringSwitch<llvm::Triple::EnvironmentType>(*XcodePlatform)
95 .Cases(CaseStrings: {"iphonesimulator", "appletvsimulator", "watchsimulator",
96 "xrsimulator"},
97 Value: llvm::Triple::Simulator)
98 .Default(Value: llvm::Triple::UnknownEnvironment);
99
100 return {OS, Environment};
101}
102
103static DarwinSDKInfo::PlatformInfoStorageType parsePlatformInfos(
104 const llvm::json::Object &Obj, std::optional<StringRef> XcodePlatform,
105 llvm::Triple::OSType SDKOS, llvm::Triple::EnvironmentType SDKEnvironment,
106 VersionTuple Version) {
107 DarwinSDKInfo::PlatformInfoStorageType PlatformInfos;
108 auto SupportedTargets = Obj.getObject(K: "SupportedTargets");
109 if (!SupportedTargets) {
110 // For older SDKs that don't have SupportedTargets, infer one from the SDK's
111 // OS/Environment.
112 StringRef PlatformPrefix;
113 if (SDKOS == llvm::Triple::DriverKit)
114 PlatformPrefix = "/System/DriverKit";
115 PlatformInfos.push_back(Elt: {llvm::Triple::Apple, SDKOS, SDKEnvironment,
116 llvm::Triple::MachO, PlatformPrefix});
117 return PlatformInfos;
118 }
119
120 for (auto SupportedTargetPair : *SupportedTargets) {
121 llvm::json::Object *SupportedTarget =
122 SupportedTargetPair.getSecond().getAsObject();
123 auto Vendor = SupportedTarget->getString(K: "LLVMTargetTripleVendor");
124 auto OS = SupportedTarget->getString(K: "LLVMTargetTripleSys");
125 if (!Vendor || !OS)
126 continue;
127
128 StringRef Arch = llvm::Triple::getArchName(Kind: llvm::Triple::UnknownArch);
129 auto Environment =
130 SupportedTarget->getString(K: "LLVMTargetTripleEnvironment");
131 llvm::Triple Triple;
132 if (Environment)
133 Triple = llvm::Triple(Arch, *Vendor, *OS, *Environment);
134 else
135 Triple = llvm::Triple(Arch, *Vendor, *OS);
136
137 // The key is either the Xcode platform, or a variant. The platform must be
138 // the first entry in the returned PlatformInfoStorageType.
139 StringRef PlatformOrVariant = SupportedTargetPair.getFirst();
140
141 StringRef EffectivePlatformPrefix;
142 // Ignore iosmac value if it exists.
143 if ((PlatformOrVariant != "iosmac") || (Version >= VersionTuple(99))) {
144 auto PlatformPrefix = SupportedTarget->getString(K: "SystemPrefix");
145 if (PlatformPrefix) {
146 EffectivePlatformPrefix = *PlatformPrefix;
147 } else {
148 // Older SDKs don't have SystemPrefix in SupportedTargets, manually add
149 // their prefixes.
150 if ((Triple.getOS() == llvm::Triple::DriverKit) &&
151 (Version < VersionTuple(22, 1)))
152 EffectivePlatformPrefix = "/System/DriverKit";
153 }
154 }
155
156 DarwinSDKInfo::SDKPlatformInfo PlatformInfo(
157 Triple.getVendor(), Triple.getOS(), Triple.getEnvironment(),
158 Triple.getObjectFormat(), EffectivePlatformPrefix);
159 if (PlatformOrVariant == XcodePlatform)
160 PlatformInfos.insert(I: PlatformInfos.begin(), Elt: PlatformInfo);
161 else
162 PlatformInfos.push_back(Elt: PlatformInfo);
163 }
164 return PlatformInfos;
165}
166
167static std::optional<VersionTuple> getVersionKey(const llvm::json::Object &Obj,
168 StringRef Key) {
169 auto Value = Obj.getString(K: Key);
170 if (!Value)
171 return std::nullopt;
172 VersionTuple Version;
173 if (Version.tryParse(string: *Value))
174 return std::nullopt;
175 return Version;
176}
177
178std::optional<DarwinSDKInfo>
179DarwinSDKInfo::parseDarwinSDKSettingsJSON(std::string FilePath,
180 const llvm::json::Object *Obj) {
181 auto Version = getVersionKey(Obj: *Obj, Key: "Version");
182 if (!Version)
183 return std::nullopt;
184 auto MaximumDeploymentVersion =
185 getVersionKey(Obj: *Obj, Key: "MaximumDeploymentTarget");
186 if (!MaximumDeploymentVersion)
187 return std::nullopt;
188 std::optional<StringRef> XcodePlatform = parseXcodePlatform(Obj: *Obj);
189 std::pair<llvm::Triple::OSType, llvm::Triple::EnvironmentType>
190 OSAndEnvironment = parseOSAndEnvironment(XcodePlatform);
191 // DisplayName should always be present, but don't require it.
192 StringRef DisplayName =
193 Obj->getString(K: "DisplayName")
194 .value_or(u: Obj->getString(K: "CanonicalName").value_or(u: "<unknown>"));
195 PlatformInfoStorageType PlatformInfos =
196 parsePlatformInfos(Obj: *Obj, XcodePlatform, SDKOS: OSAndEnvironment.first,
197 SDKEnvironment: OSAndEnvironment.second, Version: *Version);
198 llvm::DenseMap<OSEnvPair::StorageType,
199 std::optional<RelatedTargetVersionMapping>>
200 VersionMappings;
201 if (const auto *VM = Obj->getObject(K: "VersionMap")) {
202 // FIXME: Generalize this out beyond iOS-deriving targets.
203 // Look for ios_<targetos> version mapping for targets that derive from ios.
204 for (const auto &KV : *VM) {
205 auto Pair = StringRef(KV.getFirst()).split(Separator: "_");
206 if (Pair.first.compare_insensitive(RHS: "ios") == 0) {
207 llvm::Triple TT(llvm::Twine("--") + Pair.second.lower());
208 if (TT.getOS() != llvm::Triple::UnknownOS) {
209 auto Mapping = RelatedTargetVersionMapping::parseJSON(
210 Obj: *KV.getSecond().getAsObject(), MaximumDeploymentTarget: *MaximumDeploymentVersion);
211 if (Mapping)
212 VersionMappings[OSEnvPair(llvm::Triple::IOS,
213 llvm::Triple::UnknownEnvironment,
214 TT.getOS(),
215 llvm::Triple::UnknownEnvironment)
216 .Value] = std::move(Mapping);
217 }
218 }
219 }
220
221 if (const auto *Mapping = VM->getObject(K: "macOS_iOSMac")) {
222 auto VersionMap = RelatedTargetVersionMapping::parseJSON(
223 Obj: *Mapping, MaximumDeploymentTarget: *MaximumDeploymentVersion);
224 if (!VersionMap)
225 return std::nullopt;
226 VersionMappings[OSEnvPair::macOStoMacCatalystPair().Value] =
227 std::move(VersionMap);
228 }
229 if (const auto *Mapping = VM->getObject(K: "iOSMac_macOS")) {
230 auto VersionMap = RelatedTargetVersionMapping::parseJSON(
231 Obj: *Mapping, MaximumDeploymentTarget: *MaximumDeploymentVersion);
232 if (!VersionMap)
233 return std::nullopt;
234 VersionMappings[OSEnvPair::macCatalystToMacOSPair().Value] =
235 std::move(VersionMap);
236 }
237 }
238
239 return DarwinSDKInfo(std::move(FilePath), OSAndEnvironment.first,
240 OSAndEnvironment.second, std::move(*Version),
241 DisplayName, std::move(*MaximumDeploymentVersion),
242 std::move(PlatformInfos), std::move(VersionMappings));
243}
244
245Expected<std::optional<DarwinSDKInfo>>
246clang::parseDarwinSDKInfo(llvm::vfs::FileSystem &VFS, StringRef SDKRootPath) {
247 llvm::SmallString<256> Filepath = SDKRootPath;
248 llvm::sys::path::append(path&: Filepath, a: "SDKSettings.json");
249 llvm::ErrorOr<std::unique_ptr<llvm::MemoryBuffer>> File =
250 VFS.getBufferForFile(Name: Filepath);
251 if (!File) {
252 // If the file couldn't be read, assume it just doesn't exist.
253 return std::nullopt;
254 }
255 Expected<llvm::json::Value> Result =
256 llvm::json::parse(JSON: File.get()->getBuffer());
257 if (!Result)
258 return Result.takeError();
259
260 if (const auto *Obj = Result->getAsObject()) {
261 if (auto SDKInfo = DarwinSDKInfo::parseDarwinSDKSettingsJSON(
262 FilePath: Filepath.str().str(), Obj))
263 return std::move(SDKInfo);
264 }
265 return llvm::make_error<llvm::StringError>(Args: "invalid SDKSettings.json",
266 Args: llvm::inconvertibleErrorCode());
267}
268