diff --git a/CoreFoundation/PlugIn.subproj/CFBundle.c b/CoreFoundation/PlugIn.subproj/CFBundle.c index 6a756d025b..5380e96655 100644 --- a/CoreFoundation/PlugIn.subproj/CFBundle.c +++ b/CoreFoundation/PlugIn.subproj/CFBundle.c @@ -135,7 +135,7 @@ static void _CFBundleEnsureBundlesExistForImagePaths(CFArrayRef imagePaths); #pragma mark - -#if !DEPLOYMENT_RUNTIME_OBJC && !TARGET_OS_WIN32 && !TARGET_OS_ANDROID +#if !(DEPLOYMENT_RUNTIME_OBJC || TARGET_OS_WINDOWS || TARGET_OS_ANDROID) /* FHS_BUNDLES */ // Functions and constants for FHS bundles: #define _CFBundleFHSDirectory_share CFSTR("share") @@ -160,14 +160,14 @@ static Boolean _CFBundleURLIsForFHSInstalledBundle(CFURLRef bundleURL) { return isFHSBundle; } -#endif // !DEPLOYMENT_RUNTIME_OBJC && !TARGET_OS_WIN32 && !TARGET_OS_ANDROID +#endif /* FHS_BUNDLES */ CF_CROSS_PLATFORM_EXPORT Boolean _CFBundleSupportsFHSBundles() { -#if !DEPLOYMENT_RUNTIME_OBJC && !DEPLOYMENT_TARGET_WINDOWS && !DEPLOYMENT_TARGET_ANDROID +#if !(DEPLOYMENT_RUNTIME_OBJC || TARGET_OS_WINDOWS || TARGET_OS_ANDROID) /* FHS_BUNDLES */ return true; #else return false; -#endif +#endif /* FHS_BUNDLES */ } #pragma mark - @@ -726,9 +726,9 @@ static CFBundleRef _CFBundleCreate(CFAllocatorRef allocator, CFURLRef bundleURL, bundle->_url = newURL; -#if !DEPLOYMENT_RUNTIME_OBJC && !TARGET_OS_WIN32 && !TARGET_OS_ANDROID +#if !(DEPLOYMENT_RUNTIME_OBJC || TARGET_OS_WINDOWS || TARGET_OS_ANDROID) /* FHS_BUNDLES */ bundle->_isFHSInstalledBundle = _CFBundleURLIsForFHSInstalledBundle(newURL); -#endif +#endif /* FHS_BUNDLES */ bundle->_version = localVersion; bundle->_infoDict = NULL; diff --git a/CoreFoundation/PlugIn.subproj/CFBundle_Executable.c b/CoreFoundation/PlugIn.subproj/CFBundle_Executable.c index dbb33d8eb3..fe06797545 100644 --- a/CoreFoundation/PlugIn.subproj/CFBundle_Executable.c +++ b/CoreFoundation/PlugIn.subproj/CFBundle_Executable.c @@ -1,12 +1,12 @@ -/* CFBundle_Executable.c - Copyright (c) 1999-2017, Apple Inc. and the Swift project authors - - Portions Copyright (c) 2014-2017, Apple Inc. and the Swift project authors - Licensed under Apache License v2.0 with Runtime Library Exception - See http://swift.org/LICENSE.txt for license information - See http://swift.org/CONTRIBUTORS.txt for the list of Swift project authors - Responsibility: Tony Parker -*/ +/* CFBundle_Executable.c + Copyright (c) 1999-2018, Apple Inc. and the Swift project authors + + Portions Copyright (c) 2014-2018, Apple Inc. and the Swift project authors + Licensed under Apache License v2.0 with Runtime Library Exception + See http://swift.org/LICENSE.txt for license information + See http://swift.org/CONTRIBUTORS.txt for the list of Swift project authors + Responsibility: Tony Parker + */ #include #include "CFBundle_Internal.h" @@ -15,7 +15,13 @@ #include #endif -#if !DEPLOYMENT_RUNTIME_OBJC && !DEPLOYMENT_TARGET_WINDOWS && !DEPLOYMENT_TARGET_ANDROID +#if !(DEPLOYMENT_RUNTIME_OBJC || TARGET_OS_WINDOWS || TARGET_OS_ANDROID) /* FHS_BUNDLES */ + #define _CFBundleFHSExecutablesDirectorySuffix CFSTR(".executables") + #define _CFBundleFHSDirectoryCLiteral_libexec "libexec" + + CONST_STRING_DECL(_kCFBundleFHSDirectory_bin, "bin"); + CONST_STRING_DECL(_kCFBundleFHSDirectory_sbin, "sbin"); + CONST_STRING_DECL(_kCFBundleFHSDirectory_lib, "lib"); #if DEPLOYMENT_TARGET_LINUX #if __LP64__ @@ -23,32 +29,23 @@ #else // !__LP64__ #define _CFBundleFHSArchDirectorySuffix "32" #endif // __LP64__ - #endif // DEPLOYMENT_TARGET_LINUX - CONST_STRING_DECL(_kCFBundleFHSDirectory_bin, "bin"); - CONST_STRING_DECL(_kCFBundleFHSDirectory_sbin, "sbin"); - CONST_STRING_DECL(_kCFBundleFHSDirectory_lib, "lib"); - #if DEPLOYMENT_TARGET_LINUX CONST_STRING_DECL(_kCFBundleFHSDirectory_libWithArchSuffix, "lib" _CFBundleFHSArchDirectorySuffix); #endif - #define _CFBundleFHSExecutablesDirectorySuffix CFSTR(".executables") - #define _CFBundleFHSDirectoryCLiteral_libexec "libexec" - -#if DEPLOYMENT_TARGET_LINUX - #define _CFBundleFHSDirectoriesInExecutableSearchOrder \ + #if DEPLOYMENT_TARGET_LINUX + #define _CFBundleFHSDirectoriesInExecutableSearchOrder \ _kCFBundleFHSDirectory_bin, \ _kCFBundleFHSDirectory_sbin, \ _kCFBundleFHSDirectory_libWithArchSuffix, \ _kCFBundleFHSDirectory_lib -#else - #define _CFBundleFHSDirectoriesInExecutableSearchOrder \ + #else + #define _CFBundleFHSDirectoriesInExecutableSearchOrder \ _kCFBundleFHSDirectory_bin, \ _kCFBundleFHSDirectory_sbin, \ _kCFBundleFHSDirectory_lib -#endif // DEPLOYMENT_TARGET_LINUX - -#endif // !DEPLOYMENT_RUNTIME_OBJC && !DEPLOYMENT_TARGET_WINDOWS && !DEPLOYMENT_TARGET_ANDROID + #endif +#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. @@ -69,26 +66,14 @@ static Boolean _binaryLoadable(CFURLRef url) { } static CFURLRef _CFBundleCopyExecutableURLRaw(CFURLRef urlPath, CFStringRef exeName) { - // Given an url to a folder and a name, this returns the url to the executable in that folder with that name, if it exists, and NULL otherwise. This function deals with appending the ".exe" or ".dll" on Windows. + // Given an url to a folder and a name, this returns the url to the executable in that folder + // with that name, if it exists, and NULL otherwise. + // This function deals with appending the library and executable prefix/suffixes of each platform. + CFURLRef executableURL = NULL; if (!urlPath || !exeName) return NULL; -#if !DEPLOYMENT_RUNTIME_OBJC && !DEPLOYMENT_TARGET_WINDOWS && !DEPLOYMENT_TARGET_ANDROID - if (!executableURL) { - executableURL = CFURLCreateWithFileSystemPathRelativeToBase(kCFAllocatorSystemDefault, exeName, kCFURLPOSIXPathStyle, false, urlPath); - if (!_binaryLoadable(executableURL)) { - CFRelease(executableURL); - - CFStringRef sharedLibraryName = CFStringCreateWithFormat(kCFAllocatorSystemDefault, NULL, CFSTR("%@%@%@"), _CFBundleFHSSharedLibraryFilenamePrefix, exeName, _CFBundleFHSSharedLibraryFilenameSuffix); - - executableURL = CFURLCreateWithFileSystemPathRelativeToBase(kCFAllocatorSystemDefault, sharedLibraryName, kCFURLPOSIXPathStyle, false, urlPath); - if (!_binaryLoadable(executableURL)) { - CFRelease(executableURL); - executableURL = NULL; - } - } - } -#elif DEPLOYMENT_TARGET_MACOSX || DEPLOYMENT_TARGET_EMBEDDED || DEPLOYMENT_TARGET_EMBEDDED_MINI +#if DEPLOYMENT_TARGET_MACOSX || DEPLOYMENT_TARGET_EMBEDDED || DEPLOYMENT_TARGET_EMBEDDED_MINI const uint8_t *image_suffix = (uint8_t *)__CFgetenvIfNotRestricted("DYLD_IMAGE_SUFFIX"); if (image_suffix) { @@ -109,54 +94,58 @@ static CFURLRef _CFBundleCopyExecutableURLRaw(CFURLRef urlPath, CFStringRef exeN CFRelease(newExeName); CFRelease(imageSuffix); } +#endif + if (!executableURL) { - executableURL = CFURLCreateWithFileSystemPathRelativeToBase(kCFAllocatorSystemDefault, exeName, kCFURLPOSIXPathStyle, false, urlPath); - if (executableURL && !_binaryLoadable(executableURL)) { + // Try finding the executable with input name. + executableURL = CFURLCreateWithFileSystemPathRelativeToBase(kCFAllocatorSystemDefault, exeName, PLATFORM_PATH_STYLE, false, urlPath); + + if (!_binaryLoadable(executableURL)) { CFRelease(executableURL); executableURL = NULL; } } -#elif DEPLOYMENT_TARGET_WINDOWS + +#if !DEPLOYMENT_RUNTIME_OBJC /* FREESTANDING_BUNDLES || FHS_BUNDLES */ + +#if defined(DEBUG) && TARGET_OS_WINDOWS + CFStringRef executableDebugSuffix = _CFBundleFilenameDebugSuffix; +#else + CFStringRef executableDebugSuffix = CFSTR(""); +#endif + if (!executableURL) { - executableURL = CFURLCreateWithFileSystemPathRelativeToBase(kCFAllocatorSystemDefault, exeName, kCFURLWindowsPathStyle, false, urlPath); - if (executableURL && !_urlExists(executableURL)) { + // Try finding the executable using library prefix/suffix. + CFStringRef sharedLibraryName = CFStringCreateWithFormat(kCFAllocatorSystemDefault, NULL, CFSTR("%@%@%@%@"), _CFBundleSharedLibraryFilenamePrefix, exeName, executableDebugSuffix, _CFBundleSharedLibraryFilenameSuffix); + + executableURL = CFURLCreateWithFileSystemPathRelativeToBase(kCFAllocatorSystemDefault, sharedLibraryName, PLATFORM_PATH_STYLE, false, urlPath); + + if (!_binaryLoadable(executableURL)) { CFRelease(executableURL); executableURL = NULL; } + + CFRelease(sharedLibraryName); } + +#if TARGET_OS_WINDOWS if (!executableURL) { - if (!CFStringFindWithOptions(exeName, CFSTR(".dll"), CFRangeMake(0, CFStringGetLength(exeName)), kCFCompareAnchored|kCFCompareBackwards|kCFCompareCaseInsensitive, NULL)) { -#if defined(DEBUG) - CFStringRef extension = CFSTR("_debug.dll"); -#else - CFStringRef extension = CFSTR(".dll"); -#endif - CFStringRef newExeName = CFStringCreateWithFormat(kCFAllocatorSystemDefault, NULL, CFSTR("%@%@"), exeName, extension); - executableURL = CFURLCreateWithString(kCFAllocatorSystemDefault, newExeName, urlPath); - if (executableURL && !_binaryLoadable(executableURL)) { - CFRelease(executableURL); - executableURL = NULL; - } - CFRelease(newExeName); - } - } - if (!executableURL) { - if (!CFStringFindWithOptions(exeName, CFSTR(".exe"), CFRangeMake(0, CFStringGetLength(exeName)), kCFCompareAnchored|kCFCompareBackwards|kCFCompareCaseInsensitive, NULL)) { -#if defined(DEBUG) - CFStringRef extension = CFSTR("_debug.exe"); -#else - CFStringRef extension = CFSTR(".exe"); -#endif - CFStringRef newExeName = CFStringCreateWithFormat(kCFAllocatorSystemDefault, NULL, CFSTR("%@%@"), exeName, extension); - executableURL = CFURLCreateWithString(kCFAllocatorSystemDefault, newExeName, urlPath); - if (executableURL && !_binaryLoadable(executableURL)) { - CFRelease(executableURL); - executableURL = NULL; - } - CFRelease(newExeName); + // Try finding the executable using executable prefix/suffix. + CFStringRef sharedLibraryName = CFStringCreateWithFormat(kCFAllocatorSystemDefault, NULL, CFSTR("%@%@%@%@"), _CFBundleExecutableFilenamePrefix, exeName, executableDebugSuffix, _CFBundleExecutableFilenameSuffix); + + executableURL = CFURLCreateWithFileSystemPathRelativeToBase(kCFAllocatorSystemDefault, executableName, PLATFORM_PATH_STYLE, false, urlPath); + + if (!_binaryLoadable(executableURL)) { + CFRelease(executableURL); + executableURL = NULL; } + + CFRelease(executableName); } -#endif +#endif /* TARGET_OS_WINDOWS */ + +#endif /* FREESTANDING_BUNDLES || FHS_BUNDLES */ + return executableURL; } @@ -165,7 +154,6 @@ static CFURLRef _CFBundleCopyExecutableURLInDirectory2(CFBundleRef bundle, CFURL CFDictionaryRef infoDict = NULL; CFStringRef executablePath = NULL; CFURLRef executableURL = NULL; - Boolean foundIt = false; Boolean lookupMainExe = (executableName ? false : true); if (bundle) { @@ -183,14 +171,11 @@ static CFURLRef _CFBundleCopyExecutableURLInDirectory2(CFBundleRef bundle, CFURL __CFUnlock(&bundle->_lock); if (executablePath) { executableURL = CFURLCreateWithFileSystemPath(kCFAllocatorSystemDefault, executablePath, PLATFORM_PATH_STYLE, false); - if (executableURL) { - foundIt = true; - } CFRelease(executablePath); } } - if (!foundIt) { + if (!executableURL) { if (lookupMainExe) executableName = _CFBundleCopyExecutableName(bundle, url, infoDict); if (executableName) { #if (DEPLOYMENT_TARGET_EMBEDDED && !TARGET_IPHONE_SIMULATOR) @@ -199,94 +184,107 @@ static CFURLRef _CFBundleCopyExecutableURLInDirectory2(CFBundleRef bundle, CFURL Boolean doExecSearch = true; #endif -#if !DEPLOYMENT_RUNTIME_OBJC && !DEPLOYMENT_TARGET_WINDOWS && !DEPLOYMENT_TARGET_ANDROID - if (lookupMainExe && bundle && bundle->_isFHSInstalledBundle) { - // For a FHS installed bundle, the URL points to share/Bundle.resources, and the binary is in: +#if !(DEPLOYMENT_RUNTIME_OBJC || TARGET_OS_WINDOWS || TARGET_OS_ANDROID) /* FHS_BUNDLES */ + if (bundle && bundle->_isFHSInstalledBundle) { - CFURLRef sharePath = CFURLCreateCopyDeletingLastPathComponent(kCFAllocatorSystemDefault, url); - CFURLRef prefixPath = CFURLCreateCopyDeletingLastPathComponent(kCFAllocatorSystemDefault, sharePath); - CFRelease(sharePath); - - CFStringRef directories[] = { _CFBundleFHSDirectoriesInExecutableSearchOrder }; - size_t directoriesCount = sizeof(directories) / sizeof(directories[0]); - - for (size_t i = 0; i < directoriesCount; i++) { - CFURLRef where = CFURLCreateCopyAppendingPathComponent(kCFAllocatorSystemDefault, prefixPath, directories[i], true); - executableURL = _CFBundleCopyExecutableURLRaw(where, executableName); - CFRelease(where); + if (lookupMainExe) { + // For a FHS installed bundles we try to find the main executable in the different prefix locations: + CFURLRef sharePath = CFURLCreateCopyDeletingLastPathComponent(kCFAllocatorSystemDefault, url); + CFURLRef prefixPath = CFURLCreateCopyDeletingLastPathComponent(kCFAllocatorSystemDefault, sharePath); + CFRelease(sharePath); - if (executableURL) { - foundIt = true; - break; + CFStringRef directories[] = { _CFBundleFHSDirectoriesInExecutableSearchOrder }; + size_t directoriesCount = sizeof(directories) / sizeof(directories[0]); + + for (size_t i = 0; i < directoriesCount; i++) { + CFURLRef where = CFURLCreateCopyAppendingPathComponent(kCFAllocatorSystemDefault, prefixPath, directories[i], true); + executableURL = _CFBundleCopyExecutableURLRaw(where, executableName); + CFRelease(where); } - } - - CFRelease(prefixPath); - } -#endif // !DEPLOYMENT_RUNTIME_OBJC && !DEPLOYMENT_TARGET_WINDOWS && !DEPLOYMENT_TARGET_ANDROID - - // 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 (bundle && bundle->_isFHSInstalledBundle) { + + CFRelease(prefixPath); + } else { + // For FHS bundles we try to find the auxiliary executables in `$(PREFIX)/libexec`. + CFURLRef withoutExtension = CFURLCreateCopyDeletingPathExtension(kCFAllocatorSystemDefault, url); CFStringRef lastPathComponent = CFURLCopyLastPathComponent(withoutExtension); CFURLRef libexec = CFURLCreateWithString(kCFAllocatorSystemDefault, CFSTR("../../" _CFBundleFHSDirectoryCLiteral_libexec), url); CFStringRef exeDirName = CFStringCreateWithFormat(kCFAllocatorSystemDefault, NULL, CFSTR("%@%@"), lastPathComponent, _CFBundleFHSExecutablesDirectorySuffix); - exeDirURL = CFURLCreateCopyAppendingPathComponent(kCFAllocatorSystemDefault, libexec, exeDirName, true); + CFURLRef exeDirURL = CFURLCreateCopyAppendingPathComponent(kCFAllocatorSystemDefault, libexec, exeDirName, true); + executableURL = _CFBundleCopyExecutableURLRaw(exeDirURL, executableName); CFRelease(withoutExtension); CFRelease(lastPathComponent); CFRelease(libexec); CFRelease(exeDirName); - } else -#endif // !DEPLOYMENT_RUNTIME_OBJC && !DEPLOYMENT_TARGET_WINDOWS && !DEPLOYMENT_TARGET_ANDROID - if (1 == version) { - exeDirURL = CFURLCreateWithString(kCFAllocatorSystemDefault, _CFBundleExecutablesURLFromBase1, url); - } else if (2 == version) { - exeDirURL = CFURLCreateWithString(kCFAllocatorSystemDefault, _CFBundleExecutablesURLFromBase2, url); - } else { -#if DEPLOYMENT_TARGET_WINDOWS || !DEPLOYMENT_RUNTIME_OBJC - // On Windows and on targets that support FHS bundles, if the bundle URL is foo.resources, then the executable is at the same level as the .resources directory - CFStringRef extension = CFURLCopyPathExtension(url); - if (extension && CFEqual(extension, _CFBundleSiblingResourceDirectoryExtension)) { - exeDirURL = CFURLCreateCopyDeletingLastPathComponent(kCFAllocatorSystemDefault, url); + CFRelease(exeDirURL); + } + } else { +#endif /* FHS_BUNDLES */ + + // Now, look for the executable inside the bundle. + if (!executableURL && doExecSearch && 0 != version) { + CFURLRef exeDirURL = NULL; + + // First, we find the executable directory: + + if (1 == version) { + exeDirURL = CFURLCreateWithString(kCFAllocatorSystemDefault, _CFBundleExecutablesURLFromBase1, url); + } else if (2 == version) { + exeDirURL = CFURLCreateWithString(kCFAllocatorSystemDefault, _CFBundleExecutablesURLFromBase2, url); } else { - exeDirURL = (CFURLRef)CFRetain(url); + +#if !DEPLOYMENT_RUNTIME_OBJC /* FREESTANDING_BUNDLES */ + // For freestanding bundles if the directory has a `.resources` extension, the executable is at the same level. + CFStringRef extension = CFURLCopyPathExtension(url); + if (extension && CFEqual(extension, _CFBundleSiblingResourceDirectoryExtension)) { + exeDirURL = CFURLCreateCopyDeletingLastPathComponent(kCFAllocatorSystemDefault, url); + } +#endif /* FREESTANDING_BUNDLES */ + + if (!exeDirURL) { + exeDirURL = (CFURLRef)CFRetain(url); + } } -#else - exeDirURL = (CFURLRef)CFRetain(url); -#endif + + // After finding the executable directory, we search the executable: + // + // Historical note: This used to search the directories "Mac OS X", "MacOSClassic", then "MacOS8". + // As of 10.13 we only look in "MacOS". + CFURLRef exeSubdirURL = CFURLCreateWithFileSystemPathRelativeToBase(kCFAllocatorSystemDefault, _CFBundleGetPlatformExecutablesSubdirectoryName(), PLATFORM_PATH_STYLE, true, exeDirURL); + executableURL = _CFBundleCopyExecutableURLRaw(exeSubdirURL, executableName); + CFRelease(exeSubdirURL); + + if (!executableURL) { + executableURL = _CFBundleCopyExecutableURLRaw(exeDirURL, executableName); + } + + CFRelease(exeDirURL); } - - // Historical note: This used to search the directories "Mac OS X", "MacOSClassic", then "MacOS8". As of 10.13 we only look in "MacOS". - CFURLRef exeSubdirURL = CFURLCreateWithFileSystemPathRelativeToBase(kCFAllocatorSystemDefault, _CFBundleGetPlatformExecutablesSubdirectoryName(), kCFURLPOSIXPathStyle, true, exeDirURL); - executableURL = _CFBundleCopyExecutableURLRaw(exeSubdirURL, executableName); - CFRelease(exeSubdirURL); - - if (!executableURL) executableURL = _CFBundleCopyExecutableURLRaw(exeDirURL, executableName); - CFRelease(exeDirURL); - } - - // If this was an old bundle, or we did not find the executable in the Executables subdirectory, look directly in the bundle wrapper. - if (!executableURL) executableURL = _CFBundleCopyExecutableURLRaw(url, executableName); - -#if DEPLOYMENT_TARGET_WINDOWS - // Windows only: If we still haven't found the exe, look in the Executables folder. - // But only for the main bundle exe - if (lookupMainExe && !executableURL) { - CFURLRef exeDirURL = CFURLCreateWithString(kCFAllocatorSystemDefault, CFSTR("../../Executables"), url); - executableURL = _CFBundleCopyExecutableURLRaw(exeDirURL, executableName); - CFRelease(exeDirURL); + + // If this was an old bundle, or we did not find the executable in the Executables subdirectory, look directly in the bundle wrapper. + if (!executableURL) { + executableURL = _CFBundleCopyExecutableURLRaw(url, executableName); + } + +#if TARGET_OS_WINDOWS + // Windows only: If we still haven't found the exe, look in the Executables folder. + // But only for the main bundle exe + if (lookupMainExe && !executableURL) { + CFURLRef exeDirURL = CFURLCreateWithString(kCFAllocatorSystemDefault, CFSTR("../../Executables"), url); + executableURL = _CFBundleCopyExecutableURLRaw(exeDirURL, executableName); + CFRelease(exeDirURL); + } +#endif /* TARGET_OS_WINDOWS */ + +#if !(DEPLOYMENT_RUNTIME_OBJC || TARGET_OS_WINDOWS || TARGET_OS_ANDROID) /* FHS_BUNDLES */ } -#endif +#endif /* FHS_BUNDLES */ if (lookupMainExe && !ignoreCache && bundle && executableURL) { - // We found it. Cache the path. + // We found it. Cache the path. CFURLRef absURL = CFURLCopyAbsoluteURL(executableURL); executablePath = CFURLCopyFileSystemPath(absURL, PLATFORM_PATH_STYLE); CFRelease(absURL); @@ -295,46 +293,133 @@ static CFURLRef _CFBundleCopyExecutableURLInDirectory2(CFBundleRef bundle, CFURL __CFUnlock(&bundle->_lock); CFRelease(executablePath); } + if (lookupMainExe && bundle && !executableURL) bundle->_binaryType = __CFBundleNoBinary; if (lookupMainExe) CFRelease(executableName); } } + if (!bundle && infoDict) CFRelease(infoDict); return executableURL; } +static CFStringRef _CFBundleCopyNameByRemovingExcutablePlatformPrefixSuffix(CFStringRef str, CFStringRef prefix, CFStringRef suffix) { + CFStringRef bundleName = NULL; + + // Check if the executable has shared library prefix/suffix. + CFRange prefixRange = CFStringFind(str, prefix, + kCFCompareAnchored); + CFRange suffixRange = CFStringFind(str, suffix, + kCFCompareAnchored|kCFCompareBackwards); + + // Only return the stripped string if both the prefix and suffix are found. + CFIndex inputStringLength = CFStringGetLength(str); + + if (prefixRange.location != kCFNotFound && + suffixRange.location != kCFNotFound && + prefixRange.location == 0 && + suffixRange.location + suffixRange.length == inputStringLength) { + + CFRange bundleNameRange = CFRangeMake(prefixRange.length, + inputStringLength - prefixRange.length - suffixRange.length); + + bundleName = CFStringCreateWithSubstring(kCFAllocatorSystemDefault, str, bundleNameRange); + } + + return bundleName; +} + 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 detect to 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 = _CFBundleCopyNameByRemovingExcutablePlatformPrefixSuffix(executableName, + _CFBundleSharedLibraryFilenamePrefix, + _CFBundleSharedLibraryFilenameSuffix); + } + +#if TARGET_OS_WINDOWS + // Check if the executable has executable-style prefix/suffix (CF.exe). + if (!bundleName) { + bundleName = _CFBundleCopyNameByRemovingExcutablePlatformPrefixSuffix(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 = _CFBundleCopyNameByRemovingExcutablePlatformPrefixSuffix(bundleName, + _CFBundleFilenameDebugPrefix, + _CFBundleFilenameDebugSuffix); + + if (strippedName) { + 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) { @@ -373,6 +458,7 @@ static CFURLRef _CFBundleCopyBundleURLForExecutablePath(CFStringRef str) { CFRelease(outstr); } } + return url; } @@ -391,12 +477,14 @@ static CFURLRef _CFBundleCopyResolvedURLForExecutableURL(CFURLRef url) { if (len1 > 0 && len1 + 1 < buffLen) { str1 = CFStringCreateWithCharacters(kCFAllocatorSystemDefault, buff, len1); CFIndex skipSlashCount = 1; -#if DEPLOYMENT_TARGET_WINDOWS + +#if TARGET_OS_WINDOWS // On Windows, _CFLengthAfterDeletingLastPathComponent will return a value of 3 if the path is at the root (e.g. C:\). This includes the \, which is not the case for URLs with subdirectories if (len1 == 3 && buff[1] == ':' && buff[2] == '\\') { skipSlashCount = 0; } #endif + str2 = CFStringCreateWithCharacters(kCFAllocatorSystemDefault, buff + len1 + skipSlashCount, buffLen - len1 - skipSlashCount); if (str1 && str2) { url1 = CFURLCreateWithFileSystemPath(kCFAllocatorSystemDefault, str1, PLATFORM_PATH_STYLE, true); diff --git a/CoreFoundation/PlugIn.subproj/CFBundle_Internal.h b/CoreFoundation/PlugIn.subproj/CFBundle_Internal.h index 140f0f0ac4..437e82e318 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 && !TARGET_OS_ANDROID +// 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 -#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 && !TARGET_OS_ANDROID +#endif /* FREESTANDING_BUNDLES || FHS_BUNDLES */ #define CFBundleExecutableNotFoundError 4 #define CFBundleExecutableNotLoadableError 3584 @@ -80,7 +92,7 @@ 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 diff --git a/Foundation/Bundle.swift b/Foundation/Bundle.swift index 0a2601edde..2b73755230 100644 --- a/Foundation/Bundle.swift +++ b/Foundation/Bundle.swift @@ -67,6 +67,20 @@ open class Bundle: NSObject { _bundle = result } + internal convenience init?(executableURL: URL) { + guard let bundleURL = _CFBundleCopyBundleURLForExecutableURL(executableURL._cfObject)?.takeRetainedValue() else { + return nil + } + + self.init(url: bundleURL._swiftObject) + } + + internal convenience init?(executablePath: String) { + let executableURL = URL(fileURLWithPath: executablePath) + + self.init(executableURL: executableURL) + } + override open var description: String { return "\(String(describing: Bundle.self)) <\(bundleURL.path)> (\(isLoaded ? "loaded" : "not yet loaded"))" } diff --git a/TestFoundation/TestBundle.swift b/TestFoundation/TestBundle.swift index de0af0fd49..285ade092c 100644 --- a/TestFoundation/TestBundle.swift +++ b/TestFoundation/TestBundle.swift @@ -9,6 +9,14 @@ import CoreFoundation +#if NS_FOUNDATION_ALLOWS_TESTABLE_IMPORT +#if DEPLOYMENT_RUNTIME_OBJC || os(Linux) || os(Android) +@testable import Foundation +#else +@testable import SwiftFoundation +#endif +#endif + internal func testBundle() -> Bundle { #if DARWIN_COMPATIBILITY_TESTS for bundle in Bundle.allBundles { @@ -70,6 +78,7 @@ class BundlePlayground { let layout: Layout private(set) var bundlePath: String! + private(set) var executablePath: String! private var playgroundPath: String? init?(bundleName: String, @@ -108,7 +117,8 @@ class BundlePlayground { try FileManager.default.createDirectory(atPath: bundleURL.path, withIntermediateDirectories: false, attributes: nil) // Make a main and an auxiliary executable: - guard FileManager.default.createFile(atPath: bundleURL.appendingPathComponent(bundleName).path, contents: nil) else { + self.executablePath = bundleURL.appendingPathComponent(bundleName).path + guard FileManager.default.createFile(atPath: executablePath, contents: nil) else { return false } guard FileManager.default.createFile(atPath: bundleURL.appendingPathComponent(auxiliaryExecutableName).path, contents: nil) else { @@ -146,13 +156,14 @@ class BundlePlayground { try FileManager.default.createDirectory(atPath: temporaryDirectory.appendingPathComponent("lib").path, withIntermediateDirectories: false, attributes: nil) // Make a main and an auxiliary executable: - #if os(macOS) || os(iOS) || os(watchOS) || os(tvOS) + #if canImport(Darwin) let pathExtension = "dylib" #else let pathExtension = "so" #endif - guard FileManager.default.createFile(atPath: temporaryDirectory.appendingPathComponent("lib").appendingPathComponent("lib\(bundleName).\(pathExtension)").path, contents: nil) else { return false } + self.executablePath = temporaryDirectory.appendingPathComponent("lib").appendingPathComponent("lib\(bundleName).\(pathExtension)").path + guard FileManager.default.createFile(atPath: executablePath, contents: nil) else { return false } let executablesDirectory = temporaryDirectory.appendingPathComponent("libexec").appendingPathComponent("\(bundleName).executables") try FileManager.default.createDirectory(atPath: executablesDirectory.path, withIntermediateDirectories: true, attributes: nil) @@ -187,7 +198,8 @@ class BundlePlayground { try FileManager.default.createDirectory(atPath: temporaryDirectory.path, withIntermediateDirectories: false, attributes: nil) // Make a main executable: - guard FileManager.default.createFile(atPath: temporaryDirectory.appendingPathComponent(bundleName).path, contents: nil) else { return false } + self.executablePath = temporaryDirectory.appendingPathComponent(bundleName).path + guard FileManager.default.createFile(atPath: executablePath, contents: nil) else { return false } // Make a .resources directory: let resourcesDirectory = temporaryDirectory.appendingPathComponent("\(bundleName).resources") @@ -244,6 +256,7 @@ class TestBundle : XCTestCase { ("test_bundleLoadWithError", test_bundleLoadWithError), ("test_bundleWithInvalidPath", test_bundleWithInvalidPath), ("test_bundlePreflight", test_bundlePreflight), + ("test_bundleReverseBundleLookup", test_bundleReverseBundleLookup), ("test_bundleFindExecutable", test_bundleFindExecutable), ("test_bundleFindAuxiliaryExecutables", test_bundleFindAuxiliaryExecutables), ("test_mainBundleExecutableURL", test_mainBundleExecutableURL), @@ -416,6 +429,24 @@ class TestBundle : XCTestCase { } } + func test_bundleReverseBundleLookup() { + _withEachPlaygroundLayout { (playground) in + guard playground.layout != .fhsInstalled else { + // TODO: FHS Bundles reverse lookup pending to be implemented. + // + // Implementation required at `_CFBundleCopyBundleURLForExecutablePath()` on Core Foundation. + return + } + + #if NS_FOUNDATION_ALLOWS_TESTABLE_IMPORT + let bundle = Bundle(executablePath: playground.executablePath) + + XCTAssertNotNil(bundle) + XCTAssertEqual(bundle?.bundlePath, playground.bundlePath) + #endif + } + } + func test_bundleFindExecutable() { XCTAssertNotNil(testBundle().executableURL)