mirror of
https://github.com/l0ng-ai/openidea.git
synced 2026-11-06 08:07:49 +00:00
fix(tomcat): resolve the WAR artifact of the selected Maven module (#529)
A Tomcat run with a WAR from a nested Maven module failed with "The built WAR artifact does not exist": the artifact path query was evaluated for the reactor parent, not the selected module. The build now resolves the selected POM's effective identity first, using the same options and environment as the build, and asks for that project's artifact. Custom outputs, .mvn settings and root modules are covered, Gradle WAR manifests are written as UTF-8, and Tomcat deployment errors now name the checked path. Fixes #515
This commit is contained in:
Generated
+1
@@ -4461,6 +4461,7 @@ name = "ide-run"
|
||||
version = "0.1.29"
|
||||
dependencies = [
|
||||
"encoding_rs",
|
||||
"ide-build",
|
||||
"ide-core",
|
||||
"libc",
|
||||
"parking_lot",
|
||||
|
||||
@@ -58,6 +58,18 @@ pub(crate) fn run(
|
||||
cwd: &Path,
|
||||
env: &BTreeMap<String, String>,
|
||||
cancel: &CancellationToken,
|
||||
on_line: impl FnMut(Stream, &str),
|
||||
) -> IdeResult<Output> {
|
||||
run_with_env(program, args, cwd, env, false, cancel, on_line)
|
||||
}
|
||||
|
||||
pub(crate) fn run_with_env(
|
||||
program: &str,
|
||||
args: &[String],
|
||||
cwd: &Path,
|
||||
env: &BTreeMap<String, String>,
|
||||
clear_env: bool,
|
||||
cancel: &CancellationToken,
|
||||
mut on_line: impl FnMut(Stream, &str),
|
||||
) -> IdeResult<Output> {
|
||||
tracing::info!("build: {program} {} (in {})", args.join(" "), cwd.display());
|
||||
@@ -66,7 +78,7 @@ pub(crate) fn run(
|
||||
ide_core::output_encoding::fix(program, args, env, |k| std::env::var(k).ok(), "UTF-8", cfg!(windows));
|
||||
let program = program_path(program, env);
|
||||
let mut command = Command::new(&program);
|
||||
command.args(args).current_dir(cwd).envs(env).envs(encoding.env).stdin(Stdio::null());
|
||||
command.args(args).current_dir(cwd).envs(env).envs(encoding.env.iter().map(|(k, v)| (k, v))).stdin(Stdio::null());
|
||||
command.stdout(Stdio::piped()).stderr(Stdio::piped());
|
||||
#[cfg(unix)]
|
||||
{
|
||||
@@ -74,6 +86,9 @@ pub(crate) fn run(
|
||||
command.process_group(0);
|
||||
}
|
||||
ide_core::process::prepare(&mut command);
|
||||
if clear_env {
|
||||
command.env_clear().envs(env).envs(encoding.env);
|
||||
}
|
||||
let mut child =
|
||||
command.spawn().map_err(|e| IdeError::unavailable(format!("cannot start {}: {e}", program.display())))?;
|
||||
let (tx, rx) = mpsc::channel::<(Stream, String)>();
|
||||
|
||||
+104
-8
@@ -1,6 +1,6 @@
|
||||
//! WAR modules and build-tool-owned archive resolution (including custom output names).
|
||||
use crate::BuildModel;
|
||||
use ide_core::{IdeError, IdeResult};
|
||||
use ide_core::{CancellationToken, IdeError, IdeResult};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use specta::Type;
|
||||
use std::{collections::BTreeMap, fs, path::Path};
|
||||
@@ -47,6 +47,50 @@ pub struct WebBuild {
|
||||
pub args: Vec<String>,
|
||||
pub cwd: String,
|
||||
pub env: BTreeMap<String, String>,
|
||||
maven_project: Option<(Vec<String>, std::path::PathBuf)>,
|
||||
}
|
||||
|
||||
impl WebBuild {
|
||||
/// Resolve the selected module's effective identity using the build's options and environment.
|
||||
/// Runs off the UI thread, before launching the reactor build.
|
||||
pub fn resolve_project(
|
||||
&mut self,
|
||||
root: &Path,
|
||||
env: &BTreeMap<String, String>,
|
||||
cancel: &CancellationToken,
|
||||
) -> IdeResult<()> {
|
||||
let Some((args, output)) = &self.maven_project else {
|
||||
return Ok(());
|
||||
};
|
||||
if output.exists() {
|
||||
fs::remove_file(output)?;
|
||||
}
|
||||
let cwd = root.join(&self.cwd).canonicalize()?;
|
||||
let mut env = env.clone();
|
||||
if !env.iter().any(|(k, v)| k.eq_ignore_ascii_case("MAVEN_BASEDIR") && !v.is_empty()) {
|
||||
// -f changes Maven's .mvn search origin; keep the reactor's launcher settings.
|
||||
let base = cwd.ancestors().find(|p| p.join(".mvn").is_dir()).unwrap_or(&cwd);
|
||||
env.retain(|k, _| !k.eq_ignore_ascii_case("MAVEN_BASEDIR"));
|
||||
env.insert("MAVEN_BASEDIR".into(), base.to_string_lossy().into_owned());
|
||||
}
|
||||
let result = crate::process::run_with_env(&self.command, args, &cwd, &env, true, cancel, |_, _| {})?;
|
||||
if !result.success {
|
||||
return Err(IdeError::invalid(format!("Cannot resolve Maven WAR artifact: {}", result.text.trim())));
|
||||
}
|
||||
let id = fs::read_to_string(output)?;
|
||||
let parts: Vec<_> = id.trim().split(':').collect();
|
||||
if parts.len() != 4 || parts[2] != "war" || parts.iter().any(|s| s.is_empty()) {
|
||||
return Err(IdeError::invalid(format!("Cannot resolve Maven WAR artifact: {}", id.trim())));
|
||||
}
|
||||
let project = format!("{}:{}:{}", parts[0], parts[1], parts[3]);
|
||||
self.args.push(format!("-Dexpression=session.projectMap({project}).artifact.file"));
|
||||
self.maven_project = None;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
fn groovy_literal(s: &str) -> String {
|
||||
format!("'{}'", s.replace('\\', "\\\\").replace('\'', "\\'"))
|
||||
}
|
||||
|
||||
pub fn build(root: &Path, model: &BuildModel, id: &str, cache: &Path) -> IdeResult<WebBuild> {
|
||||
@@ -81,21 +125,36 @@ pub fn build(root: &Path, model: &BuildModel, id: &str, cache: &Path) -> IdeResu
|
||||
.replace('\\', "/");
|
||||
args.extend(["-pl".into(), module, "-am".into()]);
|
||||
}
|
||||
// help:evaluate runs once, in the reactor's first project. Query the selected
|
||||
// POM's effective identity first; profiles and -D options can change its GAV.
|
||||
let output = cache.join("artifact-project.txt");
|
||||
let mut probe = top.options.clone();
|
||||
probe.extend([
|
||||
"-f".into(),
|
||||
root.join(&p.pom).to_string_lossy().into_owned(),
|
||||
"help:evaluate".into(),
|
||||
"-Dexpression=project.id".into(),
|
||||
format!("-Doutput={}", output.display()),
|
||||
]);
|
||||
args.extend([
|
||||
"-DskipTests".into(),
|
||||
"package".into(),
|
||||
"help:evaluate".into(),
|
||||
"-Dexpression=project.artifact.file".into(),
|
||||
format!("-Doutput={}", manifest.display()),
|
||||
]);
|
||||
return Ok(WebBuild { command: top.command.clone(), args, cwd: top.dir.clone(), env: p.env.clone() });
|
||||
return Ok(WebBuild {
|
||||
command: top.command.clone(),
|
||||
args,
|
||||
cwd: top.dir.clone(),
|
||||
env: p.env.clone(),
|
||||
maven_project: Some((probe, output)),
|
||||
});
|
||||
}
|
||||
if let Some(path) = id.strip_prefix("gradle:") {
|
||||
let g = model.gradle.as_ref().ok_or_else(|| IdeError::invalid("Choose a Gradle WAR module"))?;
|
||||
if !artifacts(root, model).iter().any(|a| a.id == id) {
|
||||
return Err(IdeError::invalid("Choose a Gradle WAR module"));
|
||||
}
|
||||
let literal = |s: &str| format!("'{}'", s.replace('\\', "\\\\").replace('\'', "\\'"));
|
||||
let script = cache.join("war.init.gradle");
|
||||
fs::write(
|
||||
&script,
|
||||
@@ -105,12 +164,12 @@ pub fn build(root: &Path, model: &BuildModel, id: &str, cache: &Path) -> IdeResu
|
||||
def warTask = p.tasks.named('war')
|
||||
p.tasks.register('openideaTomcatArtifact') {{
|
||||
dependsOn warTask
|
||||
doLast {{ new File({}).text = warTask.get().archiveFile.get().asFile.absolutePath }}
|
||||
doLast {{ new File({}).setText(warTask.get().archiveFile.get().asFile.absolutePath, 'UTF-8') }}
|
||||
}}
|
||||
}}
|
||||
"#,
|
||||
literal(path),
|
||||
literal(&manifest.to_string_lossy())
|
||||
groovy_literal(path),
|
||||
groovy_literal(&manifest.to_string_lossy())
|
||||
),
|
||||
)?;
|
||||
let task = format!("{}:openideaTomcatArtifact", path.trim_end_matches(':'));
|
||||
@@ -119,6 +178,7 @@ pub fn build(root: &Path, model: &BuildModel, id: &str, cache: &Path) -> IdeResu
|
||||
args: vec!["--init-script".into(), script.to_string_lossy().into_owned(), task],
|
||||
cwd: g.dir.clone(),
|
||||
env: g.env.clone(),
|
||||
maven_project: None,
|
||||
});
|
||||
}
|
||||
Err(IdeError::invalid("Choose a WAR artifact"))
|
||||
@@ -136,7 +196,9 @@ mod tests {
|
||||
assert_eq!(a.len(), 1);
|
||||
assert_eq!(a[0].name, "web");
|
||||
let plan = build(tmp.path(), &model, &a[0].id, &tmp.path().join("base with spaces")).unwrap();
|
||||
assert!(plan.args.contains(&"-Dexpression=project.artifact.file".into()));
|
||||
let (probe, _) = plan.maven_project.as_ref().unwrap();
|
||||
assert!(probe.contains(&"-Dexpression=project.id".into()));
|
||||
assert!(probe.contains(&tmp.path().join("pom.xml").to_string_lossy().into_owned()));
|
||||
assert!(
|
||||
plan.args.iter().any(|a| a.ends_with("base with spaces/artifact-path.txt")
|
||||
|| a.ends_with("base with spaces\\artifact-path.txt"))
|
||||
@@ -152,6 +214,13 @@ mod tests {
|
||||
let plan = build(tmp.path(), &model, "maven:web/pom.xml", &tmp.path().join("base")).unwrap();
|
||||
assert_eq!(plan.cwd, "");
|
||||
assert_eq!(&plan.args[..3], &["-pl", "web", "-am"]);
|
||||
assert!(
|
||||
plan.maven_project
|
||||
.as_ref()
|
||||
.unwrap()
|
||||
.0
|
||||
.contains(&tmp.path().join("web/pom.xml").to_string_lossy().into_owned())
|
||||
);
|
||||
}
|
||||
#[test]
|
||||
fn gradle_war_uses_archive_provider_and_ignores_comments() {
|
||||
@@ -169,4 +238,31 @@ mod tests {
|
||||
assert!(fs::read_to_string(tmp.path().join("base/war.init.gradle")).unwrap().contains("archiveFile.get()"));
|
||||
assert!(!crate::gradle::has_war_plugin("// plugins { war }"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn gradle_manifest_paths_preserve_windows_characters() {
|
||||
for path in [r"C:\工作 区\Tomcat's cache\artifact-path.txt", r"\\server\share\artifact-path.txt"] {
|
||||
let literal = groovy_literal(path);
|
||||
assert_eq!(literal[1..literal.len() - 1].replace("\\'", "'").replace("\\\\", "\\"), path);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn gradle_root_and_nested_modules_write_utf8_archive_paths() {
|
||||
for module in [":", ":apps:timesheet"] {
|
||||
let tmp = tempfile::tempdir().unwrap();
|
||||
let dir = if module == ":" { "" } else { "apps/timesheet" };
|
||||
fs::create_dir_all(tmp.path().join(dir)).unwrap();
|
||||
fs::write(tmp.path().join("settings.gradle"), "rootProject.name = 'timesheet'\ninclude 'apps:timesheet'")
|
||||
.unwrap();
|
||||
fs::write(tmp.path().join(dir).join("build.gradle"), "plugins { id 'war' }").unwrap();
|
||||
let model = crate::load(tmp.path(), &tmp.path().join("cache"), None);
|
||||
let cache = tmp.path().join("工作 cache");
|
||||
let plan = build(tmp.path(), &model, &format!("gradle:{module}"), &cache).unwrap();
|
||||
assert!(plan.args.contains(&format!("{}:openideaTomcatArtifact", module.trim_end_matches(':'))));
|
||||
let script = fs::read_to_string(cache.join("war.init.gradle")).unwrap();
|
||||
assert!(script.contains(&format!("rootProject.project('{}')", module)));
|
||||
assert!(script.contains("archiveFile.get().asFile.absolutePath, 'UTF-8'"));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -81,22 +81,21 @@ pub async fn run_tomcat_build(host: &Host, window: &str, config: RunConfig) -> I
|
||||
.filter(|s| !s.is_empty())
|
||||
.or_else(|| artifacts.first().map(|a| &a.id))
|
||||
.ok_or_else(|| IdeError::invalid("Choose a WAR artifact"))?;
|
||||
let plan = ide_build::web::build(
|
||||
let mut plan = ide_build::web::build(
|
||||
&project.root,
|
||||
&model,
|
||||
id,
|
||||
&ide_run::tomcat::base_dir(&project.cache_dir, &config.id),
|
||||
)?;
|
||||
let mut env = plan.env;
|
||||
env.extend(config.env.clone());
|
||||
Ok(Some(RunConfig {
|
||||
plan.env.extend(config.env.clone());
|
||||
let mut build = RunConfig {
|
||||
id: format!("tomcat-build:{}", config.id),
|
||||
name: format!("Build {}", config.name),
|
||||
kind: if id.starts_with("maven:") { "maven" } else { "gradle" }.into(),
|
||||
command: plan.command,
|
||||
args: plan.args,
|
||||
cwd: Some(std::path::PathBuf::from(plan.cwd).into()),
|
||||
env,
|
||||
command: plan.command.clone(),
|
||||
args: vec![],
|
||||
cwd: Some(std::path::PathBuf::from(&plan.cwd).into()),
|
||||
env: plan.env.clone(),
|
||||
detected: false,
|
||||
is_test: false,
|
||||
debug_adapter: None,
|
||||
@@ -106,7 +105,14 @@ pub async fn run_tomcat_build(host: &Host, window: &str, config: RunConfig) -> I
|
||||
env_files: config.extras.env_files,
|
||||
..Default::default()
|
||||
},
|
||||
}))
|
||||
};
|
||||
plan.resolve_project(
|
||||
&project.root,
|
||||
&ide_run::resolved_env(&project.root, &build)?,
|
||||
&ide_core::CancellationToken::new(),
|
||||
)?;
|
||||
build.args = plan.args;
|
||||
Ok(Some(build))
|
||||
})
|
||||
.await
|
||||
.map_err(|e| IdeError::internal(e.to_string()))?
|
||||
|
||||
@@ -24,4 +24,5 @@ quick-xml = "0.42"
|
||||
libc = "0.2"
|
||||
|
||||
[dev-dependencies]
|
||||
ide-build = { path = "../ide-build" }
|
||||
tempfile.workspace = true
|
||||
|
||||
@@ -240,6 +240,11 @@ pub fn system_env() -> BTreeMap<String, String> {
|
||||
ide_core::process::host_vars().filter_map(|(k, v)| Some((k.into_string().ok()?, v.into_string().ok()?))).collect()
|
||||
}
|
||||
|
||||
/// Complete environment for a preparatory tool belonging to this run configuration.
|
||||
pub fn resolved_env(root: &Path, config: &RunConfig) -> IdeResult<BTreeMap<String, String>> {
|
||||
process::resolved_env(root, config)
|
||||
}
|
||||
|
||||
/// Path of the user run configuration file for `root`.
|
||||
pub fn user_configs_path(root: &Path) -> PathBuf {
|
||||
config::config_path(root)
|
||||
|
||||
@@ -661,6 +661,18 @@ fn launch_env(root: &Path, config: &RunConfig) -> IdeResult<LaunchEnv> {
|
||||
Ok(LaunchEnv { clear: !extras.include_system_env, vars })
|
||||
}
|
||||
|
||||
pub(crate) fn resolved_env(root: &Path, config: &RunConfig) -> IdeResult<BTreeMap<String, String>> {
|
||||
let env = launch_env(root, config)?;
|
||||
let mut vars = if env.clear { BTreeMap::new() } else { crate::system_env() };
|
||||
if !env.clear {
|
||||
vars.extend(
|
||||
ide_core::process::shell_env().filter_map(|(k, v)| Some((k.into_string().ok()?, v.into_string().ok()?))),
|
||||
);
|
||||
}
|
||||
vars.extend(env.vars);
|
||||
Ok(vars)
|
||||
}
|
||||
|
||||
fn apply_env(cmd: &mut Command, env: &LaunchEnv) {
|
||||
if env.clear {
|
||||
cmd.env_clear();
|
||||
|
||||
@@ -216,7 +216,7 @@ pub fn deployment(root: &Path, base: &Path, config: &RunConfig, resources_only:
|
||||
.map_err(|_| invalid("Build the WAR artifact before launching Tomcat"))?;
|
||||
let war = PathBuf::from(war.trim());
|
||||
if !war.is_file() || !war.extension().is_some_and(|e| e.eq_ignore_ascii_case("war")) {
|
||||
return Err(invalid("The built WAR artifact does not exist"));
|
||||
return Err(invalid(format!("The built WAR artifact does not exist: {}", war.display())));
|
||||
}
|
||||
let dest = base.join("deployment");
|
||||
explode(&war, &dest, resources_only)?;
|
||||
@@ -227,11 +227,14 @@ pub fn deployment(root: &Path, base: &Path, config: &RunConfig, resources_only:
|
||||
.get("deploymentPath")
|
||||
.filter(|v| !v.trim().is_empty())
|
||||
.ok_or_else(|| invalid("Choose a WAR file or exploded directory"))?;
|
||||
let path = root.join(value).canonicalize()?;
|
||||
let path = root.join(value);
|
||||
let path = path
|
||||
.canonicalize()
|
||||
.map_err(|e| invalid(format!("Invalid Tomcat deployment: {} ({e})", path.display())))?;
|
||||
if (kind == "war" && (!path.is_file() || !path.extension().is_some_and(|e| e.eq_ignore_ascii_case("war"))))
|
||||
|| (kind == "directory" && !path.is_dir())
|
||||
{
|
||||
return Err(invalid("Invalid Tomcat deployment"));
|
||||
return Err(invalid(format!("Invalid Tomcat deployment: {}", path.display())));
|
||||
}
|
||||
Ok(path)
|
||||
}
|
||||
@@ -436,6 +439,28 @@ mod tests {
|
||||
assert!(deployment(tmp.path(), &base, &c, false).unwrap().ends_with("WebContent"));
|
||||
}
|
||||
#[test]
|
||||
fn deployment_errors_identify_the_checked_path() {
|
||||
let tmp = tempfile::tempdir().unwrap();
|
||||
let base = tmp.path().join("cache");
|
||||
fs::create_dir_all(&base).unwrap();
|
||||
for path in ["missing.war", r"C:\工作\timesheet\target\timesheet.war", r"\\server\share\timesheet.war"] {
|
||||
fs::write(base.join("artifact-path.txt"), format!("{path}\r\n")).unwrap();
|
||||
let error = deployment(tmp.path(), &base, &config(), false).unwrap_err().to_string();
|
||||
assert!(error.contains(path), "{error}");
|
||||
}
|
||||
for kind in ["war", "directory"] {
|
||||
let mut c = config();
|
||||
c.extras.params.insert("deployment".into(), kind.into());
|
||||
c.extras.params.insert("deploymentPath".into(), "missing".into());
|
||||
let error = deployment(tmp.path(), &base, &c, false).unwrap_err().to_string();
|
||||
assert!(error.contains(&tmp.path().join("missing").display().to_string()), "{error}");
|
||||
c.extras.params.insert("deploymentPath".into(), "cache".into());
|
||||
if kind == "war" {
|
||||
assert!(deployment(tmp.path(), &base, &c, false).unwrap_err().to_string().contains("cache"));
|
||||
}
|
||||
}
|
||||
}
|
||||
#[test]
|
||||
fn prepares_isolated_base_and_jdwp_only_for_run() {
|
||||
let tmp = tempfile::tempdir().unwrap();
|
||||
let home = tmp.path().join("Tomcat Home");
|
||||
|
||||
@@ -291,6 +291,10 @@ fn env_files_system_env_and_redirects() {
|
||||
config.extras.include_system_env = false;
|
||||
config.extras.stdin_file = Some("input.txt".into());
|
||||
config.extras.output_file = Some("logs/out.log".into());
|
||||
let prepared = ide_run::resolved_env(root, &config).unwrap();
|
||||
assert_eq!(prepared.get("FROM_FILE").map(String::as_str), Some("file"));
|
||||
assert_eq!(prepared.get("OVERRIDDEN").map(String::as_str), Some("config"));
|
||||
assert!(!prepared.contains_key("HOME"));
|
||||
let (id, chunks) = start(&h, &config);
|
||||
// stdin comes from the file: writing to it is refused.
|
||||
assert!(h.pm.write_stdin(id, "x").is_err());
|
||||
@@ -302,6 +306,7 @@ fn env_files_system_env_and_redirects() {
|
||||
|
||||
// With the system environment, HOME is inherited.
|
||||
config.extras.include_system_env = true;
|
||||
assert!(ide_run::resolved_env(root, &config).unwrap().contains_key("HOME"));
|
||||
config.extras.stdin_file = None;
|
||||
config.extras.output_file = None;
|
||||
config.args = vec!["-c".into(), "printf '%s' \"${HOME:+set}\"".into()];
|
||||
|
||||
@@ -0,0 +1,150 @@
|
||||
//! Opt-in build-tool integration checks: cargo test -p ide-run --test tomcat_artifact -- --ignored
|
||||
use std::{fs, path::Path, process::Command};
|
||||
|
||||
fn write(root: &Path, path: &str, text: &str) {
|
||||
let path = root.join(path);
|
||||
fs::create_dir_all(path.parent().unwrap()).unwrap();
|
||||
fs::write(path, text).unwrap();
|
||||
}
|
||||
|
||||
fn build_and_deploy(root: &Path, id: &str, expected: &Path) {
|
||||
let cache = root.join("工作 cache");
|
||||
let model = ide_build::load(root, &cache, None);
|
||||
let mut plan = ide_build::web::build(root, &model, id, &cache).unwrap();
|
||||
plan.resolve_project(root, &std::env::vars().collect(), &ide_core::CancellationToken::new()).unwrap();
|
||||
let output = Command::new(&plan.command)
|
||||
.args(&plan.args)
|
||||
.envs(&plan.env)
|
||||
.current_dir(root.join(&plan.cwd))
|
||||
.output()
|
||||
.unwrap();
|
||||
assert!(
|
||||
output.status.success(),
|
||||
"{}\n{}",
|
||||
String::from_utf8_lossy(&output.stdout),
|
||||
String::from_utf8_lossy(&output.stderr)
|
||||
);
|
||||
let manifest = fs::read_to_string(cache.join("artifact-path.txt")).unwrap();
|
||||
assert_eq!(Path::new(manifest.trim()).canonicalize().unwrap(), expected.canonicalize().unwrap());
|
||||
let mut config: ide_run::RunConfig = serde_json::from_value(serde_json::json!({
|
||||
"id": "tomcat:test", "name": "Tomcat", "kind": "tomcat", "command": "catalina",
|
||||
"args": [], "env": {}, "detected": false, "isTest": false
|
||||
}))
|
||||
.unwrap();
|
||||
config.cwd = Some("timesheet".into());
|
||||
config.extras.params.insert("artifact".into(), id.into());
|
||||
let deployed = ide_run::tomcat::deployment(root, &cache, &config, false).unwrap();
|
||||
assert_eq!(fs::read_to_string(deployed.join("index.html")).unwrap(), "issue 515 timesheet");
|
||||
let siblings: Vec<_> = fs::read_dir(expected.parent().unwrap())
|
||||
.unwrap()
|
||||
.map(|e| e.unwrap().file_name().to_string_lossy().into_owned())
|
||||
.filter(|name| name.contains(".war."))
|
||||
.collect();
|
||||
assert!(siblings.is_empty(), "Unexpected WAR siblings: {siblings:?}");
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[ignore = "requires Maven and downloads Maven plugins"]
|
||||
fn maven_packaged_artifacts_deploy_from_root_and_nested_reactors() {
|
||||
for (reactor, module, custom) in
|
||||
[("", "", false), ("", "timesheet", false), ("parent", "apps/timesheet", true), ("timesheet", "", true)]
|
||||
{
|
||||
let tmp = tempfile::Builder::new().prefix("tomcat maven ").tempdir().unwrap();
|
||||
let root = tmp.path();
|
||||
let reactor_dir = root.join(reactor);
|
||||
let module_dir = reactor_dir.join(module);
|
||||
let plugins = "<pluginManagement><plugins><plugin><artifactId>maven-war-plugin</artifactId><version>3.4.0</version><configuration><failOnMissingWebXml>false</failOnMissingWebXml></configuration></plugin></plugins></pluginManagement>";
|
||||
let parent = if module.is_empty() && custom {
|
||||
write(&reactor_dir, ".mvn/maven.config", "-Drevision=2\n");
|
||||
"<groupId>test</groupId><version>${revision}</version><properties><revision>1</revision></properties>"
|
||||
.to_owned()
|
||||
} else if module.is_empty() {
|
||||
"<groupId>test</groupId><version>1</version>".to_owned()
|
||||
} else {
|
||||
write(
|
||||
&reactor_dir,
|
||||
"pom.xml",
|
||||
&format!(
|
||||
"<project><modelVersion>4.0.0</modelVersion><groupId>test</groupId><artifactId>parent</artifactId><version>1</version><packaging>pom</packaging><modules><module>core</module><module>{module}</module></modules><build>{plugins}</build></project>"
|
||||
),
|
||||
);
|
||||
write(
|
||||
&reactor_dir,
|
||||
"core/pom.xml",
|
||||
"<project><modelVersion>4.0.0</modelVersion><parent><groupId>test</groupId><artifactId>parent</artifactId><version>1</version></parent><artifactId>core</artifactId></project>",
|
||||
);
|
||||
let relative = "../".repeat(module.split('/').count());
|
||||
format!(
|
||||
"<parent><groupId>test</groupId><artifactId>parent</artifactId><version>1</version><relativePath>{relative}pom.xml</relativePath></parent>"
|
||||
)
|
||||
};
|
||||
let dependency = if module.is_empty() {
|
||||
""
|
||||
} else {
|
||||
"<dependencies><dependency><groupId>test</groupId><artifactId>core</artifactId><version>1</version></dependency></dependencies>"
|
||||
};
|
||||
let output = if custom {
|
||||
"<directory>${project.basedir}/custom output</directory><finalName>custom-timesheet</finalName>"
|
||||
} else {
|
||||
"<finalName>timesheet</finalName>"
|
||||
};
|
||||
write(
|
||||
&module_dir,
|
||||
"pom.xml",
|
||||
&format!(
|
||||
"<project><modelVersion>4.0.0</modelVersion>{parent}<artifactId>timesheet</artifactId><packaging>war</packaging>{dependency}<build>{plugins}{output}</build></project>"
|
||||
),
|
||||
);
|
||||
write(&module_dir, "src/main/webapp/index.html", "issue 515 timesheet");
|
||||
if custom && !module.is_empty() {
|
||||
for dir in [&reactor_dir, &reactor_dir.join("core"), &module_dir] {
|
||||
let pom = dir.join("pom.xml");
|
||||
let text =
|
||||
fs::read_to_string(&pom).unwrap().replace("<version>1</version>", "<version>${revision}</version>");
|
||||
fs::write(pom, text).unwrap();
|
||||
}
|
||||
let pom = reactor_dir.join("pom.xml");
|
||||
fs::write(
|
||||
&pom,
|
||||
fs::read_to_string(&pom).unwrap().replace(
|
||||
"<packaging>pom</packaging>",
|
||||
"<packaging>pom</packaging><properties><revision>1</revision></properties>",
|
||||
),
|
||||
)
|
||||
.unwrap();
|
||||
write(&reactor_dir, ".mvn/maven.config", "-Drevision=2\n");
|
||||
write(&module_dir, ".mvn/maven.config", "-Drevision=3\n");
|
||||
}
|
||||
let pom = module_dir.join("pom.xml").strip_prefix(root).unwrap().to_string_lossy().replace('\\', "/");
|
||||
let expected =
|
||||
module_dir.join(if custom { "custom output/custom-timesheet.war" } else { "target/timesheet.war" });
|
||||
build_and_deploy(root, &format!("maven:{pom}"), &expected);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[ignore = "requires Gradle"]
|
||||
fn gradle_packaged_artifacts_deploy_with_custom_archive_names() {
|
||||
for module in ["", "apps/timesheet"] {
|
||||
let tmp = tempfile::Builder::new().prefix("tomcat gradle ").tempdir().unwrap();
|
||||
let root = tmp.path();
|
||||
let settings = if module.is_empty() {
|
||||
"rootProject.name = 'timesheet'\n"
|
||||
} else {
|
||||
"rootProject.name = 'timesheet'\ninclude 'apps:timesheet'\n"
|
||||
};
|
||||
write(root, "settings.gradle", settings);
|
||||
write(
|
||||
&root.join(module),
|
||||
"build.gradle",
|
||||
"plugins { id 'war' }\ntasks.named('war') { archiveFileName = 'custom-timesheet.war'; destinationDirectory = layout.projectDirectory.dir('custom output') }\n",
|
||||
);
|
||||
write(&root.join(module), "src/main/webapp/index.html", "issue 515 timesheet");
|
||||
let project = if module.is_empty() { ":" } else { ":apps:timesheet" };
|
||||
build_and_deploy(
|
||||
root,
|
||||
&format!("gradle:{project}"),
|
||||
&root.join(module).join("custom output/custom-timesheet.war"),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -4936,9 +4936,10 @@
|
||||
"Choose a Gradle WAR module": "请选择 Gradle WAR 模块",
|
||||
"Build the selected WAR artifact before launching Tomcat": "请先构建所选 WAR 构件,再启动 Tomcat",
|
||||
"Build the WAR artifact before launching Tomcat": "请先构建 WAR 构件,再启动 Tomcat",
|
||||
"The built WAR artifact does not exist": "构建的 WAR 构件不存在",
|
||||
"The built WAR artifact does not exist: {path}": "构建的 WAR 构件不存在:{path}",
|
||||
"Cannot resolve Maven WAR artifact: {details}": "无法解析 Maven WAR 构件:{details}",
|
||||
"Choose a WAR file or exploded directory": "请选择 WAR 文件或展开目录",
|
||||
"Invalid Tomcat deployment": "无效的 Tomcat 部署",
|
||||
"Invalid Tomcat deployment: {path}": "无效的 Tomcat 部署:{path}",
|
||||
"Unknown Tomcat deployment type": "未知的 Tomcat 部署类型",
|
||||
"Choose a Tomcat installation directory": "请选择 Tomcat 安装目录",
|
||||
"HTTP port must be between 1 and 65535": "HTTP 端口必须介于 1 和 65535 之间",
|
||||
|
||||
Reference in New Issue
Block a user