blob: 15ee8011a4a4815b79f5b9204c5d54e0502906f2 [file] [log] [blame]
Jonas Devliegherebdc984c2017-11-30 10:25:28 +00001//===- 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"
Jonas Devlieghereb8b7cba2017-12-28 14:05:49 +000014#include "llvm/Support/Path.h"
Jonas Devliegherebdc984c2017-11-30 10:25:28 +000015#include "llvm/Support/raw_ostream.h"
16#include <CoreFoundation/CoreFoundation.h>
17#include <assert.h>
18#include <glob.h>
19#include <memory>
Jonas Devlieghere6bb5dbc2017-11-30 10:41:31 +000020#endif
Jonas Devliegherebdc984c2017-11-30 10:25:28 +000021
22namespace llvm {
23namespace dsymutil {
24
Jonas Devlieghere6bb5dbc2017-11-30 10:41:31 +000025#ifdef __APPLE__
Jonas Devliegherebdc984c2017-11-30 10:25:28 +000026/// Deleter that calls CFRelease rather than deleting the pointer.
27template <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.
37template <typename T>
38using 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.
43class CFString : public CFReleaser<CFStringRef> {
44public:
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
Jonas Devlieghereb8b7cba2017-12-28 14:05:49 +000060/// Static function that puts a copy of the UTF-8 contents of CFStringRef into
Jonas Devliegherebdc984c2017-11-30 10:25:28 +000061/// 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.
66const 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.
87class CFBundle : public CFReleaser<CFBundleRef> {
88public:
Jonas Devlieghereb8b7cba2017-12-28 14:05:49 +000089 CFBundle(StringRef Path) : CFReleaser<CFBundleRef>() { SetFromPath(Path); }
Jonas Devliegherebdc984c2017-11-30 10:25:28 +000090
Jonas Devlieghereb8b7cba2017-12-28 14:05:49 +000091 CFBundle(CFURLRef Url)
92 : CFReleaser<CFBundleRef>(Url ? ::CFBundleCreate(nullptr, Url)
Jonas Devliegherebdc984c2017-11-30 10:25:28 +000093 : 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
109private:
Jonas Devlieghereb8b7cba2017-12-28 14:05:49 +0000110 /// 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);
Jonas Devliegherebdc984c2017-11-30 10:25:28 +0000114};
115
Jonas Devlieghereb8b7cba2017-12-28 14:05:49 +0000116void CFBundle::SetFromPath(StringRef Path) {
117 // Start from an empty/invalid CFBundle.
Jonas Devliegherebdc984c2017-11-30 10:25:28 +0000118 reset();
119
Jonas Devlieghereb8b7cba2017-12-28 14:05:49 +0000120 if (Path.empty() || !sys::fs::exists(Path))
121 return;
Jonas Devliegherebdc984c2017-11-30 10:25:28 +0000122
Jonas Devlieghereb8b7cba2017-12-28 14:05:49 +0000123 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.
Jonas Devliegherebdc984c2017-11-30 10:25:28 +0000128 CFReleaser<CFURLRef> BundleURL(::CFURLCreateFromFileSystemRepresentation(
Jonas Devlieghereb8b7cba2017-12-28 14:05:49 +0000129 kCFAllocatorDefault, (const UInt8 *)RealPath.data(), RealPath.size(),
130 false));
131 reset(::CFBundleCreate(kCFAllocatorDefault, BundleURL.get()));
Jonas Devliegherebdc984c2017-11-30 10:25:28 +0000132
Jonas Devlieghereb8b7cba2017-12-28 14:05:49 +0000133 // 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();
Jonas Devliegherebdc984c2017-11-30 10:25:28 +0000138 }
Jonas Devliegherebdc984c2017-11-30 10:25:28 +0000139
Jonas Devlieghereb8b7cba2017-12-28 14:05:49 +0000140 // 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));
Jonas Devliegherebdc984c2017-11-30 10:25:28 +0000144}
Jonas Devliegherebdc984c2017-11-30 10:25:28 +0000145#endif
146
Jonas Devlieghereb8b7cba2017-12-28 14:05:49 +0000147/// On Darwin, try and find the original executable's Info.plist to extract
148/// information about the bundle. Return default values on other platforms.
Jonas Devliegherebdc984c2017-11-30 10:25:28 +0000149CFBundleInfo getBundleInfo(StringRef ExePath) {
150 CFBundleInfo BundleInfo;
151
152#ifdef __APPLE__
Jonas Devliegherebdc984c2017-11-30 10:25:28 +0000153 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
Jonas Devlieghereb8b7cba2017-12-28 14:05:49 +0000161 CFBundle Bundle(ExePath);
Jonas Devliegherebdc984c2017-11-30 10:25:28 +0000162 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