diff --git a/crates/ide-build/src/lib.rs b/crates/ide-build/src/lib.rs index a362b4808..7ca4789c0 100644 --- a/crates/ide-build/src/lib.rs +++ b/crates/ide-build/src/lib.rs @@ -53,7 +53,8 @@ pub fn load_linked( settings: &ide_core::settings::MavenSettings, ) -> BuildModel { let maven = maven::is_maven_project(root).then(|| { - let mut m = maven::load_with_repo(root, maven_repo.or_else(maven::default_local_repo)); + let profiles = sync::maven_profiles(cache_dir); + let mut m = maven::load_with(root, maven_repo.or_else(maven::default_local_repo), &profiles); maven::UnlinkedProjects::from_model(&m, settings).filter(&mut m); sync::apply_maven_cache(root, cache_dir, &mut m); m diff --git a/crates/ide-build/src/maven.rs b/crates/ide-build/src/maven.rs index ff4f2cf28..12c8b14a8 100644 --- a/crates/ide-build/src/maven.rs +++ b/crates/ide-build/src/maven.rs @@ -460,6 +460,8 @@ const MAX_PARENT_DEPTH: usize = 10; struct Resolver { local_repo: Option, + /// The chosen profiles (`-P` entries; `!id` / `-id` deactivates). + profiles: Vec, poms: HashMap>, contexts: HashMap, } @@ -484,8 +486,16 @@ pub fn local_repository_of(settings: &Path) -> Option { } impl Resolver { - fn new(local_repo: Option) -> Self { - Self { local_repo, poms: HashMap::new(), contexts: HashMap::new() } + fn new(local_repo: Option, profiles: &[String]) -> Self { + Self { local_repo, profiles: profiles.to_vec(), poms: HashMap::new(), contexts: HashMap::new() } + } + + /// The active profiles of a POM: the chosen ones, and the `activeByDefault` ones not turned + /// off while none of the POM's other profiles is chosen (Maven's rule). + fn active<'a>(&self, profiles: &'a [RawProfile]) -> impl Iterator { + let chosen = |id: &str| profile_choice(&self.profiles, id); + let any_chosen = profiles.iter().any(|p| chosen(&p.id) == Some(true)); + profiles.iter().filter(move |p| chosen(&p.id).unwrap_or(p.active_by_default && !any_chosen)) } fn pom(&mut self, path: &Path) -> Option { @@ -550,7 +560,7 @@ impl Resolver { if pom.compiler_release.is_some() { ctx.compiler_release = pom.compiler_release.clone(); } - for p in pom.profiles.iter().filter(|p| p.active_by_default) { + for p in self.active(&pom.profiles) { merge_by(&mut ctx.properties, &p.properties, |(k, _)| k.clone()); merge_by(&mut ctx.deps, &p.deps, RawDep::key); merge_plugins(&mut ctx.plugins, &p.plugins); @@ -666,7 +676,7 @@ pub(crate) fn launch_inputs( } Some(()) } - let mut resolver = Resolver::new(local_repo.or_else(default_local_repo)); + let mut resolver = Resolver::new(local_repo.or_else(default_local_repo), &[]); let mut out = Vec::new(); for project in projects { let dir = root.join(&project.dir); @@ -1055,8 +1065,32 @@ pub fn load(root: &Path) -> MavenModel { load_with_repo(root, default_local_repo()) } +/// [`load`] with the chosen `profiles` (`-P` entries; `!id` deactivates): their modules, +/// dependencies and properties are part of the model, those turned off are not. +pub fn load_with_profiles(root: &Path, profiles: &[String]) -> MavenModel { + load_with(root, default_local_repo(), profiles) +} + pub(crate) fn load_with_repo(root: &Path, local_repo: Option) -> MavenModel { - let mut resolver = Resolver::new(local_repo); + load_with(root, local_repo, &[]) +} + +/// The `-P` entries' choice for profile `id`: on, off (`!id` / `-id`), or none (the last wins). +fn profile_choice(chosen: &[String], id: &str) -> Option { + chosen.iter().rev().find_map(|c| { + let c = c.trim(); + if c == id { + Some(true) + } else if c.strip_prefix('!').or_else(|| c.strip_prefix('-')) == Some(id) { + Some(false) + } else { + None + } + }) +} + +pub(crate) fn load_with(root: &Path, local_repo: Option, profiles: &[String]) -> MavenModel { + let mut resolver = Resolver::new(local_repo, profiles); let mut model = MavenModel::default(); let mut seen: HashSet = HashSet::new(); // (pom, aggregated) @@ -1076,7 +1110,7 @@ pub(crate) fn load_with_repo(root: &Path, local_repo: Option) -> MavenM let i = |s: &str| interpolate(s, &props); let mut module_names: Vec = pom.modules.clone(); - for p in pom.profiles.iter().filter(|p| p.active_by_default) { + for p in resolver.active(&pom.profiles) { module_names.extend(p.modules.iter().cloned()); } let mut modules: Vec = Vec::new(); @@ -1699,6 +1733,52 @@ mod tests { assert!(model.projects.iter().all(|p| p.command == if cfg!(windows) { "mvn.cmd" } else { "mvn" })); } + #[test] + fn chosen_profiles_bring_their_modules_and_dependencies() { + let tmp = tempfile::tempdir().unwrap(); + let root = tmp.path(); + write( + root, + "pom.xml", + r#"demoparent1.0pom + core + + flink-1.17true + flink-1.17 + flink-1.20flink-1.20 + org.apache.flinkflink-core1.20.0 + + + "#, + ); + for m in ["core", "flink-1.17", "flink-1.20"] { + write( + root, + &format!("{m}/pom.xml"), + &format!( + "demoparent1.0{m}" + ), + ); + } + let dirs = |m: &MavenModel| m.projects.iter().map(|p| p.dir.clone()).collect::>(); + let flink = |m: &MavenModel, dir: &str| { + m.projects.iter().find(|p| p.dir == dir).unwrap().dependencies.iter().any(|d| d.artifact_id == "flink-core") + }; + + let default = load_with(root, None, &[]); + assert_eq!(dirs(&default), ["", "core", "flink-1.17"]); + assert!(!flink(&default, "core")); + + // Choosing another profile of the POM turns its activeByDefault one off (as `-P` does). + let chosen = load_with(root, None, &["flink-1.20".into()]); + assert_eq!(dirs(&chosen), ["", "core", "flink-1.20"]); + assert!(flink(&chosen, "core"), "a module inherits the chosen profile's dependencies"); + + let both = load_with(root, None, &["flink-1.20".into(), "flink-1.17".into()]); + assert_eq!(dirs(&both), ["", "core", "flink-1.17", "flink-1.20"]); + assert_eq!(dirs(&load_with(root, None, &["!flink-1.17".into()])), ["", "core"]); + } + #[test] fn profiles_of_a_parent_pom_are_inherited() { let tmp = tempfile::tempdir().unwrap(); diff --git a/crates/ide-build/src/sync.rs b/crates/ide-build/src/sync.rs index 46e058f8d..407df4541 100644 --- a/crates/ide-build/src/sync.rs +++ b/crates/ide-build/src/sync.rs @@ -283,7 +283,7 @@ pub fn sync_maven( cancel: &CancellationToken, progress: &dyn Fn(SyncProgress), ) -> IdeResult<()> { - let mut model = maven::load(root); + let mut model = maven::load_with_profiles(root, &options.profiles); maven::UnlinkedProjects::from_model(&model, &options.maven_settings).filter(&mut model); std::fs::create_dir_all(cache_dir)?; let mut cache = MavenCache { diff --git a/crates/ide-host/src/ops/build.rs b/crates/ide-host/src/ops/build.rs index 77a406462..95417fcde 100644 --- a/crates/ide-host/src/ops/build.rs +++ b/crates/ide-host/src/ops/build.rs @@ -116,6 +116,11 @@ pub async fn build_sync( tracing::warn!("Maven profiles not saved: {e}"); false }); + if profiles_changed { + // The modules of the chosen profiles are read from the POMs with them from now on (the + // project tree's module folders too), whether or not Maven itself can run. + project.sink.emit(ide_core::IdeEvent::ProjectRootsChanged { root: project.root.clone().into() }); + } let token = host.begin_project_request(window, request); let result = tokio::task::spawn_blocking(move || { let current = load(&root, &cache, &settings);