From 6939757f21646e4ea1c15e71688d1b2fd238e9c5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pedro=20Jose=CC=81=20Pereira=20Vieito?= Date: Thu, 18 Apr 2019 00:53:17 +0200 Subject: [PATCH] Completed Freestanding Bundles support in all platforms - Adds support for finding the bundle path for implicitly loaded bundles (for which we only know the executable path). - Unifies Windows and other platforms implementation. - Enables Freestanding Bundles tests. --- .../PlugIn.subproj/CFBundle_Executable.c | 151 ++++++++++++++---- .../PlugIn.subproj/CFBundle_Internal.h | 36 +++-- TestFoundation/TestBundle.swift | 14 +- 3 files changed, 150 insertions(+), 51 deletions(-) diff --git a/CoreFoundation/PlugIn.subproj/CFBundle_Executable.c b/CoreFoundation/PlugIn.subproj/CFBundle_Executable.c index b4c5c64e71..c9b5a5a94a 100644 --- a/CoreFoundation/PlugIn.subproj/CFBundle_Executable.c +++ b/CoreFoundation/PlugIn.subproj/CFBundle_Executable.c @@ -16,7 +16,7 @@ #include #endif -#if !DEPLOYMENT_RUNTIME_OBJC && !DEPLOYMENT_TARGET_WINDOWS && !DEPLOYMENT_TARGET_ANDROID +#if !(DEPLOYMENT_RUNTIME_OBJC || TARGET_OS_WINDOWS || TARGET_OS_ANDROID) /* FHS_BUNDLES */ #if DEPLOYMENT_TARGET_LINUX #if TARGET_RT_64_BIT @@ -49,7 +49,7 @@ _kCFBundleFHSDirectory_lib #endif // DEPLOYMENT_TARGET_LINUX -#endif // !DEPLOYMENT_RUNTIME_OBJC && !DEPLOYMENT_TARGET_WINDOWS && !DEPLOYMENT_TARGET_ANDROID +#endif /* FHS_BUNDLES */ // This is here because on iPhoneOS with the dyld shared cache, we remove binaries from their // original locations on disk, so checking whether a binary's path exists is no longer sufficient. @@ -80,7 +80,7 @@ static CFURLRef _CFBundleCopyExecutableURLRaw(CFURLRef urlPath, CFStringRef exeN if (!_binaryLoadable(executableURL)) { CFRelease(executableURL); - CFStringRef sharedLibraryName = CFStringCreateWithFormat(kCFAllocatorSystemDefault, NULL, CFSTR("%@%@%@"), _CFBundleFHSSharedLibraryFilenamePrefix, exeName, _CFBundleFHSSharedLibraryFilenameSuffix); + CFStringRef sharedLibraryName = CFStringCreateWithFormat(kCFAllocatorSystemDefault, NULL, CFSTR("%@%@%@"), _CFBundleSharedLibraryFilenamePrefix, exeName, _CFBundleSharedLibraryFilenameSuffix); executableURL = CFURLCreateWithFileSystemPathRelativeToBase(kCFAllocatorSystemDefault, sharedLibraryName, kCFURLPOSIXPathStyle, false, urlPath); if (!_binaryLoadable(executableURL)) { @@ -200,7 +200,7 @@ static CFURLRef _CFBundleCopyExecutableURLInDirectory2(CFBundleRef bundle, CFURL Boolean doExecSearch = true; #endif -#if !DEPLOYMENT_RUNTIME_OBJC && !DEPLOYMENT_TARGET_WINDOWS && !DEPLOYMENT_TARGET_ANDROID +#if !(DEPLOYMENT_RUNTIME_OBJC || TARGET_OS_WINDOWS || TARGET_OS_ANDROID) /* FHS_BUNDLES */ if (lookupMainExe && bundle && bundle->_isFHSInstalledBundle) { // For a FHS installed bundle, the URL points to share/Bundle.resources, and the binary is in: @@ -224,13 +224,13 @@ static CFURLRef _CFBundleCopyExecutableURLInDirectory2(CFBundleRef bundle, CFURL CFRelease(prefixPath); } -#endif // !DEPLOYMENT_RUNTIME_OBJC && !DEPLOYMENT_TARGET_WINDOWS && !DEPLOYMENT_TARGET_ANDROID +#endif /* FHS_BUNDLES */ // Now, look for the executable inside the bundle. if (!foundIt && doExecSearch && 0 != version) { CFURLRef exeDirURL = NULL; -#if !DEPLOYMENT_RUNTIME_OBJC && !DEPLOYMENT_TARGET_WINDOWS && !DEPLOYMENT_TARGET_ANDROID +#if !(DEPLOYMENT_RUNTIME_OBJC || TARGET_OS_WINDOWS || TARGET_OS_ANDROID) /* FHS_BUNDLES */ if (bundle && bundle->_isFHSInstalledBundle) { CFURLRef withoutExtension = CFURLCreateCopyDeletingPathExtension(kCFAllocatorSystemDefault, url); CFStringRef lastPathComponent = CFURLCopyLastPathComponent(withoutExtension); @@ -245,7 +245,7 @@ static CFURLRef _CFBundleCopyExecutableURLInDirectory2(CFBundleRef bundle, CFURL CFRelease(libexec); CFRelease(exeDirName); } else -#endif // !DEPLOYMENT_RUNTIME_OBJC && !DEPLOYMENT_TARGET_WINDOWS && !DEPLOYMENT_TARGET_ANDROID +#endif /* FHS_BUNDLES */ if (1 == version) { exeDirURL = CFURLCreateWithString(kCFAllocatorSystemDefault, _CFBundleExecutablesURLFromBase1, url); } else if (2 == version) { @@ -304,38 +304,129 @@ static CFURLRef _CFBundleCopyExecutableURLInDirectory2(CFBundleRef bundle, CFURL return executableURL; } +static CFStringRef _CFBundleCopyNameByRemovingPrefixSuffixIfMatches(CFStringRef name, CFStringRef prefix, CFStringRef suffix) { + CFStringRef strippedName = NULL; + CFIndex nameLength = CFStringGetLength(name); + CFIndex prefixLength = CFStringGetLength(prefix); + CFIndex suffixLength = CFStringGetLength(suffix); + CFRange prefixRange = CFRangeMake(0, 0); + CFRange suffixRange = CFRangeMake(nameLength, 0); + + // Check if the executable has shared library prefix/suffix. + if (prefixLength) { + prefixRange = CFStringFind(name, prefix, kCFCompareAnchored); + } + if (suffixLength) { + suffixRange = CFStringFind(name, suffix, kCFCompareAnchored | kCFCompareBackwards); + } + + // Only return the stripped string if both the prefix and suffix are found. + if (prefixRange.location != kCFNotFound && + suffixRange.location != kCFNotFound && + prefixRange.location == 0 && + suffixRange.location + suffixRange.length == nameLength) { + + CFRange strippedNameRange = CFRangeMake( + prefixRange.length, nameLength - prefixRange.length - suffixRange.length); + + strippedName = CFStringCreateWithSubstring(kCFAllocatorSystemDefault, name, strippedNameRange); + } + + return strippedName; +} + + static CFURLRef _CFBundleCopyBundleURLForExecutablePath(CFStringRef str) { - //!!! need to handle frameworks, NT; need to integrate with NSBundle - drd - UniChar buff[CFMaxPathSize]; - CFIndex buffLen; + // Needs to handle: + // - Classic NEXTStep-style bundles (`.framework`, `.app`, `.appex`) + // - Freestanding bundles (`Bundle.resource` as sibling of the library/executable) + // - FHS bundles (`$(PREFIX)/share/Bundle.resource`) + // + // Note: + // For freestanding and FHS bundles, this function has to support removing library/executable prefix/suffix + // on each platform. For example, it has to extract the bundle name as `Bundle` for executables + // like `Bundle.exe` in Windows or `libBundle.so` on Linux. + CFURLRef url = NULL; - CFStringRef outstr; - buffLen = CFStringGetLength(str); - if (buffLen > CFMaxPathSize) buffLen = CFMaxPathSize; - CFStringGetCharacters(str, CFRangeMake(0, buffLen), buff); +#if !(DEPLOYMENT_RUNTIME_OBJC || TARGET_OS_WINDOWS || TARGET_OS_ANDROID) /* FHS_BUNDLES */ + // TODO: FHS Bundles reverse lookup pending to be implemented. +#endif /* FHS_BUNDLES */ -#if DEPLOYMENT_TARGET_WINDOWS - // Is this a .dll or .exe? - if (buffLen >= 5 && (_wcsnicmp((wchar_t *)&(buff[buffLen-4]), L".dll", 4) == 0 || _wcsnicmp((wchar_t *)&(buff[buffLen-4]), L".exe", 4) == 0)) { - CFIndex extensionLength = CFStringGetLength(_CFBundleSiblingResourceDirectoryExtension); - buffLen -= 4; - // If this is an _debug, we should strip that before looking for the bundle - if (buffLen >= 7 && (_wcsnicmp((wchar_t *)&buff[buffLen-6], L"_debug", 6) == 0)) buffLen -= 6; +#if !DEPLOYMENT_RUNTIME_OBJC /* FREESTANDING_BUNDLES */ + if (!url) { + CFStringRef bundleName = NULL; - if (buffLen + 1 + extensionLength < CFMaxPathSize) { - buff[buffLen] = '.'; - buffLen ++; - CFStringGetCharacters(_CFBundleSiblingResourceDirectoryExtension, CFRangeMake(0, extensionLength), buff + buffLen); - buffLen += extensionLength; - outstr = CFStringCreateWithCharactersNoCopy(kCFAllocatorSystemDefault, buff, buffLen, kCFAllocatorNull); - url = CFURLCreateWithFileSystemPath(kCFAllocatorSystemDefault, outstr, PLATFORM_PATH_STYLE, true); - CFRelease(outstr); + CFURLRef executableURL = CFURLCreateWithFileSystemPath(kCFAllocatorSystemDefault, str, PLATFORM_PATH_STYLE, false); + CFStringRef executableName = CFURLCopyLastPathComponent(executableURL); + + // Check if the executable has shared library prefix/suffix (libCF.dylib, libCF.so, CF.dll). + if (!bundleName) { + bundleName = _CFBundleCopyNameByRemovingPrefixSuffixIfMatches( + executableName, _CFBundleSharedLibraryFilenamePrefix, _CFBundleSharedLibraryFilenameSuffix); + } + +#if TARGET_OS_WINDOWS + // Check if the executable has executable-style prefix/suffix (CF.exe). + if (!bundleName) { + bundleName = _CFBundleCopyNameByRemovingPrefixSuffixIfMatches( + executableName, _CFBundleExecutableFilenamePrefix, _CFBundleExecutableFilenameSuffix); + } +#endif /* TARGET_OS_WINDOWS */ + + // Otherwise, use the executable name as bundle name (CF). + if (!bundleName) { + bundleName = CFStringCreateCopy(kCFAllocatorSystemDefault, executableName); + } + +#if TARGET_OS_WINDOWS + // Windows: If this is a debug executable, strip the debug suffix to find the bundle name. + if (bundleName) { + CFStringRef strippedName = _CFBundleCopyNameByRemovingPrefixSuffixIfMatches( + bundleName, _CFBundleFilenameDebugPrefix, _CFBundleFilenameDebugSuffix); + + if (strippedName) { + CFRelease(bundleName); + bundleName = CFStringCreateCopy(kCFAllocatorSystemDefault, strippedName); + CFRelease(strippedName); + } + } +#endif /* TARGET_OS_WINDOWS */ + + // Find the sibling `.resources` directory (CF.resources). + CFURLRef parentDirectory = CFURLCreateCopyDeletingLastPathComponent(kCFAllocatorSystemDefault, executableURL); + + CFRelease(executableName); + CFRelease(executableURL); + + CFStringRef siblingResourceDirectoryName = CFStringCreateWithFormat( + kCFAllocatorSystemDefault, NULL, CFSTR("%@.%@"), bundleName, _CFBundleSiblingResourceDirectoryExtension); + CFRelease(bundleName); + + CFURLRef siblingResourceDirectoryURL = CFURLCreateCopyAppendingPathComponent( + kCFAllocatorSystemDefault, parentDirectory, siblingResourceDirectoryName, true); + + CFRelease(parentDirectory); + CFRelease(siblingResourceDirectoryName); + + // Check that the bundle directory exists. + if (_CFURLExists(siblingResourceDirectoryURL)) { + url = siblingResourceDirectoryURL; + } else { + CFRelease(siblingResourceDirectoryURL); } } -#endif +#endif /* FREESTANDING_BUNDLES */ if (!url) { + UniChar buff[CFMaxPathSize]; + CFIndex buffLen; + CFStringRef outstr; + + buffLen = CFStringGetLength(str); + if (buffLen > CFMaxPathSize) buffLen = CFMaxPathSize; + CFStringGetCharacters(str, CFRangeMake(0, buffLen), buff); + buffLen = _CFLengthAfterDeletingLastPathComponent(buff, buffLen); // Remove exe name if (buffLen > 0) { diff --git a/CoreFoundation/PlugIn.subproj/CFBundle_Internal.h b/CoreFoundation/PlugIn.subproj/CFBundle_Internal.h index 0d58b694df..ab0e0a9dc7 100644 --- a/CoreFoundation/PlugIn.subproj/CFBundle_Internal.h +++ b/CoreFoundation/PlugIn.subproj/CFBundle_Internal.h @@ -30,20 +30,32 @@ CF_EXTERN_C_BEGIN #define PLATFORM_PATH_STYLE kCFURLPOSIXPathStyle #endif -// FHS bundles are supported on the Swift and C runtimes, except on Windows. -#if !DEPLOYMENT_RUNTIME_OBJC && !TARGET_OS_WIN32 +// Freestanding bundles are not supported with the Objective-C Runtime. +// !DEPLOYMENT_RUNTIME_OBJC /* FREESTANDING_BUNDLES */ + +// FHS bundles are not supported with the Objective-C Runtime nor on Windows or Android. +// !(DEPLOYMENT_RUNTIME_OBJC || TARGET_OS_WINDOWS || TARGET_OS_ANDROID) /* FHS_BUNDLES */ + +#if !DEPLOYMENT_RUNTIME_OBJC /* FREESTANDING_BUNDLES || FHS_BUNDLES */ #if TARGET_OS_LINUX || TARGET_OS_BSD || TARGET_OS_ANDROID -#define _CFBundleFHSSharedLibraryFilenamePrefix CFSTR("lib") -#define _CFBundleFHSSharedLibraryFilenameSuffix CFSTR(".so") -#elif TARGET_OS_MAC -#define _CFBundleFHSSharedLibraryFilenamePrefix CFSTR("lib") -#define _CFBundleFHSSharedLibraryFilenameSuffix CFSTR(".dylib") +#define _CFBundleSharedLibraryFilenamePrefix CFSTR("lib") +#define _CFBundleSharedLibraryFilenameSuffix CFSTR(".so") +#elif TARGET_OS_DARWIN +#define _CFBundleSharedLibraryFilenamePrefix CFSTR("lib") +#define _CFBundleSharedLibraryFilenameSuffix CFSTR(".dylib") +#elif TARGET_OS_WINDOWS +#define _CFBundleSharedLibraryFilenamePrefix CFSTR("") +#define _CFBundleSharedLibraryFilenameSuffix CFSTR(".dll") +#define _CFBundleExecutableFilenamePrefix CFSTR("") +#define _CFBundleExecutableFilenameSuffix CFSTR(".exe") +#define _CFBundleFilenameDebugPrefix CFSTR("") +#define _CFBundleFilenameDebugSuffix CFSTR("_debug") #else // a non-covered DEPLOYMENT_TARGET… -#error Disable FHS bundles or specify shared library prefixes and suffixes for this platform. -#endif // DEPLOYMENT_TARGET_… +#error Disable Freestanding and FHS bundles or specify shared library / executable prefixes and suffixes for this platform. +#endif -#endif // !DEPLOYMENT_RUNTIME_OBJC && !TARGET_OS_WIN32 +#endif /* FREESTANDING_BUNDLES || FHS_BUNDLES */ #define CFBundleExecutableNotFoundError 4 #define CFBundleExecutableNotLoadableError 3584 @@ -80,9 +92,9 @@ struct __CFBundle { CFURLRef _url; -#if !DEPLOYMENT_RUNTIME_OBJC && !TARGET_OS_WIN32 && !TARGET_OS_ANDROID +#if !(DEPLOYMENT_RUNTIME_OBJC || TARGET_OS_WINDOWS || TARGET_OS_ANDROID) /* FHS_BUNDLES */ Boolean _isFHSInstalledBundle; -#endif +#endif /* FHS_BUNDLES */ CFDictionaryRef _infoDict; CFDictionaryRef _localInfoDict; diff --git a/TestFoundation/TestBundle.swift b/TestFoundation/TestBundle.swift index bed38c0e02..cd7af785be 100644 --- a/TestFoundation/TestBundle.swift +++ b/TestFoundation/TestBundle.swift @@ -242,7 +242,7 @@ class BundlePlayground { // Create a FHS /usr/local-style hierarchy: try FileManager.default.createDirectory(atPath: temporaryDirectory.path, withIntermediateDirectories: false, attributes: nil) try FileManager.default.createDirectory(atPath: temporaryDirectory.appendingPathComponent("share").path, withIntermediateDirectories: false, attributes: nil) - try FileManager.default.createDirectory(atPath: temporaryDirectory.appendingPathComponent("lib").path, withIntermediateDirectories: false, attributes: nil) + try FileManager.default.createDirectory(atPath: temporaryDirectory.appendingPathComponent(executableType.fhsPrefix).path, withIntermediateDirectories: false, attributes: nil) // Make a main and an auxiliary executable: self.mainExecutableURL = temporaryDirectory @@ -462,6 +462,9 @@ class TestBundle : XCTestCase { try execute(playground) playground.destroy() } + else { + XCTFail("Error creating playground bundle for layout '\(layout)'.") + } } } @@ -541,14 +544,7 @@ class TestBundle : XCTestCase { } func test_bundleReverseBundleLookup() { - _withEachPlaygroundLayout { (playground) in - #if !os(Windows) - if playground.layout.isFreestanding { - // TODO: Freestanding bundles reverse lookup pending to be implemented on non-Windows platforms. - return - } - #endif - + _withEachPlaygroundLayout { (playground) in if playground.layout.isFHS { // TODO: FHS bundles reverse lookup pending to be implemented on all platforms. return