Skip to content

Commit 66ff3c9

Browse files
committed
fix(run): isolate service toolchain requirements
1 parent 1668f65 commit 66ff3c9

20 files changed

Lines changed: 1199 additions & 103 deletions

File tree

macos/Sources/LitheExecutionModule/Application/ExecutionFeatureModels.swift

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -91,9 +91,7 @@ package final class RunFeatureModel: ObservableObject {
9191
package var configurationSaveError: String? { service.configurationSaveError }
9292
package var projectToolchain: ProjectToolchainSelection { service.projectToolchain }
9393
package var blockingToolchainDiagnostic: RunConfigurationDiagnostic? {
94-
service.configurationDiagnostics.first {
95-
$0.code == "missingToolchain" || $0.code == "toolchainVersionMismatch"
96-
}
94+
service.blockingToolchainDiagnostic(for: service.selectedConfiguration)
9795
}
9896
package var sourceSearchRoots: [URL] { service.sourceSearchRoots }
9997

macos/Sources/LitheExecutionModule/Services/RunService.swift

Lines changed: 22 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -417,7 +417,7 @@ package final class RunService: ObservableObject {
417417
fail("Project run configuration is missing. Identify the project before running.")
418418
return
419419
}
420-
if let diagnostic = configurationDiagnostics.first(where: { Self.isBlockingToolchainDiagnostic($0) }) {
420+
if let diagnostic = blockingToolchainDiagnostic(for: configuration) {
421421
fail(diagnostic.message)
422422
return
423423
}
@@ -636,6 +636,16 @@ package final class RunService: ObservableObject {
636636
return descriptor.id != "java"
637637
}
638638

639+
package func blockingToolchainDiagnostic(
640+
for configuration: RunConfiguration?
641+
) -> RunConfigurationDiagnostic? {
642+
configurationDiagnostics.first { diagnostic in
643+
Self.isBlockingToolchainDiagnostic(diagnostic)
644+
&& (diagnostic.configurationID == nil
645+
|| diagnostic.configurationID == configuration?.id)
646+
}
647+
}
648+
639649
private static func isBlockingToolchainDiagnostic(_ diagnostic: RunConfigurationDiagnostic) -> Bool {
640650
diagnostic.code == "missingToolchain" || diagnostic.code == "toolchainVersionMismatch"
641651
}
@@ -866,6 +876,17 @@ package final class RunService: ObservableObject {
866876
guard configurationStatus == .ready,
867877
let projectURL else { return }
868878
moduleSessions.removeAll { $0.id == configuration.id }
879+
if let diagnostic = blockingToolchainDiagnostic(for: configuration) {
880+
moduleSessions.append(RunSession(
881+
id: configuration.id,
882+
configurationID: configuration.id,
883+
title: configuration.name,
884+
output: diagnostic.message + "\n",
885+
isRunning: false,
886+
exitCode: 1
887+
))
888+
return
889+
}
869890
if extensionRequiredLanguageIDs.contains(configuration.kind.providerID),
870891
languageRunExtension(providerID: configuration.kind.providerID) == nil {
871892
moduleSessions.append(RunSession(

macos/Tests/LitheTests/RunConfigurationIntegrationTests.swift

Lines changed: 66 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2674,6 +2674,64 @@ struct RunConfigurationIntegrationTests {
26742674
#expect(requests[1].environment?["JAVA_HOME"] == nil)
26752675
}
26762676

2677+
@Test
2678+
func scopedNodeDiagnosticBlocksOnlyTheFrontendService() async throws {
2679+
let backend = JavaRunConfiguration(
2680+
id: "module:backend",
2681+
name: "backend",
2682+
kind: .mavenModule,
2683+
execution: .service,
2684+
modulePath: "backend",
2685+
mainClass: nil
2686+
)
2687+
let frontend = RunConfiguration(
2688+
id: "npm.script:web/dev",
2689+
name: "dev",
2690+
kind: .process(provider: "npm.script"),
2691+
execution: .service,
2692+
modulePath: nil,
2693+
mainClass: nil
2694+
)
2695+
let backendPlan = SharedLaunchPlan(
2696+
executable: .toolchain("project-maven"),
2697+
arguments: ["spring-boot:run"],
2698+
workingDirectory: "."
2699+
)
2700+
let diagnostic = RunConfigurationDiagnostic(
2701+
configurationID: frontend.id,
2702+
code: "missingToolchain",
2703+
message: "No local node toolchain is selected"
2704+
)
2705+
let fixture = makeFixture(
2706+
status: .ready,
2707+
effective: [backend, frontend].map {
2708+
EffectiveRunConfiguration(configuration: $0, options: RunOptions())
2709+
},
2710+
plans: [backend.id: backendPlan],
2711+
diagnostics: [diagnostic]
2712+
)
2713+
2714+
await fixture.service.loadProject(
2715+
at: fixture.root,
2716+
files: [],
2717+
mavenProject: fixture.mavenProject
2718+
)
2719+
fixture.service.runAllServices()
2720+
2721+
#expect(fixture.operations.launchPlanIDs == [backend.id])
2722+
#expect(fixture.processFactory.processes.count == 1)
2723+
let backendSession = try #require(
2724+
fixture.service.moduleSessions.first(where: { $0.id == backend.id })
2725+
)
2726+
let frontendSession = try #require(
2727+
fixture.service.moduleSessions.first(where: { $0.id == frontend.id })
2728+
)
2729+
#expect(backendSession.isRunning)
2730+
#expect(!frontendSession.isRunning)
2731+
#expect(frontendSession.exitCode == 1)
2732+
#expect(frontendSession.output.contains("No local node toolchain"))
2733+
}
2734+
26772735
@Test
26782736
func serviceAddressUsesExplicitArgumentsAndEnvironmentPorts() async throws {
26792737
let argumentService = JavaRunConfiguration(
@@ -3789,6 +3847,7 @@ struct RunConfigurationIntegrationTests {
37893847
plans: [String: SharedLaunchPlan] = [:],
37903848
generationEntryCount: Int? = nil,
37913849
defaultConfigurationID: String? = nil,
3850+
diagnostics: [RunConfigurationDiagnostic] = [],
37923851
preferences: RunTestKeyValueStore = RunTestKeyValueStore()
37933852
) -> RunServiceFixture {
37943853
let root = URL(fileURLWithPath: "/tmp/lithe-run-service", isDirectory: true)
@@ -3797,7 +3856,8 @@ struct RunConfigurationIntegrationTests {
37973856
effective: effective,
37983857
plans: plans,
37993858
generationEntryCount: generationEntryCount,
3800-
defaultConfigurationID: defaultConfigurationID
3859+
defaultConfigurationID: defaultConfigurationID,
3860+
diagnostics: diagnostics
38013861
)
38023862
let process = RecordingStreamingProcess()
38033863
let processFactory = RecordingProcessFactory()
@@ -3942,6 +4002,7 @@ private final class RecordingRunConfigurationOperations: RunConfigurationOperati
39424002
let plans: [String: SharedLaunchPlan]
39434003
let generationEntryCount: Int?
39444004
let defaultConfigurationID: String?
4005+
let diagnostics: [RunConfigurationDiagnostic]
39454006
private(set) var resolveCalls = 0
39464007
private(set) var migrationCalls = 0
39474008
private(set) var launchPlanIDs: [String] = []
@@ -3959,13 +4020,15 @@ private final class RecordingRunConfigurationOperations: RunConfigurationOperati
39594020
effective: [EffectiveRunConfiguration],
39604021
plans: [String: SharedLaunchPlan],
39614022
generationEntryCount: Int? = nil,
3962-
defaultConfigurationID: String? = nil
4023+
defaultConfigurationID: String? = nil,
4024+
diagnostics: [RunConfigurationDiagnostic] = []
39634025
) {
39644026
self.status = status
39654027
self.effective = effective
39664028
self.plans = plans
39674029
self.generationEntryCount = generationEntryCount
39684030
self.defaultConfigurationID = defaultConfigurationID
4031+
self.diagnostics = diagnostics
39694032
}
39704033

39714034
func inspect(at projectURL: URL) -> ProjectRunConfigurationInspection {
@@ -3985,7 +4048,7 @@ private final class RecordingRunConfigurationOperations: RunConfigurationOperati
39854048
}
39864049
return RunConfigurationResolution(
39874050
configurations: effective,
3988-
diagnostics: [],
4051+
diagnostics: diagnostics,
39894052
defaultConfigurationID: defaultConfigurationID
39904053
)
39914054
}

0 commit comments

Comments
 (0)