forked from Cutieguwu/raven
273 lines
7.4 KiB
Rust
273 lines
7.4 KiB
Rust
mod cli;
|
|
mod env;
|
|
mod fs;
|
|
mod io;
|
|
mod java;
|
|
mod nest;
|
|
|
|
use std::fs::OpenOptions;
|
|
use std::io::{Read, Write};
|
|
use std::path::{Path, PathBuf};
|
|
use std::sync::LazyLock;
|
|
|
|
use cli::{CONFIG, Command};
|
|
use java::{JAVA_EXT_CLASS, JAVA_EXT_SOURCE};
|
|
use nest::{Class, NEST, NestLock};
|
|
|
|
use anyhow::Context;
|
|
use bytesize::ByteSize;
|
|
|
|
pub static PROJECT_ROOT: LazyLock<PathBuf> = LazyLock::new(|| {
|
|
// Start from CWD
|
|
let cwd = std::env::current_dir().expect("Failed to get current working directory");
|
|
let mut probe = cwd.clone();
|
|
|
|
while !probe.join(F_NEST_TOML.as_path()).exists() {
|
|
if !probe.pop() {
|
|
// This is easier than having a Result everywhere. For now.
|
|
panic!(
|
|
"No {} found in current directory or any ancestor. (cwd: {})",
|
|
F_NEST_TOML.display(),
|
|
cwd.display()
|
|
)
|
|
}
|
|
}
|
|
|
|
probe
|
|
});
|
|
|
|
const DIR_TARGET: LazyLock<PathBuf> = LazyLock::new(|| PROJECT_ROOT.join("target/"));
|
|
const DIR_SRC: LazyLock<PathBuf> = LazyLock::new(|| PROJECT_ROOT.join("src/"));
|
|
const DIR_MAIN: LazyLock<PathBuf> = LazyLock::new(|| DIR_SRC.join("main/"));
|
|
const DIR_TEST: LazyLock<PathBuf> = LazyLock::new(|| DIR_SRC.join("test/"));
|
|
|
|
const F_NEST_TOML: LazyLock<PathBuf> = LazyLock::new(|| PathBuf::from("Nest.toml"));
|
|
const F_NEST_LOCK: LazyLock<PathBuf> = LazyLock::new(|| PathBuf::from("Nest.lock"));
|
|
|
|
fn main() -> anyhow::Result<()> {
|
|
// Ensure that Nest.toml exists, if the requested command depends upon it.
|
|
if CONFIG.command.depends_on_nest() && NEST.is_err() {
|
|
println!("No Nest.toml found in project directory");
|
|
println!("Aborting...");
|
|
return Ok(());
|
|
}
|
|
|
|
match &CONFIG.command {
|
|
Command::Init => init()?,
|
|
Command::New { project_name } => {
|
|
new(project_name.to_owned())?;
|
|
init()?;
|
|
}
|
|
Command::Build => {
|
|
build()?;
|
|
}
|
|
Command::Run {
|
|
entry_point,
|
|
assertions,
|
|
} => {
|
|
build()?;
|
|
run(entry_point, assertions.into())?;
|
|
}
|
|
Command::Test { assertions } => {
|
|
test(assertions.into())?;
|
|
}
|
|
Command::Clean => clean(),
|
|
}
|
|
|
|
println!("Done.");
|
|
|
|
Ok(())
|
|
}
|
|
|
|
fn init() -> anyhow::Result<()> {
|
|
let cwd = std::env::current_dir()?;
|
|
|
|
let is_empty = std::fs::read_dir(cwd.as_path()).is_ok_and(|tree| tree.count() == 0);
|
|
|
|
let project_name = cwd
|
|
.file_name()
|
|
.context("Invalid directory name")?
|
|
.to_str()
|
|
.context("Unable to convert OsStr to str")?
|
|
.to_owned();
|
|
|
|
// ORDER MATTERS. THIS MUST COME FIRST.
|
|
// Make config file.
|
|
if let Result::Ok(mut f) = OpenOptions::new()
|
|
.write(true)
|
|
.create_new(true)
|
|
.open(F_NEST_TOML.as_path())
|
|
{
|
|
f.write_all(
|
|
toml::to_string_pretty(&nest::Nest {
|
|
package: nest::Package {
|
|
name: project_name.to_owned(),
|
|
..Default::default()
|
|
},
|
|
})?
|
|
.as_bytes(),
|
|
)?;
|
|
}
|
|
|
|
// Make .java-version
|
|
if let Result::Ok(mut f) = OpenOptions::new()
|
|
.write(true)
|
|
.create_new(true)
|
|
.open(PathBuf::from(".java-version"))
|
|
{
|
|
f.write_all(
|
|
{
|
|
let mut version = crate::env::get_javac_ver()?.major.to_string();
|
|
version.push('\n');
|
|
version
|
|
}
|
|
.as_bytes(),
|
|
)?;
|
|
}
|
|
|
|
// Make src, target, tests
|
|
for dir in [DIR_SRC, DIR_MAIN, DIR_TARGET, DIR_TEST] {
|
|
std::fs::create_dir_all(dir.clone())?;
|
|
}
|
|
|
|
// Make src/main/Main.java
|
|
if let Result::Ok(mut f) = OpenOptions::new().write(true).create_new(is_empty).open(
|
|
DIR_MAIN
|
|
.clone()
|
|
.join("Main")
|
|
.with_extension(JAVA_EXT_SOURCE),
|
|
) {
|
|
f.write_all(include_bytes!("../assets/src/main/Main.java"))?;
|
|
}
|
|
|
|
// Make src/test/Test.java
|
|
if let Result::Ok(mut f) = OpenOptions::new().write(true).create_new(is_empty).open(
|
|
DIR_TEST
|
|
.clone()
|
|
.join("Test")
|
|
.with_extension(JAVA_EXT_SOURCE),
|
|
) {
|
|
f.write_all(include_bytes!("../assets/src/test/Test.java"))?;
|
|
}
|
|
|
|
// git init .
|
|
crate::io::run_process(&["git", "init", "."])?;
|
|
|
|
// Append to .gitignore
|
|
if let Result::Ok(mut f) = OpenOptions::new()
|
|
.append(true)
|
|
.create(true)
|
|
.read(true)
|
|
.open(".gitignore")
|
|
{
|
|
let mut buf = String::new();
|
|
f.read_to_string(&mut buf)?;
|
|
|
|
for ignored in [
|
|
DIR_TARGET.as_path().display().to_string(),
|
|
format!("*.{}", JAVA_EXT_CLASS),
|
|
] {
|
|
if !buf.contains(&ignored) {
|
|
f.write(format!("{}\n", ignored).as_bytes())?;
|
|
}
|
|
}
|
|
}
|
|
|
|
Ok(())
|
|
}
|
|
|
|
fn new(project_name: String) -> anyhow::Result<()> {
|
|
let cwd = std::env::current_dir()?.join(project_name);
|
|
|
|
std::fs::create_dir(&cwd)?;
|
|
std::env::set_current_dir(&cwd)?;
|
|
|
|
Ok(())
|
|
}
|
|
|
|
fn build() -> anyhow::Result<()> {
|
|
let mut targets: Vec<PathBuf> = crate::fs::expand_files(DIR_SRC.as_path())?
|
|
.into_iter()
|
|
.filter(|f| {
|
|
f.extension()
|
|
.is_some_and(|ext| ext.to_str().is_some_and(|ext| ext == JAVA_EXT_SOURCE))
|
|
})
|
|
.collect();
|
|
let mut nest_lock = NestLock::load().unwrap_or_default();
|
|
|
|
nest_lock.update();
|
|
|
|
let mut retained_targets = vec![];
|
|
for path in targets.into_iter() {
|
|
if let Option::Some((_path, class)) = nest_lock.0.get_key_value(&path)
|
|
&& class.is_updated()
|
|
{
|
|
continue;
|
|
}
|
|
|
|
retained_targets.push(path);
|
|
}
|
|
|
|
targets = retained_targets;
|
|
|
|
let javac = java::CompilerBuilder::new()
|
|
.class_path(DIR_TARGET.as_path())
|
|
.destination(DIR_TARGET.as_path())
|
|
.build();
|
|
|
|
for target in targets {
|
|
println!("Compiling {}", target.display());
|
|
if javac.clone().compile(dbg!(target.as_path())).is_ok()
|
|
&& let Result::Ok(class) = Class::try_from(target.clone())
|
|
{
|
|
nest_lock.0.insert(target, class);
|
|
}
|
|
}
|
|
|
|
OpenOptions::new()
|
|
.create(true)
|
|
.write(true)
|
|
.truncate(true)
|
|
.open(F_NEST_LOCK.as_path())?
|
|
.write_all(toml::to_string_pretty(&nest_lock)?.as_bytes())
|
|
.with_context(|| format!("Failed to write {}", F_NEST_LOCK.display()))
|
|
}
|
|
|
|
fn run<P: AsRef<Path>>(
|
|
entry_point: P,
|
|
assertions: bool,
|
|
) -> anyhow::Result<(Option<String>, Option<String>)> {
|
|
// JRE pathing will be messed up without this.
|
|
crate::env::set_cwd(DIR_TARGET.as_path())?;
|
|
|
|
java::JVMBuilder::new()
|
|
.assertions(assertions)
|
|
.monitor(true)
|
|
.build()
|
|
.run(entry_point)
|
|
}
|
|
|
|
fn test(assertions: bool) -> anyhow::Result<(Option<String>, Option<String>)> {
|
|
java::CompilerBuilder::new()
|
|
.class_path(DIR_TARGET.as_path())
|
|
.destination(DIR_TARGET.as_path())
|
|
.build()
|
|
.compile(DIR_TEST.as_path())?;
|
|
|
|
// Change cwd to avoid Java pathing issues.
|
|
crate::env::set_cwd(DIR_TARGET.as_path())?;
|
|
|
|
java::JVMBuilder::new()
|
|
.assertions(assertions)
|
|
.ram_min(ByteSize::mib(128))
|
|
.ram_max(ByteSize::mib(512))
|
|
.monitor(true)
|
|
.build()
|
|
.run(DIR_TARGET.as_path())
|
|
}
|
|
|
|
fn clean() {
|
|
let _ = std::fs::remove_file(PROJECT_ROOT.join(F_NEST_LOCK.as_path()));
|
|
let _ = std::fs::remove_dir_all(DIR_TARGET.join("/*").as_path());
|
|
}
|