Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
41 changes: 41 additions & 0 deletions Sources/SPMBuildCore/Plugins/PluginInvocation.swift
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,46 @@ public enum PluginAction {
}

extension PluginTarget {
public func invoke(
action: PluginAction,
buildEnvironment: BuildEnvironment,
scriptRunner: PluginScriptRunner,
workingDirectory: AbsolutePath,
outputDirectory: AbsolutePath,
toolSearchDirectories: [AbsolutePath],
accessibleTools: [String: (path: AbsolutePath, triples: [String]?)],
writableDirectories: [AbsolutePath],
readOnlyDirectories: [AbsolutePath],
allowNetworkConnections: [SandboxNetworkPermission],
pkgConfigDirectories: [AbsolutePath],
sdkRootPath: AbsolutePath?,
fileSystem: FileSystem,
observabilityScope: ObservabilityScope,
callbackQueue: DispatchQueue,
delegate: PluginInvocationDelegate
) async throws -> Bool {
try await safe_async {
self.invoke(
action: action,
buildEnvironment: buildEnvironment,
scriptRunner: scriptRunner,
workingDirectory: workingDirectory,
outputDirectory: outputDirectory,
toolSearchDirectories: toolSearchDirectories,
accessibleTools: accessibleTools,
writableDirectories: writableDirectories,
readOnlyDirectories: readOnlyDirectories,
allowNetworkConnections: allowNetworkConnections,
pkgConfigDirectories: pkgConfigDirectories,
sdkRootPath: sdkRootPath,
fileSystem: fileSystem,
observabilityScope: observabilityScope,
callbackQueue: callbackQueue,
delegate: delegate,
completion: $0
)
}
}
/// Invokes the plugin by compiling its source code (if needed) and then running it as a subprocess. The specified
/// plugin action determines which entry point is called in the subprocess, and the package and the tool mapping
/// determine the context that is available to the plugin.
Expand All @@ -47,6 +87,7 @@ extension PluginTarget {
/// - fileSystem: The file system to which all of the paths refers.
///
/// - Returns: A PluginInvocationResult that contains the results of invoking the plugin.
@available(*, noasync, message: "Use the async alternative")
public func invoke(
action: PluginAction,
buildEnvironment: BuildEnvironment,
Expand Down
24 changes: 24 additions & 0 deletions Sources/SPMBuildCore/Plugins/PluginScriptRunner.swift
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ import PackageGraph
public protocol PluginScriptRunner {

/// Public protocol function that starts compiling the plugin script to an executable. The name is used as the basename for the executable and auxiliary files. The tools version controls the availability of APIs in PackagePlugin, and should be set to the tools version of the package that defines the plugin (not of the target to which it is being applied). This function returns immediately and then calls the completion handler on the callback queue when compilation ends.
@available(*, noasync, message: "Use the async alternative")
func compilePluginScript(
sourceFiles: [AbsolutePath],
pluginName: String,
Expand Down Expand Up @@ -61,6 +62,29 @@ public protocol PluginScriptRunner {
var hostTriple: Triple { get throws }
}

public extension PluginScriptRunner {
func compilePluginScript(
sourceFiles: [AbsolutePath],
pluginName: String,
toolsVersion: ToolsVersion,
observabilityScope: ObservabilityScope,
callbackQueue: DispatchQueue,
delegate: PluginScriptCompilerDelegate
) async throws -> PluginCompilationResult {
try await safe_async {
self.compilePluginScript(
sourceFiles: sourceFiles,
pluginName: pluginName,
toolsVersion: toolsVersion,
observabilityScope: observabilityScope,
callbackQueue: callbackQueue,
delegate: delegate,
completion: $0
)
}
}
}

/// Protocol by which `PluginScriptRunner` communicates back to the caller as it compiles plugins.
public protocol PluginScriptCompilerDelegate {
/// Called immediately before compiling a plugin. Will not be called if the plugin didn't have to be compiled. This call is always followed by a `didCompilePlugin()` but is mutually exclusive with a `skippedCompilingPlugin()` call.
Expand Down
11 changes: 6 additions & 5 deletions Sources/SPMTestSupport/misc.swift
Original file line number Diff line number Diff line change
Expand Up @@ -48,22 +48,23 @@ public func testWithTemporaryDirectory(
)
}

public func testWithTemporaryDirectory(
@discardableResult
public func testWithTemporaryDirectory<Result>(
function: StaticString = #function,
body: (AbsolutePath) async throws -> Void
) async throws {
body: (AbsolutePath) async throws -> Result
) async throws -> Result {
let cleanedFunction = function.description
.replacingOccurrences(of: "(", with: "")
.replacingOccurrences(of: ")", with: "")
.replacingOccurrences(of: ".", with: "")
.replacingOccurrences(of: ":", with: "_")
try await withTemporaryDirectory(prefix: "spm-tests-\(cleanedFunction)") { tmpDirPath in
return try await withTemporaryDirectory(prefix: "spm-tests-\(cleanedFunction)") { tmpDirPath in
defer {
// Unblock and remove the tmp dir on deinit.
try? localFileSystem.chmod(.userWritable, path: tmpDirPath, options: [.recursive])
try? localFileSystem.removeFileTree(tmpDirPath)
}
try await body(tmpDirPath)
return try await body(tmpDirPath)
}
}

Expand Down
22 changes: 22 additions & 0 deletions Sources/SourceControl/RepositoryManager.swift
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,27 @@ public class RepositoryManager: Cancellable {
self.concurrencySemaphore = DispatchSemaphore(value: maxConcurrentOperations)
}

public func lookup(
package: PackageIdentity,
repository: RepositorySpecifier,
updateStrategy: RepositoryUpdateStrategy,
observabilityScope: ObservabilityScope,
delegateQueue: DispatchQueue,
callbackQueue: DispatchQueue
) async throws -> RepositoryHandle {
try await safe_async {
self.lookup(
package: package,
repository: repository,
updateStrategy: updateStrategy,
observabilityScope: observabilityScope,
delegateQueue: delegateQueue,
callbackQueue: callbackQueue,
completion: $0
)
}
}

/// Get a handle to a repository.
///
/// This will initiate a clone of the repository automatically, if necessary.
Expand All @@ -107,6 +128,7 @@ public class RepositoryManager: Cancellable {
/// - delegateQueue: Dispatch queue for delegate events
/// - callbackQueue: Dispatch queue for callbacks
/// - completion: The completion block that should be called after lookup finishes.
@available(*, noasync, message: "Use the async alternative")
public func lookup(
package: PackageIdentity,
repository: RepositorySpecifier,
Expand Down
20 changes: 8 additions & 12 deletions Tests/CommandsTests/PackageRegistryToolTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -643,7 +643,7 @@ final class PackageRegistryToolTests: CommandsTestCase {

// Validate signatures
var verifierConfiguration = VerifierConfiguration()
verifierConfiguration.trustedRoots = try temp_await { self.testRoots(callback: $0) }
verifierConfiguration.trustedRoots = try testRoots()

// archive signature
let archivePath = workingDirectory.appending("\(packageIdentity)-\(version).zip")
Expand Down Expand Up @@ -753,7 +753,7 @@ final class PackageRegistryToolTests: CommandsTestCase {

// Validate signatures
var verifierConfiguration = VerifierConfiguration()
verifierConfiguration.trustedRoots = try temp_await { self.testRoots(callback: $0) }
verifierConfiguration.trustedRoots = try testRoots()

// archive signature
let archivePath = workingDirectory.appending("\(packageIdentity)-\(version).zip")
Expand Down Expand Up @@ -860,7 +860,7 @@ final class PackageRegistryToolTests: CommandsTestCase {

// Validate signatures
var verifierConfiguration = VerifierConfiguration()
verifierConfiguration.trustedRoots = try temp_await { self.testRoots(callback: $0) }
verifierConfiguration.trustedRoots = try testRoots()

// archive signature
let archivePath = workingDirectory.appending("\(packageIdentity)-\(version).zip")
Expand Down Expand Up @@ -920,15 +920,11 @@ final class PackageRegistryToolTests: CommandsTestCase {
XCTAssertEqual(try SwiftPackageRegistryTool.Login.loginURL(from: registryURL, loginAPIPath: "/secret-sign-in").absoluteString, "https://packages.example.com:8081/secret-sign-in")
}

private func testRoots(callback: (Result<[[UInt8]], Error>) -> Void) {
do {
try fixture(name: "Signing", createGitRepo: false) { fixturePath in
let rootCA = try localFileSystem
.readFileContents(fixturePath.appending(components: "Certificates", "TestRootCA.cer")).contents
callback(.success([rootCA]))
}
} catch {
callback(.failure(error))
private func testRoots() throws -> [[UInt8]] {
try fixture(name: "Signing", createGitRepo: false) { fixturePath in
let rootCA = try localFileSystem
.readFileContents(fixturePath.appending(components: "Certificates", "TestRootCA.cer")).contents
return [rootCA]
}
}

Expand Down
15 changes: 6 additions & 9 deletions Tests/CommandsTests/PackageToolTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -2903,11 +2903,11 @@ final class PackageToolTests: CommandsTestCase {
}
}

func testSinglePluginTarget() throws {
func testSinglePluginTarget() async throws {
// Only run the test if the environment in which we're running actually supports Swift concurrency (which the plugin APIs require).
try XCTSkipIf(!UserToolchain.default.supportsSwiftConcurrency(), "skipping because test environment doesn't support concurrency")

try testWithTemporaryDirectory { tmpPath in
try await testWithTemporaryDirectory { tmpPath in
// Create a sample package with a library target and a plugin.
let packageDir = tmpPath.appending(components: "MyPackage")
try localFileSystem.createDirectory(packageDir, recursive: true)
Expand Down Expand Up @@ -2956,13 +2956,10 @@ final class PackageToolTests: CommandsTestCase {

// Load the root manifest.
let rootInput = PackageGraphRootInput(packages: [packageDir], dependencies: [])
let rootManifests = try temp_await {
workspace.loadRootManifests(
packages: rootInput.packages,
observabilityScope: observability.topScope,
completion: $0
)
}
let rootManifests = try await workspace.loadRootManifests(
packages: rootInput.packages,
observabilityScope: observability.topScope
)
XCTAssert(rootManifests.count == 1, "\(rootManifests)")

// Load the package graph.
Expand Down
Loading