compiles main classes (todo dependencies)

This commit is contained in:
Shautvast 2024-01-15 18:14:18 +01:00
parent 4ce945450c
commit caa440a27a
11 changed files with 240 additions and 62 deletions

11
Cargo.lock generated
View file

@ -98,6 +98,16 @@ version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
[[package]]
name = "colored"
version = "2.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cbf2150cce219b664a8a70df7a1f933836724b503f8a413af9365b4dcc4d90b8"
dependencies = [
"lazy_static",
"windows-sys 0.48.0",
]
[[package]] [[package]]
name = "core-foundation" name = "core-foundation"
version = "0.9.4" version = "0.9.4"
@ -423,6 +433,7 @@ version = "0.1.0"
dependencies = [ dependencies = [
"anyhow", "anyhow",
"bytes", "bytes",
"colored",
"hex", "hex",
"home", "home",
"reqwest", "reqwest",

View file

@ -9,6 +9,7 @@ edition = "2021"
toml = "0.8" toml = "0.8"
anyhow = "1.0" anyhow = "1.0"
strong-xml = "0.6" strong-xml = "0.6"
colored = "2.0"
reqwest = {version = "0.11", features = ["blocking"]} reqwest = {version = "0.11", features = ["blocking"]}
tokio = { version = "1", features = ["full"] } tokio = { version = "1", features = ["full"] }
bytes = "1.5" bytes = "1.5"

View file

@ -1,10 +1,8 @@
**Jargo** **Jargo**
Exploring the need/possibility to move from maven/gradle to cargo. An experimental build tool for Java taking inspiration from Cargo.
That is, build tool for java taking inspiration from Cargo And it's called *Jargo*. I do not wish to put a J in front of anything, as is the java tradition,
And it's called Jargo. I do not wish to put a J in front of anything, as is the java tradition,
but 'jargo' actually sounds kinda nice and it conveys pretty much what it is. but 'jargo' actually sounds kinda nice and it conveys pretty much what it is.
It is NOT a new maven (not yet at least). That's the reason it's not called 'raven.' It is NOT a new maven (not yet at least). That's the reason it's not called 'raven.'
@ -12,8 +10,8 @@ It is NOT a new maven (not yet at least). That's the reason it's not called 'rav
Basic premisses: Basic premisses:
1. written in Rust 1. written in Rust
2. does NOT copy anything from the maven philosophy (phases, goals etc). Instead find out on the go what would be 2. does NOT copy anything from the maven philosophy (phases, goals etc). Instead find out on the go what would be
a good design a good design. _That said, some things are just good to keep using, such as the default project structure._
3. uses TOML 3. configured in TOML. ie. no XML, **yay!**, AND no Turing-completeness (groovy/kotlin in gradle), **yay2!!**
see [tests/sample_project/Jargo.toml](https://github.com/shautvast/jargo/blob/main/tests/sample_project/Jargo.toml) to get an impression of what that looks like. see [tests/sample_project/Jargo.toml](https://github.com/shautvast/jargo/blob/main/tests/sample_project/Jargo.toml) to get an impression of what that looks like.
@ -38,7 +36,8 @@ _Every tool is currently being rewritten in rust._ And for good reason!
2. Why not create a drop-in replacement for maven written in rust? 2. Why not create a drop-in replacement for maven written in rust?
_While that would make migration a no-brainer, it seems too ambitious based on what I've seen of the maven _While that would (in theory) make migration a no-brainer, it seems too ambitious based on what I've seen of the maven
codebase_ codebase. Other than that you will most likely run into onforeseen issues while migrating this way, because this or
that is subtly different here and there. Better avoid the promise of easy migration altogether._

107
src/compile/mod.rs Normal file
View file

@ -0,0 +1,107 @@
use std::fs;
use std::path::{Path, PathBuf};
use std::process::Command;
use anyhow::Error;
use colored::Colorize;
use crate::compile::PathNode::*;
use crate::Project;
const SOURCES: &str = "src/main/java";
const TESTSOURCES: &str = "src/test/java";
const RESOURCES: &str = "src/main/resources";
const TESTRESOURCES: &str = "src/main/java";
const TARGET_MAIN: &str = "target/classes";
const TARGET_TEST: &str = "target/test-classes";
#[derive(Debug)]
enum PathNode {
DirNode(PathBuf, Vec<PathNode>, Vec<PathNode>),
FileNode(PathBuf),
}
pub fn run(project: &Project) -> Result<(), Error> {
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)?;
Ok(())
}
fn compile_sourcedir(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") {
vec!["/C"]
} else {
vec![]
};
let classes = PathBuf::from(&project.project_root).join(TARGET_MAIN);
let classes = classes.to_str().unwrap();
javac.append(&mut vec!["javac", "-d", classes, "-sourcepath"]);
javac.push(dir_name.to_str().unwrap().into());
for source in contents {
if let FileNode(source_name) = source {
let name = source_name.to_str().unwrap();
javac.push(name);
}
}
let output = if cfg!(target_os = "windows") {
Command::new("cmd")
.args(javac)
.output()
.expect("failed to execute process")
} else {
Command::new("sh")
.arg("-c")
.arg(javac.join(" "))
.output()
.expect("failed to execute process")
};
if !output.stderr.is_empty() {
println!("{}", String::from_utf8(output.stderr)?.red());
}
println!("{}", String::from_utf8(output.stdout)?);
}
for subdir in subdirs {
compile_sourcedir(project, subdir)?;
}
}
Ok(())
}
fn determine_src_tree(parent: PathBuf, parent_node: &mut PathNode) -> Result<(), Error> {
let paths = fs::read_dir(&parent)?;
for path in paths {
let path = path?;
if path.metadata()?.is_dir() {
let mut subdir = DirNode(path.path(), Vec::new(), Vec::new());
determine_src_tree(path.path(), &mut subdir)?;
if let DirNode(_, subdirs, _) = parent_node {
subdirs.push(subdir);
}
} else {
let name = path.file_name();
let name = name.to_str().unwrap().to_owned();
if name.ends_with(".java") {
if let DirNode(_, _, contents) = parent_node {
contents.push(FileNode(path.path()));
}
}
}
}
Ok(())
}

View file

@ -9,11 +9,11 @@ pub struct Config {
pub static CONFIG: OnceLock<Config> = OnceLock::new(); pub static CONFIG: OnceLock<Config> = OnceLock::new();
pub fn get_config() -> &'static Config { 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(), cache_location: format!("{}/jargo/repo", user_home.to_str().unwrap()).into(),//TODO make '.jargo'
user_home, user_home,
maven_central: "https://repo.maven.apache.org/maven2".into() maven_central: "https://repo.maven.apache.org/maven2".into()
} }

View file

@ -10,8 +10,9 @@ use sha1::{Digest, Sha1};
use strong_xml::XmlRead; use strong_xml::XmlRead;
use crate::Artifact; use crate::Artifact;
use crate::config::get_config; use crate::config::config;
use crate::pom::model::Pom; use crate::pom::model::Pom;
use colored::Colorize;
pub fn load_artifacts(artifacts: &[Artifact]) -> Result<(), Error> { pub fn load_artifacts(artifacts: &[Artifact]) -> Result<(), Error> {
for art in artifacts { for art in artifacts {
@ -21,37 +22,22 @@ pub fn load_artifacts(artifacts: &[Artifact]) -> Result<(), Error> {
} }
pub fn load_artifact(art: &Artifact) -> Result<(), Error> { pub fn load_artifact(art: &Artifact) -> Result<(), Error> {
let artifact_path = format!("{}/{}/{}", let artifact_path = format!("{}/{}/{}", art.group.replace(".", "/"), art.name, art.version);
art.group.replace(".", "/"),
art.name, // check/create artifact directory
art.version, let local_artifact_loc = format!("{}/{}", config().cache_location, artifact_path);
);
let local_artifact_loc = format!("{}/{}", get_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 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, art.name, art.version);
if !exists(&local_artifact_jar_path) { if !exists(&local_artifact_jar_path) {
load_verify_artifact_jar(art, &artifact_path, &local_artifact_jar_path)?; download_verify_artifact_jar(art, &artifact_path, &local_artifact_jar_path)?;
} }
let local_artifact_pom_path = format!("{}/{}-{}.pom", local_artifact_loc, art.name, art.version); // download remote pom if not in cache //TODO check its SHA-1 checksum
let pom_xml = if !exists(&local_artifact_pom_path) { let pom_xml = download_verify_pom(art, artifact_path, local_artifact_loc)?;
// download pom
let remote_artifact_pom_url = format!("{}/{}/{}-{}.pom", get_config().maven_central, artifact_path, art.name, art.version);
println!("Downloading {}", remote_artifact_pom_url);
let body = reqwest::blocking::get(&remote_artifact_pom_url)?.text().unwrap();
println!("Downloaded {}", remote_artifact_pom_url);
write_text(&local_artifact_pom_path, &body)?;
body
} else {
// read local pom file
let mut file = File::open(local_artifact_pom_path)?;
let mut contents = String::new();
file.read_to_string(&mut contents)?;
contents
};
// parse pom file // parse pom file
let pom = Pom::from_str(&pom_xml).unwrap(); let pom = Pom::from_str(&pom_xml).unwrap();
@ -61,50 +47,91 @@ pub fn load_artifact(art: &Artifact) -> Result<(), Error> {
Ok(()) Ok(())
} }
fn load_verify_artifact_jar(art: &Artifact, artifact_path: &str, local_artifact_jar_path: &str) -> Result<(), Error> { fn download_verify_pom(art: &Artifact, artifact_path: String, local_artifact_loc: String) -> Result<String, Error> {
let remote_artifact_jar_url = format!("{}/{}/{}-{}.jar", get_config().maven_central, artifact_path, art.name, art.version); 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 {
read_file_to_string(local_artifact_pom_path)?
};
println!("Downloading {}", remote_artifact_jar_url); 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)?
} 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) }
}
/// 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);
println!("{} {}", "Downloading".green(), remote_artifact_jar_url);
let jar = reqwest::blocking::get(&remote_artifact_jar_url)?.bytes().unwrap(); let jar = reqwest::blocking::get(&remote_artifact_jar_url)?.bytes().unwrap();
println!("Downloaded {}", remote_artifact_jar_url); println!("{} {}", "Downloaded".green(), remote_artifact_jar_url);
write_bytes(&local_artifact_jar_path, jar.clone())?; 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
let checksum = hex::decode( let checksum = hex::decode(
if !exists(&local_artifact_jar_sha1_path) { if !exists(&local_artifact_jar_sha1_path) {
let remote_artifact_jar_sha1_url = format!("{}.sha1", remote_artifact_jar_url); download_checksum(&remote_artifact_jar_url, &local_artifact_jar_sha1_path)?
println!("{}", remote_artifact_jar_sha1_url);
let jar_checksum = reqwest::blocking::get(&remote_artifact_jar_sha1_url)?.bytes().unwrap();
write_bytes(&local_artifact_jar_sha1_path, jar_checksum.clone())?;
jar_checksum.to_vec()
} else { } else {
let mut file = File::open(local_artifact_jar_sha1_path)?; read_file_to_bytes(local_artifact_jar_sha1_path)?
let mut contents = Vec::new();
file.read_to_end(&mut contents)?;
contents
})?; })?;
let validated = validate_checksum_bytes(&jar, checksum);
let mut hasher = Sha1::new();
hasher.update(&jar.to_vec());
let result = hasher.finalize();
let validated = result[..] == checksum;
if !validated { if !validated {
Err(anyhow!("SHA1 checksum for {} is not valid", remote_artifact_jar_url)) Err(anyhow!("SHA1 checksum for {} is not valid", remote_artifact_jar_url))
} else { Ok(()) } } else { Ok(()) }
} }
fn download_checksum(remote_artifact_url: &String, local_artifact_jar_sha1_path: &String) -> Result<Vec<u8>, 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())
}
fn validate_checksum_bytes(jar: &Bytes, checksum: Vec<u8>) -> bool {
let mut hasher = Sha1::new();
hasher.update(&jar.to_vec());
let result = hasher.finalize();
result[..] == checksum
}
fn validate_checksum_text(text: &str, checksum: Vec<u8>) -> bool {
let mut hasher = Sha1::new();
hasher.update(text.as_bytes());
let result = hasher.finalize();
result[..] == checksum
}
fn exists(path: &str) -> bool { fn exists(path: &str) -> bool {
Path::new(path).exists() Path::new(path).exists()
} }
fn write_bytes(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)?;
Ok(()) Ok(())
} }
@ -113,3 +140,17 @@ fn write_text(path: &str, contents: &String) -> Result<(), Error> {
file.write(contents.as_bytes())?; file.write(contents.as_bytes())?;
Ok(()) Ok(())
} }
fn read_file_to_string(local_artifact_pom_path: &str) -> Result<String, Error> {
let mut file = File::open(local_artifact_pom_path)?;
let mut contents = String::new();
file.read_to_string(&mut contents)?;
Ok(contents)
}
fn read_file_to_bytes(local_artifact_jar_sha1_path: String) -> Result<Vec<u8>, Error> {
let mut file = File::open(local_artifact_jar_sha1_path)?;
let mut contents = Vec::new();
file.read_to_end(&mut contents)?;
Ok(contents)
}

View file

@ -1,4 +1,5 @@
use std::fs; use std::fs;
use std::path::Path;
use anyhow::{anyhow, Error}; use anyhow::{anyhow, Error};
use toml::{Table, Value}; use toml::{Table, Value};
@ -6,6 +7,7 @@ use toml::{Table, Value};
pub mod pom; pub mod pom;
mod config; mod config;
pub mod deploader; pub mod deploader;
pub mod compile;
#[derive(Debug)] #[derive(Debug)]
pub struct Project { pub struct Project {
@ -14,6 +16,7 @@ pub struct Project {
pub version: String, pub version: String,
pub dependencies: Vec<Artifact>, pub dependencies: Vec<Artifact>,
pub test_dependencies: Vec<Artifact>, pub test_dependencies: Vec<Artifact>,
pub project_root: String,
} }
#[derive(Debug)] #[derive(Debug)]
@ -51,19 +54,23 @@ impl Artifact {
} }
pub fn load_project(jargo_file: Option<&str>) -> Result<Project, Error> { pub fn load_project(jargo_file: Option<&str>) -> Result<Project, Error> {
let project_table = fs::read_to_string(jargo_file.unwrap_or("./Jargo.toml"))?.parse::<Table>()?; 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 package = project_table.get("package").expect("package info missing");
let dependencies = get_dependencies(project_table.get("dependencies"))?; let dependencies = get_dependencies(project_table.get("dependencies"))?;
let test_dependencies = get_dependencies(project_table.get("test-dependencies"))?; let test_dependencies = get_dependencies(project_table.get("test-dependencies"))?;
Ok(Project { Ok(Project {
group: package.get("group").unwrap().to_string(), group: strip_first_last(package.get("group").unwrap().to_string()),
name: package.get("name").unwrap().to_string(), name: strip_first_last(package.get("name").unwrap().to_string()),
version: package.get("version").unwrap().to_string(), version: strip_first_last(package.get("version").unwrap().to_string()),
dependencies, dependencies,
test_dependencies, test_dependencies,
project_root: jargo.parent().map(Path::to_str).unwrap().expect(&format!("projectroot {:?} not usable", jargo)).into(),
}) })
} }
@ -81,3 +88,7 @@ fn get_dependencies(table: Option<&Value>) -> Result<Vec<Artifact>, Error> {
} }
Ok(dependencies) Ok(dependencies)
} }
fn strip_first_last(text: String) -> String{
text[1..text.len()-1].into()
}

View file

@ -3,5 +3,6 @@ use anyhow::Error;
fn main() -> anyhow::Result<(), Error> { fn main() -> anyhow::Result<(), Error> {
let project = jargo::load_project(Some("tests/sample_project/Jargo.toml"))?; let project = jargo::load_project(Some("tests/sample_project/Jargo.toml"))?;
jargo::deploader::load_artifacts(&project.test_dependencies)?; jargo::deploader::load_artifacts(&project.test_dependencies)?;
jargo::compile::run(&project)?;
Ok(()) Ok(())
} }

View file

@ -0,0 +1,7 @@
package nl.sander.sample;
public class SampleMain {
public static void main(String[] args) {
System.out.println(Sample.getTheNumber());
}
}