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 "llvm/TargetParser/ARMTargetParser.h"
16#include <optional>
17
18using namespace clang;
19
20std::optional<VersionTuple> DarwinSDKInfo::RelatedTargetVersionMapping::map(
21 const VersionTuple &Key, const VersionTuple &MinimumValue,
22 std::optional<VersionTuple> MaximumValue) const {
23 if (Key < MinimumKeyVersion)
24 return MinimumValue;
25 if (Key > MaximumKeyVersion)
26 return MaximumValue;
27 auto KV = Mapping.find(Val: Key.normalize());
28 if (KV != Mapping.end())
29 return KV->getSecond();
30 // If no exact entry found, try just the major key version. Only do so when
31 // a minor version number is present, to avoid recursing indefinitely into
32 // the major-only check.
33 if (Key.getMinor())
34 return map(Key: VersionTuple(Key.getMajor()), MinimumValue, MaximumValue);
35 // If this a major only key, return std::nullopt for a missing entry.
36 return std::nullopt;
37}
38
39std::optional<DarwinSDKInfo::RelatedTargetVersionMapping>
40DarwinSDKInfo::RelatedTargetVersionMapping::parseJSON(
41 const llvm::json::Object &Obj, VersionTuple MaximumDeploymentTarget) {
42 VersionTuple Min = VersionTuple(std::numeric_limits<unsigned>::max());
43 VersionTuple Max = VersionTuple(0);
44 VersionTuple MinValue = Min;
45 llvm::DenseMap<VersionTuple, VersionTuple> Mapping;
46 for (const auto &KV : Obj) {
47 if (auto Val = KV.getSecond().getAsString()) {
48 llvm::VersionTuple KeyVersion;
49 llvm::VersionTuple ValueVersion;
50 if (KeyVersion.tryParse(string: KV.getFirst()) || ValueVersion.tryParse(string: *Val))
51 return std::nullopt;
52 Mapping[KeyVersion.normalize()] = ValueVersion;
53 if (KeyVersion < Min)
54 Min = KeyVersion;
55 if (KeyVersion > Max)
56 Max = KeyVersion;
57 if (ValueVersion < MinValue)
58 MinValue = ValueVersion;
59 }
60 }
61 if (Mapping.empty())
62 return std::nullopt;
63 return RelatedTargetVersionMapping(
64 Min, Max, MinValue, MaximumDeploymentTarget, std::move(Mapping));
65}
66
67static std::optional<StringRef>
68parseXcodePlatform(const llvm::json::Object &Obj) {
69 // The CanonicalName is the Xcode platform followed by a version, e.g.
70 // macosx15.0.
71 auto CanonicalName = Obj.getString(K: "CanonicalName");
72 if (!CanonicalName)
73 return std::nullopt;
74 size_t VersionStart = CanonicalName->find_first_of(Chars: "0123456789");
75 return CanonicalName->slice(Start: 0, End: VersionStart);
76}
77
78static std::pair<llvm::Triple::OSType, llvm::Triple::EnvironmentType>
79parseOSAndEnvironment(std::optional<StringRef> XcodePlatform) {
80 if (!XcodePlatform)
81 return {llvm::Triple::UnknownOS, llvm::Triple::UnknownEnvironment};
82
83 llvm::Triple::OSType OS =
84 llvm::StringSwitch<llvm::Triple::OSType>(*XcodePlatform)
85 .Case(S: "macosx", Value: llvm::Triple::MacOSX)
86 .Cases(CaseStrings: {"iphoneos", "iphonesimulator"}, Value: llvm::Triple::IOS)
87 .Cases(CaseStrings: {"appletvos", "appletvsimulator"}, Value: llvm::Triple::TvOS)
88 .Cases(CaseStrings: {"watchos", "watchsimulator"}, Value: llvm::Triple::WatchOS)
89 .Case(S: "bridgeos", Value: llvm::Triple::BridgeOS)
90 .Cases(CaseStrings: {"xros", "xrsimulator"}, Value: llvm::Triple::XROS)
91 .Case(S: "driverkit", Value: llvm::Triple::DriverKit)
92 .Default(Value: llvm::Triple::UnknownOS);
93
94 llvm::Triple::EnvironmentType Environment =
95 llvm::StringSwitch<llvm::Triple::EnvironmentType>(*XcodePlatform)
96 .Cases(CaseStrings: {"iphonesimulator", "appletvsimulator", "watchsimulator",
97 "xrsimulator"},
98 Value: llvm::Triple::Simulator)
99 .Default(Value: llvm::Triple::UnknownEnvironment);
100
101 return {OS, Environment};
102}
103
104static DarwinSDKInfo::PlatformInfoStorageType
105legacyPlatformInfos(llvm::Triple::OSType SDKOS,
106 llvm::Triple::EnvironmentType SDKEnvironment) {
107 DarwinSDKInfo::PlatformInfoStorageType PlatformInfos;
108 // Synthesize platform infos for older SDKs from the first SDKs with
109 // SupportedTargets: macOS 10.15 (DriverKit 19.0), iOS 13.0, tvOS 13.0,
110 // watchOS 6.0. Older macOS SDKs supported ppc and i386 architectures, and
111 // maybe ppc64. Support for those is difficult to identify from the SDK, and
112 // so aren't listed here. Other older SDKs (especially iOS) most likely
113 // supported armv6 and armv7 architectures that aren't listed here either.
114 switch (SDKOS) {
115 case llvm::Triple::MacOSX:
116 PlatformInfos.push_back(Elt: {{llvm::Triple("x86_64-apple-macosx")}, ""});
117 // macOS 10.15 also has a Mac Catalyst supported target, but Mac Catalyst
118 // was new in that version so omit it for older versions.
119 break;
120 case llvm::Triple::DriverKit:
121 // DriverKit 19.0 only had SDKSettings.plist, which isn't used. So this code
122 // path is used in 19.x as well, not just earlier versions.
123 PlatformInfos.push_back(
124 Elt: {{llvm::Triple("x86_64-apple-driverkit")}, "/System/DriverKit"});
125 break;
126 case llvm::Triple::IOS:
127 switch (SDKEnvironment) {
128 case llvm::Triple::UnknownEnvironment:
129 PlatformInfos.push_back(
130 Elt: {{llvm::Triple("armv7-apple-ios"), llvm::Triple("armv7s-apple-ios"),
131 llvm::Triple("arm64-apple-ios")},
132 ""});
133 break;
134 case llvm::Triple::Simulator:
135 PlatformInfos.push_back(
136 Elt: {{llvm::Triple("x86_64-apple-ios-simulator")}, ""});
137 break;
138 default:
139 break;
140 }
141 break;
142 case llvm::Triple::TvOS:
143 switch (SDKEnvironment) {
144 case llvm::Triple::UnknownEnvironment:
145 PlatformInfos.push_back(Elt: {{llvm::Triple("arm64-apple-tvos")}, ""});
146 break;
147 case llvm::Triple::Simulator:
148 PlatformInfos.push_back(
149 Elt: {{llvm::Triple("x86_64-apple-tvos-simulator")}, ""});
150 break;
151 default:
152 break;
153 }
154 break;
155 case llvm::Triple::WatchOS:
156 switch (SDKEnvironment) {
157 case llvm::Triple::UnknownEnvironment:
158 PlatformInfos.push_back(Elt: {{llvm::Triple("armv7k-apple-watchos"),
159 llvm::Triple("arm64_32-apple-watchos")},
160 ""});
161 break;
162 case llvm::Triple::Simulator:
163 PlatformInfos.push_back(
164 Elt: {{llvm::Triple("x86_64-apple-watchos-simulator")}, ""});
165 break;
166 default:
167 break;
168 }
169 break;
170 case llvm::Triple::BridgeOS:
171 PlatformInfos.push_back(Elt: {{llvm::Triple("armv7-apple-bridgeos"),
172 llvm::Triple("armv7s-apple-bridgeos"),
173 llvm::Triple("arm64-apple-bridgeos")},
174 ""});
175 break;
176 default:
177 break;
178 }
179 return PlatformInfos;
180}
181
182static DarwinSDKInfo::PlatformInfoStorageType parsePlatformInfos(
183 const llvm::json::Object &Obj, std::optional<StringRef> XcodePlatform,
184 llvm::Triple::OSType SDKOS, llvm::Triple::EnvironmentType SDKEnvironment,
185 VersionTuple Version) {
186 DarwinSDKInfo::PlatformInfoStorageType PlatformInfos;
187 auto SupportedTargets = Obj.getObject(K: "SupportedTargets");
188 if (!SupportedTargets)
189 return legacyPlatformInfos(SDKOS, SDKEnvironment);
190
191 for (const auto &SupportedTargetPair : *SupportedTargets) {
192 const llvm::json::Object *SupportedTarget =
193 SupportedTargetPair.getSecond().getAsObject();
194 if (!SupportedTarget)
195 continue;
196
197 auto Archs = SupportedTarget->getArray(K: "Archs");
198 auto Vendor = SupportedTarget->getString(K: "LLVMTargetTripleVendor");
199 auto OS = SupportedTarget->getString(K: "LLVMTargetTripleSys");
200 if (!Archs || !Vendor || !OS)
201 continue;
202
203 DarwinSDKInfo::SDKPlatformInfo::TripleStorageType Triples;
204 auto Environment =
205 SupportedTarget->getString(K: "LLVMTargetTripleEnvironment");
206 for (const auto &ArchValue : *Archs) {
207 if (auto Arch = ArchValue.getAsString()) {
208 if (Environment && !Environment->empty())
209 Triples.emplace_back(Args&: *Arch, Args&: *Vendor, Args&: *OS, Args&: *Environment);
210 else
211 Triples.emplace_back(Args&: *Arch, Args&: *Vendor, Args&: *OS);
212 }
213 }
214 if (Triples.empty())
215 continue;
216
217 // The key is either the Xcode platform, or a variant. The platform must be
218 // the first entry in the returned PlatformInfoStorageType.
219 StringRef PlatformOrVariant = SupportedTargetPair.getFirst();
220
221 StringRef EffectivePlatformPrefix;
222 // Ignore iosmac value if it exists.
223 if ((PlatformOrVariant != "iosmac") || (Version >= VersionTuple(99))) {
224 auto PlatformPrefix = SupportedTarget->getString(K: "SystemPrefix");
225 if (PlatformPrefix) {
226 EffectivePlatformPrefix = *PlatformPrefix;
227 } else {
228 // Older SDKs don't have SystemPrefix in SupportedTargets, manually add
229 // their prefixes.
230 if ((Triples[0].getOS() == llvm::Triple::DriverKit) &&
231 (Version < VersionTuple(22, 1)))
232 EffectivePlatformPrefix = "/System/DriverKit";
233 }
234 }
235
236 if (PlatformOrVariant == XcodePlatform)
237 PlatformInfos.insert(I: PlatformInfos.begin(),
238 Elt: {std::move(Triples), EffectivePlatformPrefix});
239 else
240 PlatformInfos.emplace_back(Args: std::move(Triples), Args&: EffectivePlatformPrefix);
241 }
242 return PlatformInfos;
243}
244
245static std::optional<VersionTuple> getVersionKey(const llvm::json::Object &Obj,
246 StringRef Key) {
247 auto Value = Obj.getString(K: Key);
248 if (!Value)
249 return std::nullopt;
250 VersionTuple Version;
251 if (Version.tryParse(string: *Value))
252 return std::nullopt;
253 return Version;
254}
255
256std::optional<DarwinSDKInfo>
257DarwinSDKInfo::parseDarwinSDKSettingsJSON(std::string FilePath,
258 const llvm::json::Object *Obj) {
259 auto Version = getVersionKey(Obj: *Obj, Key: "Version");
260 if (!Version)
261 return std::nullopt;
262 auto MaximumDeploymentVersion =
263 getVersionKey(Obj: *Obj, Key: "MaximumDeploymentTarget");
264 if (!MaximumDeploymentVersion)
265 return std::nullopt;
266 std::optional<StringRef> XcodePlatform = parseXcodePlatform(Obj: *Obj);
267 std::pair<llvm::Triple::OSType, llvm::Triple::EnvironmentType>
268 OSAndEnvironment = parseOSAndEnvironment(XcodePlatform);
269 // DisplayName should always be present, but don't require it.
270 StringRef DisplayName =
271 Obj->getString(K: "DisplayName")
272 .value_or(u: Obj->getString(K: "CanonicalName").value_or(u: "<unknown>"));
273 PlatformInfoStorageType PlatformInfos =
274 parsePlatformInfos(Obj: *Obj, XcodePlatform, SDKOS: OSAndEnvironment.first,
275 SDKEnvironment: OSAndEnvironment.second, Version: *Version);
276 if (PlatformInfos.empty())
277 return std::nullopt;
278 llvm::DenseMap<OSEnvPair::StorageType,
279 std::optional<RelatedTargetVersionMapping>>
280 VersionMappings;
281 if (const auto *VM = Obj->getObject(K: "VersionMap")) {
282 // FIXME: Generalize this out beyond iOS-deriving targets.
283 // Look for ios_<targetos> version mapping for targets that derive from ios.
284 for (const auto &KV : *VM) {
285 auto Pair = StringRef(KV.getFirst()).split(Separator: "_");
286 if (Pair.first.compare_insensitive(RHS: "ios") == 0) {
287 llvm::Triple TT(llvm::Twine("--") + Pair.second.lower());
288 if (TT.getOS() != llvm::Triple::UnknownOS) {
289 auto Mapping = RelatedTargetVersionMapping::parseJSON(
290 Obj: *KV.getSecond().getAsObject(), MaximumDeploymentTarget: *MaximumDeploymentVersion);
291 if (Mapping)
292 VersionMappings[OSEnvPair(llvm::Triple::IOS,
293 llvm::Triple::UnknownEnvironment,
294 TT.getOS(),
295 llvm::Triple::UnknownEnvironment)
296 .Value] = std::move(Mapping);
297 }
298 }
299 }
300
301 if (const auto *Mapping = VM->getObject(K: "macOS_iOSMac")) {
302 auto VersionMap = RelatedTargetVersionMapping::parseJSON(
303 Obj: *Mapping, MaximumDeploymentTarget: *MaximumDeploymentVersion);
304 if (!VersionMap)
305 return std::nullopt;
306 VersionMappings[OSEnvPair::macOStoMacCatalystPair().Value] =
307 std::move(VersionMap);
308 }
309 if (const auto *Mapping = VM->getObject(K: "iOSMac_macOS")) {
310 auto VersionMap = RelatedTargetVersionMapping::parseJSON(
311 Obj: *Mapping, MaximumDeploymentTarget: *MaximumDeploymentVersion);
312 if (!VersionMap)
313 return std::nullopt;
314 VersionMappings[OSEnvPair::macCatalystToMacOSPair().Value] =
315 std::move(VersionMap);
316 }
317 }
318
319 return DarwinSDKInfo(std::move(FilePath), OSAndEnvironment.first,
320 OSAndEnvironment.second, std::move(*Version),
321 DisplayName, std::move(*MaximumDeploymentVersion),
322 std::move(PlatformInfos), std::move(VersionMappings));
323}
324
325Expected<std::optional<DarwinSDKInfo>>
326clang::parseDarwinSDKInfo(llvm::vfs::FileSystem &VFS, StringRef SDKRootPath) {
327 llvm::SmallString<256> Filepath = SDKRootPath;
328 llvm::sys::path::append(path&: Filepath, a: "SDKSettings.json");
329 llvm::ErrorOr<std::unique_ptr<llvm::MemoryBuffer>> File =
330 VFS.getBufferForFile(Name: Filepath);
331 if (!File) {
332 // If the file couldn't be read, assume it just doesn't exist.
333 return std::nullopt;
334 }
335 Expected<llvm::json::Value> Result =
336 llvm::json::parse(JSON: File.get()->getBuffer());
337 if (!Result)
338 return Result.takeError();
339
340 if (const auto *Obj = Result->getAsObject()) {
341 if (auto SDKInfo = DarwinSDKInfo::parseDarwinSDKSettingsJSON(
342 FilePath: Filepath.str().str(), Obj))
343 return std::move(SDKInfo);
344 }
345 return llvm::make_error<llvm::StringError>(Args: "invalid SDKSettings.json",
346 Args: llvm::inconvertibleErrorCode());
347}
348
349DarwinSDKInfo::DarwinSDKInfo(llvm::Triple::OSType OS,
350 llvm::Triple::EnvironmentType Environment,
351 VersionTuple Version, StringRef DisplayName,
352 VersionTuple MaximumDeploymentTarget)
353 : DarwinSDKInfo("", OS, Environment, Version, DisplayName,
354 MaximumDeploymentTarget,
355 legacyPlatformInfos(SDKOS: OS, SDKEnvironment: Environment)) {}
356
357static DarwinSDKInfo::PlatformInfoStorageType::const_iterator
358findPlatformInfo(const DarwinSDKInfo::PlatformInfoStorageType &PlatformInfos,
359 const llvm::Triple &Triple) {
360 auto PlatformInfoIt = llvm::find_if(
361 Range: PlatformInfos,
362 P: [&Triple](const DarwinSDKInfo::SDKPlatformInfo &PlatformInfo) {
363 const auto &Triples = PlatformInfo.getTriples();
364 return llvm::find(Range: Triples, Val: Triple) != Triples.end();
365 });
366
367 // The SDK specifies values for Xcode to use for the -target argument. It's
368 // hard to perfectly match the triple passed to this function against those
369 // values though. The passed in triple might have been computed from just
370 // -arch, or it might have been modified by -march and several other arguments
371 // that can effect any of the triple components. It's not really possible to
372 // account for all of the triple variations, but one common modification is
373 // that "arm" gets changed to "thumb". If the passed in triple is "thumb", try
374 // mapping it back to an "arm" triple since that's what the SDK will specify.
375 if (PlatformInfoIt == PlatformInfos.end() &&
376 Triple.getArch() == llvm::Triple::thumb) {
377 StringRef ARMArch = llvm::Triple::getArchName(Kind: llvm::Triple::arm);
378
379 // Preserve the sub-arch from the triple.
380 llvm::ARM::ArchKind ArchKind = llvm::ARM::parseArch(Arch: Triple.getArchName());
381 StringRef SubArch = llvm::ARM::getSubArch(AK: ArchKind);
382
383 llvm::Triple ARMTriple(Triple);
384 ARMTriple.setArchName((ARMArch + SubArch).str());
385 if (ARMTriple.getArch() != llvm::Triple::thumb) {
386 // Sometimes changing the architecture name in the triple doesn't change
387 // its parsed architecture. e.g. armv6m parses as thumb, so it won't help
388 // to search for armv6m if thumbv6m already failed.
389 PlatformInfoIt = findPlatformInfo(PlatformInfos, Triple: ARMTriple);
390 }
391 }
392
393 return PlatformInfoIt;
394}
395
396bool DarwinSDKInfo::supportsTriple(const llvm::Triple &Triple) const {
397 return findPlatformInfo(PlatformInfos, Triple) != PlatformInfos.end();
398}
399
400StringRef DarwinSDKInfo::getPlatformPrefix(const llvm::Triple &Triple) const {
401 auto PlatformInfoIt = findPlatformInfo(PlatformInfos, Triple);
402 if (PlatformInfoIt != PlatformInfos.end())
403 return PlatformInfoIt->getPlatformPrefix();
404
405 // This triple probably isn't supported by the SDK. However, almost every SDK
406 // just has a single prefix where all of its contents are, so return that.
407 StringRef PlatformPrefix = PlatformInfos[0].getPlatformPrefix();
408 for (PlatformInfoIt = std::next(x: PlatformInfos.begin());
409 PlatformInfoIt != PlatformInfos.end(); ++PlatformInfoIt) {
410 if (PlatformInfoIt->getPlatformPrefix() != PlatformPrefix) {
411 // This SDK has multiple prefixes, give up and return nothing.
412 return StringRef();
413 }
414 }
415 return PlatformPrefix;
416}
417