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/Support/ErrorOr.h"
11 #include "llvm/Support/JSON.h"
12 #include "llvm/Support/MemoryBuffer.h"
13 #include "llvm/Support/Path.h"
14 #include <optional>
15 
16 using namespace clang;
17 
map(const VersionTuple & Key,const VersionTuple & MinimumValue,std::optional<VersionTuple> MaximumValue) const18 std::optional<VersionTuple> DarwinSDKInfo::RelatedTargetVersionMapping::map(
19     const VersionTuple &Key, const VersionTuple &MinimumValue,
20     std::optional<VersionTuple> MaximumValue) const {
21   if (Key < MinimumKeyVersion)
22     return MinimumValue;
23   if (Key > MaximumKeyVersion)
24     return MaximumValue;
25   auto KV = Mapping.find(Key.normalize());
26   if (KV != Mapping.end())
27     return KV->getSecond();
28   // If no exact entry found, try just the major key version. Only do so when
29   // a minor version number is present, to avoid recursing indefinitely into
30   // the major-only check.
31   if (Key.getMinor())
32     return map(VersionTuple(Key.getMajor()), MinimumValue, MaximumValue);
33   // If this a major only key, return std::nullopt for a missing entry.
34   return std::nullopt;
35 }
36 
37 std::optional<DarwinSDKInfo::RelatedTargetVersionMapping>
parseJSON(const llvm::json::Object & Obj,VersionTuple MaximumDeploymentTarget)38 DarwinSDKInfo::RelatedTargetVersionMapping::parseJSON(
39     const llvm::json::Object &Obj, VersionTuple MaximumDeploymentTarget) {
40   VersionTuple Min = VersionTuple(std::numeric_limits<unsigned>::max());
41   VersionTuple Max = VersionTuple(0);
42   VersionTuple MinValue = Min;
43   llvm::DenseMap<VersionTuple, VersionTuple> Mapping;
44   for (const auto &KV : Obj) {
45     if (auto Val = KV.getSecond().getAsString()) {
46       llvm::VersionTuple KeyVersion;
47       llvm::VersionTuple ValueVersion;
48       if (KeyVersion.tryParse(KV.getFirst()) || ValueVersion.tryParse(*Val))
49         return std::nullopt;
50       Mapping[KeyVersion.normalize()] = ValueVersion;
51       if (KeyVersion < Min)
52         Min = KeyVersion;
53       if (KeyVersion > Max)
54         Max = KeyVersion;
55       if (ValueVersion < MinValue)
56         MinValue = ValueVersion;
57     }
58   }
59   if (Mapping.empty())
60     return std::nullopt;
61   return RelatedTargetVersionMapping(
62       Min, Max, MinValue, MaximumDeploymentTarget, std::move(Mapping));
63 }
64 
getVersionKey(const llvm::json::Object & Obj,StringRef Key)65 static std::optional<VersionTuple> getVersionKey(const llvm::json::Object &Obj,
66                                                  StringRef Key) {
67   auto Value = Obj.getString(Key);
68   if (!Value)
69     return std::nullopt;
70   VersionTuple Version;
71   if (Version.tryParse(*Value))
72     return std::nullopt;
73   return Version;
74 }
75 
76 std::optional<DarwinSDKInfo>
parseDarwinSDKSettingsJSON(const llvm::json::Object * Obj)77 DarwinSDKInfo::parseDarwinSDKSettingsJSON(const llvm::json::Object *Obj) {
78   auto Version = getVersionKey(*Obj, "Version");
79   if (!Version)
80     return std::nullopt;
81   auto MaximumDeploymentVersion =
82       getVersionKey(*Obj, "MaximumDeploymentTarget");
83   if (!MaximumDeploymentVersion)
84     return std::nullopt;
85   llvm::DenseMap<OSEnvPair::StorageType,
86                  std::optional<RelatedTargetVersionMapping>>
87       VersionMappings;
88   if (const auto *VM = Obj->getObject("VersionMap")) {
89     // FIXME: Generalize this out beyond iOS-deriving targets.
90     // Look for ios_<targetos> version mapping for targets that derive from ios.
91     for (const auto &KV : *VM) {
92       auto Pair = StringRef(KV.getFirst()).split("_");
93       if (Pair.first.compare_insensitive("ios") == 0) {
94         llvm::Triple TT(llvm::Twine("--") + Pair.second.lower());
95         if (TT.getOS() != llvm::Triple::UnknownOS) {
96           auto Mapping = RelatedTargetVersionMapping::parseJSON(
97               *KV.getSecond().getAsObject(), *MaximumDeploymentVersion);
98           if (Mapping)
99             VersionMappings[OSEnvPair(llvm::Triple::IOS,
100                                       llvm::Triple::UnknownEnvironment,
101                                       TT.getOS(),
102                                       llvm::Triple::UnknownEnvironment)
103                                 .Value] = std::move(Mapping);
104         }
105       }
106     }
107 
108     if (const auto *Mapping = VM->getObject("macOS_iOSMac")) {
109       auto VersionMap = RelatedTargetVersionMapping::parseJSON(
110           *Mapping, *MaximumDeploymentVersion);
111       if (!VersionMap)
112         return std::nullopt;
113       VersionMappings[OSEnvPair::macOStoMacCatalystPair().Value] =
114           std::move(VersionMap);
115     }
116     if (const auto *Mapping = VM->getObject("iOSMac_macOS")) {
117       auto VersionMap = RelatedTargetVersionMapping::parseJSON(
118           *Mapping, *MaximumDeploymentVersion);
119       if (!VersionMap)
120         return std::nullopt;
121       VersionMappings[OSEnvPair::macCatalystToMacOSPair().Value] =
122           std::move(VersionMap);
123     }
124   }
125 
126   return DarwinSDKInfo(std::move(*Version),
127                        std::move(*MaximumDeploymentVersion),
128                        std::move(VersionMappings));
129 }
130 
131 Expected<std::optional<DarwinSDKInfo>>
parseDarwinSDKInfo(llvm::vfs::FileSystem & VFS,StringRef SDKRootPath)132 clang::parseDarwinSDKInfo(llvm::vfs::FileSystem &VFS, StringRef SDKRootPath) {
133   llvm::SmallString<256> Filepath = SDKRootPath;
134   llvm::sys::path::append(Filepath, "SDKSettings.json");
135   llvm::ErrorOr<std::unique_ptr<llvm::MemoryBuffer>> File =
136       VFS.getBufferForFile(Filepath);
137   if (!File) {
138     // If the file couldn't be read, assume it just doesn't exist.
139     return std::nullopt;
140   }
141   Expected<llvm::json::Value> Result =
142       llvm::json::parse(File.get()->getBuffer());
143   if (!Result)
144     return Result.takeError();
145 
146   if (const auto *Obj = Result->getAsObject()) {
147     if (auto SDKInfo = DarwinSDKInfo::parseDarwinSDKSettingsJSON(Obj))
148       return std::move(SDKInfo);
149   }
150   return llvm::make_error<llvm::StringError>("invalid SDKSettings.json",
151                                              llvm::inconvertibleErrorCode());
152 }
153