Skip to content

Add API to query executable paths for runnable targets #482

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Draft
wants to merge 3 commits into
base: main
Choose a base branch
from
Draft
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
47 changes: 47 additions & 0 deletions Sources/BuildService/Session.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
/// Generate information about a runnable target, including its executable path.
public func generateRunnableInfo(
for request: SWBBuildRequest,
targetID: String,
delegate: any SWBPlanningOperationDelegate
) async throws -> SWBRunnableInfo {
guard let workspaceContext = self.workspaceContext else {
throw WorkspaceNotLoadedError()
}

guard let target = workspaceContext.workspace.target(for: targetID) else {
throw TargetNotFoundError(targetID: targetID)
}

guard target.type.isExecutable else {
throw TargetNotRunnableError(targetID: targetID, targetType: target.type)
}

guard let project = workspaceContext.workspace.project(for: target) else {
throw ProjectNotFoundError(targetID: targetID)
}

let coreParameters: BuildParameters
do {
coreParameters = try BuildParameters(from: request.parameters)
} catch {
throw ParameterConversionError(underlyingError: error)
}

let buildRequestContext = BuildRequestContext(workspaceContext: workspaceContext)

let settings = Settings(workspaceContext: workspaceContext, buildRequestContext: buildRequestContext, parameters: coreParameters, project: project, target: target, purpose: .build)

let scope = settings.globalScope
let builtProductsDirPath = scope.evaluate(BuiltinMacros.BUILT_PRODUCTS_DIR)
let executableSubPathString = scope.evaluate(BuiltinMacros.EXECUTABLE_PATH)

let finalExecutablePath = builtProductsDirPath.join(Path(executableSubPathString))
let absoluteExecutablePath: AbsolutePath
do {
absoluteExecutablePath = try AbsolutePath(validating: finalExecutablePath.str)
} catch {
throw PathValidationError(pathString: finalExecutablePath.str, underlyingError: error)
}

return SWBRunnableInfo(executablePath: absoluteExecutablePath)
}
9 changes: 9 additions & 0 deletions Sources/Protocol/SWBRunnableInfo.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import SWBUtil
/// Information about a runnable target, primarily its executable path.
public struct SWBRunnableInfo: Codable, Sendable {
public let executablePath: AbsolutePath

public init(executablePath: AbsolutePath) {
self.executablePath = executablePath
}
}
18 changes: 5 additions & 13 deletions Sources/SWBBuildService/Session.swift
Original file line number Diff line number Diff line change
Expand Up @@ -205,10 +205,8 @@ public final class Session {
return startPIFTransfer(workspaceSignature: workspaceSignature)
}


// MARK: Support for saving macro evaluation scopes to look up by a handle (UUID).


/// The active Settings objects being vended for use by the client.
/// - remark: This is presently only used in `PlanningOperation` to be able to evaluate some settings after receiving provisioning inputs from the client, without having to reconstruct the `ConfiguredTarget` in that asyncronous operation.
var registeredSettings = Registry<String, Settings>()
Expand All @@ -235,10 +233,8 @@ public final class Session {
return settings
}


// MARK: Planning operation support


/// The active planning operations, if any.
///
/// The client is responsible for closing these.
Expand Down Expand Up @@ -267,7 +263,6 @@ public final class Session {
planningOperation.request.send(PlanningOperationDidFinish(sessionHandle: UID, planningOperationHandle: planningOperation.uuid.description))
}


// MARK: Client exchange objects

// FIXME: This should just map on the UUID type, not a string.
Expand All @@ -291,7 +286,6 @@ public final class Session {
activeClientExchanges.removeValue(forKey: exchange.uuid.description)
}


// MARK: Information operation support

/// The active information operations
Expand All @@ -317,7 +311,7 @@ public final class Session {
/// Cancel ongoing information operations
func cancelInfoOperations() {
activeInfoOperations.forEach {
$0.1.cancel()
$0.1.cancel()
}
}

Expand All @@ -329,20 +323,19 @@ public final class Session {
}
}


// MARK: Build operation support

/// The active build operations
private var activeBuilds = Registry<Int, any ActiveBuildOperation>()

/// Returns the normal build operations, excluding the ones that are for the index.
private var activeNormalBuilds: [any ActiveBuildOperation] {
return activeBuilds.values.filter{ !$0.buildRequest.enableIndexBuildArena && !$0.onlyCreatesBuildDescription }
return activeBuilds.values.filter { !$0.buildRequest.enableIndexBuildArena && !$0.onlyCreatesBuildDescription }
}

/// Returns index build operations.
private var activeIndexBuilds: [any ActiveBuildOperation] {
return activeBuilds.values.filter{ $0.buildRequest.enableIndexBuildArena && !$0.onlyCreatesBuildDescription }
return activeBuilds.values.filter { $0.buildRequest.enableIndexBuildArena && !$0.onlyCreatesBuildDescription }
}

/// Registers a build operation with the session
Expand All @@ -351,7 +344,8 @@ public final class Session {
// But we do allow build description creation operations to run concurrently with normal builds. These are important for index queries to function properly even during a build.
// We also allow 'prepare-for-index' build operations to run concurrently with a normal build but only one at a time. These are important for functionality in the Xcode editor to work properly, that the user directly interacts with.
if !build.onlyCreatesBuildDescription {
let (buildType, existingBuilds) = build.buildRequest.enableIndexBuildArena
let (buildType, existingBuilds) =
build.buildRequest.enableIndexBuildArena
? ("index", activeIndexBuilds)
: ("normal", activeNormalBuilds)

Expand Down Expand Up @@ -380,14 +374,12 @@ public final class Session {
}
}


/// A client exchange is used to send a request to the client and handle its response. The service creates and discards these, and is responsible for adding and removing them from the session.
protocol ClientExchange {
/// The stable UUID of the receiver.
var uuid: UUID { get }
}


// Session Extensions

extension Request {
Expand Down
10 changes: 10 additions & 0 deletions Sources/SWBProtocol/SWBRunnableInfo.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
import SWBUtil

/// Information about a runnable target, primarily its executable path.
public struct SWBRunnableInfo: Codable, Sendable {
public let executablePath: AbsolutePath

public init(executablePath: AbsolutePath) {
self.executablePath = executablePath
}
}
17 changes: 9 additions & 8 deletions Sources/SWBTestSupport/AssertMatch.swift
Original file line number Diff line number Diff line change
Expand Up @@ -119,9 +119,9 @@ extension StringPattern: ExpressibleByStringInterpolation {
}
}

package func ~=(pattern: StringPattern, value: String) -> Bool {
package func ~= (pattern: StringPattern, value: String) -> Bool {
switch pattern {
// These cases never matches individual items, they are just used for matching string lists.
// These cases never matches individual items, they are just used for matching string lists.
case .start, .end, .anySequence:
return false

Expand Down Expand Up @@ -155,7 +155,7 @@ package func ~=(pattern: StringPattern, value: String) -> Bool {
}
}

package func ~=(patterns: [StringPattern], input: [String]) -> Bool {
package func ~= (patterns: [StringPattern], input: [String]) -> Bool {
let startIndex = input.startIndex
let endIndex = input.endIndex

Expand Down Expand Up @@ -224,11 +224,12 @@ package func XCTAssertMatch(_ value: @autoclosure @escaping () -> String?, _ pat
XCTAssertMatchImpl(pattern ~= value, { value }, pattern, message, sourceLocation: sourceLocation)
}
package func XCTAssertNoMatch(_ value: @autoclosure @escaping () -> String?, _ pattern: StringPattern, _ message: String? = nil, sourceLocation: SourceLocation = #_sourceLocation) {
XCTAssertMatchImpl({
// `nil` always matches, so in this case we return true to ensure the underlying XCTAssert succeeds
guard let value = value() else { return true }
return !(pattern ~= value)
}(), value, pattern, message, sourceLocation: sourceLocation)
XCTAssertMatchImpl(
{
// `nil` always matches, so in this case we return true to ensure the underlying XCTAssert succeeds
guard let value = value() else { return true }
return !(pattern ~= value)
}(), value, pattern, message, sourceLocation: sourceLocation)
}

package func XCTAssertMatch(_ value: @autoclosure @escaping () -> [String], _ pattern: [StringPattern], _ message: String? = nil, sourceLocation: SourceLocation = #_sourceLocation) {
Expand Down
126 changes: 63 additions & 63 deletions Sources/SWBUtil/Lock.swift
Original file line number Diff line number Diff line change
Expand Up @@ -11,79 +11,79 @@
//===----------------------------------------------------------------------===//

#if canImport(os)
public import os
public import os
#elseif os(Windows)
public import WinSDK
public import WinSDK
#else
public import SWBLibc
public import SWBLibc
#endif

// FIXME: Replace the contents of this file with the Swift standard library's Mutex type once it's available everywhere we deploy.

/// A more efficient lock than a DispatchQueue (esp. under contention).
#if canImport(os)
public typealias Lock = OSAllocatedUnfairLock
public typealias Lock = OSAllocatedUnfairLock
#else
public final class Lock: @unchecked Sendable {
#if os(Windows)
@usableFromInline
let mutex: UnsafeMutablePointer<SRWLOCK> = UnsafeMutablePointer.allocate(capacity: 1)
#elseif os(OpenBSD)
@usableFromInline
let mutex: UnsafeMutablePointer<pthread_mutex_t?> = UnsafeMutablePointer.allocate(capacity: 1)
#else
@usableFromInline
let mutex: UnsafeMutablePointer<pthread_mutex_t> = UnsafeMutablePointer.allocate(capacity: 1)
#endif

public init() {
public final class Lock: @unchecked Sendable {
#if os(Windows)
InitializeSRWLock(self.mutex)
@usableFromInline
let mutex: UnsafeMutablePointer<SRWLOCK> = UnsafeMutablePointer.allocate(capacity: 1)
#elseif os(OpenBSD)
@usableFromInline
let mutex: UnsafeMutablePointer<pthread_mutex_t?> = UnsafeMutablePointer.allocate(capacity: 1)
#else
let err = pthread_mutex_init(self.mutex, nil)
precondition(err == 0)
@usableFromInline
let mutex: UnsafeMutablePointer<pthread_mutex_t> = UnsafeMutablePointer.allocate(capacity: 1)
#endif
}

deinit {
#if os(Windows)
// SRWLOCK does not need to be freed
#else
let err = pthread_mutex_destroy(self.mutex)
precondition(err == 0)
#endif
mutex.deallocate()
}
public init() {
#if os(Windows)
InitializeSRWLock(self.mutex)
#else
let err = pthread_mutex_init(self.mutex, nil)
precondition(err == 0)
#endif
}

@usableFromInline
func lock() {
#if os(Windows)
AcquireSRWLockExclusive(self.mutex)
#else
let err = pthread_mutex_lock(self.mutex)
precondition(err == 0)
#endif
}
deinit {
#if os(Windows)
// SRWLOCK does not need to be freed
#else
let err = pthread_mutex_destroy(self.mutex)
precondition(err == 0)
#endif
mutex.deallocate()
}

@usableFromInline
func unlock() {
#if os(Windows)
ReleaseSRWLockExclusive(self.mutex)
#else
let err = pthread_mutex_unlock(self.mutex)
precondition(err == 0)
#endif
}
@usableFromInline
func lock() {
#if os(Windows)
AcquireSRWLockExclusive(self.mutex)
#else
let err = pthread_mutex_lock(self.mutex)
precondition(err == 0)
#endif
}

@inlinable
public func withLock<T>(_ body: () throws -> T) rethrows -> T {
self.lock()
defer {
self.unlock()
@usableFromInline
func unlock() {
#if os(Windows)
ReleaseSRWLockExclusive(self.mutex)
#else
let err = pthread_mutex_unlock(self.mutex)
precondition(err == 0)
#endif
}

@inlinable
public func withLock<T>(_ body: () throws -> T) rethrows -> T {
self.lock()
defer {
self.unlock()
}
return try body()
}
return try body()
}
}
#endif

/// Small wrapper to provide only locked access to its value.
Expand Down Expand Up @@ -122,15 +122,15 @@ extension LockedValue {
}

#if canImport(Darwin)
@available(macOS, deprecated: 15.0, renamed: "Synchronization.Mutex")
@available(iOS, deprecated: 18.0, renamed: "Synchronization.Mutex")
@available(tvOS, deprecated: 18.0, renamed: "Synchronization.Mutex")
@available(watchOS, deprecated: 11.0, renamed: "Synchronization.Mutex")
@available(visionOS, deprecated: 2.0, renamed: "Synchronization.Mutex")
public typealias SWBMutex = LockedValue
@available(macOS, deprecated: 15.0, renamed: "Synchronization.Mutex")
@available(iOS, deprecated: 18.0, renamed: "Synchronization.Mutex")
@available(tvOS, deprecated: 18.0, renamed: "Synchronization.Mutex")
@available(watchOS, deprecated: 11.0, renamed: "Synchronization.Mutex")
@available(visionOS, deprecated: 2.0, renamed: "Synchronization.Mutex")
public typealias SWBMutex = LockedValue
#else
public import Synchronization
public typealias SWBMutex = Mutex
public import Synchronization
public typealias SWBMutex = Mutex
#endif

extension SWBMutex where Value: ~Copyable, Value == Void {
Expand Down
Loading