Index: lld/MachO/DriverUtils.cpp =================================================================== --- lld/MachO/DriverUtils.cpp +++ lld/MachO/DriverUtils.cpp @@ -199,6 +199,57 @@ return {}; } +static Optional replace(StringRef in, + StringRef what, + StringRef with) { + size_t p = in.find(what); + if (p == StringRef::npos) + return None; + return (in.substr(0, p) + with + in.substr(p + what.size())).str(); +} + +static Optional insert_before(StringRef in, + StringRef before, + StringRef what) { + size_t p = in.find(before); + if (p == StringRef::npos) + return None; + return (in.substr(0, p) + what + in.substr(p)).str(); +} + +// Catalyst uses three kinds of libraries: +// 1. Normal mac libraries. These are often, but not always, marked +// as libraries that work on both mac and catalyst. This is the +// common case. +// 2. Catalyst-only libraries, which are available for catalyst apps, +// but not for non-catalyst apps (e.g. UIKit.framework) +// 3. Libraries that are available to both mac and catalyst apps, +// but that are slightly different for mac and catalyst +// (e.g. PDFKit.framework). +// +// Libraries for case 1 are the normal libraries in MacOSX.sdk. +// Libraries for cases 2 and 3 are below /System/iOSSupport in the SDK. +// This function returns true for libraries in case 3. +// +// Case 3 is called "unzippered twins" in ld64. +static bool hasSeparateMacAndCatalystVersion(StringRef p) { + // First case: Library's path is inside /System/iOSSupport. + // This is certainly a catalyst library. Check if the library also + // exists outside /System/iOSSupport to differentiate cases 2 and 3. + if (auto inside = replace(p, "/System/iOSSupport/", "/")) + return fs::exists(*inside); + + // Second case: Library's path is outside /System/iOSSupport. + // Check if it exists inside /System/iOSSupport too to differentiate cases 1 + // and 3. + if (auto outside = insert_before(p, "/System/Library/", "/System/iOSSupport")) + return fs::exists(*outside); + if (auto outside = insert_before(p, "/usr/lib/", "/System/iOSSupport")) + return fs::exists(*outside); + + return false; +} + // It's not uncommon to have multiple attempts to load a single dylib, // especially if it's a commonly re-exported core library. static DenseMap loadedDylibs; @@ -219,7 +270,8 @@ ": " + toString(result.takeError())); return nullptr; } - file = make(**result, umbrella, isBundleLoader); + file = make(**result, umbrella, isBundleLoader, + hasSeparateMacAndCatalystVersion(path.val())); // parseReexports() can recursively call loadDylib(). That's fine since // we wrote the DylibFile we just loaded to the loadDylib cache via the Index: lld/MachO/InputFiles.h =================================================================== --- lld/MachO/InputFiles.h +++ lld/MachO/InputFiles.h @@ -188,8 +188,9 @@ explicit DylibFile(MemoryBufferRef mb, DylibFile *umbrella, bool isBundleLoader = false); explicit DylibFile(const llvm::MachO::InterfaceFile &interface, - DylibFile *umbrella = nullptr, - bool isBundleLoader = false); + DylibFile *umbrella, + bool isBundleLoader, + bool hasSeparateMacAndCatalystVersion); void parseLoadCommands(MemoryBufferRef mb); void parseReexports(const llvm::MachO::InterfaceFile &interface); @@ -216,6 +217,7 @@ // implemented in the bundle. When used like this, it is very similar // to a dylib, so we've used the same class to represent it. bool isBundleLoader; + bool hasSeparateMacAndCatalystVersion = false; private: bool handleLDSymbol(StringRef originalName); Index: lld/MachO/InputFiles.cpp =================================================================== --- lld/MachO/InputFiles.cpp +++ lld/MachO/InputFiles.cpp @@ -1125,7 +1125,8 @@ make_pointee_range(currentTopLevelTapi->documents())) { assert(child.documents().empty()); if (path == child.getInstallName()) { - auto file = make(child, umbrella); + auto file = make(child, umbrella, /*isBundleLoader=*/false, + umbrella->hasSeparateMacAndCatalystVersion); file->parseReexports(child); return file; } @@ -1277,7 +1278,7 @@ } } -// Some versions of XCode ship with .tbd files that don't have the right +// Some versions of Xcode ship with .tbd files that don't have the right // platform settings. constexpr std::array skipPlatformChecks{ "/usr/lib/system/libsystem_kernel.dylib", @@ -1285,10 +1286,23 @@ "/usr/lib/system/libsystem_pthread.dylib", "/usr/lib/system/libcompiler_rt.dylib"}; +static bool skipPlatformCheckForCatalyst( + const InterfaceFile &interface, bool hasSeparateMacAndCatalystVersion) { + // Catalyst outputs can link against macOS libraries, as long those libraries + // do not have a dedicated catalyst version. See also the comment above + // hasSeparateMacAndCatalystVersion() in DriverUtils.cpp. + if (config->platform() != PLATFORM_MACCATALYST || + hasSeparateMacAndCatalystVersion) + return false; + return is_contained(interface.targets(), + MachO::Target(config->arch(), PLATFORM_MACOS)); +} + DylibFile::DylibFile(const InterfaceFile &interface, DylibFile *umbrella, - bool isBundleLoader) + bool isBundleLoader, bool hasSeparateMacAndCatalystVersion) : InputFile(DylibKind, interface), refState(RefState::Unreferenced), - isBundleLoader(isBundleLoader) { + isBundleLoader(isBundleLoader), + hasSeparateMacAndCatalystVersion(hasSeparateMacAndCatalystVersion) { // FIXME: Add test for the missing TBD code path. if (umbrella == nullptr) @@ -1304,7 +1318,8 @@ inputFiles.insert(this); if (!is_contained(skipPlatformChecks, installName) && - !is_contained(interface.targets(), config->platformInfo.target)) { + !is_contained(interface.targets(), config->platformInfo.target) && + !skipPlatformCheckForCatalyst(interface, hasSeparateMacAndCatalystVersion)) { error(toString(this) + " is incompatible with " + std::string(config->platformInfo.target)); return; Index: lld/test/MachO/Inputs/MacOSX.sdk/System/Library/Frameworks/MacOnly.framework/MacOnly.tbd =================================================================== --- /dev/null +++ lld/test/MachO/Inputs/MacOSX.sdk/System/Library/Frameworks/MacOnly.framework/MacOnly.tbd @@ -0,0 +1,12 @@ +--- !tapi-tbd +tbd-version: 4 +targets: [ x86_64-macos ] +uuids: + - target: x86_64-macos + value: 00000000-0000-0000-0000-000000000000 +install-name: 'MacOnly.dylib' +current-version: 0001.001.1 +exports: + - targets: [ x86_64-macos ] + symbols: [ _bar ] +... Index: lld/test/MachO/Inputs/MacOSX.sdk/System/Library/Frameworks/SeparateMacAndCatalyst.framework/SeparateMacAndCatalyst.tbd =================================================================== --- /dev/null +++ lld/test/MachO/Inputs/MacOSX.sdk/System/Library/Frameworks/SeparateMacAndCatalyst.framework/SeparateMacAndCatalyst.tbd @@ -0,0 +1,12 @@ +--- !tapi-tbd +tbd-version: 4 +targets: [ x86_64-macos ] +uuids: + - target: x86_64-macos + value: 00000000-0000-0000-0000-000000000000 +install-name: 'SeparateMacAndCatalyst.dylib' +current-version: 0001.001.1 +exports: + - targets: [ x86_64-macos ] + symbols: [ _bar ] +... Index: lld/test/MachO/Inputs/MacOSX.sdk/System/iOSSupport/System/Library/Frameworks/SeparateMacAndCatalyst.framework/SeparateMacAndCatalyst.tbd =================================================================== --- /dev/null +++ lld/test/MachO/Inputs/MacOSX.sdk/System/iOSSupport/System/Library/Frameworks/SeparateMacAndCatalyst.framework/SeparateMacAndCatalyst.tbd @@ -0,0 +1,12 @@ +--- !tapi-tbd +tbd-version: 4 +targets: [ x86_64-maccatalyst ] +uuids: + - target: x86_64-maccatalyst + value: 00000000-0000-0000-0000-000000000000 +install-name: 'SeparateMacAndCatalyst.dylib' +current-version: 0001.001.1 +exports: + - targets: [ x86_64-maccatalyst ] + symbols: [ _bar ] +... Index: lld/test/MachO/zippered.yaml =================================================================== --- lld/test/MachO/zippered.yaml +++ lld/test/MachO/zippered.yaml @@ -8,6 +8,11 @@ # RUN: %lld -lSystem -dylib %t/test.dylib %t/test_macos.o -o /dev/null # RUN: %no-arg-lld -syslibroot %S/Inputs/MacOSX.sdk -lSystem -dylib -arch x86_64 -platform_version mac-catalyst 13.15.0 14.0 %t/test.dylib %t/test_maccatalyst.o -o /dev/null +# RUN: %no-arg-lld -syslibroot %S/Inputs/MacOSX.sdk -lSystem -dylib -arch x86_64 -platform_version mac-catalyst 13.15.0 14.0 %t/test.dylib %t/test_maccatalyst.o -o /dev/null -framework MacOnly + +# RUN: not %no-arg-lld -syslibroot %S/Inputs/MacOSX.sdk -lSystem -dylib -arch x86_64 -platform_version mac-catalyst 13.15.0 14.0 %t/test.dylib %t/test_maccatalyst.o -o /dev/null -framework SeparateMacAndCatalyst 2>&1 | FileCheck --check-prefix=UNZIPPERED_TWIN %s +# UNZIPPERED_TWIN: System/Library/Frameworks/SeparateMacAndCatalyst.framework/SeparateMacAndCatalyst.tbd(SeparateMacAndCatalyst.dylib) is incompatible with x86_64 (macCatalyst) + # RUN: not %no-arg-lld -syslibroot %S/Inputs/MacOSX.sdk -lSystem -dylib -arch x86_64 -platform_version ios 13.15.0 14.0 %t/test.dylib %t/test_ios.o -o /dev/null 2>&1 | FileCheck %s # CHECK: test.dylib has platform macOS/macCatalyst, which is different from target platform iOS