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