use super::{BloatDir, EnforcePolicy, PackageManager, dir_size};
use anyhow::{Result, anyhow};
use std::fs;
use std::path::Path;
const MANIFEST: &str = "build.sbt";
pub struct Sbt;
impl PackageManager for Sbt {
fn name(&self) -> &'static str {
"sbt"
}
fn detect(&self, path: &Path) -> bool {
path.join(MANIFEST).is_file()
}
fn bloat_dirs(&self, path: &Path) -> Vec<BloatDir> {
let mut dirs = Vec::new();
let target = path.join("target");
if target.is_dir() {
dirs.push(BloatDir {
name: "target".to_string(),
size_bytes: dir_size(&target),
path: target,
shared_bytes: 0,
});
}
let project_target = path.join("project").join("target");
if project_target.is_dir() {
dirs.push(BloatDir {
name: "project/target".to_string(),
size_bytes: dir_size(&project_target),
path: project_target,
shared_bytes: 0,
});
}
dirs
}
fn enforce_lockfile(&self, path: &Path, _policy: EnforcePolicy) -> Result<()> {
let manifest = path.join(MANIFEST);
let content = fs::read_to_string(&manifest).map_err(|e| {
anyhow!("`{MANIFEST}` could not be read ({e}): nothing to rebuild the targets from.")
})?;
if content.contains(":=") {
return Ok(());
}
let properties = path.join("project").join("build.properties");
if fs::read_to_string(&properties)
.map(|p| p.contains("sbt.version"))
.unwrap_or(false)
{
return Ok(());
}
Err(anyhow!(
"`{MANIFEST}` assigns no settings and `project/build.properties` pins no \
`sbt.version`: refusing to treat the build trees as regenerable from it."
))
}
fn restore(&self, _path: &Path, _timeout: std::time::Duration) -> Result<()> {
println!("sbt's build trees regenerate on the next `sbt compile`");
Ok(())
}
fn lockfiles(&self) -> &'static [&'static str] {
&[MANIFEST]
}
fn opt_in(&self) -> bool {
true
}
}
#[cfg(test)]
mod tests {
use super::*;
use std::path::PathBuf;
use tempfile::tempdir;
fn project(dir: &Path) -> PathBuf {
fs::write(
dir.join(MANIFEST),
"name := \"demo\"\n\nscalaVersion := \"3.4.2\"\n",
)
.unwrap();
dir.to_path_buf()
}
fn claimed(project: &Path) -> Vec<String> {
Sbt.bloat_dirs(project)
.into_iter()
.map(|b| b.name)
.collect()
}
#[test]
fn detects_on_the_build_definition() {
let dir = tempdir().unwrap();
assert!(!Sbt.detect(dir.path()));
project(dir.path());
assert!(Sbt.detect(dir.path()));
}
#[test]
fn both_target_trees_are_claimed_and_nothing_else() {
let dir = tempdir().unwrap();
let root = project(dir.path());
fs::create_dir(root.join("target")).unwrap();
fs::create_dir_all(root.join("project").join("target")).unwrap();
fs::create_dir(root.join("src")).unwrap();
assert_eq!(claimed(&root), vec!["target", "project/target"]);
}
#[test]
fn a_settingless_root_passes_when_build_properties_pins_sbt() {
let dir = tempdir().unwrap();
fs::write(dir.path().join(MANIFEST), "// aggregation root\n").unwrap();
fs::create_dir(dir.path().join("project")).unwrap();
fs::write(
dir.path().join("project").join("build.properties"),
"sbt.version=1.10.1\n",
)
.unwrap();
assert!(
Sbt.enforce_lockfile(dir.path(), EnforcePolicy::default())
.is_ok()
);
}
#[test]
fn a_missing_or_bogus_build_definition_is_refused() {
let dir = tempdir().unwrap();
let policy = EnforcePolicy::default();
assert!(Sbt.enforce_lockfile(dir.path(), policy).is_err());
fs::write(dir.path().join(MANIFEST), "// nothing assigned\n").unwrap();
assert!(Sbt.enforce_lockfile(dir.path(), policy).is_err());
project(dir.path());
assert!(Sbt.enforce_lockfile(dir.path(), policy).is_ok());
}
#[test]
fn sbt_is_opt_in() {
assert!(Sbt.opt_in());
}
}