resolve properties for versions of dependencies

This commit is contained in:
Shautvast 2025-07-24 16:39:15 +02:00
parent 3b170f7866
commit 04b94f35b2
8 changed files with 160 additions and 61 deletions

View file

@ -1,7 +1,7 @@
use std::path::Path; use std::path::Path;
use undeepend::maven::project_parser::parse_project; use undeepend::maven::project::parse_project;
fn main() { fn main() {
let project = parse_project(Path::new("tests/maven/resources/sample_project")).unwrap(); let project = parse_project(Path::new("tests/maven/resources/sample_project")).unwrap();
println!("{:?}", project); println!("{:?}", project.get_dependencies(&project.root));
} }

View file

@ -2,4 +2,4 @@ pub mod metadata;
pub mod pom; pub mod pom;
pub mod pom_view; pub mod pom_view;
pub mod pom_parser; pub mod pom_parser;
pub mod project_parser; pub mod project;

View file

@ -1,4 +1,5 @@
use std::collections::HashMap; use std::collections::HashMap;
use std::path::{Path, PathBuf};
/// The Maven variant to parse poms /// The Maven variant to parse poms
/// These structs is directly modelled after the XML because that is what strong-xml plugin requires /// These structs is directly modelled after the XML because that is what strong-xml plugin requires
@ -14,7 +15,13 @@ pub struct Pom {
pub dependencies: Vec<Dependency>, pub dependencies: Vec<Dependency>,
pub dependency_management: Vec<Dependency>, pub dependency_management: Vec<Dependency>,
pub properties: HashMap<String, String>, pub properties: HashMap<String, String>,
pub modules: Vec<String>, pub module_names: Vec<String>,
pub modules: Vec<Pom>,
pub directory: PathBuf,
}
impl Pom {
} }
#[derive(PartialEq, Debug)] #[derive(PartialEq, Debug)]
@ -42,13 +49,4 @@ pub struct Dependency {
pub group_id: String, pub group_id: String,
pub artifact_id: String, pub artifact_id: String,
pub version: Option<String>, pub version: Option<String>,
} }
#[cfg(test)]
mod test {
use crate::maven::pom::Pom;
#[test]
fn parse_should_not_fail() {}
}

View file

@ -2,6 +2,7 @@ use crate::maven::pom::{Dependency, Developer, Parent, Pom};
use crate::xml::SaxError; use crate::xml::SaxError;
use crate::xml::dom_parser::{Node, get_document}; use crate::xml::dom_parser::{Node, get_document};
use std::collections::HashMap; use std::collections::HashMap;
use std::path::PathBuf;
pub fn get_pom(xml: impl Into<String>) -> Result<Pom, SaxError> { pub fn get_pom(xml: impl Into<String>) -> Result<Pom, SaxError> {
let mut group_id = None; let mut group_id = None;
@ -14,7 +15,7 @@ pub fn get_pom(xml: impl Into<String>) -> Result<Pom, SaxError> {
let mut dependencies = vec![]; let mut dependencies = vec![];
let mut dependency_management = vec![]; let mut dependency_management = vec![];
let mut properties = HashMap::new(); // useless assignment... let mut properties = HashMap::new(); // useless assignment...
let mut modules = vec![]; // not useless assignment... let mut module_names = vec![]; // not useless assignment...
for child in get_document(xml.into().as_str())?.root.children { for child in get_document(xml.into().as_str())?.root.children {
match child.name.as_str() { match child.name.as_str() {
@ -28,7 +29,7 @@ pub fn get_pom(xml: impl Into<String>) -> Result<Pom, SaxError> {
"dependencies" => dependencies = get_dependencies(child), "dependencies" => dependencies = get_dependencies(child),
"dependencyManagement" => dependency_management = get_dependency_mgmt(child), "dependencyManagement" => dependency_management = get_dependency_mgmt(child),
"properties" => properties = get_properties(child), "properties" => properties = get_properties(child),
"modules" => add_modules(child, &mut modules), "modules" => add_modules(child, &mut module_names),
_ => {} _ => {}
} }
} }
@ -43,7 +44,9 @@ pub fn get_pom(xml: impl Into<String>) -> Result<Pom, SaxError> {
dependencies, dependencies,
dependency_management, dependency_management,
properties, properties,
modules, module_names,
modules: vec![],
directory: PathBuf::new(), // resolved later, make optional?
}) })
} }

139
src/maven/project.rs Normal file
View file

@ -0,0 +1,139 @@
use crate::maven::pom::{Dependency, Pom};
use crate::maven::pom_parser::get_pom;
use regex::Regex;
use std::fs;
use std::path::Path;
use std::sync::LazyLock;
static PROPERTY_EXPR: LazyLock<Regex> = LazyLock::new(|| Regex::new(r"\$\{(.+)}").unwrap());
pub fn parse_project(project_dir: &Path) -> Result<Project, String> {
if !project_dir.is_dir() {
return Err(format!("{:?} is not a directory", project_dir));
}
let mut pom_file = project_dir.to_path_buf();
pom_file.push(Path::new("pom.xml"));
let pom_file = fs::read_to_string(pom_file).map_err(|e| e.to_string())?;
let mut root = get_pom(pom_file).map_err(|e| e.to_string())?;
resolve_modules(project_dir, &mut root);
Ok(Project { root })
}
fn resolve_modules(project_dir: &Path, pom: &mut Pom) {
let mut modules = pom
.module_names
.iter()
.map(|module| read_module_pom(project_dir, module))
.collect();
for module in &mut modules {
resolve_modules(project_dir, module);
}
pom.modules.append(&mut modules);
}
fn read_module_pom(project_dir: &Path, module: &String) -> Pom {
let mut module_dir = project_dir.to_path_buf();
module_dir.push(Path::new(module));
let mut module_file = module_dir.clone();
module_file.push(Path::new("pom.xml"));
let module_pom =
fs::read_to_string(module_file).expect(format!("Cannot read file {}", module).as_str());
let mut pom =
get_pom(module_pom).expect(format!("Cannot create module pom {}", module).as_str());
pom.directory = module_dir;
pom
}
#[derive(Debug)]
pub struct Project {
pub root: Pom,
}
impl Project {
pub fn get_dependencies(&self, pom: &Pom) -> Vec<Dependency> {
pom.dependencies
.iter()
.map(|dependency| {
let version = self.get_version(pom, &dependency.group_id, &dependency.artifact_id);
Dependency {
group_id: dependency.group_id.clone(),
artifact_id: dependency.artifact_id.clone(),
version,
}
})
.collect()
}
fn get_version(&self, pom: &Pom, group_id: &str, artifact_id: &str) -> Option<String> {
pom.dependencies
.iter()
.find(|d| d.group_id == group_id && d.artifact_id == artifact_id)
.and_then(|d| d.version.clone())
.and_then(|version| {
if PROPERTY_EXPR.is_match(&version) {
let property_name = &PROPERTY_EXPR.captures(&version).unwrap()[1];
self.get_property(pom, property_name)
} else {
Some(version)
}
})
.or_else(|| {
pom.dependency_management
.iter()
.find(|d| d.group_id == group_id && d.artifact_id == artifact_id)
.and_then(|d| d.version.clone())
.and_then(|version| {
if PROPERTY_EXPR.is_match(&version) {
let property_name = &PROPERTY_EXPR.captures(&version).unwrap()[1];
self.get_property(pom, property_name)
} else {
Some(version)
}
})
})
}
fn get_property(&self, pom: &Pom, name: &str) -> Option<String> {
if pom.properties.contains_key(name) {
pom.properties.get(name).cloned()
} else if let Some(parent) = &pom.parent {
if let Some(parent_pom) = self.get_pom(&parent.group_id, &parent.artifact_id) {
self.get_property(parent_pom, name)
} else {
None
}
} else {
None
}
}
fn get_pom<'a>(&'a self, group_id: &str, artifact_id: &str) -> Option<&'a Pom> {
fn get_project_pom<'a>(pom: &'a Pom, group_id: &str, artifact_id: &str) -> Option<&'a Pom> {
if is_same(pom, group_id, artifact_id) {
return Some(pom);
} else {
for module in &pom.modules {
return get_project_pom(module, group_id, artifact_id);
}
}
None
}
get_project_pom(&self.root, group_id, artifact_id)
}
}
fn is_same(pom: &Pom, group_id: &str, artifact_id: &str) -> bool {
if pom.artifact_id == artifact_id {
if let Some(pom_group_id) = &pom.group_id {
pom_group_id == group_id
} else {
false
}
} else {
false
}
}

View file

@ -1,41 +0,0 @@
use crate::maven::pom::Pom;
use crate::maven::pom_parser::get_pom;
use std::fs;
use std::path::Path;
pub fn parse_project(project_dir: &Path) -> Result<Project, String> {
if !project_dir.is_dir() {
return Err(format!("{:?} is not a directory", project_dir));
}
let mut pom_file = project_dir.to_path_buf();
pom_file.push(Path::new("pom.xml"));
let pom_file = fs::read_to_string(pom_file).map_err(|e| e.to_string())?;
let root = get_pom(pom_file).map_err(|e| e.to_string())?;
let modules= root.modules
.iter()
.map(|module| read_module_pom(project_dir, module))
.collect();
Ok(Project {
root,
modules,
})
}
fn read_module_pom(project_dir: &Path, module: &String) -> Pom {
let mut module_file = project_dir.to_path_buf();
module_file.push(Path::new(module));
module_file.push(Path::new("pom.xml"));
let module_pom = fs::read_to_string(module_file)
.expect(format!("Cannot read file {}", module).as_str());
get_pom(module_pom).expect(format!("Cannot create module pom {}", module).as_str())
}
#[derive(Debug)]
pub struct Project {
pub root: Pom,
pub modules: Vec<Pom>,
}

View file

@ -28,8 +28,8 @@ fn test_pom_parser_is_correct() {
assert_eq!(Some("1.0".to_string()), objenesis.version); assert_eq!(Some("1.0".to_string()), objenesis.version);
assert_eq!(2, pom.modules.len()); assert_eq!(2, pom.modules.len());
assert_eq!("a", pom.modules[0]); assert_eq!("a", pom.module_names[0]);
assert_eq!("b", pom.modules[1]); assert_eq!("b", pom.module_names[1]);
assert_eq!(1, pom.dependency_management.len()); assert_eq!(1, pom.dependency_management.len());
let hamcrest = &pom.dependency_management[0]; let hamcrest = &pom.dependency_management[0];

View file

@ -1,3 +1,3 @@
use std::path::Path; use std::path::Path;
use undeepend::maven::project_parser::parse_project; use undeepend::maven::project::parse_project;