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"
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]]
name = "core-foundation"
version = "0.9.4"
@ -423,6 +433,7 @@ version = "0.1.0"
dependencies = [
"anyhow",
"bytes",
"colored",
"hex",
"home",
"reqwest",

View file

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

View file

@ -1,10 +1,8 @@
**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.
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:
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
a good design
3. uses TOML
a good design. _That said, some things are just good to keep using, such as the default project structure._
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.
@ -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?
_While that would make migration a no-brainer, it seems too ambitious based on what I've seen of the maven
codebase_
_While that would (in theory) make migration a no-brainer, it seems too ambitious based on what I've seen of the maven
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 fn get_config() -> &'static Config {
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(),
cache_location: format!("{}/jargo/repo", user_home.to_str().unwrap()).into(),//TODO make '.jargo'
user_home,
maven_central: "https://repo.maven.apache.org/maven2".into()
}

View file

@ -10,8 +10,9 @@ use sha1::{Digest, Sha1};
use strong_xml::XmlRead;
use crate::Artifact;
use crate::config::get_config;
use crate::config::config;
use crate::pom::model::Pom;
use colored::Colorize;
pub fn load_artifacts(artifacts: &[Artifact]) -> Result<(), Error> {
for art in artifacts {
@ -21,37 +22,22 @@ pub fn load_artifacts(artifacts: &[Artifact]) -> Result<(), Error> {
}
pub fn load_artifact(art: &Artifact) -> Result<(), Error> {
let artifact_path = format!("{}/{}/{}",
art.group.replace(".", "/"),
art.name,
art.version,
);
let local_artifact_loc = format!("{}/{}", get_config().cache_location, artifact_path);
let artifact_path = format!("{}/{}/{}", art.group.replace(".", "/"), art.name, art.version);
// check/create artifact directory
let local_artifact_loc = format!("{}/{}", config().cache_location, artifact_path);
if !exists(&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);
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);
let pom_xml = if !exists(&local_artifact_pom_path) {
// 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
};
// 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)?;
// parse pom file
let pom = Pom::from_str(&pom_xml).unwrap();
@ -61,50 +47,91 @@ pub fn load_artifact(art: &Artifact) -> Result<(), Error> {
Ok(())
}
fn load_verify_artifact_jar(art: &Artifact, artifact_path: &str, local_artifact_jar_path: &str) -> Result<(), Error> {
let remote_artifact_jar_url = format!("{}/{}/{}-{}.jar", get_config().maven_central, artifact_path, art.name, art.version);
fn download_verify_pom(art: &Artifact, artifact_path: String, local_artifact_loc: String) -> Result<String, Error> {
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();
println!("Downloaded {}", remote_artifact_jar_url);
write_bytes(&local_artifact_jar_path, jar.clone())?;
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);
// verify jarfile with SHA1 checksum
// verify jarfile with SHA1 checksum (which is hex encoded)
let checksum = hex::decode(
if !exists(&local_artifact_jar_sha1_path) {
let remote_artifact_jar_sha1_url = format!("{}.sha1", remote_artifact_jar_url);
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()
download_checksum(&remote_artifact_jar_url, &local_artifact_jar_sha1_path)?
} else {
let mut file = File::open(local_artifact_jar_sha1_path)?;
let mut contents = Vec::new();
file.read_to_end(&mut contents)?;
contents
read_file_to_bytes(local_artifact_jar_sha1_path)?
})?;
let mut hasher = Sha1::new();
hasher.update(&jar.to_vec());
let result = hasher.finalize();
let validated = result[..] == checksum;
let validated = validate_checksum_bytes(&jar, checksum);
if !validated {
Err(anyhow!("SHA1 checksum for {} is not valid", remote_artifact_jar_url))
} 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 {
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)?;
file.write_all(&bytes)?;
file.write_all(bytes)?;
Ok(())
}
@ -113,3 +140,17 @@ fn write_text(path: &str, contents: &String) -> Result<(), Error> {
file.write(contents.as_bytes())?;
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::path::Path;
use anyhow::{anyhow, Error};
use toml::{Table, Value};
@ -6,6 +7,7 @@ use toml::{Table, Value};
pub mod pom;
mod config;
pub mod deploader;
pub mod compile;
#[derive(Debug)]
pub struct Project {
@ -14,6 +16,7 @@ pub struct Project {
pub version: String,
pub dependencies: Vec<Artifact>,
pub test_dependencies: Vec<Artifact>,
pub project_root: String,
}
#[derive(Debug)]
@ -51,19 +54,23 @@ impl Artifact {
}
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 dependencies = get_dependencies(project_table.get("dependencies"))?;
let test_dependencies = get_dependencies(project_table.get("test-dependencies"))?;
Ok(Project {
group: package.get("group").unwrap().to_string(),
name: package.get("name").unwrap().to_string(),
version: package.get("version").unwrap().to_string(),
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(),
})
}
@ -81,3 +88,7 @@ fn get_dependencies(table: Option<&Value>) -> Result<Vec<Artifact>, Error> {
}
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> {
let project = jargo::load_project(Some("tests/sample_project/Jargo.toml"))?;
jargo::deploader::load_artifacts(&project.test_dependencies)?;
jargo::compile::run(&project)?;
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());
}
}