Skip to content
Open
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
2 changes: 1 addition & 1 deletion Package.swift
Original file line number Diff line number Diff line change
Expand Up @@ -290,7 +290,7 @@ let package = Package(
swiftSettings: swiftSettings(languageMode: .v6)),
.target(
name: "SWBTestSupport",
dependencies: ["SwiftBuild", "SWBBuildSystem", "SWBCore", "SWBTaskConstruction", "SWBTaskExecution", "SWBUtil", "SWBLLBuild", "SWBMacro"],
dependencies: ["SwiftBuild", "SWBBuildService", "SWBBuildSystem", "SWBCore", "SWBTaskConstruction", "SWBTaskExecution", "SWBUtil", "SWBLLBuild", "SWBMacro"],
swiftSettings: swiftSettings(languageMode: .v5)),

// Tests
Expand Down
10 changes: 5 additions & 5 deletions Sources/SWBBuildService/BuildServiceEntryPoint.swift
Original file line number Diff line number Diff line change
Expand Up @@ -76,7 +76,7 @@ extension BuildService {
do {
try await Service.main { inputFD, outputFD in
// Launch the Swift Build service.
try await BuildService.run(inputFD: inputFD, outputFD: outputFD, connectionMode: .outOfProcess, pluginsDirectory: Bundle.main.builtInPlugInsURL, arguments: arguments, pluginLoadingFinished: {
try await BuildService.run(inputFD: inputFD, outputFD: outputFD, connectionMode: .outOfProcess, arguments: arguments, pluginLoadingFinished: {
// Already using DYLD_IMAGE_SUFFIX, clear it to avoid propagating ASan to children.
// This must happen after plugin loading.
if let suffix = getEnvironmentVariable("DYLD_IMAGE_SUFFIX"), suffix == "_asan" {
Expand All @@ -94,7 +94,7 @@ extension BuildService {
/// Common entry point to the build service for in-process and out-of-process connections.
///
/// Called directly from the exported C entry point `swiftbuildServiceEntryPoint` for in-process connections, or from `BuildService.main()` (after some basic file descriptor setup) for out-of-process connections.
fileprivate static func run(inputFD: FileDescriptor, outputFD: FileDescriptor, connectionMode: ServiceHostConnectionMode, pluginsDirectory: URL?, arguments: [String], pluginLoadingFinished: () throws -> Void) async throws {
fileprivate static func run(inputFD: FileDescriptor, outputFD: FileDescriptor, connectionMode: ServiceHostConnectionMode, arguments: [String], pluginLoadingFinished: () throws -> Void) async throws {
let pluginManager = try await { @PluginExtensionSystemActor () async throws in
// Create the plugin manager and load plugins.
let pluginManager = MutablePluginManager(pluginLoadingFilter: { _ in true })
Expand Down Expand Up @@ -150,7 +150,7 @@ extension BuildService {
staticPluginInitializers.forEach { $0(pluginManager) }
} else {
// Otherwise, load the normal plugins.
if let pluginsDirectory {
if let pluginsDirectory = Bundle(for: BuildService.self).builtInPlugInsURL {
let pluginsPath = try pluginsDirectory.filePath
pluginManager.load(at: pluginsPath)
for subpath in (try? localFS.listdir(pluginsPath).sorted().map({ pluginsPath.join($0) })) ?? [] {
Expand Down Expand Up @@ -190,11 +190,11 @@ extension BuildService {
///
/// This is exported as a C function for clients who wish to spawn the build service in-process, and which is used by the SwiftBuild client framework.
@_cdecl("swiftbuildServiceEntryPoint")
public func swiftbuildServiceEntryPoint(inputFD: Int32, outputFD: Int32, pluginsDirectory: URL?, completion: @Sendable @escaping ((any Error)?) -> Void) {
public func swiftbuildServiceEntryPoint(inputFD: Int32, outputFD: Int32, completion: @Sendable @escaping ((any Error)?) -> Void) {
_Concurrency.Task<Void, Never>.detached {
let error: (any Error)?
do {
try await BuildService.run(inputFD: FileDescriptor(rawValue: inputFD), outputFD: FileDescriptor(rawValue: outputFD), connectionMode: .inProcess, pluginsDirectory: pluginsDirectory, arguments: [buildServiceExecutableName()], pluginLoadingFinished: {})
try await BuildService.run(inputFD: FileDescriptor(rawValue: inputFD), outputFD: FileDescriptor(rawValue: outputFD), connectionMode: .inProcess, arguments: [buildServiceExecutableName()], pluginLoadingFinished: {})
error = nil
} catch let e {
error = e
Expand Down
65 changes: 1 addition & 64 deletions Sources/SWBCore/Core.swift
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ public final class Core: Sendable {
/// Get a configured instance of the core.
///
/// - returns: An initialized Core instance on which all discovery and loading will have been completed. If there are errors during that process, they will be logged to `stderr` and no instance will be returned. Otherwise, the initialized object is returned.
public static func getInitializedCore(_ delegate: any CoreDelegate, pluginManager: MutablePluginManager, developerPath: DeveloperPath? = nil, resourceSearchPaths: [Path] = [], inferiorProductsPath: Path? = nil, extraPluginRegistration: @PluginExtensionSystemActor (_ pluginManager: MutablePluginManager, _ pluginPaths: [Path]) -> Void = { _, _ in }, additionalContentPaths: [Path] = [], environment: [String:String] = [:], buildServiceModTime: Date, connectionMode: ServiceHostConnectionMode) async -> Core? {
public static func getInitializedCore(_ delegate: any CoreDelegate, pluginManager: MutablePluginManager, developerPath: DeveloperPath? = nil, resourceSearchPaths: [Path] = [], inferiorProductsPath: Path? = nil, additionalContentPaths: [Path] = [], environment: [String:String] = [:], buildServiceModTime: Date, connectionMode: ServiceHostConnectionMode) async -> Core? {
// Enable macro expression interning during loading.
return await MacroNamespace.withExpressionInterningEnabled { () -> Core? in
let hostOperatingSystem: OperatingSystem
Expand All @@ -51,14 +51,6 @@ public final class Core: Sendable {
return nil
}

if useStaticPluginInitialization {
// In a package context, plugins are statically linked into the build system.
// Load specs from service plugins if requested since we don't have a Service in certain tests
// Here we don't have access to `core.pluginPaths` like we do in the call below,
// but it doesn't matter because `core.pluginPaths` will return an empty array when USE_STATIC_PLUGIN_INITIALIZATION is defined.
await extraPluginRegistration(pluginManager, [])
}

let resolvedDeveloperPath: DeveloperPath
do {
if let resolved = developerPath {
Expand All @@ -81,12 +73,6 @@ public final class Core: Sendable {
return nil
}

if !useStaticPluginInitialization {
// In a package context, plugins are statically linked into the build system.
// Load specs from service plugins if requested since we don't have a Service in certain tests.
await extraPluginRegistration(pluginManager, Self.pluginPaths(inferiorProductsPath: inferiorProductsPath, developerPath: resolvedDeveloperPath))
}

let core: Core
do {
core = try await Core(delegate: delegate, hostOperatingSystem: hostOperatingSystem, pluginManager: pluginManager.finalize(), developerPath: resolvedDeveloperPath, resourceSearchPaths: resourceSearchPaths, inferiorProductsPath: inferiorProductsPath, additionalContentPaths: additionalContentPaths, environment: environment, buildServiceModTime: buildServiceModTime, connectionMode: connectionMode)
Expand Down Expand Up @@ -316,55 +302,6 @@ public final class Core: Sendable {
_coreSettings.value
}

/// The list of plugin search paths.
private static func pluginPaths(inferiorProductsPath: Path?, developerPath: DeveloperPath) -> [Path] {
if useStaticPluginInitialization {
// In a package context, plugins are statically linked into the build system.
return []
}

var result = [Path]()

// If we are inferior, then search the built products directory first.
//
// FIXME: This is error prone, as it won't validate that any of these are installed in the expected location.
// FIXME: If we remove, move or rename something in the built Xcode, then this will still find the old item in the installed Xcode.
if let inferiorProductsPath {
result.append(inferiorProductsPath)
}

guard let coreFrameworkPath = try? Bundle(for: Self.self).bundleURL.filePath.dirname else {
return result
}

// Search paths relative to SWBCore itself.
do {
func appendInferior(_ path: Path) {
if !developerPath.path.dirname.isAncestor(of: path) {
result.append(path)
}
}

// flat layout, when SWBCore is directly nested under BUILT_PRODUCTS_DIR
appendInferior(coreFrameworkPath)

// flat layout, when SWBCore is nested in SWBBuildServiceBundle in SwiftBuild.framework in BUILT_PRODUCTS_DIR
appendInferior(coreFrameworkPath.join("../../../../../../.."))
}

// In the superior or a hierarchical build, look for plugins in the build service bundle relative to SWBCore.framework.
let pluginPath = coreFrameworkPath.join("../PlugIns")
result.append(pluginPath)
for subdirectory in (try? localFS.listdir(pluginPath)) ?? [] {
let subdirectoryPath = pluginPath.join(subdirectory)
if localFS.isDirectory(subdirectoryPath) {
result.append(subdirectoryPath)
}
}

return result.map { $0.normalize() }
}

/// The list of toolchain search paths.
@_spi(Testing) public let toolchainPaths: [ToolchainRegistry.SearchPath]

Expand Down
20 changes: 19 additions & 1 deletion Sources/SWBTestSupport/CoreTestSupport.swift
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ package import SWBUtil
import SWBTaskConstruction
import SWBTaskExecution
import SWBServiceCore
private import SWBBuildService
import Testing

#if USE_STATIC_PLUGIN_INITIALIZATION
Expand Down Expand Up @@ -170,8 +171,25 @@ extension Core {
registerExtraPlugins(pluginManager)
}

var pluginPaths: [Path] = []
if let buildServicePlugInsDirectory = Bundle(for: BuildService.self).builtInPlugInsURL {
let path = try buildServicePlugInsDirectory.filePath
pluginPaths.append(path)

pluginPaths += ((try? localFS.listdir(path)) ?? []).compactMap {
let subdirectoryPath = path.join($0)
if localFS.isDirectory(subdirectoryPath) {
return subdirectoryPath
} else {
return nil
}
}
}

await extraPluginRegistration(pluginManager: pluginManager, pluginPaths: pluginPaths)

let delegate = inputDelegate ?? TestingCoreDelegate()
guard let core = await Core.getInitializedCore(delegate, pluginManager: pluginManager, developerPath: developerPath, inferiorProductsPath: inferiorProductsPath, extraPluginRegistration: extraPluginRegistration, additionalContentPaths: additionalContentPaths, environment: environment, buildServiceModTime: Date(), connectionMode: .inProcess) else {
guard let core = await Core.getInitializedCore(delegate, pluginManager: pluginManager, developerPath: developerPath, inferiorProductsPath: inferiorProductsPath, additionalContentPaths: additionalContentPaths, environment: environment, buildServiceModTime: Date(), connectionMode: .inProcess) else {
// If we weren't passed a delgate, the caller couldn't possibly have emitted the diagnostics themselves (and CoreInitializationError deliberately doesn't include them in its error output).
if inputDelegate == nil {
// Emit one failure per error, which will be significantly easier to read.
Expand Down
40 changes: 25 additions & 15 deletions Sources/SWBUtil/PluginManager.swift
Original file line number Diff line number Diff line change
Expand Up @@ -72,17 +72,16 @@ import SWBLibc
// If we found a bundle, load it and look for a registration function.
let name = path.basename

let pluginPath: Path
let executablePath: Path
let pluginIdentifier: String
switch path.fileSuffix {
case ".bundle":
pluginPath = path
let shallow = !localFS.exists(path.join("Contents"))
let executableBasePath = shallow
? path.join(Path(name).basenameWithoutSuffix)
: path.join("Contents").join("MacOS").join(Path(name).basenameWithoutSuffix)
executablePath = {
func pluginInfo(forPluginAt path: Path,
withShallowBundleTestDirectory shallowBundleTestDirectory: Path,
deepExecutablesDirectory: Path,
infoPlistFile: Path,
) -> (Path, Path, String) {
let shallow = !localFS.exists(shallowBundleTestDirectory)

let executablesDirectory = shallow ? path : deepExecutablesDirectory
let executableBasePath = executablesDirectory.join(Path(name).basenameWithoutSuffix)
let executablePath = {
if let suffix = getEnvironmentVariable("DYLD_IMAGE_SUFFIX")?.nilIfEmpty {
let candidate = executableBasePath.appendingFileNameSuffix(suffix)
if localFS.exists(candidate) {
Expand All @@ -91,15 +90,26 @@ import SWBLibc
}
return executableBasePath
}()
let infoPlistPath = shallow
? path.join("Info.plist")
: path.join("Contents").join("Info.plist")
if let plist = try? PropertyList.fromPath(infoPlistPath, fs: localFS), let cfBundleIdentifier = plist.dictValue?["CFBundleIdentifier"]?.stringValue {

let pluginIdentifier: String
if let plist = try? PropertyList.fromPath(infoPlistFile, fs: localFS), let cfBundleIdentifier = plist.dictValue?["CFBundleIdentifier"]?.stringValue {
pluginIdentifier = cfBundleIdentifier
}
else {
pluginIdentifier = path.basename
}

return (path, executablePath, pluginIdentifier)
}

let pluginPath: Path
let executablePath: Path
let pluginIdentifier: String
switch path.fileSuffix {
case ".bundle":
(pluginPath, executablePath, pluginIdentifier) = pluginInfo(forPluginAt: path, withShallowBundleTestDirectory: path.join("Contents"), deepExecutablesDirectory: path.join("Contents").join("MacOS"), infoPlistFile: path.join("Contents").join("Info.plist"))
case ".framework":
(pluginPath, executablePath, pluginIdentifier) = pluginInfo(forPluginAt: path, withShallowBundleTestDirectory: path.join("Versions"), deepExecutablesDirectory: path.join("Versions").join("Current"), infoPlistFile: path.join("Versions").join("Current").join("Resources").join("Info.plist"))
default:
return
}
Expand Down
Loading
Loading