Skip to content

Commit fe2478d

Browse files
authored
Merge branch 'main' into topics/async-platform-registry-and-windows-link-discovery
2 parents aab9fa9 + f8cceac commit fe2478d

File tree

5 files changed

+74
-23
lines changed

5 files changed

+74
-23
lines changed

Sources/SWBAndroidPlatform/Plugin.swift

Lines changed: 24 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -16,11 +16,23 @@ import SWBMacro
1616
import Foundation
1717

1818
@PluginExtensionSystemActor public func initializePlugin(_ manager: PluginManager) {
19+
let plugin = AndroidPlugin()
1920
manager.register(AndroidPlatformSpecsExtension(), type: SpecificationsExtensionPoint.self)
20-
manager.register(AndroidEnvironmentExtension(), type: EnvironmentExtensionPoint.self)
21+
manager.register(AndroidEnvironmentExtension(plugin: plugin), type: EnvironmentExtensionPoint.self)
2122
manager.register(AndroidPlatformExtension(), type: PlatformInfoExtensionPoint.self)
22-
manager.register(AndroidSDKRegistryExtension(), type: SDKRegistryExtensionPoint.self)
23-
manager.register(AndroidToolchainRegistryExtension(), type: ToolchainRegistryExtensionPoint.self)
23+
manager.register(AndroidSDKRegistryExtension(plugin: plugin), type: SDKRegistryExtensionPoint.self)
24+
manager.register(AndroidToolchainRegistryExtension(plugin: plugin), type: ToolchainRegistryExtensionPoint.self)
25+
}
26+
27+
final class AndroidPlugin: Sendable {
28+
private let androidSDKInstallations = AsyncCache<OperatingSystem, [AndroidSDK]>()
29+
30+
func cachedAndroidSDKInstallations(host: OperatingSystem) async throws -> [AndroidSDK] {
31+
try await androidSDKInstallations.value(forKey: host) {
32+
// Always pass localFS because this will be cached, and executes a process on the host system so there's no reason to pass in any proxy.
33+
try await AndroidSDK.findInstallations(host: host, fs: localFS)
34+
}
35+
}
2436
}
2537

2638
struct AndroidPlatformSpecsExtension: SpecificationsExtension {
@@ -34,10 +46,12 @@ struct AndroidPlatformSpecsExtension: SpecificationsExtension {
3446
}
3547

3648
struct AndroidEnvironmentExtension: EnvironmentExtension {
49+
let plugin: AndroidPlugin
50+
3751
func additionalEnvironmentVariables(context: any EnvironmentExtensionAdditionalEnvironmentVariablesContext) async throws -> [String: String] {
3852
switch context.hostOperatingSystem {
3953
case .windows, .macOS, .linux:
40-
if let latest = try? await AndroidSDK.findInstallations(host: context.hostOperatingSystem, fs: context.fs).first {
54+
if let latest = try? await plugin.cachedAndroidSDKInstallations(host: context.hostOperatingSystem).first {
4155
return [
4256
"ANDROID_SDK_ROOT": latest.path.str,
4357
"ANDROID_NDK_ROOT": latest.ndkPath?.str,
@@ -71,6 +85,8 @@ struct AndroidPlatformExtension: PlatformInfoExtension {
7185
}
7286

7387
struct AndroidSDKRegistryExtension: SDKRegistryExtension {
88+
let plugin: AndroidPlugin
89+
7490
func additionalSDKs(context: any SDKRegistryExtensionAdditionalSDKsContext) async throws -> [(path: Path, platform: SWBCore.Platform?, data: [String: PropertyListItem])] {
7591
let host = context.hostOperatingSystem
7692
guard let androidPlatform = context.platformRegistry.lookup(name: "android") else {
@@ -96,7 +112,7 @@ struct AndroidSDKRegistryExtension: SDKRegistryExtension {
96112
"AR": .plString(host.imageFormat.executableName(basename: "llvm-ar")),
97113
]
98114

99-
guard let androidSdk = try? await AndroidSDK.findInstallations(host: host, fs: context.fs).first else {
115+
guard let androidSdk = try? await plugin.cachedAndroidSDKInstallations(host: host).first else {
100116
return []
101117
}
102118

@@ -138,8 +154,10 @@ struct AndroidSDKRegistryExtension: SDKRegistryExtension {
138154
}
139155

140156
struct AndroidToolchainRegistryExtension: ToolchainRegistryExtension {
157+
let plugin: AndroidPlugin
158+
141159
func additionalToolchains(context: any ToolchainRegistryExtensionAdditionalToolchainsContext) async -> [Toolchain] {
142-
guard let toolchainPath = try? await AndroidSDK.findInstallations(host: context.hostOperatingSystem, fs: context.fs).first?.toolchainPath else {
160+
guard let toolchainPath = try? await plugin.cachedAndroidSDKInstallations(host: context.hostOperatingSystem).first?.toolchainPath else {
143161
return []
144162
}
145163

Sources/SWBQNXPlatform/Plugin.swift

Lines changed: 24 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -16,11 +16,23 @@ import SWBMacro
1616
import Foundation
1717

1818
@PluginExtensionSystemActor public func initializePlugin(_ manager: PluginManager) {
19+
let plugin = QNXPlugin()
1920
manager.register(QNXPlatformSpecsExtension(), type: SpecificationsExtensionPoint.self)
20-
manager.register(QNXEnvironmentExtension(), type: EnvironmentExtensionPoint.self)
21+
manager.register(QNXEnvironmentExtension(plugin: plugin), type: EnvironmentExtensionPoint.self)
2122
manager.register(QNXPlatformExtension(), type: PlatformInfoExtensionPoint.self)
22-
manager.register(QNXSDKRegistryExtension(), type: SDKRegistryExtensionPoint.self)
23-
manager.register(QNXToolchainRegistryExtension(), type: ToolchainRegistryExtensionPoint.self)
23+
manager.register(QNXSDKRegistryExtension(plugin: plugin), type: SDKRegistryExtensionPoint.self)
24+
manager.register(QNXToolchainRegistryExtension(plugin: plugin), type: ToolchainRegistryExtensionPoint.self)
25+
}
26+
27+
final class QNXPlugin: Sendable {
28+
private let qnxInstallations = AsyncCache<OperatingSystem, [QNXSDP]>()
29+
30+
func cachedQNXSDPInstallations(host: OperatingSystem) async throws -> [QNXSDP] {
31+
try await qnxInstallations.value(forKey: host) {
32+
// Always pass localFS because this will be cached, and executes a process on the host system so there's no reason to pass in any proxy.
33+
try await QNXSDP.findInstallations(host: host, fs: localFS)
34+
}
35+
}
2436
}
2537

2638
struct QNXPlatformSpecsExtension: SpecificationsExtension {
@@ -30,8 +42,10 @@ struct QNXPlatformSpecsExtension: SpecificationsExtension {
3042
}
3143

3244
struct QNXEnvironmentExtension: EnvironmentExtension {
45+
let plugin: QNXPlugin
46+
3347
func additionalEnvironmentVariables(context: any EnvironmentExtensionAdditionalEnvironmentVariablesContext) async throws -> [String : String] {
34-
if let latest = try await QNXSDP.findInstallations(host: context.hostOperatingSystem, fs: context.fs).first {
48+
if let latest = try await plugin.cachedQNXSDPInstallations(host: context.hostOperatingSystem).first {
3549
return latest.environment
3650
}
3751
return [:]
@@ -59,12 +73,14 @@ struct QNXPlatformExtension: PlatformInfoExtension {
5973
}
6074

6175
struct QNXSDKRegistryExtension: SDKRegistryExtension {
76+
let plugin: QNXPlugin
77+
6278
func additionalSDKs(context: any SDKRegistryExtensionAdditionalSDKsContext) async throws -> [(path: Path, platform: SWBCore.Platform?, data: [String : PropertyListItem])] {
6379
guard let qnxPlatform = context.platformRegistry.lookup(name: "qnx") else {
6480
return []
6581
}
6682

67-
guard let qnxSdk = try? await QNXSDP.findInstallations(host: context.hostOperatingSystem, fs: context.fs).first else {
83+
guard let qnxSdk = try? await plugin.cachedQNXSDPInstallations(host: context.hostOperatingSystem).first else {
6884
return []
6985
}
7086

@@ -123,8 +139,10 @@ struct QNXSDKRegistryExtension: SDKRegistryExtension {
123139
}
124140

125141
struct QNXToolchainRegistryExtension: ToolchainRegistryExtension {
142+
let plugin: QNXPlugin
143+
126144
func additionalToolchains(context: any ToolchainRegistryExtensionAdditionalToolchainsContext) async -> [Toolchain] {
127-
guard let toolchainPath = try? await QNXSDP.findInstallations(host: context.hostOperatingSystem, fs: context.fs).first?.hostPath else {
145+
guard let toolchainPath = try? await plugin.cachedQNXSDPInstallations(host: context.hostOperatingSystem).first?.hostPath else {
128146
return []
129147
}
130148

Sources/SWBWindowsPlatform/Plugin.swift

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,11 +15,24 @@ import SWBCore
1515
import Foundation
1616

1717
@PluginExtensionSystemActor public func initializePlugin(_ manager: PluginManager) {
18+
let plugin = WindowsPlugin()
1819
manager.register(WindowsPlatformSpecsExtension(), type: SpecificationsExtensionPoint.self)
19-
manager.register(WindowsEnvironmentExtension(), type: EnvironmentExtensionPoint.self)
20+
manager.register(WindowsEnvironmentExtension(plugin: plugin), type: EnvironmentExtensionPoint.self)
2021
manager.register(WindowsPlatformExtension(), type: PlatformInfoExtensionPoint.self)
2122
manager.register(WindowsSDKRegistryExtension(), type: SDKRegistryExtensionPoint.self)
2223
}
24+
25+
final class WindowsPlugin: Sendable {
26+
private let vsInstallations = AsyncSingleValueCache<[VSInstallation], any Error>()
27+
28+
func cachedVSInstallations() async throws -> [VSInstallation] {
29+
try await vsInstallations.value {
30+
// Always pass localFS because this will be cached, and executes a process on the host system so there's no reason to pass in any proxy.
31+
try await VSInstallation.findInstallations(fs: localFS)
32+
}
33+
}
34+
}
35+
2336
struct WindowsPlatformSpecsExtension: SpecificationsExtension {
2437
func specificationFiles(resourceSearchPaths: [Path]) -> Bundle? {
2538
findResourceBundle(nameWhenInstalledInToolchain: "SwiftBuild_SWBWindowsPlatform", resourceSearchPaths: resourceSearchPaths, defaultBundle: Bundle.module)
@@ -41,6 +54,8 @@ private func findLatestInstallDirectory(fs: any FSProxy) async throws -> Path? {
4154
}
4255

4356
struct WindowsEnvironmentExtension: EnvironmentExtension {
57+
let plugin: WindowsPlugin
58+
4459
func additionalEnvironmentVariables(context: any EnvironmentExtensionAdditionalEnvironmentVariablesContext) async throws -> [String: String] {
4560
guard context.hostOperatingSystem == .windows else {
4661
return [:]

Tests/SWBTaskExecutionTests/PBXCpTests.swift

Lines changed: 0 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -555,22 +555,19 @@ fileprivate struct PBXCpTests: CoreBasedTests {
555555
#expect(result.success == true)
556556
#expect(result.output == "copying src...\n 9 bytes\n")
557557
#expect(try fs.read(dst) == "contents1")
558-
let modificationDate = try fs.getFileInfo(dst).modificationDate
559558

560559
try await Task.sleep(for: .milliseconds(500))
561560
let result2 = await pbxcp(["builtin-copy", "-skip-copy-if-contents-equal", "-rename", "-v", src.str, dst.str], cwd: Path("/"))
562561
#expect(result2.success == true)
563562
#expect(result2.output == "note: skipping copy of '\(src.str)' because it has the same contents as '\(dst.str)'\n")
564563
#expect(try fs.read(dst) == "contents1")
565-
#expect(try fs.getFileInfo(dst).modificationDate == modificationDate)
566564

567565
try await Task.sleep(for: .milliseconds(500))
568566
try fs.write(src, contents: "contents2")
569567
let result3 = await pbxcp(["builtin-copy", "-skip-copy-if-contents-equal", "-rename", "-v", src.str, dst.str], cwd: Path("/"))
570568
#expect(result3.success == true)
571569
#expect(result3.output == "copying src...\n 9 bytes\n")
572570
#expect(try fs.read(dst) == "contents2")
573-
#expect(try fs.getFileInfo(dst).modificationDate != modificationDate)
574571
}
575572

576573
try await withTemporaryDirectory { tmp in
@@ -587,22 +584,19 @@ fileprivate struct PBXCpTests: CoreBasedTests {
587584
#expect(result.success == true)
588585
#expect(result.output == "copying file...\n 9 bytes\n")
589586
#expect(try fs.read(dstFile) == "contents1")
590-
let modificationDate = try fs.getFileInfo(dstFile).modificationDate
591587

592588
try await Task.sleep(for: .milliseconds(500))
593589
let result2 = await pbxcp(["builtin-copy", "-skip-copy-if-contents-equal", "-rename", "-v", src.str, dst.str], cwd: Path("/"))
594590
#expect(result2.success == true)
595591
#expect(result2.output == "note: skipping copy of '\(src.str)' because it has the same contents as '\(dst.str)'\n")
596592
#expect(try fs.read(dstFile) == "contents1")
597-
#expect(try fs.getFileInfo(dstFile).modificationDate == modificationDate)
598593

599594
try await Task.sleep(for: .milliseconds(500))
600595
try fs.write(srcFile, contents: "contents2")
601596
let result3 = await pbxcp(["builtin-copy", "-skip-copy-if-contents-equal", "-rename", "-v", src.str, dst.str], cwd: Path("/"))
602597
#expect(result3.success == true)
603598
#expect(result3.output == "copying src/...\n")
604599
#expect(try fs.read(dstFile) == "contents2")
605-
#expect(try fs.getFileInfo(dstFile).modificationDate != modificationDate)
606600
}
607601
}
608602

Tests/SWBUtilTests/FSProxyTests.swift

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -260,14 +260,14 @@ import SWBTestSupport
260260
}
261261
}
262262

263-
@Test(.skipHostOS(.linux)) // flaky on Linux because timestamps have been observed to vary by 0.001s after copying
263+
@Test
264264
func localCopyFile() throws {
265265
try withTemporaryDirectory { tmpDir in
266266
try _testCopyFile(localFS, basePath: tmpDir)
267267
}
268268
}
269269

270-
@Test(.skipHostOS(.linux)) // flaky on Linux because timestamps have been observed to vary by 0.001s after copying
270+
@Test
271271
func localCopyTree() throws {
272272
try withTemporaryDirectory { tmpDir in
273273
try _testCopyTree(localFS, basePath: tmpDir)
@@ -1111,7 +1111,10 @@ import SWBTestSupport
11111111
if try ProcessInfo.processInfo.hostOperatingSystem() != .windows {
11121112
#expect(try fs.getFilePermissions(testDataPathDst) == permissions)
11131113
}
1114-
#expect(try fs.getFileInfo(testDataPath).modificationDate == fs.getFileInfo(testDataPathDst).modificationDate)
1114+
if fs is PseudoFS {
1115+
// There is no guarantee that the implementation of copy() will preserve the modification timestamp on either files and/or directories, on any real filesystem, so only make this assertion for the pseudo filesystem which we wholly control.
1116+
#expect(try fs.getFileInfo(testDataPath).modificationDate == fs.getFileInfo(testDataPathDst).modificationDate)
1117+
}
11151118
#expect(try ByteString(testData) == fs.read(testDataPathDst))
11161119
}
11171120

@@ -1121,7 +1124,10 @@ import SWBTestSupport
11211124
#expect(lhs.isDirectory == rhs.isDirectory, sourceLocation: sourceLocation)
11221125
#expect(lhs.isExecutable == rhs.isExecutable, sourceLocation: sourceLocation)
11231126
#expect(lhs.isSymlink == rhs.isSymlink, sourceLocation: sourceLocation)
1124-
#expect(lhs.modificationDate == rhs.modificationDate, sourceLocation: sourceLocation)
1127+
if fs is PseudoFS {
1128+
// There is no guarantee that the implementation of copy() will preserve the modification timestamp on either files and/or directories, on any real filesystem, so only make this assertion for the pseudo filesystem which we wholly control.
1129+
#expect(lhs.modificationDate == rhs.modificationDate, sourceLocation: sourceLocation)
1130+
}
11251131
#expect(lhs.owner == rhs.owner, sourceLocation: sourceLocation)
11261132
#expect(lhs.permissions == rhs.permissions, sourceLocation: sourceLocation)
11271133
}

0 commit comments

Comments
 (0)