diff --git a/src/compile/mod.rs b/src/compile/mod.rs index 258d83f..5d57cba 100644 --- a/src/compile/mod.rs +++ b/src/compile/mod.rs @@ -2,11 +2,11 @@ use std::fs; use std::path::{Path, PathBuf}; use std::process::Command; +use crate::compile::PathNode::*; use anyhow::Error; use colored::Colorize; -use crate::compile::PathNode::*; -use crate::Project; +use crate::project::Project; const SOURCES: &str = "src/main/java"; const TESTSOURCES: &str = "src/test/java"; @@ -16,7 +16,6 @@ const TESTRESOURCES: &str = "src/main/java"; const TARGET_MAIN: &str = "target/classes"; const TARGET_TEST: &str = "target/test-classes"; - /// internal view of the src filesystem #[derive(Debug)] enum PathNode { @@ -26,21 +25,25 @@ enum PathNode { /// runs the compile stage pub fn run(project: &Project) -> Result<(), Error> { - println!("{} {}.{}-{}", "Compiling".green(), project.group, project.name, project.version); + println!( + "{} {}.{}-{}", + "Compiling".green(), + project.group, + project.name, + project.version + ); let root = PathBuf::from(&project.project_root).join(SOURCES); let mut src_tree = DirNode(root.clone(), Vec::new(), Vec::new()); determine_src_tree(root, &mut src_tree)?; - println!("{:?}", src_tree); - - compile_sourcedir(project, &mut src_tree)?; + compile_source_dir(project, &mut src_tree)?; Ok(()) } /// walks the source tree and compiles any java files -fn compile_sourcedir(project: &Project, src_tree: &mut PathNode) -> Result<(), Error> { +fn compile_source_dir(project: &Project, src_tree: &mut PathNode) -> Result<(), Error> { if let DirNode(dir_name, subdirs, contents) = src_tree { if !contents.is_empty() { let mut javac = if cfg!(target_os = "windows") { @@ -78,7 +81,7 @@ fn compile_sourcedir(project: &Project, src_tree: &mut PathNode) -> Result<(), E println!("{}", String::from_utf8(output.stdout)?); } for subdir in subdirs { - compile_sourcedir(project, subdir)?; + compile_source_dir(project, subdir)?; } } Ok(()) @@ -108,4 +111,4 @@ fn determine_src_tree(parent: PathBuf, parent_node: &mut PathNode) -> Result<(), } } Ok(()) -} \ No newline at end of file +} diff --git a/src/config.rs b/src/config.rs index 92041c4..504a577 100644 --- a/src/config.rs +++ b/src/config.rs @@ -4,7 +4,6 @@ use std::sync::OnceLock; /// Contains any config elements pub struct Config { pub cache_location: String, - pub maven_central: String, pub user_home: PathBuf, } @@ -15,10 +14,9 @@ pub fn config() -> &'static Config { CONFIG.get_or_init(|| { let user_home = home::home_dir().unwrap(); Config { - cache_location: format!("{}/jargo/repo", user_home.to_str().unwrap()).into(),//TODO make '.jargo' + cache_location: format!("{}/jargo/repo", user_home.to_str().unwrap()).into(), //TODO make it '.jargo' user_home, - maven_central: "https://repo.maven.apache.org/maven2".into() } }); CONFIG.get().unwrap() -} \ No newline at end of file +} diff --git a/src/deploader.rs b/src/deploader.rs index 59a93bb..79386a5 100644 --- a/src/deploader.rs +++ b/src/deploader.rs @@ -9,10 +9,13 @@ use bytes::Bytes; use sha1::{Digest, Sha1}; use strong_xml::XmlRead; -use crate::Artifact; use crate::config::config; -use crate::pom::model::Pom; +use crate::maven; +use crate::maven::metadata::Metadata; +use crate::maven::pom::Pom; +use crate::project::{Artifact, Project}; use colored::Colorize; +use reqwest::StatusCode; /// Loads a list of artifacts from remote repo or local cache /// @@ -25,100 +28,277 @@ use colored::Colorize; /// 7. if not downloads it from a repo (now mavencentral only) /// 8. verifies the SHA1 as for the jar /// 9. extracts the transitive dependencies from the pom and recurses to (1) for the list of dependencies -pub fn load_artifacts(artifacts: &[Artifact]) -> Result<(), Error> { +pub fn load(project: &Project) -> Result<(), Error> { + load_artifacts(project, &project.main_dependencies)?; + load_artifacts(project, &project.test_dependencies)?; + Ok(()) +} + +fn load_artifacts(project: &Project, artifacts: &Vec) -> Result<(), Error> { for art in artifacts { - load_artifact(art)?; + load_artifact(project, art)?; } Ok(()) } -fn load_artifact(art: &Artifact) -> Result<(), Error> { - let artifact_path = format!("{}/{}/{}", art.group.replace(".", "/"), art.name, art.version); - +/// loads the artifact (all data and metadata for 1 artifact) +/// 1. create dir in local cache if necessary +/// 2. look up the pom +/// 3. look up the jar +fn load_artifact(project: &Project, artifact: &Artifact) -> Result<(), Error> { // check/create artifact directory - let local_artifact_loc = format!("{}/{}", config().cache_location, artifact_path); + let local_artifact_loc = format!("{}/{}", config().cache_location, artifact.path); if !exists(&local_artifact_loc) { fs::create_dir_all(&local_artifact_loc)?; } + // download remote pom if not in cache + let pom_lookup = lookup_verified_pom(project, artifact, &local_artifact_loc)?; + // download remote jar if not in cache and check its SHA-1 checksum - let local_artifact_jar_path = format!("{}/{}-{}.jar", local_artifact_loc, art.name, art.version); + let local_artifact_jar_path = format!( + "{}/{}-{}.jar", + local_artifact_loc, artifact.name, artifact.version + ); if !exists(&local_artifact_jar_path) { - download_verify_artifact_jar(art, &artifact_path, &local_artifact_jar_path)?; + lookup_verified_jar( + project, + artifact, + &local_artifact_jar_path, + &pom_lookup.resolved_repo.unwrap(), + &pom_lookup.resolved_version.unwrap(), + )?; } - // download remote pom if not in cache //TODO check its SHA-1 checksum - let pom_xml = download_verify_pom(art, artifact_path, local_artifact_loc)?; - + println!("{}", pom_lookup.pom_xml); // parse pom file - let pom = Pom::from_str(&pom_xml).unwrap(); + let pom = Pom::from_str(&pom_lookup.pom_xml).unwrap(); - let dependencies: Vec = pom.dependencies.value.into_iter().map(|d| d.into()).collect(); - load_artifacts(&dependencies)?; + //TODO exclusions + if let Some(dependencies) = pom.dependencies { + let artifacts = dependencies.value.into_iter().map(|d| d.into()).collect(); + load_artifacts(project, &artifacts)?; + } Ok(()) } -fn download_verify_pom(art: &Artifact, artifact_path: String, local_artifact_loc: String) -> Result { - let local_artifact_pom_path = &format!("{}/{}-{}.pom", local_artifact_loc, art.name, art.version); - let remote_artifact_pom_url = format!("{}/{}/{}-{}.pom", config().maven_central, artifact_path, art.name, art.version); - let pom_xml = if !exists(local_artifact_pom_path) { - println!("{} {}", "Downloading".green(), remote_artifact_pom_url); - let body = reqwest::blocking::get(&remote_artifact_pom_url)?.text().unwrap(); - println!("{} {}", "Downloaded".green(), remote_artifact_pom_url); - write_text(local_artifact_pom_path, &body)?; - body - } else { +/// main function to download and verify the pom xml. +/// 1. check if file is locally cached and load if it is +/// 2. or find a suitable repo (deferred to find_pom) +/// 3. this function returns the downloaded pom, together with the repo it was found in and the "resolved version' +/// this is only applicable to SNAPSHOT's where x-SNAPSHOT is resolved to x-- +/// 4. download the SHA1 file from the same location +/// 5. validate if the checksum equals the checksum calculated from the pom +/// +/// The result from find_pom is passed on to the caller so that the information can be used +/// for subsequent requests. +fn lookup_verified_pom( + project: &Project, + artifact: &Artifact, + local_artifact_loc: &str, +) -> Result { + let local_artifact_pom_path = &format!( + "{}/{}-{}.pom", + local_artifact_loc, artifact.name, artifact.version + ); + let result = if exists(local_artifact_pom_path) { read_file_to_string(local_artifact_pom_path)? + } else { + find_pom(project, artifact, local_artifact_pom_path)? }; + if result.is_none() { + panic!("Could not find pom for {}", artifact.path) + } else { + let result = result.unwrap(); + let repo_with_pom = result.resolved_repo.as_ref(); //TODO replace tuple with struct + let pom_xml = &result.pom_xml; - let local_artifact_pom_sha1_path = format!("{}.sha1", local_artifact_pom_path); + let local_artifact_pom_sha1_path = format!("{}.sha1", local_artifact_pom_path); - // verify jarfile with SHA1 checksum (which is hex encoded) - let checksum = hex::decode( - if !exists(&local_artifact_pom_sha1_path) { - download_checksum(&remote_artifact_pom_url, &local_artifact_pom_sha1_path)? + // verify jarfile with SHA1 checksum (which is hex encoded) + let checksum = if !exists(&local_artifact_pom_sha1_path) { + download_checksum(repo_with_pom, &local_artifact_pom_sha1_path)? } else { read_file_to_bytes(local_artifact_pom_sha1_path)? - })?; - let validated = validate_checksum_text(&pom_xml, checksum); - if !validated { - Err(anyhow!("SHA1 checksum for {} is not valid", remote_artifact_pom_url)) - } else { Ok(pom_xml) } + }; + if let Some(checksum) = checksum { + let validated = validate_checksum_text(&pom_xml, hex::decode(checksum)?); + return if !validated { + Err(anyhow!("SHA1 checksum for {} is not valid", artifact.path)) + } else { + Ok(result.clone()) // SHA1 ok + }; + } else { + // no SHA1 found + Ok(result.clone()) + } + } } +#[derive(Debug, Clone)] +struct PomLookupResult { + pom_xml: String, + resolved_repo: Option, + resolved_version: Option, +} + +fn find_pom( + project: &Project, + artifact: &Artifact, + local_artifact_pom_path: &str, +) -> Result, Error> { + for repo in &project.repositories { + let resolved_version = resolve_version(&artifact, repo)?; + let r = download_pom(artifact, &resolved_version, local_artifact_pom_path, &repo)?; + if r.is_some() { + return Ok(Some(PomLookupResult { + pom_xml: r.unwrap(), + resolved_repo: Some(repo.clone()), + resolved_version: Some(resolved_version), + })); + } + } + Ok(None) +} + +/// returns the pom and the repo where it was found +fn download_pom( + artifact: &Artifact, + resolved_version: &str, + local_artifact_pom_path: &str, + repo: &str, +) -> Result, Error> { + let remote_artifact_pom_url = format!( + "{}/{}/{}-{}.pom", + repo, artifact.path, artifact.name, resolved_version + ); + + println!("{} {}", "Downloading".green(), remote_artifact_pom_url); + let response = reqwest::blocking::get(&remote_artifact_pom_url)?; + if response.status().is_success() { + let body = response.text().unwrap(); + println!("{} {}", "Downloaded".green(), remote_artifact_pom_url); + write_text(local_artifact_pom_path, &body)?; + Ok(Some((body))) + } else { + Ok(None) + } +} /// Download jar from remote repo and check its signature /// For now it's a blocking call, because async and recursion add unwanted complexity/I don't understand that /// TODO add progress bar -fn download_verify_artifact_jar(art: &Artifact, artifact_path: &str, local_artifact_jar_path: &str) -> Result<(), Error> { - let remote_artifact_jar_url = format!("{}/{}/{}-{}.jar", config().maven_central, artifact_path, art.name, art.version); +fn lookup_verified_jar( + project: &Project, + artifact: &Artifact, + local_artifact_jar_path: &str, + resolved_repo: &str, + resolved_version: &str, +) -> Result<(), Error> { + let remote_artifact_jar_url = format!( + "{}/{}/{}-{}.jar", + resolved_repo, artifact.path, artifact.name, resolved_version + ); println!("{} {}", "Downloading".green(), remote_artifact_jar_url); - let jar = reqwest::blocking::get(&remote_artifact_jar_url)?.bytes().unwrap(); - println!("{} {}", "Downloaded".green(), remote_artifact_jar_url); - write_bytes_to_file(&local_artifact_jar_path, &jar)?; + let response = reqwest::blocking::get(&remote_artifact_jar_url)?; + if response.status().is_success() { + let jar = response.bytes().unwrap(); + println!("{} {}", "Downloaded".green(), remote_artifact_jar_url); + write_bytes_to_file(&local_artifact_jar_path, &jar)?; - let local_artifact_jar_sha1_path = format!("{}.sha1", local_artifact_jar_path); + let local_artifact_jar_sha1_path = format!("{}.sha1", local_artifact_jar_path); - // verify jarfile with SHA1 checksum (which is hex encoded) - let checksum = hex::decode( - if !exists(&local_artifact_jar_sha1_path) { - download_checksum(&remote_artifact_jar_url, &local_artifact_jar_sha1_path)? + // verify jarfile with SHA1 checksum (which is hex encoded) + let checksum = if !exists(&local_artifact_jar_sha1_path) { + download_checksum( + Some(&remote_artifact_jar_url), + &local_artifact_jar_sha1_path, + )? } else { read_file_to_bytes(local_artifact_jar_sha1_path)? - })?; - let validated = validate_checksum_bytes(&jar, checksum); - if !validated { - Err(anyhow!("SHA1 checksum for {} is not valid", remote_artifact_jar_url)) - } else { Ok(()) } + }; + return if let Some(checksum) = checksum { + let validated = validate_checksum_bytes(&jar, hex::decode(checksum)?); + if !validated { + Err(anyhow!( + "SHA1 checksum for {} is not valid", + remote_artifact_jar_url + )) + } else { + Ok(()) // checksum found and ok + } + } else { + Ok(()) // no checksum found + }; + } + panic!( + "Artifact {} not found in remote repository {}", + artifact.path, resolved_repo + ); } +fn resolve_version(artifact: &Artifact, repo: &String) -> Result { + Ok(if artifact.is_snapshot() { + let build_nr = load_snapshot_build_nr(&artifact.path, repo)?; + if let Some(build_nr) = build_nr { + artifact.version.replace("SNAPSHOT", &build_nr) + } else { + artifact.version.clone() + } + } else { + artifact.version.clone() + }) +} -fn download_checksum(remote_artifact_url: &String, local_artifact_jar_sha1_path: &String) -> Result, Error> { - let remote_artifact_jar_sha1_url = format!("{}.sha1", remote_artifact_url); - let jar_checksum = reqwest::blocking::get(&remote_artifact_jar_sha1_url)?.bytes().unwrap(); - write_bytes_to_file(&local_artifact_jar_sha1_path, &jar_checksum)?; - Ok(jar_checksum.to_vec()) +/// Snapshots in a maven repo can be in the form +/// 'spring-boot-starter-web-3.0.0-20221124.170206-1099.jar' +/// while we ask for +/// 'spring-boot-starter-web-3.0.0-SNAPSHOT.jar' +/// the metadata xml contains the info on what snapshot to download +/// so we download and parse it +fn load_snapshot_build_nr(artifact_path: &str, repo: &String) -> Result, Error> { + let metadata_url = format!("{}/{}/maven-metadata.xml", repo, artifact_path); + let response = reqwest::blocking::get(&metadata_url)?; + if response.status().is_success() { + let body = response.text().unwrap(); + write_text( + format!( + "{}/{}/maven-metadata.xml", + config().cache_location, + artifact_path + ) + .as_str(), + &body, + )?; + let metadata = Metadata::from_str(&body)?; + Ok(Some(format!( + "{}-{}", + metadata.versioning.snapshot.timestamp.value, + metadata.versioning.snapshot.build_number.value + ))) + } else { + Ok(None) + } +} + +fn download_checksum( + remote_artifact_url: Option<&String>, + local_artifact_jar_sha1_path: &str, +) -> Result>, Error> { + if let Some(remote_artifact_url) = remote_artifact_url { + let remote_artifact_jar_sha1_url = format!("{}.sha1", remote_artifact_url); + let response = reqwest::blocking::get(&remote_artifact_jar_sha1_url)?; + if response.status() == StatusCode::OK { + let jar_checksum = response.bytes().unwrap(); + write_bytes_to_file(&local_artifact_jar_sha1_path, &jar_checksum)?; + Ok(Some(jar_checksum.to_vec())) + } else { + Ok(None) + } + } else { + Ok(None) + } } fn validate_checksum_bytes(jar: &Bytes, checksum: Vec) -> bool { @@ -139,7 +319,6 @@ fn exists(path: &str) -> bool { Path::new(path).exists() } - fn write_bytes_to_file(jar_path: &str, bytes: &Bytes) -> Result<(), Error> { let mut file = File::create(jar_path)?; file.write_all(bytes)?; @@ -152,16 +331,20 @@ fn write_text(path: &str, contents: &String) -> Result<(), Error> { Ok(()) } -fn read_file_to_string(local_artifact_pom_path: &str) -> Result { +fn read_file_to_string(local_artifact_pom_path: &str) -> Result, Error> { let mut file = File::open(local_artifact_pom_path)?; let mut contents = String::new(); file.read_to_string(&mut contents)?; - Ok(contents) + Ok(Some(PomLookupResult { + pom_xml: contents, + resolved_repo: None, + resolved_version: None, + })) } -fn read_file_to_bytes(local_artifact_jar_sha1_path: String) -> Result, Error> { +fn read_file_to_bytes(local_artifact_jar_sha1_path: String) -> Result>, Error> { let mut file = File::open(local_artifact_jar_sha1_path)?; let mut contents = Vec::new(); file.read_to_end(&mut contents)?; - Ok(contents) -} \ No newline at end of file + Ok(Some(contents)) +} diff --git a/src/lib.rs b/src/lib.rs index 8574cd3..4a3de3e 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,102 +1,5 @@ -use std::fs; -use std::path::Path; - -use anyhow::{anyhow, Error}; -use toml::{Table, Value}; - -pub mod pom; +pub mod compile; mod config; pub mod deploader; -pub mod compile; - -/// Top struct for jargo project data -#[derive(Debug)] -pub struct Project { - pub group: String, - pub name: String, - pub version: String, - pub dependencies: Vec, - pub test_dependencies: Vec, - pub project_root: String, -} - -/// The identifier for any released bundle (jar, war etc) like in maven -#[derive(Debug)] -pub struct Artifact { - pub group: String, - pub name: String, - pub version: String, -} - -/// Convert from XML view -impl From for Artifact { - fn from(value: pom::model::Dependency) -> Self { - Artifact { - group: value.group_id.value, - name: value.artifact_id.value, - version: value.version.value, - } - } -} - -impl Artifact { - - /// Convert from TOML view - pub fn from_table_entry(name_group: &str, version: String) -> Result{ - let name_group_split: Vec<&str> = name_group.split(":").collect(); - if 2 != name_group_split.len(){ - return Err(anyhow!("dependency {} not well formatted", name_group)); - } - let group = name_group_split[0].into(); - let name = name_group_split[1].into(); - - Ok(Self{ - group, - name, - version: version[1..version.len()-1].to_owned(), - }) - } -} - -/// loads the project from the TOML file -pub fn load_project(jargo_file: Option<&str>) -> Result { - let jargo = Path::new(jargo_file.unwrap_or("./Jargo.toml")); - - let project_table = fs::read_to_string(jargo)?.parse::()?; - let package = project_table.get("package").expect("package info missing"); - - let dependencies = get_dependencies(project_table.get("dependencies"))?; - - let test_dependencies = get_dependencies(project_table.get("test-dependencies"))?; - - - Ok(Project { - group: strip_first_last(package.get("group").unwrap().to_string()), - name: strip_first_last(package.get("name").unwrap().to_string()), - version: strip_first_last(package.get("version").unwrap().to_string()), - dependencies, - test_dependencies, - project_root: jargo.parent().map(Path::to_str).unwrap().expect(&format!("projectroot {:?} not usable", jargo)).into(), - }) -} - -/// convert dependencies from the TOML view -fn get_dependencies(table: Option<&Value>) -> Result, Error> { - let mut dependencies = vec![]; - if let Some(table) = table { - let table = table.as_table(); - if let Some(table) = table { - for dep in table { - dependencies.push( - Artifact::from_table_entry(dep.0, dep.1.to_string())? - ) - } - } - } - Ok(dependencies) -} - -/// Because strings in the toml are surrounded by double quotes -fn strip_first_last(text: String) -> String{ - text[1..text.len()-1].into() -} \ No newline at end of file +pub mod maven; +pub mod project; diff --git a/src/main.rs b/src/main.rs index dff755d..ee00026 100644 --- a/src/main.rs +++ b/src/main.rs @@ -2,8 +2,9 @@ use anyhow::Error; /// sample main that will be replaced by a generic one later fn main() -> anyhow::Result<(), Error> { - let project = jargo::load_project(Some("tests/sample_project/Jargo.toml"))?; - jargo::deploader::load_artifacts(&project.test_dependencies)?; + let project = jargo::project::load_project(Some("tests/sample_project/Jargo.toml"))?; + println!("{:?}", project); + jargo::deploader::load(&project)?; jargo::compile::run(&project)?; Ok(()) } diff --git a/src/maven/metadata.rs b/src/maven/metadata.rs new file mode 100644 index 0000000..e06bed6 --- /dev/null +++ b/src/maven/metadata.rs @@ -0,0 +1,107 @@ +use strong_xml::XmlRead; + +use crate::maven::pom::{ArtifactId, GroupId, Version}; + +/// The Maven variant to parse poms +/// These structs is directly modelled after the XML because that is what strong-xml plugin requires +#[derive(XmlRead, PartialEq, Debug)] +#[xml(tag = "metadata")] +pub struct Metadata { + #[xml(child = "groupId")] + pub group_id: GroupId, + #[xml(child = "artifactId")] + pub artifact_id: ArtifactId, + #[xml(child = "version")] + pub version: Version, + #[xml(child = "versioning")] + pub versioning: Versioning, +} + +#[derive(XmlRead, PartialEq, Debug)] +#[xml(tag = "versioning")] +pub struct Versioning { + #[xml(child = "snapshot")] + pub snapshot: Snapshot, + #[xml(child = "lastUpdated")] + pub last_updated: LastUpdated, + #[xml(child = "snapshotVersions")] + pub snapshot_versions: SnapshotVersions, +} + +#[derive(XmlRead, PartialEq, Debug)] +#[xml(tag = "snapshot")] +pub struct Snapshot { + #[xml(child = "timestamp")] + pub timestamp: Timestamp, + #[xml(child = "buildNumber")] + pub build_number: BuildNumber, +} + +#[derive(XmlRead, PartialEq, Debug)] +#[xml(tag = "snapshotVersions")] +pub struct SnapshotVersions { + #[xml(child = "snapshotVersion")] + pub snapshot_versions: Vec, +} + +#[derive(XmlRead, PartialEq, Debug)] +#[xml(tag = "snapshotVersion")] +pub struct SnapshotVersion { + #[xml(child = "classifier")] + pub classifier: Option, + #[xml(child = "extension")] + pub extension: Extension, + #[xml(child = "value")] + pub value: Value, + #[xml(child = "updated")] + pub updated: Updated, +} + +#[derive(XmlRead, PartialEq, Debug)] +#[xml(tag = "timestamp")] +pub struct Timestamp { + #[xml(text)] + pub value: String, +} + +#[derive(XmlRead, PartialEq, Debug)] +#[xml(tag = "buildNumber")] +pub struct BuildNumber { + #[xml(text)] + pub value: String, +} + +#[derive(XmlRead, PartialEq, Debug)] +#[xml(tag = "lastUpdated")] +pub struct LastUpdated { + #[xml(text)] + pub value: String, +} + +#[derive(XmlRead, PartialEq, Debug)] +#[xml(tag = "updated")] +pub struct Updated { + #[xml(text)] + pub value: String, +} + +#[derive(XmlRead, PartialEq, Debug)] +#[xml(tag = "extension")] +pub struct Extension { + #[xml(text)] + pub value: String, +} + +#[derive(XmlRead, PartialEq, Debug)] +#[xml(tag = "classifier")] +pub struct Classifier { + #[xml(text)] + pub value: String, +} + +#[derive(XmlRead, PartialEq, Debug)] +#[xml(tag = "value")] +pub struct Value { + #[xml(text)] + pub value: String, +} diff --git a/src/maven/mod.rs b/src/maven/mod.rs new file mode 100644 index 0000000..341e379 --- /dev/null +++ b/src/maven/mod.rs @@ -0,0 +1,2 @@ +pub mod metadata; +pub mod pom; diff --git a/src/pom/model.rs b/src/maven/pom.rs similarity index 89% rename from src/pom/model.rs rename to src/maven/pom.rs index 564d634..11c4d68 100644 --- a/src/pom/model.rs +++ b/src/maven/pom.rs @@ -1,4 +1,5 @@ -use strong_xml::{XmlRead}; +use strong_xml::XmlRead; +use crate::maven::metadata::Versioning; /// The Maven variant to parse poms /// These structs is directly modelled after the XML because that is what strong-xml plugin requires @@ -7,6 +8,8 @@ use strong_xml::{XmlRead}; pub struct Pom { #[xml(child = "modelVersion")] pub model_version: ModelVersion, + #[xml(child = "parent")] + pub parent: Parent, #[xml(child = "groupId")] pub group_id: GroupId, #[xml(child = "artifactId")] @@ -16,7 +19,7 @@ pub struct Pom { #[xml(child = "name")] pub name: Name, #[xml(child = "packaging")] - pub packaging: Packaging, + pub packaging: Option, #[xml(child = "url")] pub url: Url, #[xml(child = "description")] @@ -28,7 +31,7 @@ pub struct Pom { #[xml(child = "developers")] pub developers: Developers, #[xml(child = "dependencies")] - pub dependencies: Dependencies, + pub dependencies: Option, } #[derive(XmlRead, PartialEq, Debug)] @@ -91,7 +94,7 @@ pub struct Url { #[xml(tag = "description")] pub struct Description { #[xml(text)] - value:String, + value: String, } #[derive(XmlRead, PartialEq, Debug)] @@ -119,6 +122,17 @@ pub struct License { distribution: Option, } +#[derive(XmlRead, PartialEq, Debug)] +#[xml(tag = "parent")] +pub struct Parent { + #[xml(child = "groupId")] + group_id: GroupId, + #[xml(child = "artifactId")] + artifact_id: ArtifactId, + #[xml(child = "version")] + version: Version, +} + #[derive(XmlRead, PartialEq, Debug)] #[xml(tag = "scm")] pub struct Scm { @@ -145,7 +159,7 @@ struct Developer { #[derive(XmlRead, PartialEq, Debug)] #[xml(tag = "dependencies")] pub struct Dependencies { - #[xml(child = "developer")] + #[xml(child = "dependency")] pub value: Vec, } @@ -157,14 +171,14 @@ pub struct Dependency { #[xml(child = "artifactId")] pub artifact_id: ArtifactId, #[xml(child = "version")] - pub version: Version, + pub version: Option, } #[cfg(test)] mod test { use strong_xml::XmlRead; - use crate::pom::model::Pom; + use crate::maven::pom::Pom; #[test] fn parse_should_not_fail() { @@ -210,4 +224,4 @@ mod test { "#).unwrap(); } -} \ No newline at end of file +} diff --git a/src/pom/mod.rs b/src/pom/mod.rs deleted file mode 100644 index 99bbd12..0000000 --- a/src/pom/mod.rs +++ /dev/null @@ -1 +0,0 @@ -pub mod model; \ No newline at end of file diff --git a/src/project.rs b/src/project.rs new file mode 100644 index 0000000..7395104 --- /dev/null +++ b/src/project.rs @@ -0,0 +1,135 @@ +use std::fs; +use std::path::Path; + +use crate::maven::pom::Dependency; +use anyhow::{anyhow, Error}; +use toml::{Table, Value}; + +/// Top struct for jargo project data +#[derive(Debug)] +pub struct Project { + pub group: String, + pub name: String, + pub version: String, + pub main_dependencies: Vec, + pub test_dependencies: Vec, + pub project_root: String, + pub repositories: Vec, +} + +/// The identifier for any released bundle (jar, war etc) like in maven +#[derive(Debug)] +pub struct Artifact { + pub group: String, + pub name: String, + pub version: String, + pub path: String, +} + +impl Artifact { + pub fn new(group: &str, name: &str, version: &str) -> Self { + Self { + group: group.into(), + name: name.into(), + version: version.into(), + path: format!("{}/{}/{}", group.replace(".", "/"), name, version), + } + } + + pub fn is_snapshot(&self) -> bool { + self.version.ends_with("-SNAPSHOT") + } +} + +/// Convert from XML view +impl From for Artifact { + fn from(value: Dependency) -> Self { + Self::new( + &value.group_id.value, + &value.artifact_id.value, + &value.version.unwrap().value, + ) + } +} + +impl Artifact { + /// Convert from TOML view + pub fn from_table_entry(name_group: &str, version: String) -> Result { + let name_group_split: Vec<&str> = name_group.split(":").collect(); + if 2 != name_group_split.len() { + return Err(anyhow!("dependency {} not well formatted", name_group)); + } + let group = name_group_split[0].into(); + let name = name_group_split[1].into(); + + Ok(Self::new( + group, + name, + version[1..version.len() - 1].to_owned().as_str(), + )) + } +} + +/// loads the project from the TOML file +pub fn load_project(jargo_file: Option<&str>) -> Result { + let jargo = Path::new(jargo_file.unwrap_or("./Jargo.toml")); + + let project_table = fs::read_to_string(jargo)?.parse::
()?; + let package = project_table.get("package").expect("package info missing"); + + let repositories = repositories(project_table.get("repositories"))?; + let main_dependencies = dependencies(project_table.get("dependencies"))?; + let test_dependencies = dependencies(project_table.get("test-dependencies"))?; + + Ok(Project { + group: strip_first_last(package.get("group").unwrap().to_string()), + name: strip_first_last(package.get("name").unwrap().to_string()), + version: strip_first_last(package.get("version").unwrap().to_string()), + repositories, + main_dependencies, + test_dependencies, + project_root: jargo + .parent() + .map(Path::to_str) + .unwrap() + .expect(&format!("projectroot {:?} not usable", jargo)) + .into(), + }) +} + +fn repositories(table: Option<&Value>) -> Result, Error> { + let mut repositories = vec!["https://repo.maven.apache.org/maven2".to_owned()]; + if let Some(table) = table { + let table = table.as_table(); + if let Some(table) = table { + for repo in table { + let repo_details = repo.1.clone(); + if let Value::Table(repo_details) = repo_details { + if let Some(Value::String(url)) = repo_details.get("url") { + repositories.push(url.into()); + } + } + } + } + } + Ok(repositories) +} + +/// convert dependencies from the TOML view +fn dependencies(table: Option<&Value>) -> Result, Error> { + let mut dependencies = vec![]; + if let Some(table) = table { + let table = table.as_table(); + if let Some(table) = table { + for dep in table { + dependencies.push(Artifact::from_table_entry(dep.0, dep.1.to_string())?); + } + } + } + Ok(dependencies) +} + +/// Because strings in the toml are surrounded by double quotes +fn strip_first_last(text: String) -> String { + text[1..text.len() - 1].into() +} diff --git a/tests/sample_project/Jargo.toml b/tests/sample_project/Jargo.toml index 6e96f73..a343ac4 100644 --- a/tests/sample_project/Jargo.toml +++ b/tests/sample_project/Jargo.toml @@ -3,8 +3,13 @@ group = "nl.sander" name = "sample_project" version = "0.1-SNAPSHOT" +[repositories] +spring-milestones = {url = "https://repo.spring.io/milestone"} +spring-snapshots = {url = "https://repo.spring.io/snapshot"} + [dependencies] +"org.springframework.boot:spring-boot-starter-web" = "3.0.0-SNAPSHOT" [test-dependencies] "junit:junit" = "4.8.2" -"org.mockito:mockito-core" = "1.9.5" \ No newline at end of file +"org.mockito:mockito-core" = "1.9.5"