use std::path::Path;
use std::process::Command;
use super::output_util;
use super::tool_param_types::{GradleTasksArgs, MavenCompileArgs, MavenTestArgs};
const MAX_OUTPUT_LINES: usize = 800;
fn has_pom(workspace_root: &Path) -> bool {
workspace_root.join("pom.xml").is_file()
}
fn has_gradle(workspace_root: &Path) -> bool {
workspace_root.join("build.gradle").is_file()
|| workspace_root.join("build.gradle.kts").is_file()
|| workspace_root.join("settings.gradle").is_file()
|| workspace_root.join("settings.gradle.kts").is_file()
}
fn safe_maven_profile(s: &str) -> Result<(), String> {
let t = s.trim();
if t.is_empty() {
return Err("profile 不能为空".to_string());
}
if t.chars()
.any(|c| c.is_whitespace() || c == '/' || c == '\\')
{
return Err("profile 含非法字符".to_string());
}
if !t
.chars()
.all(|c| c.is_ascii_alphanumeric() || c == '_' || c == '-' || c == '.')
{
return Err("profile 仅允许字母数字与 _-. ".to_string());
}
Ok(())
}
fn safe_gradle_task_token(s: &str) -> Result<(), String> {
let t = s.trim();
if t.is_empty() {
return Err("任务名不能为空".to_string());
}
if !t
.chars()
.all(|c| c.is_ascii_alphanumeric() || matches!(c, '_' | ':' | '.' | '-'))
{
return Err("任务名仅允许字母数字与 _:.-".to_string());
}
Ok(())
}
fn run_and_format(cmd: Command, max_output_len: usize, title: &str) -> String {
output_util::run_command_output_formatted(
cmd,
title,
max_output_len,
MAX_OUTPUT_LINES,
output_util::ProcessOutputMerge::ConcatStdoutStderr,
output_util::CommandSpawnErrorStyle::CannotStartCommand,
)
}
pub fn maven_compile(args_json: &str, workspace_root: &Path, max_output_len: usize) -> String {
let v = match crate::cm_tools::tools::parse_args_json(args_json) {
Ok(v) => v,
Err(e) => return e,
};
let args: MavenCompileArgs = match serde_json::from_value(v) {
Ok(a) => a,
Err(e) => return format!("参数解析错误: {e}"),
};
if !has_pom(workspace_root) {
return "maven_compile: 跳过(未找到 pom.xml)".to_string();
}
if let Some(ref p) = args.profile
&& let Err(e) = safe_maven_profile(p)
{
return format!("错误:{}", e);
}
let mut cmd = Command::new("mvn");
cmd.arg("-q").arg("compile");
if let Some(p) = args.profile.as_deref().map(str::trim)
&& !p.is_empty()
{
cmd.arg("-P").arg(p);
}
cmd.current_dir(workspace_root);
run_and_format(cmd, max_output_len, "mvn -q compile")
}
pub fn maven_test(args_json: &str, workspace_root: &Path, max_output_len: usize) -> String {
let v = match crate::cm_tools::tools::parse_args_json(args_json) {
Ok(v) => v,
Err(e) => return e,
};
let args: MavenTestArgs = match serde_json::from_value(v) {
Ok(a) => a,
Err(e) => return format!("参数解析错误: {e}"),
};
if !has_pom(workspace_root) {
return "maven_test: 跳过(未找到 pom.xml)".to_string();
}
if let Some(ref p) = args.profile
&& let Err(e) = safe_maven_profile(p)
{
return format!("错误:{}", e);
}
if let Some(ref t) = args.test {
let t = t.trim();
if !t.is_empty()
&& !t
.chars()
.all(|c| c.is_ascii_alphanumeric() || matches!(c, '.' | '_' | '-' | '#'))
{
return "错误:test 参数含非法字符(仅允许类名/方法片段等安全子集)".to_string();
}
}
let mut cmd = Command::new("mvn");
cmd.arg("-q").arg("test");
if let Some(p) = args.profile.as_deref().map(str::trim)
&& !p.is_empty()
{
cmd.arg("-P").arg(p);
}
if let Some(t) = args.test.as_deref().map(str::trim)
&& !t.is_empty()
{
cmd.arg(format!("-Dtest={t}"));
}
cmd.current_dir(workspace_root);
run_and_format(cmd, max_output_len, "mvn -q test")
}
fn gradle_command(workspace_root: &Path) -> Command {
let gw = workspace_root.join("gradlew");
if gw.is_file() {
return Command::new(gw);
}
#[cfg(windows)]
{
let gwb = workspace_root.join("gradlew.bat");
if gwb.is_file() {
return Command::new(gwb);
}
}
Command::new("gradle")
}
pub fn gradle_compile(args_json: &str, workspace_root: &Path, max_output_len: usize) -> String {
let v = match crate::cm_tools::tools::parse_args_json(args_json) {
Ok(v) => v,
Err(e) => return e,
};
let args: GradleTasksArgs = match serde_json::from_value(v) {
Ok(a) => a,
Err(e) => return format!("参数解析错误: {e}"),
};
if !has_gradle(workspace_root) {
return "gradle_compile: 跳过(未找到 build.gradle / build.gradle.kts / settings.gradle*)"
.to_string();
}
let tasks: Vec<String> = if args.tasks.iter().any(|t| !t.trim().is_empty()) {
args.tasks
.iter()
.filter_map(|x| {
let t = x.trim();
(!t.is_empty()).then(|| t.to_string())
})
.collect()
} else {
vec!["classes".to_string()]
};
for t in &tasks {
if let Err(e) = safe_gradle_task_token(t) {
return format!("错误:任务 `{}` {}", t, e);
}
}
let mut cmd = gradle_command(workspace_root);
cmd.arg("-q");
for t in &tasks {
cmd.arg(t);
}
cmd.current_dir(workspace_root);
run_and_format(cmd, max_output_len, "gradle -q …")
}
pub fn gradle_test(args_json: &str, workspace_root: &Path, max_output_len: usize) -> String {
let v = match crate::cm_tools::tools::parse_args_json(args_json) {
Ok(v) => v,
Err(e) => return e,
};
let args: GradleTasksArgs = match serde_json::from_value(v) {
Ok(a) => a,
Err(e) => return format!("参数解析错误: {e}"),
};
if !has_gradle(workspace_root) {
return "gradle_test: 跳过(未找到 build.gradle / build.gradle.kts / settings.gradle*)"
.to_string();
}
let tasks: Vec<String> = if args.tasks.iter().any(|t| !t.trim().is_empty()) {
args.tasks
.iter()
.filter_map(|x| {
let t = x.trim();
(!t.is_empty()).then(|| t.to_string())
})
.collect()
} else {
vec!["test".to_string()]
};
for t in &tasks {
if let Err(e) = safe_gradle_task_token(t) {
return format!("错误:任务 `{}` {}", t, e);
}
}
let mut cmd = gradle_command(workspace_root);
cmd.arg("-q");
for t in &tasks {
cmd.arg(t);
}
cmd.current_dir(workspace_root);
run_and_format(cmd, max_output_len, "gradle -q …")
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn safe_maven_profile_rejects_slash() {
assert!(safe_maven_profile("a/b").is_err());
}
#[test]
fn safe_gradle_task_accepts_colon() {
assert!(safe_gradle_task_token(":compileJava").is_ok());
}
}