diff --git a/test/tools/dsymutil/Inputs/Info.plist b/test/tools/dsymutil/Inputs/Info.plist
new file mode 100644
index 00000000000..97c0ae261f3
--- /dev/null
+++ b/test/tools/dsymutil/Inputs/Info.plist
@@ -0,0 +1,20 @@
+
+
+
+
+ CFBundleDevelopmentRegion
+ English
+ CFBundleIdentifier
+ custom
+ CFBundleInfoDictionaryVersion
+ 6.0
+ CFBundlePackageType
+ dSYM
+ CFBundleSignature
+ ????
+ CFBundleShortVersionString
+ 2.0
+ CFBundleVersion
+ 2
+
+
diff --git a/test/tools/dsymutil/X86/darwin-bundle.test b/test/tools/dsymutil/X86/darwin-bundle.test
new file mode 100644
index 00000000000..b49cdeab858
--- /dev/null
+++ b/test/tools/dsymutil/X86/darwin-bundle.test
@@ -0,0 +1,30 @@
+REQUIRES: system-darwin
+
+RUN: rm -rf %t
+RUN: mkdir -p %t/dsymdest
+RUN: cat %p/../Inputs/basic.macho.x86_64 > %t/basic.macho.x86_64
+RUN: cat %p/../Inputs/Info.plist > %t/Info.plist
+
+RUN: llvm-dsymutil -oso-prepend-path=%p/.. %t/basic.macho.x86_64 -o %t/dsymdest/basic.macho.x86_64.dSYM
+RUN: FileCheck %s --input-file %t/dsymdest/basic.macho.x86_64.dSYM/Contents/Info.plist
+
+CHECK:
+CHECK-NEXT:
+CHECK-NEXT:
+CHECK-NEXT:
+CHECK-NEXT: CFBundleDevelopmentRegion
+CHECK-NEXT: English
+CHECK-NEXT: CFBundleIdentifier
+CHECK-NEXT: com.apple.xcode.dsym.custom
+CHECK-NEXT: CFBundleInfoDictionaryVersion
+CHECK-NEXT: 6.0
+CHECK-NEXT: CFBundlePackageType
+CHECK-NEXT: dSYM
+CHECK-NEXT: CFBundleSignature
+CHECK-NEXT: ????
+CHECK-NEXT: CFBundleShortVersionString
+CHECK-NEXT: 2.0
+CHECK-NEXT: CFBundleVersion
+CHECK-NEXT: 2
+CHECK-NEXT:
+CHECK-NEXT:
diff --git a/tools/dsymutil/CFBundle.cpp b/tools/dsymutil/CFBundle.cpp
new file mode 100644
index 00000000000..0155dd89d22
--- /dev/null
+++ b/tools/dsymutil/CFBundle.cpp
@@ -0,0 +1,205 @@
+//===- tools/dsymutil/CFBundle.cpp - CFBundle helper ------------*- C++ -*-===//
+//
+// The LLVM Compiler Infrastructure
+//
+// This file is distributed under the University of Illinois Open Source
+// License. See LICENSE.TXT for details.
+//
+//===----------------------------------------------------------------------===//
+
+#include "CFBundle.h"
+
+#ifdef __APPLE__
+#include "llvm/Support/FileSystem.h"
+#include "llvm/Support/raw_ostream.h"
+#include
+#include
+#include
+#include
+
+namespace llvm {
+namespace dsymutil {
+
+/// Deleter that calls CFRelease rather than deleting the pointer.
+template 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
+using CFReleaser =
+ std::unique_ptr::type,
+ CFDeleter::type>>;
+
+/// RAII wrapper around CFBundleRef.
+class CFString : public CFReleaser {
+public:
+ CFString(CFStringRef CFStr = nullptr) : CFReleaser(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 UTF8 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 {
+public:
+ CFBundle(const char *Path = nullptr) : CFReleaser() {
+ if (Path && Path[0])
+ SetFromPath(Path);
+ }
+
+ CFBundle(CFURLRef url)
+ : CFReleaser(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:
+ /// Update this instance with a new bundle created from the given path.
+ bool SetFromPath(const char *Path);
+};
+
+bool CFBundle::SetFromPath(const char *InPath) {
+ // Release our old bundle and URL.
+ reset();
+
+ if (InPath && InPath[0]) {
+ char ResolvedPath[PATH_MAX];
+ const char *Path = ::realpath(InPath, ResolvedPath);
+ if (Path == nullptr)
+ Path = InPath;
+
+ CFAllocatorRef Allocator = kCFAllocatorDefault;
+ // Make our Bundle URL.
+ CFReleaser BundleURL(::CFURLCreateFromFileSystemRepresentation(
+ Allocator, (const UInt8 *)Path, strlen(Path), false));
+ if (BundleURL.get()) {
+ CFIndex LastLength = LONG_MAX;
+
+ while (BundleURL.get() != nullptr) {
+ // Check the Path range and make sure we didn't make it to just "/",
+ // ".", or "..".
+ CFRange rangeIncludingSeparators;
+ CFRange range = ::CFURLGetByteRangeForComponent(
+ BundleURL.get(), kCFURLComponentPath, &rangeIncludingSeparators);
+ if (range.length > LastLength)
+ break;
+
+ reset(::CFBundleCreate(Allocator, BundleURL.get()));
+ if (get() != nullptr) {
+ if (GetIdentifier() != nullptr)
+ break;
+ reset();
+ }
+ BundleURL.reset(::CFURLCreateCopyDeletingLastPathComponent(
+ Allocator, BundleURL.get()));
+
+ LastLength = range.length;
+ }
+ }
+ }
+
+ return get() != nullptr;
+}
+
+#endif
+
+/// On Darwin, try and find the original executable's Info.plist information
+/// using CoreFoundation calls by creating a URL for the executable and
+/// chopping off the last Path component. The CFBundle can then get the
+/// identifier and grab any needed information from it directly. Return default
+/// CFBundleInfo on other platforms.
+CFBundleInfo getBundleInfo(StringRef ExePath) {
+ CFBundleInfo BundleInfo;
+
+#ifdef __APPLE__
+ if (ExePath.empty() || !sys::fs::exists(ExePath))
+ return BundleInfo;
+
+ 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.data());
+ 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
diff --git a/tools/dsymutil/CFBundle.h b/tools/dsymutil/CFBundle.h
new file mode 100644
index 00000000000..bdbecb4785c
--- /dev/null
+++ b/tools/dsymutil/CFBundle.h
@@ -0,0 +1,26 @@
+//===- tools/dsymutil/CFBundle.h - CFBundle helper --------------*- C++ -*-===//
+//
+// The LLVM Compiler Infrastructure
+//
+// This file is distributed under the University of Illinois Open Source
+// License. See LICENSE.TXT for details.
+//
+//===----------------------------------------------------------------------===//
+
+#include "llvm/ADT/StringRef.h"
+#include
+
+namespace llvm {
+namespace dsymutil {
+
+struct CFBundleInfo {
+ std::string VersionStr = "1";
+ std::string ShortVersionStr = "1.0";
+ std::string IDStr;
+ bool OmitShortVersion() const { return ShortVersionStr.empty(); }
+};
+
+CFBundleInfo getBundleInfo(llvm::StringRef ExePath);
+
+} // end namespace dsymutil
+} // end namespace llvm
diff --git a/tools/dsymutil/CMakeLists.txt b/tools/dsymutil/CMakeLists.txt
index 3a9b29326b3..61d78b5094a 100644
--- a/tools/dsymutil/CMakeLists.txt
+++ b/tools/dsymutil/CMakeLists.txt
@@ -11,6 +11,7 @@ set(LLVM_LINK_COMPONENTS
add_llvm_tool(llvm-dsymutil
dsymutil.cpp
BinaryHolder.cpp
+ CFBundle.cpp
DebugMap.cpp
DwarfLinker.cpp
MachODebugMapParser.cpp
@@ -20,3 +21,6 @@ add_llvm_tool(llvm-dsymutil
intrinsics_gen
)
+IF(APPLE)
+ target_link_libraries(llvm-dsymutil "-framework CoreFoundation")
+ENDIF(APPLE)
diff --git a/tools/dsymutil/dsymutil.cpp b/tools/dsymutil/dsymutil.cpp
index 0f43e7919e8..5696f560217 100644
--- a/tools/dsymutil/dsymutil.cpp
+++ b/tools/dsymutil/dsymutil.cpp
@@ -13,6 +13,7 @@
//===----------------------------------------------------------------------===//
#include "dsymutil.h"
+#include "CFBundle.h"
#include "DebugMap.h"
#include "MachOUtils.h"
#include "llvm/ADT/SmallString.h"
@@ -113,7 +114,7 @@ static opt InputIsYAMLDebugMap(
"y", desc("Treat the input file is a YAML debug map rather than a binary."),
init(false), cat(DsymCategory));
-static bool createPlistFile(llvm::StringRef BundleRoot) {
+static bool createPlistFile(llvm::StringRef Bin, llvm::StringRef BundleRoot) {
if (NoOutput)
return true;
@@ -128,16 +129,15 @@ static bool createPlistFile(llvm::StringRef BundleRoot) {
return false;
}
- // FIXME: Use CoreFoundation to get executable bundle info. Use
- // dummy values for now.
- std::string bundleVersionStr = "1", bundleShortVersionStr = "1.0",
- bundleIDStr;
+ CFBundleInfo BI = getBundleInfo(Bin);
- llvm::StringRef BundleID = *llvm::sys::path::rbegin(BundleRoot);
- if (llvm::sys::path::extension(BundleRoot) == ".dSYM")
- bundleIDStr = llvm::sys::path::stem(BundleID);
- else
- bundleIDStr = BundleID;
+ if (BI.IDStr.empty()) {
+ llvm::StringRef BundleID = *llvm::sys::path::rbegin(BundleRoot);
+ if (llvm::sys::path::extension(BundleRoot) == ".dSYM")
+ BI.IDStr = llvm::sys::path::stem(BundleID);
+ else
+ BI.IDStr = BundleID;
+ }
// Print out information to the plist file.
PL << "\n"
@@ -148,17 +148,20 @@ static bool createPlistFile(llvm::StringRef BundleRoot) {
<< "\t\tCFBundleDevelopmentRegion\n"
<< "\t\tEnglish\n"
<< "\t\tCFBundleIdentifier\n"
- << "\t\tcom.apple.xcode.dsym." << bundleIDStr << "\n"
+ << "\t\tcom.apple.xcode.dsym." << BI.IDStr << "\n"
<< "\t\tCFBundleInfoDictionaryVersion\n"
<< "\t\t6.0\n"
<< "\t\tCFBundlePackageType\n"
<< "\t\tdSYM\n"
<< "\t\tCFBundleSignature\n"
- << "\t\t\?\?\?\?\n"
- << "\t\tCFBundleShortVersionString\n"
- << "\t\t" << bundleShortVersionStr << "\n"
- << "\t\tCFBundleVersion\n"
- << "\t\t" << bundleVersionStr << "\n"
+ << "\t\t\?\?\?\?\n";
+
+ if (!BI.OmitShortVersion())
+ PL << "\t\tCFBundleShortVersionString\n"
+ << "\t\t" << BI.ShortVersionStr << "\n";
+
+ PL << "\t\tCFBundleVersion\n"
+ << "\t\t" << BI.VersionStr << "\n"
<< "\t\n"
<< "\n";
@@ -206,7 +209,7 @@ static std::string getOutputFileName(llvm::StringRef InputFile) {
llvm::SmallString<128> BundleDir(OutputFileOpt);
if (BundleDir.empty())
BundleDir = DwarfFile + ".dSYM";
- if (!createBundleDir(BundleDir) || !createPlistFile(BundleDir))
+ if (!createBundleDir(BundleDir) || !createPlistFile(DwarfFile, BundleDir))
return "";
llvm::sys::path::append(BundleDir, "Contents", "Resources", "DWARF",