diff --git a/Sources/SWBCore/SpecImplementations/Tools/PrelinkedObjectLink.swift b/Sources/SWBCore/SpecImplementations/Tools/PrelinkedObjectLink.swift index d2e17180..f112e35c 100644 --- a/Sources/SWBCore/SpecImplementations/Tools/PrelinkedObjectLink.swift +++ b/Sources/SWBCore/SpecImplementations/Tools/PrelinkedObjectLink.swift @@ -35,11 +35,23 @@ public final class PrelinkedObjectLinkSpec: CommandLineToolSpec, SpecImplementat var commandLine = [toolSpecInfo.toolPath.str] commandLine += ["-r", "-arch", arch] - if let buildPlatform = cbc.producer.sdk?.targetBuildVersionPlatform(sdkVariant: cbc.producer.sdkVariant), - let deploymentTargetMacro = cbc.producer.platform?.deploymentTargetMacro, - let minDeploymentTarget = cbc.scope.evaluate(deploymentTargetMacro).nilIfEmpty, - let sdkVersion = cbc.producer.sdk?.version { - commandLine += ["-platform_version", "\(buildPlatform.rawValue)", minDeploymentTarget, sdkVersion.canonicalDeploymentTargetForm.description] + if let sdk = cbc.producer.sdk, let sdkVersion = sdk.version { + for buildPlatform in cbc.producer.targetBuildVersionPlatforms(in: cbc.scope)?.sorted() ?? [] { + let deploymentTargetSettingName = buildPlatform.deploymentTargetSettingName(infoLookup: cbc.producer) + if let minDeploymentTarget = cbc.scope.evaluate(cbc.scope.namespace.parseString("$(\(deploymentTargetSettingName)")).nilIfEmpty { + let version: Version + if cbc.scope.evaluate(BuiltinMacros.IS_ZIPPERED) && buildPlatform == .macCatalyst { + guard let correspondingVersion = sdk.versionMap["macOS_iOSMac"]?[sdkVersion] else { + delegate.error("'\(sdk.canonicalName)' is missing a Mac Catalyst version mapping for '\(sdkVersion)'") + continue + } + version = correspondingVersion + } else { + version = sdkVersion + } + commandLine += ["-platform_version", "\(buildPlatform.rawValue)", minDeploymentTarget, version.canonicalDeploymentTargetForm.description] + } + } } // We do not pass the deployment target to the linker here. Instead the linker infers the platform and deployment target from the .o files being collected. We did briefly pass it to the linker to silence a linker warning - if we ever see issues here we should confer with the linker folks to make sure we do the right thing. See for more about the history here. diff --git a/Sources/SWBUtil/MachO.swift b/Sources/SWBUtil/MachO.swift index 275394f8..6ba06dc3 100644 --- a/Sources/SWBUtil/MachO.swift +++ b/Sources/SWBUtil/MachO.swift @@ -81,7 +81,7 @@ public protocol PlatformInfoProvider { public struct BuildVersion: Equatable, Hashable, Sendable { /// Enumerates platforms as defined by dyld. /// - public struct Platform: RawRepresentable, Equatable, Hashable, Serializable, Sendable { + public struct Platform: RawRepresentable, Equatable, Hashable, Serializable, Sendable, Comparable { public typealias RawValue = UInt32 public static let macOS = Self(platformID: 1) diff --git a/Tests/SWBTaskConstructionTests/PrelinkedObjectFileTests.swift b/Tests/SWBTaskConstructionTests/PrelinkedObjectFileTests.swift index 82dfc726..64c57785 100644 --- a/Tests/SWBTaskConstructionTests/PrelinkedObjectFileTests.swift +++ b/Tests/SWBTaskConstructionTests/PrelinkedObjectFileTests.swift @@ -221,6 +221,149 @@ fileprivate struct PrelinkedObjectFileTests: CoreBasedTests { } } + @Test(.requireSDKs(.macOS, .iOS)) + func prelinkedObjectFileGenerationVariant_zippered() async throws { + let core = try await getCore() + // Test that a prelinked object file gets generated even if the target contains no sources or libraries in its build phases. + let testProject = TestProject( + "aProject", + groupTree: TestGroup( + "SomeFiles", path: "Sources", + children: [ + ]), + buildConfigurations: [ + TestBuildConfiguration( + "Debug", + buildSettings: [ + "PRODUCT_NAME": "$(TARGET_NAME)", + "MACOSX_DEPLOYMENT_TARGET": core.loadSDK(.macOS).defaultDeploymentTarget, + "IPHONEOS_DEPLOYMENT_TARGET": core.loadSDK(.iOS).defaultDeploymentTarget, + "SDKROOT": "macosx", + "IS_ZIPPERED": "YES", + ]), + ], + targets: [ + TestStandardTarget( + "AllLibraries", + type: .staticLibrary, + buildConfigurations: [ + TestBuildConfiguration( + "Debug", + buildSettings: [ + "GENERATE_PRELINK_OBJECT_FILE": "YES", + ]), + ], + buildPhases: [ + TestSourcesBuildPhase([]), + TestFrameworksBuildPhase([]), + ], + dependencies: ["Tool"]), + ]) + let testWorkspace = TestWorkspace("aWorkspace", projects: [testProject]) + let tester = try TaskConstructionTester(core, testWorkspace) + let SRCROOT = tester.workspace.projects[0].sourceRoot.str + + // Check a build for MacCatalyst. + await tester.checkBuild(runDestination: .macCatalyst, fs: localFS) { results in + // Ignore all tasks we don't want to check. + results.checkTasks(.matchRuleType("Gate")) { _ in } + results.checkTasks(.matchRuleType("WriteAuxiliaryFile")) { _ in } + results.checkTasks(.matchRuleType("CreateBuildDirectory")) { _ in } + results.checkTasks(.matchRuleType("RegisterExecutionPolicyException")) { _ in } + + results.checkTarget("AllLibraries") { target in + // There should be tasks to create the prelinked object file and then the static library. + results.checkTask(.matchTarget(target), .matchRuleType("PrelinkedObjectLink")) { task in + task.checkCommandLineMatches([.suffix("ld"), "-r", "-arch", .equal(results.runDestinationTargetArchitecture), "-platform_version", "1", .any, .any, "-platform_version", "6", .any, .any, "-syslibroot", .equal(core.loadSDK(.macOS).path.str), "-o", .equal("\(SRCROOT)/build/aProject.build/Debug/AllLibraries.build/Objects-normal/libAllLibraries.a-\(results.runDestinationTargetArchitecture)-prelink.o")]) + } + results.checkTask(.matchTarget(target), .matchRuleType("Libtool")) { task in + task.checkCommandLineMatches([.suffix("libtool"), "-static", "-arch_only", .equal(results.runDestinationTargetArchitecture), "-D", "-syslibroot", .equal(core.loadSDK(.macOS).path.str), .equal("-L\(SRCROOT)/build/Debug"), "-filelist", .equal("\(SRCROOT)/build/aProject.build/Debug/AllLibraries.build/Objects-normal/\(results.runDestinationTargetArchitecture)/AllLibraries.LinkFileList"), "-dependency_info", "\(SRCROOT)/build/aProject.build/Debug/AllLibraries.build/Objects-normal/\(results.runDestinationTargetArchitecture)/AllLibraries_libtool_dependency_info.dat", "-o", .equal("\(SRCROOT)/build/Debug/libAllLibraries.a")]) + } + } + + // There should be no other tasks. + results.checkNoTask() + + // There shouldn't be any diagnostics. + results.checkNoDiagnostics() + + // Check there are no other targets. + #expect(results.otherTargets == []) + } + } + + @Test(.requireSDKs(.macOS, .iOS)) + func prelinkedObjectFileGenerationVariant_reverseZippered() async throws { + let core = try await getCore() + // Test that a prelinked object file gets generated even if the target contains no sources or libraries in its build phases. + let testProject = TestProject( + "aProject", + groupTree: TestGroup( + "SomeFiles", path: "Sources", + children: [ + ]), + buildConfigurations: [ + TestBuildConfiguration( + "Debug", + buildSettings: [ + "PRODUCT_NAME": "$(TARGET_NAME)", + "MACOSX_DEPLOYMENT_TARGET": core.loadSDK(.macOS).defaultDeploymentTarget, + "IPHONEOS_DEPLOYMENT_TARGET": core.loadSDK(.iOS).defaultDeploymentTarget, + "SDKROOT": "macosx", + "IS_ZIPPERED": "YES", + "SDK_VARIANT": MacCatalystInfo.sdkVariantName, + ]), + ], + targets: [ + TestStandardTarget( + "AllLibraries", + type: .staticLibrary, + buildConfigurations: [ + TestBuildConfiguration( + "Debug", + buildSettings: [ + "GENERATE_PRELINK_OBJECT_FILE": "YES", + ]), + ], + buildPhases: [ + TestSourcesBuildPhase([]), + TestFrameworksBuildPhase([]), + ], + dependencies: ["Tool"]), + ]) + let testWorkspace = TestWorkspace("aWorkspace", projects: [testProject]) + let tester = try TaskConstructionTester(core, testWorkspace) + let SRCROOT = tester.workspace.projects[0].sourceRoot.str + + // Check a build for MacCatalyst. + await tester.checkBuild(runDestination: .macCatalyst, fs: localFS) { results in + // Ignore all tasks we don't want to check. + results.checkTasks(.matchRuleType("Gate")) { _ in } + results.checkTasks(.matchRuleType("WriteAuxiliaryFile")) { _ in } + results.checkTasks(.matchRuleType("CreateBuildDirectory")) { _ in } + results.checkTasks(.matchRuleType("RegisterExecutionPolicyException")) { _ in } + + results.checkTarget("AllLibraries") { target in + // There should be tasks to create the prelinked object file and then the static library. + results.checkTask(.matchTarget(target), .matchRuleType("PrelinkedObjectLink")) { task in + task.checkCommandLineMatches([.suffix("ld"), "-r", "-arch", .equal(results.runDestinationTargetArchitecture), "-platform_version", "1", .any, .any, "-platform_version", "6", .any, .any, "-syslibroot", .equal(core.loadSDK(.macOS).path.str), "-o", .equal("\(SRCROOT)/build/aProject.build/Debug-maccatalyst/AllLibraries.build/Objects-normal/libAllLibraries.a-\(results.runDestinationTargetArchitecture)-prelink.o")]) + } + results.checkTask(.matchTarget(target), .matchRuleType("Libtool")) { task in + task.checkCommandLineMatches([.suffix("libtool"), "-static", "-arch_only", .equal(results.runDestinationTargetArchitecture), "-D", "-syslibroot", .equal(core.loadSDK(.macOS).path.str), .equal("-L\(SRCROOT)/build/Debug-maccatalyst"), "-L\(core.loadSDK(.macOS).path.str)/System/iOSSupport/usr/lib", "-L\(core.developerPath.path.str)/Toolchains/XcodeDefault.xctoolchain/usr/lib/swift/maccatalyst", "-L\(core.loadSDK(.macOS).path.str)/System/iOSSupport/usr/lib", "-L\(core.developerPath.path.str)/Toolchains/XcodeDefault.xctoolchain/usr/lib/swift/maccatalyst", "-filelist", .equal("\(SRCROOT)/build/aProject.build/Debug-maccatalyst/AllLibraries.build/Objects-normal/\(results.runDestinationTargetArchitecture)/AllLibraries.LinkFileList"), "-dependency_info", "\(SRCROOT)/build/aProject.build/Debug-maccatalyst/AllLibraries.build/Objects-normal/\(results.runDestinationTargetArchitecture)/AllLibraries_libtool_dependency_info.dat", "-o", .equal("\(SRCROOT)/build/Debug-maccatalyst/libAllLibraries.a")]) + } + } + + // There should be no other tasks. + results.checkNoTask() + + // There shouldn't be any diagnostics. + results.checkNoDiagnostics() + + // Check there are no other targets. + #expect(results.otherTargets == []) + } + } + @Test(.requireSDKs(.iOS)) func prelinkedObjectFileGenerationVariant_ios() async throws { let core = try await getCore()