Compare commits

...

3 Commits

Author SHA1 Message Date
Cutieguwu
6ad82c3339 Update Cargo.lock 2026-02-16 20:41:34 -05:00
Cutieguwu
f0d22e6b79 Speed up brute force building with Prey.lock hashing and caching. 2026-02-16 20:41:31 -05:00
Cutieguwu
1009a84c06 Improve javac version fetching. 2026-02-16 20:40:36 -05:00
12 changed files with 211 additions and 132 deletions

4
Cargo.lock generated
View File

@@ -166,7 +166,7 @@ dependencies = [
[[package]] [[package]]
name = "core" name = "core"
version = "0.1.3" version = "0.1.4"
dependencies = [ dependencies = [
"anyhow", "anyhow",
"derive_more", "derive_more",
@@ -316,7 +316,7 @@ checksum = "a6cb138bb79a146c1bd460005623e142ef0181e3d0219cb493e02f7d08a35695"
[[package]] [[package]]
name = "java" name = "java"
version = "0.2.1" version = "0.2.2"
dependencies = [ dependencies = [
"bytesize", "bytesize",
"derive_more", "derive_more",

View File

@@ -1,6 +1,6 @@
[package] [package]
name = "core" name = "core"
version = "0.1.3" version = "0.1.4"
edition.workspace = true edition.workspace = true
license.workspace = true license.workspace = true

View File

@@ -1,35 +1,49 @@
use std::path::PathBuf; use std::path::{Path, PathBuf};
use java::{JAVA_EXT_CLASS, JAVA_EXT_SOURCE};
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use crate::{Error, Result};
/// Data struct /// Data struct
#[derive(Debug, Clone, Deserialize, Serialize, Hash, PartialEq, Eq, PartialOrd, Ord)] #[derive(Debug, Clone, Deserialize, Serialize, Hash, PartialEq, Eq, PartialOrd, Ord)]
pub struct Class { pub struct Class {
/// Path relative to PACKAGE/java, without file extension.
pub path: PathBuf, pub path: PathBuf,
pub checksum: String, pub checksum: String,
} }
impl Class { impl Class {
pub fn is_updated(&self) -> crate::Result<bool> { pub fn new<P: AsRef<Path>>(package_src_root: P, file_path: P) -> Result<Self> {
// If the path is local and that file has not been updated. let mut file_path = file_path.as_ref().to_path_buf();
Ok(self.checksum == sha256::try_digest(self.path.clone())?) if file_path.is_absolute() {
file_path = pathsub::sub_paths(file_path.as_path(), package_src_root.as_ref())
.ok_or(Error::MismatchedPackage)?;
} }
pub fn update(&mut self) -> crate::Result<()> { Ok(Self {
self.checksum = sha256::try_digest(self.path.clone())?; path: dbg!(file_path.with_extension("")),
checksum: sha256::try_digest(package_src_root.as_ref().join(file_path))?,
})
}
pub fn is_updated<P: AsRef<Path>>(&self, class_path: P) -> Result<bool> {
// If the path is local and that file has not been updated.
Ok(class_path
.as_ref()
.join(self.path.as_path())
.with_extension(JAVA_EXT_CLASS)
.exists()
&& self.checksum == sha256::try_digest(self.path.clone())?)
}
pub fn update<P: AsRef<Path>>(&mut self, package_src_root: P) -> Result<()> {
self.checksum = sha256::try_digest(dbg!(
package_src_root
.as_ref()
.join(self.path.as_path())
.with_extension(JAVA_EXT_SOURCE),
))?;
Ok(()) Ok(())
} }
} }
// TODO: Make it clearer that this is for general files,
// not nests.
impl TryFrom<PathBuf> for Class {
type Error = crate::Error;
fn try_from(value: PathBuf) -> Result<Self, Self::Error> {
Ok(Self {
path: value.clone(),
checksum: sha256::try_digest(value)?,
})
}
}

View File

@@ -1,6 +1,7 @@
use semver::Version; use semver::Version;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use crate::Result;
use crate::package::PackageHandler; use crate::package::PackageHandler;
/// Data struct /// Data struct
@@ -26,7 +27,7 @@ impl Dependency {
return self.name.clone(); return self.name.clone();
} }
pub fn is_updated(&self) -> crate::Result<bool> { pub fn is_updated(&self) -> Result<bool> {
// If the path is local and that file has not been updated. // If the path is local and that file has not been updated.
Ok(self.source.as_ref().is_some_and(|path| is_url(path)) Ok(self.source.as_ref().is_some_and(|path| is_url(path))
&& self.checksum == sha256::try_digest(self.source.as_ref().unwrap())?) && self.checksum == sha256::try_digest(self.source.as_ref().unwrap())?)

View File

@@ -4,6 +4,10 @@ pub type Result<T> = std::result::Result<T, Error>;
#[derive(Debug, From, Display)] #[derive(Debug, From, Display)]
pub enum Error { pub enum Error {
/// Attempted to replace a value in a hash collection,
/// but there was no prime present when one was expected.
AbsentPrimeHashingError,
#[from] #[from]
Io(std::io::Error), Io(std::io::Error),
@@ -12,6 +16,8 @@ pub enum Error {
MissingFileName, MissingFileName,
MismatchedPackage,
#[from] #[from]
TomlDeserialize(toml::de::Error), TomlDeserialize(toml::de::Error),

View File

@@ -29,16 +29,16 @@ impl Nest {
} }
} }
pub fn write<P: AsRef<Path>>(&self, path: P) -> crate::Result<()> { pub fn write<P: AsRef<Path>>(&self, project_root: P) -> crate::Result<()> {
let mut path = path.as_ref().to_path_buf(); let mut project_root = project_root.as_ref().to_path_buf();
if path.is_dir() { if project_root.is_dir() {
path = path.join(F_NEST_TOML); project_root = project_root.join(F_NEST_TOML);
} }
Ok(OpenOptions::new() Ok(OpenOptions::new()
.write(true) .write(true)
.create(true) .create(true)
.open(path)? .open(project_root)?
.write_all(toml::to_string_pretty(&self)?.as_bytes())?) .write_all(toml::to_string_pretty(&self)?.as_bytes())?)
} }

View File

@@ -1,30 +1,35 @@
use std::hash::Hash; use std::hash::Hash;
use std::path::{Path, PathBuf}; use std::path::{Path, PathBuf};
use fs::expand_files;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use crate::class::Class; use crate::class::Class;
use crate::prey::{F_PREY_LOCK, F_PREY_TOML, Prey, PreyLock}; use crate::prey::{F_PREY_LOCK, F_PREY_TOML, Prey, PreyLock};
pub const DIR_JAVA: &str = "java/";
/// Hashing is only based off the Prey. /// Hashing is only based off the Prey.
#[derive(Debug, Clone, PartialEq, Eq)] #[derive(Debug, Clone, PartialEq, Eq)]
pub struct PackageHandler { pub struct PackageHandler {
prey: Prey, prey: Prey,
prey_lock: Option<PreyLock>, prey_lock: PreyLock,
/// Path relative to WORKSPACE/src
package_root: PathBuf, package_root: PathBuf,
target_dir: PathBuf, target_dir: PathBuf,
} }
impl PackageHandler { impl PackageHandler {
const DIR_JAVA: &str = "java/";
pub fn new<P: AsRef<Path>>(src_dir: P, package_root: P, target_dir: P) -> crate::Result<Self> { pub fn new<P: AsRef<Path>>(src_dir: P, package_root: P, target_dir: P) -> crate::Result<Self> {
let package_root = src_dir.as_ref().join(package_root.as_ref()); let package_root = src_dir.as_ref().join(package_root.as_ref());
let prey_lock = if let Ok(prey_lock) = PreyLock::try_from(package_root.join(F_PREY_LOCK)) {
prey_lock
} else {
PreyLock::new(package_root.clone(), package_root.join(DIR_JAVA))?
};
Ok(Self { Ok(Self {
prey: Prey::try_from(package_root.join(F_PREY_TOML))?, prey: Prey::try_from(package_root.join(F_PREY_TOML))?,
prey_lock: PreyLock::try_from(package_root.join(F_PREY_LOCK)).ok(), prey_lock,
package_root, package_root,
target_dir: target_dir.as_ref().to_path_buf(), target_dir: target_dir.as_ref().to_path_buf(),
}) })
@@ -42,32 +47,26 @@ impl PackageHandler {
self.prey.version() self.prey.version()
} }
pub fn get_update_targets(&mut self) -> crate::Result<Vec<&Class>> { pub fn prey_lock(&mut self) -> &mut PreyLock {
if self.prey_lock.is_none() { &mut self.prey_lock
// Try to pass a reference to the class so that there's mutability of the object
// available at the workspace level instead of parsing all the way down the
// tree. How I do this, idk. My brain is friend from a few days of JS.
self.prey_lock = Some(PreyLock::from(expand_files(
self.package_root.join(Self::DIR_JAVA),
)?));
return Ok(self.prey_lock.as_ref().unwrap().classes.iter().collect());
} }
Ok(self pub fn get_outdated<P: AsRef<Path>>(&self, class_path: P) -> Vec<Class> {
.prey_lock self.prey_lock
.as_ref() .clone()
.unwrap() .classes()
.classes
.iter() .iter()
.filter_map(|tracked| { .filter(|class| {
if tracked.is_updated().is_ok_and(|v| v == true) { class
Some(tracked) .is_updated(class_path.as_ref())
} else { .is_ok_and(|b| b == false)
None
}
}) })
.collect()) .cloned()
.collect()
}
pub fn write_lock(&self) -> crate::Result<()> {
self.prey_lock.write(self.package_root.as_path())
} }
} }

View File

@@ -1,7 +1,9 @@
use std::collections::HashSet;
use std::fs::{File, OpenOptions}; use std::fs::{File, OpenOptions};
use std::io::Read; use std::io::{Read, Write};
use std::path::{Path, PathBuf}; use std::path::{Path, PathBuf};
use fs::expand_files;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use crate::class::Class; use crate::class::Class;
@@ -68,14 +70,54 @@ impl TryFrom<File> for Prey {
/// Data struct /// Data struct
#[derive(Debug, Clone, Default, Deserialize, Serialize, PartialEq, Eq)] #[derive(Debug, Clone, Default, Deserialize, Serialize, PartialEq, Eq)]
pub struct PreyLock { pub struct PreyLock {
pub classes: Vec<Class>, classes: HashSet<Class>,
} }
impl PreyLock { impl PreyLock {
pub fn new<P: AsRef<Path>>(package_root: P, package_src_root: P) -> crate::Result<Self> {
Ok(Self::from_paths(
expand_files(package_root)?,
package_src_root,
))
}
pub fn from_paths<P: AsRef<Path>>(paths: Vec<PathBuf>, package_src_root: P) -> Self {
let mut lock = Self::default();
lock.classes = paths
.iter()
.filter_map(|f| {
let dep = Class::new(package_src_root.as_ref().to_path_buf(), f.to_owned());
if dep.is_ok() {
Some(dep.unwrap())
} else {
None
}
})
.collect();
lock
}
pub fn write<P: AsRef<Path>>(&self, package_root: P) -> crate::Result<()> {
let mut package_root = package_root.as_ref().to_path_buf();
if package_root.is_dir() {
package_root = package_root.join(F_PREY_LOCK);
}
Ok(OpenOptions::new()
.write(true)
.create(true)
.open(package_root)?
.write_all(toml::to_string_pretty(&self)?.as_bytes())?)
}
pub fn with_class(&mut self, class: Class) -> &mut Self { pub fn with_class(&mut self, class: Class) -> &mut Self {
self.classes.push(class); self.classes.insert(class);
self self
} }
pub fn classes(&mut self) -> &mut HashSet<Class> {
&mut self.classes
}
} }
/// Load the PreyLock from Prey.lock file. /// Load the PreyLock from Prey.lock file.
@@ -97,21 +139,3 @@ impl TryFrom<File> for PreyLock {
Ok(toml::from_str(buf.as_str())?) Ok(toml::from_str(buf.as_str())?)
} }
} }
impl From<Vec<PathBuf>> for PreyLock {
fn from(value: Vec<PathBuf>) -> Self {
let mut lock = Self::default();
lock.classes = value
.iter()
.filter_map(|f| {
let dep = Class::try_from(f.to_owned());
if dep.is_ok() {
Some(dep.unwrap())
} else {
None
}
})
.collect();
lock
}
}

View File

@@ -9,10 +9,10 @@ use java::{JAVA_EXT_CLASS, JAVA_EXT_SOURCE};
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use subprocess::Exec; use subprocess::Exec;
use crate::Error;
use crate::nest::{F_NEST_LOCK, F_NEST_TOML, Nest, NestLock}; use crate::nest::{F_NEST_LOCK, F_NEST_TOML, Nest, NestLock};
use crate::package::PackageHandler; use crate::package::PackageHandler;
use crate::prey::{F_PREY_TOML, Prey}; use crate::prey::{F_PREY_LOCK, F_PREY_TOML, Prey};
use crate::{Error, package};
#[derive(Debug)] #[derive(Debug)]
pub struct WorkspaceHandler { pub struct WorkspaceHandler {
@@ -57,13 +57,15 @@ impl WorkspaceHandler {
Ok(workspace_manager) Ok(workspace_manager)
} }
pub fn write(&self) -> crate::Result<()> { pub fn write_locks(&self) -> crate::Result<()> {
self.write_nest()?;
if let Option::Some(lock) = self.nest_lock.clone() { if let Option::Some(lock) = self.nest_lock.clone() {
lock.write(self.project_root.join(F_NEST_LOCK))?; lock.write(self.project_root.join(F_NEST_LOCK))?;
} }
for handler in self.packages.values() {
handler.write_lock()?;
}
Ok(()) Ok(())
} }
@@ -114,24 +116,38 @@ impl WorkspaceHandler {
// This is the naive build // This is the naive build
pub fn build(&mut self) -> crate::Result<&mut Self> { pub fn build(&mut self) -> crate::Result<&mut Self> {
let mut targets = vec![];
for handler in self.packages.values_mut() {
targets.append(&mut handler.get_update_targets()?);
}
let compiler = java::compiler::CompilerBuilder::new() let compiler = java::compiler::CompilerBuilder::new()
.class_path(Self::DIR_TARGET) .class_path(Self::DIR_TARGET)
.destination(Self::DIR_TARGET) .destination(Self::DIR_TARGET)
.build(); .build();
for target in targets.iter() { // No unintentional deep copying anywhere, right?
// Possibly come up with a source file handler for this? // Probably not. We'll see.
if let Ok(_) = compiler.clone().compile(target.path.as_path()) { for handler in self.packages.values_mut() {
// TODO: Prevent unnecessary recompile let package_src_root = self
//target.update()?; .project_root
.join(Self::DIR_SRC)
.join(handler.name())
.join(package::DIR_JAVA);
let targets = handler.get_outdated(self.project_root.join(Self::DIR_TARGET));
for mut target in targets {
dbg!(&target);
if let Ok(true) = compiler
.clone()
.compile(package_src_root.join(target.path.as_path()))
{
target.update(package_src_root.as_path())?;
handler
.prey_lock()
.classes()
.replace(target)
.ok_or(Error::AbsentPrimeHashingError)?;
} }
} }
}
self.write_locks()?;
Ok(self) Ok(self)
} }
@@ -172,12 +188,30 @@ impl WorkspaceHandler {
} }
pub fn clean(&mut self) -> crate::Result<&mut Self> { pub fn clean(&mut self) -> crate::Result<&mut Self> {
// Clear Nest.lock
if let Err(err) = std::fs::remove_file(self.project_root.join(F_NEST_LOCK)) { if let Err(err) = std::fs::remove_file(self.project_root.join(F_NEST_LOCK)) {
if err.kind() != std::io::ErrorKind::NotFound { if err.kind() != std::io::ErrorKind::NotFound {
return Err(Error::from(err)); return Err(Error::from(err));
} }
} }
// Clear src/**/Prey.lock
for handler in self.packages.values() {
if let Err(err) = std::fs::remove_file(
self.project_root
.join(Self::DIR_SRC)
.join(handler.name())
.join(F_PREY_LOCK),
) {
if err.kind() != std::io::ErrorKind::NotFound {
return Err(Error::from(err));
}
}
}
// Clear target/
let _ = std::fs::remove_dir_all(Self::DIR_TARGET); let _ = std::fs::remove_dir_all(Self::DIR_TARGET);
Ok(self) Ok(self)
} }
@@ -256,7 +290,7 @@ impl WorkspaceHandler {
.create_new(true) .create_new(true)
.open(java::F_JAVA_VERSION) .open(java::F_JAVA_VERSION)
{ {
f.write_all(format!("{}\n", java::get_javac_ver()?.major.to_string()).as_bytes())?; f.write_all(format!("{}\n", java::get_javac_version()?.major.to_string()).as_bytes())?;
} }
Ok(()) Ok(())

View File

@@ -3,7 +3,7 @@
[package] [package]
name = "java" name = "java"
version = "0.2.1" version = "0.2.2"
edition.workspace = true edition.workspace = true
license.workspace = true license.workspace = true

View File

@@ -1,9 +1,11 @@
use std::fmt::Display;
use std::path::{Path, PathBuf}; use std::path::{Path, PathBuf};
use subprocess::Exec; use subprocess::Exec;
use subprocess::Redirection; use subprocess::Redirection;
use crate::JAVA_BIN_COMPILER; use crate::JAVA_BIN_COMPILER;
use crate::JAVA_EXT_SOURCE;
use crate::Result; use crate::Result;
#[derive(Debug, Default, Clone)] #[derive(Debug, Default, Clone)]
@@ -48,26 +50,6 @@ pub struct Compiler {
} }
impl Compiler { impl Compiler {
/*
pub fn compile<P: AsRef<Path>>(self, path: P) -> Result<(Option<String>, Option<String>)> {
let mut cmd: Vec<String> = vec![JAVA_BIN_COMPILER.to_string()];
cmd.extend(
self.flags
.clone()
.into_iter()
.flat_map(|f| Into::<Vec<String>>::into(f)),
);
cmd.extend(
fs::expand_files(path)?
.into_iter()
.filter_map(|f| Some(f.to_str()?.to_string())),
);
Ok(io::run_process(cmd.as_slice())?)
}
*/
pub fn compile<P: AsRef<Path>>(self, path: P) -> Result<bool> { pub fn compile<P: AsRef<Path>>(self, path: P) -> Result<bool> {
Ok(Exec::cmd(JAVA_BIN_COMPILER) Ok(Exec::cmd(JAVA_BIN_COMPILER)
.args( .args(
@@ -76,7 +58,7 @@ impl Compiler {
.into_iter() .into_iter()
.flat_map(|f| Into::<Vec<String>>::into(f)), .flat_map(|f| Into::<Vec<String>>::into(f)),
) )
.arg(path.as_ref()) .arg(path.as_ref().with_extension(JAVA_EXT_SOURCE))
.stdout(Redirection::Pipe) .stdout(Redirection::Pipe)
.detached() .detached()
.start()? .start()?
@@ -89,6 +71,7 @@ impl Compiler {
pub enum CompilerFlag { pub enum CompilerFlag {
Classpath { path: PathBuf }, Classpath { path: PathBuf },
Destination { path: PathBuf }, Destination { path: PathBuf },
Version,
} }
impl Into<Vec<String>> for CompilerFlag { impl Into<Vec<String>> for CompilerFlag {
@@ -96,6 +79,18 @@ impl Into<Vec<String>> for CompilerFlag {
match self { match self {
Self::Classpath { path } => vec!["-classpath".to_string(), path.display().to_string()], Self::Classpath { path } => vec!["-classpath".to_string(), path.display().to_string()],
Self::Destination { path } => vec!["-d".to_string(), path.display().to_string()], Self::Destination { path } => vec!["-d".to_string(), path.display().to_string()],
Self::Version => vec!["--version".to_string()],
}
}
}
// TODO: Clean this up a bit?
impl Display for CompilerFlag {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Self::Classpath { path } => write!(f, "-classpath {}", path.display()),
Self::Destination { path } => write!(f, "-d {}", path.display()),
Self::Version => write!(f, "--version"),
} }
} }
} }

View File

@@ -8,6 +8,8 @@ pub use error::{Error, Result};
use runtime::VMFlag; use runtime::VMFlag;
use subprocess::Exec; use subprocess::Exec;
use crate::compiler::CompilerFlag;
pub const JAVA_BIN_VM: &str = "java"; pub const JAVA_BIN_VM: &str = "java";
pub const JAVA_BIN_COMPILER: &str = "javac"; pub const JAVA_BIN_COMPILER: &str = "javac";
@@ -16,24 +18,22 @@ pub const JAVA_EXT_CLASS: &str = "class";
pub const F_JAVA_VERSION: &str = ".java-version"; pub const F_JAVA_VERSION: &str = ".java-version";
/// Uses the java binary to parse its stdout for version information. /// Uses the javac binary to get information about the install compiler.
/// pub fn get_javac_version() -> Result<semver::Version> {
/// This is non-caching. // Try to pull from javac first.
pub fn get_javac_ver() -> Result<semver::Version> { if let Ok(version) = semver::Version::from_str(
// TODO: Consider making this pull the version info from javac instead? Exec::cmd(JAVA_BIN_COMPILER)
.arg(CompilerFlag::Version.to_string())
/* .capture()?
* $ java --version .stdout_str()
* openjdk 21.0.9 2025-10-21 .replace("javac", "")
* OpenJDK Runtime Environment (build 21.0.9+10) .trim(),
* OpenJDK 64-Bit Server VM (build 21.0.9+10, mixed mode, sharing) ) {
*/ return Ok(version);
}
Ok(semver::Version::from_str( Ok(semver::Version::from_str(
get_java_version_info()? get_java_version_info()?
.lines()
.nth(0)
.ok_or(Error::EmptyStdout)?
.split_ascii_whitespace() .split_ascii_whitespace()
.nth(1) .nth(1)
.ok_or(Error::NthOutOfBounds)?, .ok_or(Error::NthOutOfBounds)?,
@@ -42,8 +42,14 @@ pub fn get_javac_ver() -> Result<semver::Version> {
/// Calls the java binary, returning the complete stdout version information. /// Calls the java binary, returning the complete stdout version information.
fn get_java_version_info() -> Result<String> { fn get_java_version_info() -> Result<String> {
/*
* $ java --version
* openjdk 21.0.9 2025-10-21
* OpenJDK Runtime Environment (build 21.0.9+10)
* OpenJDK 64-Bit Server VM (build 21.0.9+10, mixed mode, sharing)
*/
Ok(Exec::cmd(JAVA_BIN_VM) Ok(Exec::cmd(JAVA_BIN_VM)
.arg(VMFlag::Version.to_string().as_str()) .arg(VMFlag::Version.to_string())
.capture()? .capture()?
.stdout_str()) .stdout_str())
} }