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