diff --git a/clang/include/clang/Basic/DarwinSDKInfo.h b/clang/include/clang/Basic/DarwinSDKInfo.h --- a/clang/include/clang/Basic/DarwinSDKInfo.h +++ b/clang/include/clang/Basic/DarwinSDKInfo.h @@ -10,21 +10,132 @@ #define LLVM_CLANG_BASIC_DARWIN_SDK_INFO_H #include "clang/Basic/LLVM.h" +#include "llvm/ADT/DenseMap.h" +#include "llvm/ADT/Triple.h" #include "llvm/Support/Error.h" #include "llvm/Support/VersionTuple.h" #include "llvm/Support/VirtualFileSystem.h" +namespace llvm { +namespace json { +class Object; +} // end namespace json +} // end namespace llvm + namespace clang { /// The information about the darwin SDK that was used during this compilation. class DarwinSDKInfo { public: - DarwinSDKInfo(llvm::VersionTuple Version) : Version(Version) {} + /// A value that describes two os-environment pairs that can be used as a key + /// to the version map in the SDK. + struct OSEnvPair { + public: + using StorageType = uint64_t; + + constexpr OSEnvPair(llvm::Triple::OSType FromOS, + llvm::Triple::EnvironmentType FromEnv, + llvm::Triple::OSType ToOS, + llvm::Triple::EnvironmentType ToEnv) + : Value(((StorageType(FromOS) * StorageType(llvm::Triple::LastOSType) + + StorageType(FromEnv)) + << 32ull) | + (StorageType(ToOS) * StorageType(llvm::Triple::LastOSType) + + StorageType(ToEnv))) {} + + /// Returns the os-environment mapping pair that's used to represent the + /// macOS -> Mac Catalyst version mapping. + static inline constexpr OSEnvPair macOStoMacCatalystPair() { + return OSEnvPair(llvm::Triple::MacOSX, llvm::Triple::UnknownEnvironment, + llvm::Triple::IOS, llvm::Triple::MacABI); + } + + private: + StorageType Value; + + friend class DarwinSDKInfo; + }; + + /// Represents a version mapping that maps from a version of one target to a + /// version of a related target. + /// + /// e.g. "macOS_iOSMac":{"10.15":"13.1"} is an example of a macOS -> Mac + /// Catalyst version map. + class RelatedTargetVersionMapping { + public: + RelatedTargetVersionMapping( + VersionTuple MinimumKeyVersion, VersionTuple MaximumKeyVersion, + VersionTuple MinimumValue, VersionTuple MaximumValue, + llvm::DenseMap Mapping) + : MinimumKeyVersion(MinimumKeyVersion), + MaximumKeyVersion(MaximumKeyVersion), MinimumValue(MinimumValue), + MaximumValue(MaximumValue), Mapping(Mapping) { + assert(!this->Mapping.empty() && "unexpected empty mapping"); + } + + /// Returns the value with the lowest version in the mapping. + const VersionTuple &getMinimumValue() const { return MinimumValue; } + + /// Returns the mapped key, or the appropriate Minimum / MaximumValue if + /// they key is outside of the mapping bounds. If they key isn't mapped, but + /// within the minimum and maximum bounds, None is returned. + Optional map(const VersionTuple &Key, + const VersionTuple &MinimumValue, + Optional MaximumValue) const; + + static Optional + parseJSON(const llvm::json::Object &Obj, + VersionTuple MaximumDeploymentTarget); + + private: + VersionTuple MinimumKeyVersion; + VersionTuple MaximumKeyVersion; + VersionTuple MinimumValue; + VersionTuple MaximumValue; + llvm::DenseMap Mapping; + }; + + DarwinSDKInfo(VersionTuple Version, VersionTuple MaximumDeploymentTarget, + llvm::DenseMap> + VersionMappings = + llvm::DenseMap>()) + : Version(Version), MaximumDeploymentTarget(MaximumDeploymentTarget), + VersionMappings(std::move(VersionMappings)) {} const llvm::VersionTuple &getVersion() const { return Version; } + // Returns the optional, target-specific version mapping that maps from one + // target to another target. + // + // This mapping is constructed from an appropriate mapping in the SDKSettings, + // for instance, when building for Mac Catalyst, the mapping would contain the + // "macOS_iOSMac" mapping as it maps the macOS versions to the Mac Catalyst + // versions. + // + // This mapping does not exist when the target doesn't have an appropriate + // related version mapping, or when there was an error reading the mapping + // from the SDKSettings, or when it's missing in the SDKSettings. + const RelatedTargetVersionMapping *getVersionMapping(OSEnvPair Kind) const { + auto Mapping = VersionMappings.find(Kind.Value); + if (Mapping == VersionMappings.end()) + return nullptr; + return Mapping->getSecond().hasValue() ? Mapping->getSecond().getPointer() + : nullptr; + } + + static Optional + parseDarwinSDKSettingsJSON(const llvm::json::Object *Obj); + private: - llvm::VersionTuple Version; + VersionTuple Version; + VersionTuple MaximumDeploymentTarget; + // Need to wrap the value in an optional here as the value has to be default + // constructible, and std::unique_ptr doesn't like DarwinSDKInfo being + // Optional as Optional is trying to copy it in emplace. + llvm::DenseMap> + VersionMappings; }; /// Parse the SDK information from the SDKSettings.json file. diff --git a/clang/lib/Basic/DarwinSDKInfo.cpp b/clang/lib/Basic/DarwinSDKInfo.cpp --- a/clang/lib/Basic/DarwinSDKInfo.cpp +++ b/clang/lib/Basic/DarwinSDKInfo.cpp @@ -14,6 +14,89 @@ using namespace clang; +Optional DarwinSDKInfo::RelatedTargetVersionMapping::map( + const VersionTuple &Key, const VersionTuple &MinimumValue, + 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. + if (Key.getMinor()) + return map(VersionTuple(Key.getMajor()), MinimumValue, MaximumValue); + // If this a major only key, return None for a missing entry. + return None; +} + +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 None; + Mapping[KeyVersion.normalize()] = ValueVersion; + if (KeyVersion < Min) + Min = KeyVersion; + if (KeyVersion > Max) + Max = KeyVersion; + if (ValueVersion < MinValue) + MinValue = ValueVersion; + } + } + if (Mapping.empty()) + return None; + return RelatedTargetVersionMapping( + Min, Max, MinValue, MaximumDeploymentTarget, std::move(Mapping)); +} + +static Optional getVersionKey(const llvm::json::Object &Obj, + StringRef Key) { + auto Value = Obj.getString(Key); + if (!Value) + return None; + VersionTuple Version; + if (Version.tryParse(*Value)) + return None; + return Version; +} + +Optional +DarwinSDKInfo::parseDarwinSDKSettingsJSON(const llvm::json::Object *Obj) { + auto Version = getVersionKey(*Obj, "Version"); + if (!Version) + return None; + auto MaximumDeploymentVersion = + getVersionKey(*Obj, "MaximumDeploymentTarget"); + if (!MaximumDeploymentVersion) + return None; + llvm::DenseMap> + VersionMappings; + if (const auto *VM = Obj->getObject("VersionMap")) { + if (const auto *Mapping = VM->getObject("macOS_iOSMac")) { + auto VersionMap = RelatedTargetVersionMapping::parseJSON( + *Mapping, *MaximumDeploymentVersion); + if (!VersionMap) + return None; + VersionMappings[OSEnvPair::macOStoMacCatalystPair().Value] = + std::move(VersionMap); + } + } + + return DarwinSDKInfo(std::move(*Version), + std::move(*MaximumDeploymentVersion), + std::move(VersionMappings)); +} + Expected> clang::parseDarwinSDKInfo(llvm::vfs::FileSystem &VFS, StringRef SDKRootPath) { llvm::SmallString<256> Filepath = SDKRootPath; @@ -30,11 +113,13 @@ return Result.takeError(); if (const auto *Obj = Result->getAsObject()) { + // FIXME: Switch to use parseDarwinSDKSettingsJSON in the next commit. auto VersionString = Obj->getString("Version"); if (VersionString) { VersionTuple Version; if (!Version.tryParse(*VersionString)) - return DarwinSDKInfo(Version); + return DarwinSDKInfo(Version, Version); + } } } return llvm::make_error("invalid SDKSettings.json", diff --git a/clang/lib/Driver/ToolChains/Darwin.cpp b/clang/lib/Driver/ToolChains/Darwin.cpp --- a/clang/lib/Driver/ToolChains/Darwin.cpp +++ b/clang/lib/Driver/ToolChains/Darwin.cpp @@ -1506,7 +1506,9 @@ bool IsValid = !Version.tryParse(OSVersion); (void)IsValid; assert(IsValid && "invalid SDK version"); - return DarwinSDKInfo(Version); + return DarwinSDKInfo( + Version, + /*MaximumDeploymentTarget=*/VersionTuple(Version.getMajor(), 0, 99)); } private: diff --git a/clang/unittests/Basic/CMakeLists.txt b/clang/unittests/Basic/CMakeLists.txt --- a/clang/unittests/Basic/CMakeLists.txt +++ b/clang/unittests/Basic/CMakeLists.txt @@ -4,6 +4,7 @@ add_clang_unittest(BasicTests CharInfoTest.cpp + DarwinSDKInfoTest.cpp DiagnosticTest.cpp FileEntryTest.cpp FileManagerTest.cpp diff --git a/clang/unittests/Basic/DarwinSDKinfoTest.cpp b/clang/unittests/Basic/DarwinSDKinfoTest.cpp new file mode 100644 --- /dev/null +++ b/clang/unittests/Basic/DarwinSDKinfoTest.cpp @@ -0,0 +1,57 @@ +//===- unittests/Basic/DarwinSDKInfoTest.cpp -- SDKSettings.json test -----===// +// +// 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/Support/JSON.h" +#include "gtest/gtest.h" + +using namespace llvm; +using namespace clang; + +TEST(DarwinSDKInfoTest, ParseAndTestMapping) { + llvm::json::Object Obj; + Obj["Version"] = "11.0"; + Obj["MaximumDeploymentTarget"] = "11.99"; + llvm::json::Object VersionMap; + VersionMap["10.15"] = "13.1"; + VersionMap["11.0"] = "14.0"; + VersionMap["11.2"] = "14.2"; + llvm::json::Object MacOS2iOSMac; + MacOS2iOSMac["macOS_iOSMac"] = std::move(VersionMap); + Obj["VersionMap"] = std::move(MacOS2iOSMac); + + auto SDKInfo = DarwinSDKInfo::parseDarwinSDKSettingsJSON(&Obj); + ASSERT_TRUE(SDKInfo); + EXPECT_EQ(SDKInfo->getVersion(), VersionTuple(11, 0)); + + auto Mapping = SDKInfo->getVersionMapping( + DarwinSDKInfo::OSEnvPair::macOStoMacCatalystPair()); + ASSERT_TRUE(Mapping); + EXPECT_EQ(*Mapping->map(VersionTuple(10, 15), VersionTuple(), None), + VersionTuple(13, 1)); + EXPECT_EQ(*Mapping->map(VersionTuple(11, 0), VersionTuple(), None), + VersionTuple(14, 0)); + EXPECT_EQ(*Mapping->map(VersionTuple(11, 2), VersionTuple(), None), + VersionTuple(14, 2)); + + EXPECT_EQ(*Mapping->map(VersionTuple(11, 1), VersionTuple(), None), + VersionTuple(14, 0)); + + EXPECT_EQ(*Mapping->map(VersionTuple(10, 14), VersionTuple(99, 99), None), + VersionTuple(99, 99)); + EXPECT_EQ( + *Mapping->map(VersionTuple(11, 5), VersionTuple(), VersionTuple(99, 99)), + VersionTuple(99, 99)); +} + +TEST(DarwinSDKInfoTest, MissingKeys) { + llvm::json::Object Obj; + ASSERT_FALSE(DarwinSDKInfo::parseDarwinSDKSettingsJSON(&Obj)); + Obj["Version"] = "11.0"; + ASSERT_FALSE(DarwinSDKInfo::parseDarwinSDKSettingsJSON(&Obj)); +} diff --git a/llvm/include/llvm/Support/VersionTuple.h b/llvm/include/llvm/Support/VersionTuple.h --- a/llvm/include/llvm/Support/VersionTuple.h +++ b/llvm/include/llvm/Support/VersionTuple.h @@ -14,6 +14,7 @@ #ifndef LLVM_SUPPORT_VERSIONTUPLE_H #define LLVM_SUPPORT_VERSIONTUPLE_H +#include "llvm/ADT/DenseMapInfo.h" #include "llvm/ADT/Hashing.h" #include "llvm/ADT/Optional.h" #include @@ -95,6 +96,20 @@ return *this; } + /// Return a version tuple that contains only components that are non-zero. + VersionTuple normalize() const { + VersionTuple Result = *this; + if (Result.Build == 0) { + Result.HasBuild = false; + if (Result.Subminor == 0) { + Result.HasSubminor = false; + if (Result.Minor == 0) + Result.HasMinor = false; + } + } + return Result; + } + /// Determine if two version numbers are equivalent. If not /// provided, minor and subminor version numbers are considered to be zero. friend bool operator==(const VersionTuple &X, const VersionTuple &Y) { @@ -161,5 +176,28 @@ /// Print a version number. raw_ostream &operator<<(raw_ostream &Out, const VersionTuple &V); +// Provide DenseMapInfo for version tuples. +template <> struct DenseMapInfo { + static inline VersionTuple getEmptyKey() { return VersionTuple(0x7FFFFFFF); } + static inline VersionTuple getTombstoneKey() { + return VersionTuple(0x7FFFFFFE); + } + static unsigned getHashValue(const VersionTuple &Value) { + unsigned Result = Value.getMajor(); + if (auto Minor = Value.getMinor()) + Result = detail::combineHashValue(Result, *Minor); + if (auto Subminor = Value.getSubminor()) + Result = detail::combineHashValue(Result, *Subminor); + if (auto Build = Value.getBuild()) + Result = detail::combineHashValue(Result, *Build); + + return Result; + } + + static bool isEqual(const VersionTuple &LHS, const VersionTuple &RHS) { + return LHS == RHS; + } +}; + } // end namespace llvm #endif // LLVM_SUPPORT_VERSIONTUPLE_H