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
Original file line number Diff line number Diff line change
Expand Up @@ -188,6 +188,19 @@ Run 控件,也不在 `java-run-launch` 或平台 adapter 再做一次 `blocksR
用户点击后可以在准备状态可见的同时排队等待 Core。否则快照更新时序会产生“面板
已就绪、启动仍被准备中拦截”的自相矛盾状态。

### 每一步使用自己的工具链环境(#1216)

Windows 曾先填入自动发现的 JAVA_HOME,再用“仅为空时插入”应用 Maven JDK,
导致用户选的 Maven JDK 永远无法覆盖先填的 Java 值。两个宿主现在调用 Core 的
execution.resolveEnvironment:继承环境低于当前步骤选定的工具链,用户显式
字符串最高。Maven 资源处理步骤即使继承主程序的 Java 绑定,也选择 Maven JDK;
后续 JVM 保持项目 JDK。Windows 环境键按不区分大小写处理。

平台仍负责定位可执行文件与 JDK home,Core 不探测机器、不执行 Maven,也不
复制上游构建模型。不要在 Windows 调换两次 insert 就结束:这会让后续前置
步骤继续各自解释优先级。共享 fixture 和真实 Windows 宿主解析测试覆盖选定
Maven JDK、显式覆盖及资源步骤;#1133 的 Maven 原生资源步骤保留,不改成手工复制。

## 考虑过的备选方案

- 先执行 `mvn compile`,再手工猜 `target/classes` 与依赖:被否。自定义输出目录、
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -134,6 +134,20 @@ Windows 是独立前端实现,写入进行中到期的那次延迟自动保存
磁盘 B 不符,B 和 C 都保留。认可 B 后外部又写入 D,仍必须重新提示。
不要把“保留编辑器版本”实现成永久 `force=true`。

### 构建输出先筛选,再判断事件洪峰(#1176)

512 条阈值现在统计筛选后的有效变化。普通工作区事件由 Core 的默认目录策略
排除构建输出;Windows 原生宿主在跨 Tauri 边界前筛选,macOS 使用同一批量
命令。相对路径以最具体的显式监听根计算,项目根本身叫 build 不会被误排除。
显式监听的文件与生成源码目录仍有自己的事件。重命名先分解两端,再独立筛选,
从 target 移入 src 的文件仍能出现。

系统报告事件丢失或监听失败时始终恢复重扫,不能因为路径在 target 下就忽略。
不采用暂停 watcher、延迟几秒或屏蔽整批事件:构建期间可能同时编辑源码。
代价是原生 watcher 仍收集构建输出,改善的是跨平台业务刷新量,不宣称减少
操作系统通知量;Git 和已打开文档监听保持独立。共享 fixture、输出洪峰混合
源码、显式监听、跨过滤边界重命名及原生丢失标记回归共同约束该边界。

## 考虑过的备选方案

- 无界的全项目递归监听会让资源和事件量随整个项目增长,也无法解决漏通知后的
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,28 @@ Current File 当作最终配置执行。配置文档可读不等于运行项目
工具链探测暂停首个加载,验证加载中不能启动、重叠加载选用配置默认项,以及
完成后的用户选择能够保留。不要通过增加 CI 等待时间掩盖这类状态提交竞态。

### 不可读目录不是已删除目录(#1389)

自动发现、指纹、Node 清单和 Windows Java 文件列表使用 Core 的同一遍历策略。
先排除已知缓存和构建输出,再进入子目录;不能按“点开头”排除所有隐藏源码。
工作区根不可读仍失败,可选子目录不可读则继续兄弟目录,并记录未知路径。
重新生成保留未知路径下的旧配置及输入摘要;检查返回 incompleteProjectScan,
不从不完整快照推断删除或新鲜度。即使后续目录可读,旧基线也要完整重新生成后
才能解除诊断。两端 Run 面板都必须接纳这个诊断代码,并通过共享 i18n 显示
权限恢复与重新识别的提示;不能只在 Core 返回却被面板筛选隐藏。
代价是权限恢复前可能保留一个实际已删除的入口,优于静默丢配置。

不要只给 Windows 加 catch 并返回空数组:这样会把访问失败写成成功的空快照,
且 macOS 的指纹仍使用另一套规则。扫描不接管 JDT 的 Java 入口事实或 Maven
声明模块;只替换已有的启发式文件遍历。注入目录读取失败的测试覆盖兄弟目录、
必需根失败和链接不跟随,保留测试覆盖旧配置与输入摘要的未知/已删除区别。

Java 文件枚举的数量上限也必须限制后续目录访问,不能完整扫描后再截取数组。
共享遍历逐目录交付结果;Java 列表收集满上限后立即停止,不保留整个工作区的
文件名快照。已遇到的不可读路径仍保留在遍历诊断中,完整指纹扫描不受这个
列表上限影响。测试注入目录读取器,在达到 8,000 个 Java 文件后若继续打开
后续目录就立即失败,并验证顺序稳定、零上限与完整扫描仍能继续访问。

## 考虑过的备选方案

- 持久化大小和修改时间以复用摘要:同大小文件可在保留时间戳时发生内容变化,
Expand Down
4 changes: 4 additions & 0 deletions macos/Resources/en.lproj/Localizable.strings
Original file line number Diff line number Diff line change
Expand Up @@ -2754,6 +2754,7 @@
"Project run configuration is invalid" = "Project run configuration is invalid";
"Project run configuration not found" = "Project run configuration not found";
"Project runtime settings are saved locally for this project." = "Project runtime settings are saved locally for this project.";
"Project scan is incomplete" = "Project scan is incomplete";
"Project skills" = "Project skills";
"Project toolchain needs attention" = "Project toolchain needs attention";
"Project will be created in: %@" = "Project will be created in: %@";
Expand Down Expand Up @@ -3488,6 +3489,7 @@
"Solidity Support" = "Solidity Support";
"Some MCP configuration files could not be restored: %@. Refresh and check these files before retrying." = "Some MCP configuration files could not be restored: %@. Refresh and check these files before retrying.";
"Some dependencies are not shown." = "Some dependencies are not shown.";
"Some project folders could not be read. Existing run configurations were retained. Restore access, then regenerate configurations." = "Some project folders could not be read. Existing run configurations were retained. Restore access, then regenerate configurations.";
"Sort by Last Updated" = "Sort by Last Updated";
"Sort by Name" = "Sort by Name";
"Sort plugins" = "Sort plugins";
Expand Down Expand Up @@ -8985,6 +8987,8 @@
"results" = "results";
"revert" = "revert";
"run configuration" = "run configuration";
"run.incompleteProjectScanMessage" = "Some project folders could not be read. Existing run configurations were retained. Restore access, then regenerate configurations.";
"run.incompleteProjectScanTitle" = "Project scan is incomplete";
"running for %@" = "running for %@";
"scope managed from %@" = "scope managed from %@";
"scope not updated to %@" = "scope not updated to %@";
Expand Down
4 changes: 4 additions & 0 deletions macos/Resources/zh-Hans.lproj/Localizable.strings
Original file line number Diff line number Diff line change
Expand Up @@ -2754,6 +2754,7 @@
"Project run configuration is invalid" = "项目运行配置无效";
"Project run configuration not found" = "未找到项目运行配置";
"Project runtime settings are saved locally for this project." = "项目运行时设置会保存在本机的项目配置中。";
"Project scan is incomplete" = "项目扫描不完整";
"Project skills" = "项目技能";
"Project toolchain needs attention" = "项目工具链需要处理";
"Project will be created in: %@" = "项目将在以下位置创建:%@";
Expand Down Expand Up @@ -3488,6 +3489,7 @@
"Solidity Support" = "Solidity 支持";
"Some MCP configuration files could not be restored: %@. Refresh and check these files before retrying." = "部分 MCP 配置文件未能恢复:%@。请刷新并检查这些文件后重试。";
"Some dependencies are not shown." = "部分依赖未显示。";
"Some project folders could not be read. Existing run configurations were retained. Restore access, then regenerate configurations." = "部分项目目录无法读取,已有运行配置已保留。请恢复访问权限后重新识别运行配置。";
"Sort by Last Updated" = "按最近更新排序";
"Sort by Name" = "按名称排序";
"Sort plugins" = "插件排序";
Expand Down Expand Up @@ -8985,6 +8987,8 @@
"results" = "个结果";
"revert" = "反向提交";
"run configuration" = "运行配置";
"run.incompleteProjectScanMessage" = "部分项目目录无法读取,已有运行配置已保留。请恢复访问权限后重新识别运行配置。";
"run.incompleteProjectScanTitle" = "项目扫描不完整";
"running for %@" = "已运行 %@";
"scope managed from %@" = "依赖管理前 scope 为 %@";
"scope not updated to %@" = "未提升为 %@ scope";
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import Foundation

/// Platform discovery supplies homes; Core owns precedence for every launch step.
protocol RunLaunchEnvironmentResolving {
func resolveLaunchEnvironment(
toolchain: String?,
selectedHome: String?,
inherited: [String: String],
overrides: [String: String]
) throws -> [String: String]
}
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ final class MacDirectoryWatcher: DirectoryChangeSource, @unchecked Sendable {
}
}

private let core = RustCoreBridge()
private let configuration: DirectoryWatchConfiguration
private let visibilityRules: FileVisibilityRules
private let queue = DispatchQueue(label: "app.lithe.file-events", qos: .utility)
Expand Down Expand Up @@ -175,7 +176,23 @@ final class MacDirectoryWatcher: DirectoryChangeSource, @unchecked Sendable {
batch.requiresFullRescan = true
batch.gitStateMayHaveChanged = true
}
batch.workspacePaths = workspacePaths.sorted()
let rootPrefix = configuration.workspaceRoot.standardizedFileURL.path + "/"
let relativePaths = workspacePaths.sorted().map { path in
path.hasPrefix(rootPrefix) ? String(path.dropFirst(rootPrefix.count)) : ""
}
// One Core call per batch; native loss flags above remain unconditional.
let filtered: Result<[String], RustCoreBridge.CoreCallError> = core.executeResult(
command: "workspace.filterChanges", payload: relativePaths
)
switch filtered {
case .success(let paths):
let accepted = Set(paths)
batch.workspacePaths = zip(workspacePaths.sorted(), relativePaths)
.filter { accepted.contains($0.1) }.map(\.0)
case .failure:
// A failed classifier cannot prove that a source change is irrelevant.
batch.requiresFullRescan = true
}
return batch
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -498,7 +498,8 @@ final class MacServiceContainer {
let executableResolver = providedRunExecutableResolver ?? RunExecutableResolver(
runtimeService: runtimeService,
toolchainRegistry: runToolchainRegistry,
metadataResolver: ProcessRunToolchainMetadataResolver(processRunner: processRunner)
metadataResolver: ProcessRunToolchainMetadataResolver(processRunner: processRunner),
environmentResolver: rustCore
)
let buildSystemPlugins = pluginStartup.registeredPlugins.filter { !$0.descriptor.buildSystems.isEmpty }
let buildSystems = buildSystemPlugins.isEmpty ? nil : BuildSystemService(
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
import Foundation

extension RustCoreBridge: RunLaunchEnvironmentResolving {
private struct LaunchEnvironmentPayload: Encodable {
let toolchain: String?
let javaHome: String?
let mavenJavaHome: String?
let inherited: [String: String]
let overrides: [String: String]
}

private struct LaunchEnvironmentResult: Decodable {
let environment: [String: String]
}

func resolveLaunchEnvironment(
toolchain: String?, selectedHome: String?,
inherited: [String: String], overrides: [String: String]
) throws -> [String: String] {
let result: LaunchEnvironmentResult = try executeResult(
command: "execution.resolveEnvironment",
payload: LaunchEnvironmentPayload(
toolchain: toolchain, javaHome: selectedHome, mavenJavaHome: selectedHome,
inherited: inherited, overrides: overrides
)
).get()
return result.environment
}
}

@MainActor
extension RunExecutableResolver {
convenience init(
runtimeService: ProjectRuntimeService,
toolchainRegistry: RunToolchainRegistry? = nil,
metadataResolver: (any RunToolchainMetadataResolving)? = nil
) {
self.init(runtimeService: runtimeService, toolchainRegistry: toolchainRegistry,
metadataResolver: metadataResolver, environmentResolver: RustCoreBridge())
}
}
17 changes: 13 additions & 4 deletions macos/Sources/Lithe/Services/Java/RunExecutableResolver.swift
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import Foundation

@MainActor
final class RunExecutableResolver: RunExecutableResolving {
private let environmentResolver: any RunLaunchEnvironmentResolving
private let runtimeService: ProjectRuntimeService
private let toolchainRegistry: RunToolchainRegistry
private let metadataResolver: (any RunToolchainMetadataResolving)?
Expand All @@ -10,8 +11,10 @@ final class RunExecutableResolver: RunExecutableResolving {
init(
runtimeService: ProjectRuntimeService,
toolchainRegistry: RunToolchainRegistry? = nil,
metadataResolver: (any RunToolchainMetadataResolving)? = nil
metadataResolver: (any RunToolchainMetadataResolving)? = nil,
environmentResolver: any RunLaunchEnvironmentResolving
) {
self.environmentResolver = environmentResolver
self.runtimeService = runtimeService
self.toolchainRegistry = toolchainRegistry ?? .standard()
self.metadataResolver = metadataResolver
Expand All @@ -38,7 +41,7 @@ final class RunExecutableResolver: RunExecutableResolving {
)
resolved = ResolvedRunExecutable(
executableURL: toolchain.executableURL,
environment: toolchain.environment.merging(planEnvironment) { _, override in override }
environment: toolchain.environment
)

case .command(let command):
Expand All @@ -49,14 +52,20 @@ final class RunExecutableResolver: RunExecutableResolving {
}
resolved = ResolvedRunExecutable(
executableURL: executable,
environment: runtimeService.processEnvironment(overrides: planEnvironment)
environment: runtimeService.processEnvironment()
)
}

let identifier: String?
if case .toolchain(let id) = plan.executable { identifier = id } else { identifier = nil }
let environment = try environmentResolver.resolveLaunchEnvironment(
toolchain: identifier, selectedHome: resolved.environment["JAVA_HOME"],
inherited: resolved.environment, overrides: planEnvironment
)
return ResolvedRunExecutable(
executableURL: resolved.executableURL,
environment: RunLaunchEnvironment.augmenting(
resolved.environment,
environment,
executableURL: resolved.executableURL,
nodeCandidates: runtimeService.executableCandidates("node"),
projectURL: projectURL,
Expand Down
7 changes: 7 additions & 0 deletions macos/Sources/Lithe/Views/Run/RunView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -191,6 +191,13 @@ struct RunView: View {
}

private var configurationNotice: (title: String, message: String, systemImage: String)? {
if feature.configurationDiagnostics.contains(where: { $0.code == "incompleteProjectScan" }) {
return (
runText("Project scan is incomplete"),
runText("Some project folders could not be read. Existing run configurations were retained. Restore access, then regenerate configurations."),
"exclamationmark.triangle.fill"
)
}
if let diagnostic = feature.configurationDiagnostics.first(where: { $0.code == "staleFingerprint" }) {
return (
runText("Run configurations may be out of date"),
Expand Down
29 changes: 29 additions & 0 deletions macos/Tests/LitheTests/RunLaunchEnvironmentTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,35 @@ import Testing
struct RunLaunchEnvironmentTests {
private let projectURL = URL(fileURLWithPath: "/fixture-project", isDirectory: true)

@Test
func sharedWorkspaceChangeFilterCrossesTheNativeBridge() throws {
struct Fixture: Decodable { let paths: [String]; let expected: [String] }
let root = URL(fileURLWithPath: #filePath).deletingLastPathComponent().deletingLastPathComponent()
.deletingLastPathComponent().deletingLastPathComponent()
let fixture = try JSONDecoder().decode(Fixture.self, from: Data(contentsOf:
root.appendingPathComponent("shared/fixtures/workspace/change-filter-v1.json")))
let actual: [String] = try RustCoreBridge().executeResult(
command: "workspace.filterChanges", payload: fixture.paths
).get()
#expect(actual == fixture.expected)
}

@Test
func sharedCoreLaunchEnvironmentRetainsSelectionAndExplicitOverrides() throws {
let core = RustCoreBridge()
let selected = try core.resolveLaunchEnvironment(
toolchain: "project-maven", selectedHome: "/fixture/jdk21",
inherited: ["JAVA_HOME": "/fixture/jdk8", "PATH": "/fixture/bin"], overrides: [:]
)
#expect(selected["JAVA_HOME"] == "/fixture/jdk21")
#expect(selected["PATH"] == "/fixture/bin")
let overridden = try core.resolveLaunchEnvironment(
toolchain: "project-maven", selectedHome: "/fixture/jdk21",
inherited: selected, overrides: ["JAVA_HOME": "/fixture/explicit"]
)
#expect(overridden["JAVA_HOME"] == "/fixture/explicit")
}

@Test
func guiLaunchPathReachesTheDiscoveredNodeInterpreter() {
let environment = RunLaunchEnvironment.augmenting(
Expand Down
Loading
Loading