Index: test/tools/dsymutil/X86/darwin-bundle.test =================================================================== --- /dev/null +++ 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: Index: tools/dsymutil/CFBundle.h =================================================================== --- /dev/null +++ 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 Index: tools/dsymutil/CFBundle.cpp =================================================================== --- /dev/null +++ tools/dsymutil/CFBundle.cpp @@ -0,0 +1,296 @@ +//===- 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 { + +template struct CFDeleter { + void operator()(T *P) { + if (P) + ::CFRelease(P); + } +}; + +/// This CF helper owns any CF 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>>; + +class CFString : public CFReleaser { +public: + CFString(CFStringRef CFStr = nullptr); + CFString(const char *s, CFStringEncoding Encoding); + + const char *GetFileSystemRepresentation(std::string &Str); + CFStringRef SetFileSystemRepresentation(const char *Path); + CFStringRef SetFileSystemRepresentationFromCFType(CFTypeRef CFType); + CFStringRef SetFileSystemRepresentationAndExpandTilde(const char *Path); + const char *UTF8(std::string &Str); + CFIndex GetLength() const; + static const char *UTF8(CFStringRef CFStr, std::string &Str); + static const char *FileSystemRepresentation(CFStringRef CFStr, + std::string &Str); +}; + +class CFBundle : public CFReleaser { +public: + CFBundle(const char *Path = nullptr); + CFBundle(CFURLRef url); + + bool SetPath(const char *Path); + CFStringRef GetIdentifier() const; + CFTypeRef GetValueForInfoDictionaryKey(CFStringRef key) const; +}; + +static const char *CreatePathByExpandingTildePath(const char *Path, + std::string &ExpandedPath) { + glob_t GlobBuffer; + if (glob(Path, GLOB_TILDE, nullptr, &GlobBuffer) == 0) { + ExpandedPath = GlobBuffer.gl_pathv[0]; + globfree(&GlobBuffer); + } else + ExpandedPath.clear(); + + return ExpandedPath.c_str(); +} + +CFString::CFString(CFStringRef s) : CFReleaser(s) {} + +CFString::CFString(const char *cstr, CFStringEncoding cstr_encoding) + : CFReleaser() { + if (cstr && cstr[0]) { + reset( + ::CFStringCreateWithCString(kCFAllocatorDefault, cstr, cstr_encoding)); + } +} + +const char *CFString::GetFileSystemRepresentation(std::string &Str) { + return CFString::FileSystemRepresentation(get(), Str); +} + +CFStringRef CFString::SetFileSystemRepresentation(const char *Path) { + CFStringRef NewValue = nullptr; + if (Path && Path[0]) + NewValue = + ::CFStringCreateWithFileSystemRepresentation(kCFAllocatorDefault, Path); + reset(NewValue); + return get(); +} + +CFStringRef CFString::SetFileSystemRepresentationFromCFType(CFTypeRef CFType) { + CFStringRef NewValue = nullptr; + if (CFType != nullptr) { + CFTypeID TypeID = ::CFGetTypeID(CFType); + + if (TypeID == ::CFStringGetTypeID()) { + // Retain since we are using the existing object. + NewValue = (CFStringRef)::CFRetain(CFType); + } else if (TypeID == ::CFURLGetTypeID()) { + NewValue = + ::CFURLCopyFileSystemPath((CFURLRef)CFType, kCFURLPOSIXPathStyle); + } + } + reset(NewValue); + return get(); +} + +CFStringRef +CFString::SetFileSystemRepresentationAndExpandTilde(const char *Path) { + std::string ExpandedPath; + if (CreatePathByExpandingTildePath(Path, ExpandedPath)) + SetFileSystemRepresentation(ExpandedPath.c_str()); + else + reset(); + return get(); +} + +const char *CFString::UTF8(std::string &Str) { + return CFString::UTF8(get(), Str); +} + +/// Static function that puts a copy of the UTF8 contents of CFStr into Str and +/// returns the C string pointer that is contained in Str when successful, else +/// nullptr is returned. 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()) { + if (CFStringGetCString(CFStr, &Str[0], Str.size(), Encoding)) { + Str.resize(strlen(Str.c_str())); + return Str.c_str(); + } + } + } + + return nullptr; +} + +/// Static function that puts a copy of the file system representation of +/// CFStr into Str and returns the C string pointer that is contained in Str +/// when successful, else nullptr is returned. 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::FileSystemRepresentation(CFStringRef CFStr, + std::string &Str) { + if (!CFStr) + return nullptr; + + CFIndex max_length = + ::CFStringGetMaximumSizeOfFileSystemRepresentation(CFStr); + if (max_length > 0) { + Str.resize(max_length); + if (!Str.empty()) { + if (::CFStringGetFileSystemRepresentation(CFStr, &Str[0], Str.size())) { + Str.erase(::strlen(Str.c_str())); + return Str.c_str(); + } + } + } + + Str.erase(); + return nullptr; +} + +CFIndex CFString::GetLength() const { + if (CFStringRef Str = get()) + return CFStringGetLength(Str); + return 0; +} + +CFBundle::CFBundle(const char *Path) : CFReleaser() { + if (Path && Path[0]) + SetPath(Path); +} + +CFBundle::CFBundle(CFURLRef url) + : CFReleaser(url ? CFBundleCreate(nullptr, url) : nullptr) {} + +bool CFBundle::SetPath(const char *InPath) { + // Release our old bundle and ULR + reset(); // This class is a CFReleaser + + 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; +} + +CFStringRef CFBundle::GetIdentifier() const { + if (CFBundleRef bundle = get()) + return ::CFBundleGetIdentifier(bundle); + return nullptr; +} + +CFTypeRef CFBundle::GetValueForInfoDictionaryKey(CFStringRef key) const { + if (CFBundleRef bundle = get()) + return ::CFBundleGetValueForInfoDictionaryKey(bundle, key); + return nullptr; +} +#endif + +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"; + }; + + // 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. + 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 Index: tools/dsymutil/CMakeLists.txt =================================================================== --- tools/dsymutil/CMakeLists.txt +++ tools/dsymutil/CMakeLists.txt @@ -11,6 +11,7 @@ add_llvm_tool(llvm-dsymutil dsymutil.cpp BinaryHolder.cpp + CFBundle.cpp DebugMap.cpp DwarfLinker.cpp MachODebugMapParser.cpp @@ -20,3 +21,6 @@ intrinsics_gen ) +IF(APPLE) + target_link_libraries(llvm-dsymutil "-framework CoreFoundation") +ENDIF(APPLE) Index: tools/dsymutil/dsymutil.cpp =================================================================== --- tools/dsymutil/dsymutil.cpp +++ 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 @@ "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 @@ 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 @@ << "\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 @@ 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",