From f5ae2ec983f1641a7ed19e38657ef85c1a1bb3f5 Mon Sep 17 00:00:00 2001 From: svmkr Date: Thu, 13 Nov 2025 13:11:35 +0100 Subject: [PATCH] Implement enable/disable-get-task-allow for swift build (#8378) Rewrite BuildCommandTests.getTaskAllowEntitlement to check for entitlements using codesign. Add macOS entitlement during build process using swift build. Fix wording in warning related to get-task-allow command line options. --- Sources/CoreCommands/SwiftCommandState.swift | 2 +- .../SwiftBuildSupport/SwiftBuildSystem.swift | 40 ++++- Tests/CommandsTests/BuildCommandTests.swift | 141 +++++++++++------- 3 files changed, 125 insertions(+), 58 deletions(-) diff --git a/Sources/CoreCommands/SwiftCommandState.swift b/Sources/CoreCommands/SwiftCommandState.swift index b4a6158274b..f5b1e866eef 100644 --- a/Sources/CoreCommands/SwiftCommandState.swift +++ b/Sources/CoreCommands/SwiftCommandState.swift @@ -900,7 +900,7 @@ public final class SwiftCommandState { } static let entitlementsMacOSWarning = """ - `--disable-get-task-allow-entitlement` and `--disable-get-task-allow-entitlement` only have an effect \ + `--enable-get-task-allow-entitlement` and `--disable-get-task-allow-entitlement` only have an effect \ when building on macOS. """ diff --git a/Sources/SwiftBuildSupport/SwiftBuildSystem.swift b/Sources/SwiftBuildSupport/SwiftBuildSystem.swift index bf0e98161cf..a435deaad0b 100644 --- a/Sources/SwiftBuildSupport/SwiftBuildSystem.swift +++ b/Sources/SwiftBuildSupport/SwiftBuildSystem.swift @@ -112,13 +112,28 @@ func withSession( } private final class PlanningOperationDelegate: SWBPlanningOperationDelegate, Sendable { + private let shouldEnableMacOsDebuggingEntitlement: Bool + + init(shouldEnableMacOsDebuggingEntitlement: Bool) { + self.shouldEnableMacOsDebuggingEntitlement = shouldEnableMacOsDebuggingEntitlement + } + public func provisioningTaskInputs( targetGUID: String, provisioningSourceData: SWBProvisioningTaskInputsSourceData ) async -> SWBProvisioningTaskInputs { - let identity = provisioningSourceData.signingCertificateIdentifier + // if we need to add debug entitlement we have to do codesigning, so we need to ensure at least ad-hoc signing + let identity = if provisioningSourceData.signingCertificateIdentifier + .isEmpty && shouldEnableMacOsDebuggingEntitlement && provisioningSourceData.supportsEntitlements + { + "-" + } else { + provisioningSourceData.signingCertificateIdentifier + } + if identity == "-" { - let signedEntitlements = provisioningSourceData.entitlementsDestination == "Signature" + let signedEntitlements = provisioningSourceData + .entitlementsDestination == "Signature" && provisioningSourceData.sdkRoot.contains("iphoneos") ? provisioningSourceData.productTypeEntitlements.merging( ["application-identifier": .plString(provisioningSourceData.bundleIdentifier)], uniquingKeysWith: { _, new in new } @@ -132,6 +147,16 @@ private final class PlanningOperationDelegate: SWBPlanningOperationDelegate, Sen ).merging(provisioningSourceData.projectEntitlements ?? [:], uniquingKeysWith: { _, new in new }) : [:] + var additionalEntitlements: [String: SWBPropertyListItem] = [:] + + if provisioningSourceData.sdkRoot.contains("simulator") { + additionalEntitlements["get-task-allow"] = .plBool(true) + } + + if shouldEnableMacOsDebuggingEntitlement { + additionalEntitlements["com.apple.security.get-task-allow"] = .plBool(true) + } + return SWBProvisioningTaskInputs( identityHash: "-", identityName: "-", @@ -140,7 +165,7 @@ private final class PlanningOperationDelegate: SWBPlanningOperationDelegate, Sen profilePath: nil, designatedRequirements: nil, signedEntitlements: signedEntitlements.merging( - provisioningSourceData.sdkRoot.contains("simulator") ? ["get-task-allow": .plBool(true)] : [:], + additionalEntitlements, uniquingKeysWith: { _, new in new } ), simulatedEntitlements: simulatedEntitlements, @@ -726,7 +751,10 @@ public final class SwiftBuildSystem: SPMBuildCore.BuildSystem { let operation = try await session.createBuildOperation( request: request, - delegate: PlanningOperationDelegate(), + delegate: PlanningOperationDelegate(shouldEnableMacOsDebuggingEntitlement: self.buildParameters + .triple.darwinPlatform == .macOS && self.buildParameters.debuggingParameters + .shouldEnableDebuggingEntitlement + ), retainBuildDescription: true ) @@ -1054,7 +1082,9 @@ public final class SwiftBuildSystem: SPMBuildCore.BuildSystem { private static func constructDebuggingSettingsOverrides(from parameters: BuildParameters.Debugging) -> [String: String] { var settings: [String: String] = [:] // TODO: debugInfoFormat: https://github.com/swiftlang/swift-build/issues/560 - // TODO: shouldEnableDebuggingEntitlement: Enable/Disable get-task-allow + if parameters.shouldEnableDebuggingEntitlement { + settings["ENTITLEMENTS_DONT_REMOVE_GET_TASK_ALLOW"] = "YES" + } // TODO: omitFramePointer: https://github.com/swiftlang/swift-build/issues/561 return settings } diff --git a/Tests/CommandsTests/BuildCommandTests.swift b/Tests/CommandsTests/BuildCommandTests.swift index 35eca4d2c1d..0899a07aced 100644 --- a/Tests/CommandsTests/BuildCommandTests.swift +++ b/Tests/CommandsTests/BuildCommandTests.swift @@ -10,19 +10,21 @@ // //===----------------------------------------------------------------------===// -import Foundation +import _InternalTestSupport import Basics @testable import Commands @testable import CoreCommands +import Foundation import PackageGraph import PackageLoading import PackageModel import enum PackageModel.BuildConfiguration import SPMBuildCore -import _InternalTestSupport +import enum SWBUtil.PropertyList +import enum SWBUtil.PropertyListItem +import Testing import TSCTestSupport import Workspace -import Testing struct BuildResult { let binPath: AbsolutePath @@ -1280,7 +1282,9 @@ struct BuildCommandTestCases { } @Test( - .SWBINTTODO("Test failed because swiftbuild doesn't output precis codesign commands. Once swift run works with swiftbuild the test can be investigated."), + .SWBINTTODO( + "Implement get-task-allow entitlement for xcode build system" + ), .tags( .Feature.CommandLineArguments.DisableGetTaskAllowEntitlement, .Feature.CommandLineArguments.EnableGetTaskAllowEntitlement, @@ -1289,75 +1293,112 @@ struct BuildCommandTestCases { .tags( .Feature.CommandLineArguments.BuildSystem, ), - arguments: SupportedBuildSystemOnPlatform, + arguments: getBuildData(for: SupportedBuildSystemOnPlatform), ) func getTaskAllowEntitlement( - buildSystem: BuildSystemProvider.Kind, + data: BuildData, ) async throws { - try await withKnownIssue(isIntermittent: (ProcessInfo.hostOperatingSystem == .linux)) { + let buildSystem = data.buildSystem + let buildConfiguration = data.config + try await withKnownIssue(isIntermittent: ProcessInfo.hostOperatingSystem == .linux) { try await fixture(name: "ValidLayouts/SingleModule/ExecutableNew") { fixturePath in - #if os(macOS) - // try await building with default parameters. This should succeed. We build verbosely so we get full command - // lines. - var buildResult = try await build(["-v"], packagePath: fixturePath, configuration: .debug, buildSystem: buildSystem,) - - // TODO verification of the ad-hoc code signing can be done by `swift run` of the executable in these cases once swiftbuild build system is working with that - #expect(buildResult.stdout.contains("codesign --force --sign - --entitlements")) + #if os(macOS) + func codesignDisplay(execPath: AbsolutePath) async throws + -> (AsyncProcessResult.ExitStatus, PropertyListItem?) + { + let args = ["codesign", "-d", "--entitlements", "-", "--xml", execPath.pathString] + let result = try await AsyncProcess.popen(arguments: args) + let entitlements: PropertyListItem? = if case .success(let output) = result.output, + !output.isEmpty + { + try PropertyList.fromBytes(output) + } else { + nil + } + + return (result.exitStatus, entitlements) + } - buildResult = try await build(["-v"], packagePath: fixturePath, configuration:.debug, buildSystem: buildSystem,) + func verify(entitlements: PropertyListItem?, getTaskAllowRequired: Bool) { + if getTaskAllowRequired { + guard let entitlements, case .plDict(let dict) = entitlements else { + Issue.record("Missing expected entitlements") + return + } - #expect(buildResult.stdout.contains("codesign --force --sign - --entitlements")) + #expect(dict["com.apple.security.get-task-allow"] == .plBool(true)) + } else { + #expect(entitlements == nil) + } + } - // Build with different combinations of the entitlement flag and debug/release build configurations. + let execName = "ExecutableNew" - buildResult = try await build( - ["--enable-get-task-allow-entitlement", "-v"], + var buildResult = try await build( + ["-v"], packagePath: fixturePath, - configuration: .release, - buildSystem: buildSystem, + configuration: buildConfiguration, + cleanAfterward: false, + buildSystem: buildSystem ) + var ( + exitStatus, + entitlements + ) = try await codesignDisplay(execPath: buildResult.binPath.appending(execName)) + + // codesign performs basic verification in display mode, which is enough to confirm ad-hoc signature + // if verification fails (eg. no signature) termination code will be 1 + // though on Apple Silicon binary will always be signed because linker signs it by default + #expect(exitStatus == .terminated(code: 0)) + verify(entitlements: entitlements, getTaskAllowRequired: buildConfiguration == .debug) - #expect(buildResult.stdout.contains("codesign --force --sign - --entitlements")) + try await executeSwiftPackage(fixturePath, extraArgs: ["clean"], buildSystem: buildSystem) buildResult = try await build( - ["--enable-get-task-allow-entitlement", "-v"], + ["--enable-get-task-allow-entitlement"], packagePath: fixturePath, - configuration: .debug, - buildSystem: buildSystem, + configuration: buildConfiguration, + cleanAfterward: false, + buildSystem: buildSystem ) + ( + exitStatus, + entitlements + ) = try await codesignDisplay(execPath: buildResult.binPath.appending(execName)) + + #expect(exitStatus == .terminated(code: 0)) + verify(entitlements: entitlements, getTaskAllowRequired: true) - #expect(buildResult.stdout.contains("codesign --force --sign - --entitlements")) + try await executeSwiftPackage(fixturePath, extraArgs: ["clean"], buildSystem: buildSystem) buildResult = try await build( - ["--disable-get-task-allow-entitlement", "-v"], + ["--disable-get-task-allow-entitlement"], packagePath: fixturePath, - configuration: .debug, - buildSystem: buildSystem, + configuration: buildConfiguration, + cleanAfterward: false, + buildSystem: buildSystem ) - - #expect(!buildResult.stdout.contains("codesign --force --sign - --entitlements")) - - buildResult = try await build( - ["--disable-get-task-allow-entitlement", "-v"], + ( + exitStatus, + entitlements + ) = try await codesignDisplay(execPath: buildResult.binPath.appending(execName)) + + #expect(exitStatus == .terminated(code: 0)) + verify(entitlements: entitlements, getTaskAllowRequired: false) + #else + var buildResult = try await build( + ["-v"], packagePath: fixturePath, - configuration: .release, - buildSystem: buildSystem, + configuration: buildConfiguration, + buildSystem: buildSystem ) #expect(!buildResult.stdout.contains("codesign --force --sign - --entitlements")) - #else - var buildResult = try await build(["-v"], packagePath: fixturePath, configuration: .debug, buildSystem: buildSystem,) - - #expect(!buildResult.stdout.contains("codesign --force --sign - --entitlements")) - - buildResult = try await build(["-v"], packagePath: fixturePath, configuration: .release,buildSystem: buildSystem,) - - #expect(!buildResult.stdout.contains("codesign --force --sign - --entitlements")) buildResult = try await build( ["--disable-get-task-allow-entitlement", "-v"], packagePath: fixturePath, - configuration: .release, + configuration: buildConfiguration, buildSystem: buildSystem, ) @@ -1367,20 +1408,16 @@ struct BuildCommandTestCases { buildResult = try await build( ["--enable-get-task-allow-entitlement", "-v"], packagePath: fixturePath, - configuration: .release, + configuration: buildConfiguration, buildSystem: buildSystem, ) #expect(!buildResult.stdout.contains("codesign --force --sign - --entitlements")) #expect(buildResult.stderr.contains(SwiftCommandState.entitlementsMacOSWarning)) - #endif - - buildResult = try await build(["-v"], packagePath: fixturePath, configuration: .release, buildSystem: buildSystem) - - #expect(!buildResult.stdout.contains("codesign --force --sign - --entitlements")) + #endif } } when: { - [.swiftbuild, .xcode].contains(buildSystem) && ProcessInfo.hostOperatingSystem != .linux + [.xcode].contains(buildSystem) && ProcessInfo.hostOperatingSystem != .linux } }