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",