From acf1b692f39160b2fc36e54ae421ba714adf2f93 Mon Sep 17 00:00:00 2001 From: Jonas Devlieghere Date: Thu, 30 Nov 2017 10:25:28 +0000 Subject: [PATCH] [dsymutil] Upstream getBundleInfo implementation This patch implements `getBundleInfo`, which uses CoreFoundation to obtain information about the CFBundle. This information is needed to populate the Plist in the dSYM bundle. This change only applies to darwin and is an NFC as far as other platforms are concerned. Differential revision: https://reviews.llvm.org/D40244 llvm-svn: 319416 --- test/tools/dsymutil/Inputs/Info.plist | 20 ++ test/tools/dsymutil/X86/darwin-bundle.test | 30 +++ tools/dsymutil/CFBundle.cpp | 205 +++++++++++++++++++++ tools/dsymutil/CFBundle.h | 26 +++ tools/dsymutil/CMakeLists.txt | 4 + tools/dsymutil/dsymutil.cpp | 37 ++-- 6 files changed, 305 insertions(+), 17 deletions(-) create mode 100644 test/tools/dsymutil/Inputs/Info.plist create mode 100644 test/tools/dsymutil/X86/darwin-bundle.test create mode 100644 tools/dsymutil/CFBundle.cpp create mode 100644 tools/dsymutil/CFBundle.h 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",