CFBundle.cpp 5.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185
  1. //===- tools/dsymutil/CFBundle.cpp - CFBundle helper ------------*- C++ -*-===//
  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. #include "CFBundle.h"
  9. #ifdef __APPLE__
  10. #include "llvm/Support/FileSystem.h"
  11. #include "llvm/Support/Path.h"
  12. #include "llvm/Support/raw_ostream.h"
  13. #include <CoreFoundation/CoreFoundation.h>
  14. #include <assert.h>
  15. #include <glob.h>
  16. #include <memory>
  17. #endif
  18. namespace llvm {
  19. namespace dsymutil {
  20. #ifdef __APPLE__
  21. /// Deleter that calls CFRelease rather than deleting the pointer.
  22. template <typename T> struct CFDeleter {
  23. void operator()(T *P) {
  24. if (P)
  25. ::CFRelease(P);
  26. }
  27. };
  28. /// This helper owns any CoreFoundation pointer and will call CFRelease() on
  29. /// any valid pointer it owns unless that pointer is explicitly released using
  30. /// the release() member function.
  31. template <typename T>
  32. using CFReleaser = std::unique_ptr<std::remove_pointer_t<T>,
  33. CFDeleter<std::remove_pointer_t<T>>>;
  34. /// RAII wrapper around CFBundleRef.
  35. class CFString : public CFReleaser<CFStringRef> {
  36. public:
  37. CFString(CFStringRef CFStr = nullptr) : CFReleaser<CFStringRef>(CFStr) {}
  38. const char *UTF8(std::string &Str) const {
  39. return CFString::UTF8(get(), Str);
  40. }
  41. CFIndex GetLength() const {
  42. if (CFStringRef Str = get())
  43. return CFStringGetLength(Str);
  44. return 0;
  45. }
  46. static const char *UTF8(CFStringRef CFStr, std::string &Str);
  47. };
  48. /// Static function that puts a copy of the UTF-8 contents of CFStringRef into
  49. /// std::string and returns the C string pointer that is contained in the
  50. /// std::string when successful, nullptr otherwise.
  51. ///
  52. /// This allows the std::string parameter to own the extracted string, and also
  53. /// allows that string to be returned as a C string pointer that can be used.
  54. const char *CFString::UTF8(CFStringRef CFStr, std::string &Str) {
  55. if (!CFStr)
  56. return nullptr;
  57. const CFStringEncoding Encoding = kCFStringEncodingUTF8;
  58. CFIndex MaxUTF8StrLength = CFStringGetLength(CFStr);
  59. MaxUTF8StrLength =
  60. CFStringGetMaximumSizeForEncoding(MaxUTF8StrLength, Encoding);
  61. if (MaxUTF8StrLength > 0) {
  62. Str.resize(MaxUTF8StrLength);
  63. if (!Str.empty() &&
  64. CFStringGetCString(CFStr, &Str[0], Str.size(), Encoding)) {
  65. Str.resize(strlen(Str.c_str()));
  66. return Str.c_str();
  67. }
  68. }
  69. return nullptr;
  70. }
  71. /// RAII wrapper around CFBundleRef.
  72. class CFBundle : public CFReleaser<CFBundleRef> {
  73. public:
  74. CFBundle(StringRef Path) : CFReleaser<CFBundleRef>() { SetFromPath(Path); }
  75. CFBundle(CFURLRef Url)
  76. : CFReleaser<CFBundleRef>(Url ? ::CFBundleCreate(nullptr, Url)
  77. : nullptr) {}
  78. /// Return the bundle identifier.
  79. CFStringRef GetIdentifier() const {
  80. if (CFBundleRef bundle = get())
  81. return ::CFBundleGetIdentifier(bundle);
  82. return nullptr;
  83. }
  84. /// Return value for key.
  85. CFTypeRef GetValueForInfoDictionaryKey(CFStringRef key) const {
  86. if (CFBundleRef bundle = get())
  87. return ::CFBundleGetValueForInfoDictionaryKey(bundle, key);
  88. return nullptr;
  89. }
  90. private:
  91. /// Helper to initialize this instance with a new bundle created from the
  92. /// given path. This function will recursively remove components from the
  93. /// path in its search for the nearest Info.plist.
  94. void SetFromPath(StringRef Path);
  95. };
  96. void CFBundle::SetFromPath(StringRef Path) {
  97. // Start from an empty/invalid CFBundle.
  98. reset();
  99. if (Path.empty() || !sys::fs::exists(Path))
  100. return;
  101. SmallString<256> RealPath;
  102. sys::fs::real_path(Path, RealPath, /*expand_tilde*/ true);
  103. do {
  104. // Create a CFURL from the current path and use it to create a CFBundle.
  105. CFReleaser<CFURLRef> BundleURL(::CFURLCreateFromFileSystemRepresentation(
  106. kCFAllocatorDefault, (const UInt8 *)RealPath.data(), RealPath.size(),
  107. false));
  108. reset(::CFBundleCreate(kCFAllocatorDefault, BundleURL.get()));
  109. // If we have a valid bundle and find its identifier we are done.
  110. if (get() != nullptr) {
  111. if (GetIdentifier() != nullptr)
  112. return;
  113. reset();
  114. }
  115. // Remove the last component of the path and try again until there's
  116. // nothing left but the root.
  117. sys::path::remove_filename(RealPath);
  118. } while (RealPath != sys::path::root_name(RealPath));
  119. }
  120. #endif
  121. /// On Darwin, try and find the original executable's Info.plist to extract
  122. /// information about the bundle. Return default values on other platforms.
  123. CFBundleInfo getBundleInfo(StringRef ExePath) {
  124. CFBundleInfo BundleInfo;
  125. #ifdef __APPLE__
  126. auto PrintError = [&](CFTypeID TypeID) {
  127. CFString TypeIDCFStr(::CFCopyTypeIDDescription(TypeID));
  128. std::string TypeIDStr;
  129. errs() << "The Info.plist key \"CFBundleShortVersionString\" is"
  130. << "a " << TypeIDCFStr.UTF8(TypeIDStr)
  131. << ", but it should be a string in: " << ExePath << ".\n";
  132. };
  133. CFBundle Bundle(ExePath);
  134. if (CFStringRef BundleID = Bundle.GetIdentifier()) {
  135. CFString::UTF8(BundleID, BundleInfo.IDStr);
  136. if (CFTypeRef TypeRef =
  137. Bundle.GetValueForInfoDictionaryKey(CFSTR("CFBundleVersion"))) {
  138. CFTypeID TypeID = ::CFGetTypeID(TypeRef);
  139. if (TypeID == ::CFStringGetTypeID())
  140. CFString::UTF8((CFStringRef)TypeRef, BundleInfo.VersionStr);
  141. else
  142. PrintError(TypeID);
  143. }
  144. if (CFTypeRef TypeRef = Bundle.GetValueForInfoDictionaryKey(
  145. CFSTR("CFBundleShortVersionString"))) {
  146. CFTypeID TypeID = ::CFGetTypeID(TypeRef);
  147. if (TypeID == ::CFStringGetTypeID())
  148. CFString::UTF8((CFStringRef)TypeRef, BundleInfo.ShortVersionStr);
  149. else
  150. PrintError(TypeID);
  151. }
  152. }
  153. #endif
  154. return BundleInfo;
  155. }
  156. } // end namespace dsymutil
  157. } // end namespace llvm