1 //===- tools/dsymutil/CFBundle.cpp - CFBundle helper ------------*- C++ -*-===//
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
7 //===----------------------------------------------------------------------===//
12 #include "llvm/Support/FileSystem.h"
13 #include "llvm/Support/Path.h"
14 #include "llvm/Support/raw_ostream.h"
15 #include <CoreFoundation/CoreFoundation.h>
25 /// Deleter that calls CFRelease rather than deleting the pointer.
26 template <typename T
> struct CFDeleter
{
27 void operator()(T
*P
) {
33 /// This helper owns any CoreFoundation pointer and will call CFRelease() on
34 /// any valid pointer it owns unless that pointer is explicitly released using
35 /// the release() member function.
37 using CFReleaser
= std::unique_ptr
<std::remove_pointer_t
<T
>,
38 CFDeleter
<std::remove_pointer_t
<T
>>>;
40 /// RAII wrapper around CFBundleRef.
41 class CFString
: public CFReleaser
<CFStringRef
> {
43 CFString(CFStringRef CFStr
= nullptr) : CFReleaser
<CFStringRef
>(CFStr
) {}
45 const char *UTF8(std::string
&Str
) const {
46 return CFString::UTF8(get(), Str
);
49 CFIndex
GetLength() const {
50 if (CFStringRef Str
= get())
51 return CFStringGetLength(Str
);
55 static const char *UTF8(CFStringRef CFStr
, std::string
&Str
);
58 /// Static function that puts a copy of the UTF-8 contents of CFStringRef into
59 /// std::string and returns the C string pointer that is contained in the
60 /// std::string when successful, nullptr otherwise.
62 /// This allows the std::string parameter to own the extracted string, and also
63 /// allows that string to be returned as a C string pointer that can be used.
64 const char *CFString::UTF8(CFStringRef CFStr
, std::string
&Str
) {
68 const CFStringEncoding Encoding
= kCFStringEncodingUTF8
;
69 CFIndex MaxUTF8StrLength
= CFStringGetLength(CFStr
);
71 CFStringGetMaximumSizeForEncoding(MaxUTF8StrLength
, Encoding
);
72 if (MaxUTF8StrLength
> 0) {
73 Str
.resize(MaxUTF8StrLength
);
75 CFStringGetCString(CFStr
, &Str
[0], Str
.size(), Encoding
)) {
76 Str
.resize(strlen(Str
.c_str()));
84 /// RAII wrapper around CFBundleRef.
85 class CFBundle
: public CFReleaser
<CFBundleRef
> {
87 CFBundle(StringRef Path
) : CFReleaser
<CFBundleRef
>() { SetFromPath(Path
); }
89 CFBundle(CFURLRef Url
)
90 : CFReleaser
<CFBundleRef
>(Url
? ::CFBundleCreate(nullptr, Url
)
93 /// Return the bundle identifier.
94 CFStringRef
GetIdentifier() const {
95 if (CFBundleRef bundle
= get())
96 return ::CFBundleGetIdentifier(bundle
);
100 /// Return value for key.
101 CFTypeRef
GetValueForInfoDictionaryKey(CFStringRef key
) const {
102 if (CFBundleRef bundle
= get())
103 return ::CFBundleGetValueForInfoDictionaryKey(bundle
, key
);
108 /// Helper to initialize this instance with a new bundle created from the
109 /// given path. This function will recursively remove components from the
110 /// path in its search for the nearest Info.plist.
111 void SetFromPath(StringRef Path
);
114 void CFBundle::SetFromPath(StringRef Path
) {
115 // Start from an empty/invalid CFBundle.
118 if (Path
.empty() || !sys::fs::exists(Path
))
121 SmallString
<256> RealPath
;
122 sys::fs::real_path(Path
, RealPath
, /*expand_tilde*/ true);
125 // Create a CFURL from the current path and use it to create a CFBundle.
126 CFReleaser
<CFURLRef
> BundleURL(::CFURLCreateFromFileSystemRepresentation(
127 kCFAllocatorDefault
, (const UInt8
*)RealPath
.data(), RealPath
.size(),
129 reset(::CFBundleCreate(kCFAllocatorDefault
, BundleURL
.get()));
131 // If we have a valid bundle and find its identifier we are done.
132 if (get() != nullptr) {
133 if (GetIdentifier() != nullptr)
138 // Remove the last component of the path and try again until there's
139 // nothing left but the root.
140 sys::path::remove_filename(RealPath
);
141 } while (RealPath
!= sys::path::root_name(RealPath
));
145 /// On Darwin, try and find the original executable's Info.plist to extract
146 /// information about the bundle. Return default values on other platforms.
147 CFBundleInfo
getBundleInfo(StringRef ExePath
) {
148 CFBundleInfo BundleInfo
;
151 auto PrintError
= [&](CFTypeID TypeID
) {
152 CFString
TypeIDCFStr(::CFCopyTypeIDDescription(TypeID
));
153 std::string TypeIDStr
;
154 errs() << "The Info.plist key \"CFBundleShortVersionString\" is"
155 << "a " << TypeIDCFStr
.UTF8(TypeIDStr
)
156 << ", but it should be a string in: " << ExePath
<< ".\n";
159 CFBundle
Bundle(ExePath
);
160 if (CFStringRef BundleID
= Bundle
.GetIdentifier()) {
161 CFString::UTF8(BundleID
, BundleInfo
.IDStr
);
162 if (CFTypeRef TypeRef
=
163 Bundle
.GetValueForInfoDictionaryKey(CFSTR("CFBundleVersion"))) {
164 CFTypeID TypeID
= ::CFGetTypeID(TypeRef
);
165 if (TypeID
== ::CFStringGetTypeID())
166 CFString::UTF8((CFStringRef
)TypeRef
, BundleInfo
.VersionStr
);
170 if (CFTypeRef TypeRef
= Bundle
.GetValueForInfoDictionaryKey(
171 CFSTR("CFBundleShortVersionString"))) {
172 CFTypeID TypeID
= ::CFGetTypeID(TypeRef
);
173 if (TypeID
== ::CFStringGetTypeID())
174 CFString::UTF8((CFStringRef
)TypeRef
, BundleInfo
.ShortVersionStr
);
184 } // end namespace dsymutil
185 } // end namespace llvm