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 @@ -202,6 +202,12 @@ if ($Scope -in @("All", "WindowsRust")) {
-TargetDirectory "windows/tauri/src-tauri/target" `
-Report (Join-Path $reportRoot "windows-terminal-rust.json")

Invoke-TimedRustTests `
-Manifest "windows/tauri/src-tauri/Cargo.toml" `
-Package "lithe-legacy-tooling" `
-TargetDirectory "windows/tauri/src-tauri/target" `
-Report (Join-Path $reportRoot "windows-legacy-java-rust.json")

Invoke-TimedRustTests `
-Manifest "windows/tauri/src-tauri/Cargo.toml" `
-TargetDirectory "windows/tauri/src-tauri/target" `
Expand Down
23 changes: 16 additions & 7 deletions rust/lithe-core/src/execution/configuration.rs
Original file line number Diff line number Diff line change
Expand Up @@ -560,7 +560,9 @@ impl RunConfiguration {
#[serde(rename_all = "camelCase")]
/// Versioned requirements written separately from run configurations.
pub struct ToolchainRequirementsDocument {
/// Document version the reader must accept before trusting any requirement.
pub version: u32,
/// Requirement per toolchain kind, keyed by the kind the caller asks about.
#[serde(default)]
pub toolchains: BTreeMap<String, ToolchainRequirement>,
}
Expand Down Expand Up @@ -3125,7 +3127,8 @@ fn validate_version(version: u32) -> Result<(), CoreError> {
Ok(())
}

pub(super) fn validate_sidecar_version(version: u32) -> Result<(), CoreError> {
/// Rejects a sidecar document written for a version this Core does not read.
pub fn validate_sidecar_version(version: u32) -> Result<(), CoreError> {
if version != SIDECAR_VERSION {
return Err(
CoreError::new(ErrorCode::NotSupported, "Unsupported document version")
Expand Down Expand Up @@ -3682,9 +3685,7 @@ fn detected_ids(root: &Path) -> BTreeSet<String> {
/// generation, else derived from the project's own declarations (`pom.xml`,
/// `.java-version`, the Maven wrapper) so a JDK can be chosen before the Run
/// list has been identified.
pub(super) fn read_requirements(
root: &Path,
) -> Result<Option<ToolchainRequirementsDocument>, CoreError> {
pub fn read_requirements(root: &Path) -> Result<Option<ToolchainRequirementsDocument>, CoreError> {
if let Some(layer) = generated_layer::get(root) {
return Ok(Some(layer.requirements.clone()));
}
Expand Down Expand Up @@ -3928,7 +3929,11 @@ fn append_toolchain_diagnostics(
}
}

pub(super) fn version_satisfies(kind: &str, actual: &str, required: &str, minimum: bool) -> bool {
/// Answers whether `actual` meets `required` for one toolchain kind.
///
/// `minimum` compares ordered versions; otherwise `actual` must start with
/// `required`, which is how a dialect such as Java compares feature versions.
pub fn version_satisfies(kind: &str, actual: &str, required: &str, minimum: bool) -> bool {
let mut actual_parts = version_parts(actual);
let mut required_parts = version_parts(required);
if kind == "java" {
Expand All @@ -3945,7 +3950,11 @@ pub(super) fn version_satisfies(kind: &str, actual: &str, required: &str, minimu
}
}

pub(super) fn version_parts(value: &str) -> Vec<u32> {
/// Splits a version into its numeric parts, ignoring separators and suffixes.
///
/// An input with no digits yields an empty list, which callers treat as "not a
/// version" rather than as zero.
pub fn version_parts(value: &str) -> Vec<u32> {
value
.split(|character: char| !character.is_ascii_digit())
.filter(|part| !part.is_empty())
Expand All @@ -3955,7 +3964,7 @@ pub(super) fn version_parts(value: &str) -> Vec<u32> {

/// Drops the legacy `1.` prefix Java 8 and earlier report (`1.8.0_504`), so
/// those runtimes compare on the same feature-version scale as `8` or `17.0.12`.
pub(super) fn java_feature_version_parts(parts: Vec<u32>) -> Vec<u32> {
pub fn java_feature_version_parts(parts: Vec<u32>) -> Vec<u32> {
if parts.len() > 1 && parts[0] == 1 {
parts[1..].to_vec()
} else {
Expand Down
8 changes: 4 additions & 4 deletions rust/lithe-core/src/execution/launch_command.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,8 @@
//! host must encode it losslessly with the launcher's native platform encoding.
//! JEP 400 does not make Windows launcher argument files UTF-8.
//!
//! JDK 8 has no argument files; [`super::plan_classpath_jar_launch`] covers
//! that release with a manifest-only class-path JAR under the same budget.
//! JDK 8 has no argument files, so a launch for that release always stays
//! direct here.

use serde::{Deserialize, Serialize};
use std::path::Path;
Expand All @@ -26,7 +26,7 @@ pub const WINDOWS_COMMAND_LINE_LIMIT: usize = 32_767;
/// Headroom for the null terminator and for host quoting that this estimate
/// does not model exactly. Shortening early costs nothing; shortening too late
/// fails the launch.
pub(super) const COMMAND_LINE_MARGIN: usize = 2_048;
const COMMAND_LINE_MARGIN: usize = 2_048;

/// Options whose value is a joined list of absolute paths. These carry
/// practically all of an oversized command line, so moving them into the
Expand Down Expand Up @@ -256,7 +256,7 @@ pub fn plan_launch_command(
/// Windows counts UTF-16 code units, and each argument containing whitespace or
/// a quote is wrapped in quotes. The estimate never has to be exact because
/// [`COMMAND_LINE_MARGIN`] absorbs the difference.
pub(super) fn command_line_length(executable: &str, arguments: &[String]) -> usize {
fn command_line_length(executable: &str, arguments: &[String]) -> usize {
let mut length = quoted_length(executable);
for argument in arguments {
length += 1 + quoted_length(argument);
Expand Down
12 changes: 7 additions & 5 deletions rust/lithe-core/src/execution/mod.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
//! Run configuration generation, resolution, and project detectors.

mod argument_text;
mod classpath_jar;
mod configuration;
mod context_selection;
mod detectors;
Expand All @@ -11,16 +10,19 @@ mod launch_command;
mod project_ownership;
mod provider_run_options;
mod provider_runs;
// Toolchain requirement reading that a host-side Java selector needs without
// owning Core's in-process generated layer.
pub use configuration::{
java_feature_version_parts, read_requirements, validate_sidecar_version, version_parts,
version_satisfies, ToolchainRequirementsDocument,
};
pub use java_selection::{
compare_java_candidates, select_java, select_java_candidates, JavaSelection,
JavaSelectionCandidate, JavaSelectionRequest,
};
mod types;

pub(crate) use argument_text::{format_argument_text, ArgumentTextRequest};
pub use classpath_jar::{
plan_classpath_jar_launch, ClasspathJarPlan, ClasspathJarRequest, ClasspathStyle,
};
pub(crate) use configuration::*;
pub(crate) use context_selection::{select_context_configuration, ContextSelectionRequest};
pub(crate) use detectors::{scan, DirectoryContext};
Expand All @@ -31,7 +33,7 @@ pub use launch_command::{
};

pub(crate) mod workspace_scan;
pub use workspace_scan::{is_run_scan_excluded_directory, list_run_java_sources};
pub use workspace_scan::is_run_scan_excluded_directory;

mod environment;
pub use environment::{
Expand Down
93 changes: 0 additions & 93 deletions rust/lithe-core/src/execution/workspace_scan.rs
Original file line number Diff line number Diff line change
Expand Up @@ -176,99 +176,10 @@ fn relative(root: &Path, path: &Path) -> String {
.replace('\\', "/")
}

/// Returns Java paths using the same scan policy as run generation. Failure below
/// the root is tolerated; the subsequent Core fingerprint supplies diagnostics.
/// Stops before opening another directory once the limit is reached, retaining
/// only Java paths from the deterministic traversal rather than a full snapshot.
pub fn list_run_java_sources(root: &Path, limit: usize) -> Result<Vec<String>, CoreError> {
list_java_sources_with_reader(root, limit, &read_directory)
}

fn list_java_sources_with_reader(
root: &Path,
limit: usize,
read: &impl Fn(&Path) -> io::Result<Vec<(PathBuf, bool, bool)>>,
) -> Result<Vec<String>, CoreError> {
let mut paths = Vec::new();
if limit == 0 {
return Ok(paths);
}
visit_with_reader(root, None, read, |directory| {
for name in directory.files {
if name.to_ascii_lowercase().ends_with(".java") {
paths.push(relative(root, &directory.path.join(name)));
if paths.len() == limit {
return ControlFlow::Break(());
}
}
}
ControlFlow::Continue(())
})?;
paths.sort();
Ok(paths)
}

#[cfg(test)]
mod tests {
use super::*;

#[test]
fn java_source_limit_stops_directory_reads_and_keeps_deterministic_results() {
let root = Path::new("fixture");
for limit in [1, 8_000] {
let visited = std::cell::RefCell::new(Vec::new());
let paths = list_java_sources_with_reader(root, limit, &|path| {
visited.borrow_mut().push(path.to_owned());
if path == root {
Ok(vec![
(root.join("z-unvisited"), true, false),
(root.join("b-sources"), true, false),
(root.join("a-blocked"), true, false),
(root.join(".pytest_cache"), true, false),
(root.join("link"), true, true),
])
} else if path.ends_with("a-blocked") {
Err(io::Error::from(io::ErrorKind::PermissionDenied))
} else if path.ends_with("b-sources") {
// More than the cap in one directory must neither exceed the
// result limit nor open a later directory. Reverse native
// enumeration order to protect deterministic selection.
Ok((0..=limit)
.rev()
.map(|index| (path.join(format!("Main{index:05}.java")), false, false))
.chain([(path.join("nested"), true, false)])
.collect())
} else {
panic!("source budget must prevent reading {}", path.display());
}
})
.unwrap();
assert_eq!(
paths,
(0..limit)
.map(|index| format!("b-sources/Main{index:05}.java"))
.collect::<Vec<_>>()
);
assert_eq!(
*visited.borrow(),
[
root.to_owned(),
root.join("a-blocked"),
root.join("b-sources")
]
);
}
}

#[test]
fn java_source_zero_limit_does_not_read_the_workspace() {
let paths = list_java_sources_with_reader(Path::new("fixture"), 0, &|_| {
panic!("zero budget must not read directories")
})
.unwrap();
assert!(paths.is_empty());
}

#[test]
fn early_stop_retains_observed_unreadable_paths_without_affecting_full_scans() {
let root = Path::new("fixture");
Expand Down Expand Up @@ -300,10 +211,6 @@ mod tests {
.directories
.iter()
.any(|dir| dir.path.ends_with("z-later")));
assert_eq!(
list_java_sources_with_reader(root, 10, &read).unwrap(),
["b-sources/Main.java", "z-later/Main.java"]
);
}

#[test]
Expand Down
20 changes: 5 additions & 15 deletions rust/lithe-core/src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,5 @@
//! Deterministic application services shared by the macOS and Windows hosts.

use std::path::Path;

mod action_search;
mod agent;
pub mod ai;
Expand Down Expand Up @@ -35,6 +33,11 @@ pub use protocol::{
CoreCommand, CoreError, CoreEvent, CoreRequest, CoreResponse, ErrorCode, ResponseData,
};

/// The cancellation scope of one request, for a host crate that answers some
/// commands itself and still has to honour `cancel_operation` and timeouts the
/// way Core's own dispatcher does.
pub use protocol::cancellation::{check as check_cancellation, Scope as CancellationScope};

/// Executes one versioned application command and returns a JSON response.
pub fn execute_json(request: &str) -> String {
runtime::execute_json(request)
Expand Down Expand Up @@ -71,14 +74,6 @@ pub fn cancel_operation(operation_id: &str) -> bool {
protocol::cancellation::cancel(operation_id)
}

/// Derives the shared directory key for one JDT LS workspace state.
///
/// Platform hosts use this when clearing the same cache directory that the
/// Rust-owned language-server runtime selected during startup.
pub fn jdt_workspace_key(workspace_root: &Path, workspace_fingerprint: Option<&str>) -> String {
lsp::workspace_key(workspace_root, workspace_fingerprint)
}

#[cfg(test)]
mod tests;

Expand Down Expand Up @@ -112,11 +107,6 @@ pub use git::{
GitMetadataWatchContext,
};

/// Rewrites `<localRepository>` in a Maven settings document while keeping
/// every other element and comment, so hosts editing the user's own
/// `settings.xml` share the writer JDT LS settings generation already uses.
pub use project::settings_with_local_repository;

/// Connects an authorized native window to the local IDE broker's request notifications.
/// The host retains the callback until close; receivers claim IDs through `ideHost.control`
/// before invoking application actions. This adapter boundary has no UI dependencies.
Expand Down
2 changes: 1 addition & 1 deletion rust/lithe-core/src/lsp/languages/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,6 @@ pub(crate) use java_workspace::{
java_workspace_policy, jdt_cache_retention, jdt_workspace_fingerprint,
JavaWorkspacePolicyRequest, JdtCacheRetentionRequest, JdtWorkspaceFingerprintRequest,
};
pub(crate) use jdt::{resolve_workspace_key, workspace_key, JdtWorkspaceKeyRequest};
pub(crate) use jdt::{resolve_workspace_key, JdtWorkspaceKeyRequest};
pub(crate) use jdt_configuration::prepare_configuration_area as prepare_jdt_configuration_area;
pub(crate) use jdt_project_metadata::prepare_workspace as prepare_jdt_workspace;
Loading
Loading