add extra repo's

support for snapshots (with build timestamps)
bugfixes
This commit is contained in:
Shautvast 2024-01-16 22:06:33 +01:00
parent d3423bdf29
commit 8371757c71
11 changed files with 538 additions and 188 deletions

View file

@ -2,11 +2,11 @@ use std::fs;
use std::path::{Path, PathBuf}; use std::path::{Path, PathBuf};
use std::process::Command; use std::process::Command;
use crate::compile::PathNode::*;
use anyhow::Error; use anyhow::Error;
use colored::Colorize; use colored::Colorize;
use crate::compile::PathNode::*;
use crate::Project; use crate::project::Project;
const SOURCES: &str = "src/main/java"; const SOURCES: &str = "src/main/java";
const TESTSOURCES: &str = "src/test/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_MAIN: &str = "target/classes";
const TARGET_TEST: &str = "target/test-classes"; const TARGET_TEST: &str = "target/test-classes";
/// internal view of the src filesystem /// internal view of the src filesystem
#[derive(Debug)] #[derive(Debug)]
enum PathNode { enum PathNode {
@ -26,21 +25,25 @@ enum PathNode {
/// runs the compile stage /// runs the compile stage
pub fn run(project: &Project) -> Result<(), Error> { 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 root = PathBuf::from(&project.project_root).join(SOURCES);
let mut src_tree = DirNode(root.clone(), Vec::new(), Vec::new()); let mut src_tree = DirNode(root.clone(), Vec::new(), Vec::new());
determine_src_tree(root, &mut src_tree)?; determine_src_tree(root, &mut src_tree)?;
println!("{:?}", src_tree); compile_source_dir(project, &mut src_tree)?;
compile_sourcedir(project, &mut src_tree)?;
Ok(()) Ok(())
} }
/// walks the source tree and compiles any java files /// 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 let DirNode(dir_name, subdirs, contents) = src_tree {
if !contents.is_empty() { if !contents.is_empty() {
let mut javac = if cfg!(target_os = "windows") { 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)?); println!("{}", String::from_utf8(output.stdout)?);
} }
for subdir in subdirs { for subdir in subdirs {
compile_sourcedir(project, subdir)?; compile_source_dir(project, subdir)?;
} }
} }
Ok(()) Ok(())

View file

@ -4,7 +4,6 @@ use std::sync::OnceLock;
/// Contains any config elements /// Contains any config elements
pub struct Config { pub struct Config {
pub cache_location: String, pub cache_location: String,
pub maven_central: String,
pub user_home: PathBuf, pub user_home: PathBuf,
} }
@ -15,9 +14,8 @@ pub fn config() -> &'static Config {
CONFIG.get_or_init(|| { CONFIG.get_or_init(|| {
let user_home = home::home_dir().unwrap(); let user_home = home::home_dir().unwrap();
Config { 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, user_home,
maven_central: "https://repo.maven.apache.org/maven2".into()
} }
}); });
CONFIG.get().unwrap() CONFIG.get().unwrap()

View file

@ -9,10 +9,13 @@ use bytes::Bytes;
use sha1::{Digest, Sha1}; use sha1::{Digest, Sha1};
use strong_xml::XmlRead; use strong_xml::XmlRead;
use crate::Artifact;
use crate::config::config; 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 colored::Colorize;
use reqwest::StatusCode;
/// Loads a list of artifacts from remote repo or local cache /// 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) /// 7. if not downloads it from a repo (now mavencentral only)
/// 8. verifies the SHA1 as for the jar /// 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 /// 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<Artifact>) -> Result<(), Error> {
for art in artifacts { for art in artifacts {
load_artifact(art)?; load_artifact(project, art)?;
} }
Ok(()) Ok(())
} }
fn load_artifact(art: &Artifact) -> Result<(), Error> { /// loads the artifact (all data and metadata for 1 artifact)
let artifact_path = format!("{}/{}/{}", art.group.replace(".", "/"), art.name, art.version); /// 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 // 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) { if !exists(&local_artifact_loc) {
fs::create_dir_all(&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 // 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) { 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 println!("{}", pom_lookup.pom_xml);
let pom_xml = download_verify_pom(art, artifact_path, local_artifact_loc)?;
// parse pom file // parse pom file
let pom = Pom::from_str(&pom_xml).unwrap(); let pom = Pom::from_str(&pom_lookup.pom_xml).unwrap();
let dependencies: Vec<Artifact> = pom.dependencies.value.into_iter().map(|d| d.into()).collect(); //TODO exclusions
load_artifacts(&dependencies)?; if let Some(dependencies) = pom.dependencies {
let artifacts = dependencies.value.into_iter().map(|d| d.into()).collect();
load_artifacts(project, &artifacts)?;
}
Ok(()) Ok(())
} }
fn download_verify_pom(art: &Artifact, artifact_path: String, local_artifact_loc: String) -> Result<String, Error> { /// main function to download and verify the pom xml.
let local_artifact_pom_path = &format!("{}/{}-{}.pom", local_artifact_loc, art.name, art.version); /// 1. check if file is locally cached and load if it is
let remote_artifact_pom_url = format!("{}/{}/{}-{}.pom", config().maven_central, artifact_path, art.name, art.version); /// 2. or find a suitable repo (deferred to find_pom)
let pom_xml = if !exists(local_artifact_pom_path) { /// 3. this function returns the downloaded pom, together with the repo it was found in and the "resolved version'
println!("{} {}", "Downloading".green(), remote_artifact_pom_url); /// this is only applicable to SNAPSHOT's where x-SNAPSHOT is resolved to x-<timestamp>-<build_nr>
let body = reqwest::blocking::get(&remote_artifact_pom_url)?.text().unwrap(); /// 4. download the SHA1 file from the same location
println!("{} {}", "Downloaded".green(), remote_artifact_pom_url); /// 5. validate if the checksum equals the checksum calculated from the pom
write_text(local_artifact_pom_path, &body)?; ///
body /// The result from find_pom is passed on to the caller so that the information can be used
} else { /// for subsequent requests.
fn lookup_verified_pom(
project: &Project,
artifact: &Artifact,
local_artifact_loc: &str,
) -> Result<PomLookupResult, Error> {
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)? 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) // verify jarfile with SHA1 checksum (which is hex encoded)
let checksum = hex::decode( let checksum = if !exists(&local_artifact_pom_sha1_path) {
if !exists(&local_artifact_pom_sha1_path) { download_checksum(repo_with_pom, &local_artifact_pom_sha1_path)?
download_checksum(&remote_artifact_pom_url, &local_artifact_pom_sha1_path)?
} else { } else {
read_file_to_bytes(local_artifact_pom_sha1_path)? read_file_to_bytes(local_artifact_pom_sha1_path)?
})?; };
let validated = validate_checksum_text(&pom_xml, checksum); if let Some(checksum) = checksum {
if !validated { let validated = validate_checksum_text(&pom_xml, hex::decode(checksum)?);
Err(anyhow!("SHA1 checksum for {} is not valid", remote_artifact_pom_url)) return if !validated {
} else { Ok(pom_xml) } 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<String>,
resolved_version: Option<String>,
}
fn find_pom(
project: &Project,
artifact: &Artifact,
local_artifact_pom_path: &str,
) -> Result<Option<PomLookupResult>, 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<Option<String>, 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 /// 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 /// For now it's a blocking call, because async and recursion add unwanted complexity/I don't understand that
/// TODO add progress bar /// TODO add progress bar
fn download_verify_artifact_jar(art: &Artifact, artifact_path: &str, local_artifact_jar_path: &str) -> Result<(), Error> { fn lookup_verified_jar(
let remote_artifact_jar_url = format!("{}/{}/{}-{}.jar", config().maven_central, artifact_path, art.name, art.version); 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); println!("{} {}", "Downloading".green(), remote_artifact_jar_url);
let jar = reqwest::blocking::get(&remote_artifact_jar_url)?.bytes().unwrap(); let response = reqwest::blocking::get(&remote_artifact_jar_url)?;
println!("{} {}", "Downloaded".green(), remote_artifact_jar_url); if response.status().is_success() {
write_bytes_to_file(&local_artifact_jar_path, &jar)?; 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) // verify jarfile with SHA1 checksum (which is hex encoded)
let checksum = hex::decode( let checksum = if !exists(&local_artifact_jar_sha1_path) {
if !exists(&local_artifact_jar_sha1_path) { download_checksum(
download_checksum(&remote_artifact_jar_url, &local_artifact_jar_sha1_path)? Some(&remote_artifact_jar_url),
&local_artifact_jar_sha1_path,
)?
} else { } else {
read_file_to_bytes(local_artifact_jar_sha1_path)? read_file_to_bytes(local_artifact_jar_sha1_path)?
})?; };
let validated = validate_checksum_bytes(&jar, checksum); return if let Some(checksum) = checksum {
if !validated { let validated = validate_checksum_bytes(&jar, hex::decode(checksum)?);
Err(anyhow!("SHA1 checksum for {} is not valid", remote_artifact_jar_url)) if !validated {
} else { Ok(()) } 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<String, Error> {
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<Vec<u8>, Error> { /// Snapshots in a maven repo can be in the form
let remote_artifact_jar_sha1_url = format!("{}.sha1", remote_artifact_url); /// 'spring-boot-starter-web-3.0.0-20221124.170206-1099.jar'
let jar_checksum = reqwest::blocking::get(&remote_artifact_jar_sha1_url)?.bytes().unwrap(); /// while we ask for
write_bytes_to_file(&local_artifact_jar_sha1_path, &jar_checksum)?; /// 'spring-boot-starter-web-3.0.0-SNAPSHOT.jar'
Ok(jar_checksum.to_vec()) /// 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<Option<String>, 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<Option<Vec<u8>>, 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<u8>) -> bool { fn validate_checksum_bytes(jar: &Bytes, checksum: Vec<u8>) -> bool {
@ -139,7 +319,6 @@ fn exists(path: &str) -> bool {
Path::new(path).exists() Path::new(path).exists()
} }
fn write_bytes_to_file(jar_path: &str, bytes: &Bytes) -> Result<(), Error> { fn write_bytes_to_file(jar_path: &str, bytes: &Bytes) -> Result<(), Error> {
let mut file = File::create(jar_path)?; let mut file = File::create(jar_path)?;
file.write_all(bytes)?; file.write_all(bytes)?;
@ -152,16 +331,20 @@ fn write_text(path: &str, contents: &String) -> Result<(), Error> {
Ok(()) Ok(())
} }
fn read_file_to_string(local_artifact_pom_path: &str) -> Result<String, Error> { fn read_file_to_string(local_artifact_pom_path: &str) -> Result<Option<PomLookupResult>, Error> {
let mut file = File::open(local_artifact_pom_path)?; let mut file = File::open(local_artifact_pom_path)?;
let mut contents = String::new(); let mut contents = String::new();
file.read_to_string(&mut contents)?; 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<Vec<u8>, Error> { fn read_file_to_bytes(local_artifact_jar_sha1_path: String) -> Result<Option<Vec<u8>>, Error> {
let mut file = File::open(local_artifact_jar_sha1_path)?; let mut file = File::open(local_artifact_jar_sha1_path)?;
let mut contents = Vec::new(); let mut contents = Vec::new();
file.read_to_end(&mut contents)?; file.read_to_end(&mut contents)?;
Ok(contents) Ok(Some(contents))
} }

View file

@ -1,102 +1,5 @@
use std::fs; pub mod compile;
use std::path::Path;
use anyhow::{anyhow, Error};
use toml::{Table, Value};
pub mod pom;
mod config; mod config;
pub mod deploader; pub mod deploader;
pub mod compile; pub mod maven;
pub mod project;
/// Top struct for jargo project data
#[derive(Debug)]
pub struct Project {
pub group: String,
pub name: String,
pub version: String,
pub dependencies: Vec<Artifact>,
pub test_dependencies: Vec<Artifact>,
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<pom::model::Dependency> 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<Self, Error>{
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<Project, Error> {
let jargo = Path::new(jargo_file.unwrap_or("./Jargo.toml"));
let project_table = fs::read_to_string(jargo)?.parse::<Table>()?;
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<Vec<Artifact>, 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()
}

View file

@ -2,8 +2,9 @@ use anyhow::Error;
/// sample main that will be replaced by a generic one later /// sample main that will be replaced by a generic one later
fn main() -> anyhow::Result<(), Error> { fn main() -> anyhow::Result<(), Error> {
let project = jargo::load_project(Some("tests/sample_project/Jargo.toml"))?; let project = jargo::project::load_project(Some("tests/sample_project/Jargo.toml"))?;
jargo::deploader::load_artifacts(&project.test_dependencies)?; println!("{:?}", project);
jargo::deploader::load(&project)?;
jargo::compile::run(&project)?; jargo::compile::run(&project)?;
Ok(()) Ok(())
} }

107
src/maven/metadata.rs Normal file
View file

@ -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<SnapshotVersion>,
}
#[derive(XmlRead, PartialEq, Debug)]
#[xml(tag = "snapshotVersion")]
pub struct SnapshotVersion {
#[xml(child = "classifier")]
pub classifier: Option<Classifier>,
#[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,
}

2
src/maven/mod.rs Normal file
View file

@ -0,0 +1,2 @@
pub mod metadata;
pub mod pom;

View file

@ -1,4 +1,5 @@
use strong_xml::{XmlRead}; use strong_xml::XmlRead;
use crate::maven::metadata::Versioning;
/// 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
@ -7,6 +8,8 @@ use strong_xml::{XmlRead};
pub struct Pom { pub struct Pom {
#[xml(child = "modelVersion")] #[xml(child = "modelVersion")]
pub model_version: ModelVersion, pub model_version: ModelVersion,
#[xml(child = "parent")]
pub parent: Parent,
#[xml(child = "groupId")] #[xml(child = "groupId")]
pub group_id: GroupId, pub group_id: GroupId,
#[xml(child = "artifactId")] #[xml(child = "artifactId")]
@ -16,7 +19,7 @@ pub struct Pom {
#[xml(child = "name")] #[xml(child = "name")]
pub name: Name, pub name: Name,
#[xml(child = "packaging")] #[xml(child = "packaging")]
pub packaging: Packaging, pub packaging: Option<Packaging>,
#[xml(child = "url")] #[xml(child = "url")]
pub url: Url, pub url: Url,
#[xml(child = "description")] #[xml(child = "description")]
@ -28,7 +31,7 @@ pub struct Pom {
#[xml(child = "developers")] #[xml(child = "developers")]
pub developers: Developers, pub developers: Developers,
#[xml(child = "dependencies")] #[xml(child = "dependencies")]
pub dependencies: Dependencies, pub dependencies: Option<Dependencies>,
} }
#[derive(XmlRead, PartialEq, Debug)] #[derive(XmlRead, PartialEq, Debug)]
@ -91,7 +94,7 @@ pub struct Url {
#[xml(tag = "description")] #[xml(tag = "description")]
pub struct Description { pub struct Description {
#[xml(text)] #[xml(text)]
value:String, value: String,
} }
#[derive(XmlRead, PartialEq, Debug)] #[derive(XmlRead, PartialEq, Debug)]
@ -119,6 +122,17 @@ pub struct License {
distribution: Option<Distribution>, distribution: Option<Distribution>,
} }
#[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)] #[derive(XmlRead, PartialEq, Debug)]
#[xml(tag = "scm")] #[xml(tag = "scm")]
pub struct Scm { pub struct Scm {
@ -145,7 +159,7 @@ struct Developer {
#[derive(XmlRead, PartialEq, Debug)] #[derive(XmlRead, PartialEq, Debug)]
#[xml(tag = "dependencies")] #[xml(tag = "dependencies")]
pub struct Dependencies { pub struct Dependencies {
#[xml(child = "developer")] #[xml(child = "dependency")]
pub value: Vec<Dependency>, pub value: Vec<Dependency>,
} }
@ -157,14 +171,14 @@ pub struct Dependency {
#[xml(child = "artifactId")] #[xml(child = "artifactId")]
pub artifact_id: ArtifactId, pub artifact_id: ArtifactId,
#[xml(child = "version")] #[xml(child = "version")]
pub version: Version, pub version: Option<Version>,
} }
#[cfg(test)] #[cfg(test)]
mod test { mod test {
use strong_xml::XmlRead; use strong_xml::XmlRead;
use crate::pom::model::Pom; use crate::maven::pom::Pom;
#[test] #[test]
fn parse_should_not_fail() { fn parse_should_not_fail() {

View file

@ -1 +0,0 @@
pub mod model;

135
src/project.rs Normal file
View file

@ -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<Artifact>,
pub test_dependencies: Vec<Artifact>,
pub project_root: String,
pub repositories: Vec<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,
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<Dependency> 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<Self, Error> {
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<Project, Error> {
let jargo = Path::new(jargo_file.unwrap_or("./Jargo.toml"));
let project_table = fs::read_to_string(jargo)?.parse::<Table>()?;
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<Vec<String>, 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<Vec<Artifact>, 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()
}

View file

@ -3,7 +3,12 @@ group = "nl.sander"
name = "sample_project" name = "sample_project"
version = "0.1-SNAPSHOT" version = "0.1-SNAPSHOT"
[repositories]
spring-milestones = {url = "https://repo.spring.io/milestone"}
spring-snapshots = {url = "https://repo.spring.io/snapshot"}
[dependencies] [dependencies]
"org.springframework.boot:spring-boot-starter-web" = "3.0.0-SNAPSHOT"
[test-dependencies] [test-dependencies]
"junit:junit" = "4.8.2" "junit:junit" = "4.8.2"