Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1,297 changes: 839 additions & 458 deletions Cargo.lock

Large diffs are not rendered by default.

14 changes: 7 additions & 7 deletions Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,14 +1,14 @@
[package]
name = "ozy"
version = "0.1.14"
edition = "2021"
version = "0.2.0"
edition = "2024"

# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html

[dependencies]
anyhow = { version = "1.0", features = ["backtrace"] }
serde = "1.0"
serde_yaml = "0.9"
serde_yaml_ng = "0.10"
reqwest = { version = "0.12.8", features = ["blocking"] }
tempfile = "3"
zip-extract = "0.2.1"
Expand All @@ -18,12 +18,12 @@ clap = { version = "4.0.29", features = ["derive"] }
flate2 = "1.0.25"
tar = "0.4.38"
regex = "1.7.0"
bzip2 = "0.4.4"
bzip2 = "0.6.1"
xz2 = "0.1"
which = "6.0.3"
zip = "2.2.0"
which = "8.0.0"
zip = "^2"
openssl = { version = "0.10.48", features = ["vendored"] }
exec = "0.3.1"
semver = "1.0.16"
os_pipe = "1.1.2"
infer = "0.16.0"
infer = "0.19.0"
56 changes: 47 additions & 9 deletions src/app.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
use anyhow::{anyhow, Context, Error, Result};
use anyhow::{Context, Error, Result, anyhow};
use file_lock::{FileLock, FileOptions};

use crate::config::{apply_overrides, resolve};
Expand Down Expand Up @@ -32,13 +32,13 @@ pub struct App {
executable_path: String,
}

pub fn find_app(config: &serde_yaml::Mapping, app: &String) -> Result<App> {
pub fn find_app(config: &serde_yaml_ng::Mapping, app: &String) -> Result<App> {
App::new(app, config)
.with_context(|| format!("While attempting to find the app {} to run", app))
}

impl App {
pub fn new(name: &String, config: &serde_yaml::Mapping) -> Result<Self, Error> {
pub fn new(name: &String, config: &serde_yaml_ng::Mapping) -> Result<Self, Error> {
let app_configs = config
.get("apps")
.ok_or_else(|| anyhow!("Expected an apps section in the YAML"))?
Expand All @@ -52,27 +52,27 @@ impl App {
.unwrap()
.clone();

if let Some(serde_yaml::Value::String(template)) = app_config.get("template") {
if let Some(serde_yaml_ng::Value::String(template)) = app_config.get("template") {
let mut new_app_config = config["templates"][template].clone();
apply_overrides(&app_config, new_app_config.as_mapping_mut().unwrap());
app_config = new_app_config.as_mapping().unwrap().clone();
}

resolve(&mut app_config);
let version = match app_config.get("version") {
Some(serde_yaml::Value::String(version)) => version.clone(),
Some(serde_yaml_ng::Value::String(version)) => version.clone(),
_ => {
return Err(anyhow!("Expected a string version in config for {}", name));
}
};

let relocatable = match app_config.get("relocatable") {
Some(serde_yaml::Value::Bool(value)) => value.to_owned(),
Some(serde_yaml_ng::Value::Bool(value)) => value.to_owned(),
_ => true,
};

let app_type = match app_config.get("type") {
Some(serde_yaml::Value::String(app_type)) => match &app_type[..] {
Some(serde_yaml_ng::Value::String(app_type)) => match &app_type[..] {
"single_binary_zip" => AppType::SingleBinaryZip,
"tarball" => AppType::Tarball,
"shell_install" => AppType::Shell,
Expand Down Expand Up @@ -107,7 +107,7 @@ impl App {
};

let executable_path = match app_config.get("executable_path") {
Some(serde_yaml::Value::String(value)) => value,
Some(serde_yaml_ng::Value::String(value)) => value,
_ => name,
};

Expand Down Expand Up @@ -192,6 +192,44 @@ impl App {
.join(&self.name)
.join(&self.version))
}

pub fn versions(&self) -> Result<Vec<String>> {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this (and prune_other_versions) are unfortunately fighting an uphill battle with some history:

/home/amorton/.cache/ozy/meter:
total 8.0K
drwxrwxr-x  3 amorton amorton  135 Mar  4  2025 ./
drwxrwxr-x 46 amorton amorton 4.0K Oct 16 04:51 ../
lrwxrwxrwx  1 amorton amorton   71 Jan 28  2025 348 -> /home/amorton/.cache/ozy/meter/348.8861341d-5179-414d-95ed-06f385fe5bdc/
drwxrwxr-x 15 amorton amorton 4.0K Jan 28  2025 348.8861341d-5179-414d-95ed-06f385fe5bdc/
-rw-rw-r--  1 amorton amorton    0 Jan 28  2025 348.lock
lrwxrwxrwx  1 amorton amorton   51 Jan 28  2025 353 -> /home/amorton/.cache/ozy/internal_install/meter/353/
-rw-rw-r--  1 amorton amorton    0 Jan 28  2025 353.lock
lrwxrwxrwx  1 amorton amorton   51 Mar  4  2025 359 -> /home/amorton/.cache/ozy/internal_install/meter/359/
-rw-rw-r--  1 amorton amorton    0 Mar  4  2025 359.lock

internal_install wasn't always a thing; we used to install to <version>.<uuid> and symlink. I actually preferred that since it meant there was exactly one directory tree per app, but 🤷

I think the most robust thing to do is:

  • look for .lock files and remove the .lock suffix
  • those are your installed versions

to uninstall

  • lock .lock
  • check if is a symlink, and if it is contained within the ozy install path follow it
  • if is a symlink, remove that first
  • atomically rename directories to remove to something random (possibly into ~/.cache/ozy/to_delete/` so we can easily resume interrupted unlinks?)
  • recursively remove the directory

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Interesting...do you know why internal_install was created/favored over the uuid directory/symlink?

let mut result = vec![];
if self.is_installed()?
&& let Some(app_base) = self.get_install_path()?.parent() {
for entry in std::fs::read_dir(app_base)? {
if let Ok(entry) = entry
&& (entry.metadata()?.is_dir() || entry.metadata()?.is_symlink()) {
let installed_version =
String::from(entry.file_name().to_string_lossy());
result.push(installed_version);
}
}
}

Ok(result)
}

pub fn prune_other_versions(&self) -> Result<()> {
for installed_version in self.versions()? {
if installed_version != self.version {
println!("Pruning {} {}", self.name, installed_version);
let installed_internal_path = self
.get_internal_install_path()?
.with_file_name(&installed_version);
let installed_path = self.get_install_path()?.with_file_name(&installed_version);
delete_if_exists(installed_internal_path.as_path()).context(format!(
"While deleting {}@{}",
self.name, installed_version
))?;
delete_if_exists(installed_path.as_path()).context(format!(
"While deleting {}@{}",
self.name, installed_version
))?;
}
}
Ok(())
}
}

impl std::hash::Hash for App {
Expand Down Expand Up @@ -230,7 +268,7 @@ impl std::fmt::Display for App {

#[cfg(test)]
mod tests {
use serde_yaml::Mapping;
use serde_yaml_ng::Mapping;

use crate::config::parse_ozy_config;

Expand Down
22 changes: 11 additions & 11 deletions src/config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ use crate::files::get_ozy_dir;
use anyhow::{Context, Result};
use std::{collections::HashMap, time::SystemTime};

use serde_yaml::Mapping as Config;
use serde_yaml_ng::Mapping as Config;

pub fn config_mtime() -> Result<SystemTime> {
Ok(std::fs::metadata(get_ozy_dir()?.join("ozy.yaml"))?
Expand Down Expand Up @@ -32,26 +32,26 @@ pub fn load_config(base_config_filename_override: Option<&str>) -> Result<Config

pub fn parse_ozy_config(path: &std::path::PathBuf) -> Result<Config> {
let raw_config = std::fs::read_to_string(path).with_context(|| "Reading config YAML")?;
serde_yaml::from_str(&raw_config).with_context(|| "Deserializing config YAML")
serde_yaml_ng::from_str(&raw_config).with_context(|| "Deserializing config YAML")
}

pub fn save_ozy_user_conf(conf: &serde_yaml::Mapping) -> Result<()> {
pub fn save_ozy_user_conf(conf: &serde_yaml_ng::Mapping) -> Result<()> {
let path = get_ozy_dir()?.join("ozy.user.yaml");
let file = std::fs::File::create(path)?;
serde_yaml::to_writer(file, conf)?;
serde_yaml_ng::to_writer(file, conf)?;
Ok(())
}

pub fn get_ozy_user_conf() -> Result<serde_yaml::Mapping> {
pub fn get_ozy_user_conf() -> Result<serde_yaml_ng::Mapping> {
load_config(Some("ozy.user.yaml"))
}

pub fn apply_overrides(source: &serde_yaml::Mapping, dest: &mut serde_yaml::Mapping) {
pub fn apply_overrides(source: &serde_yaml_ng::Mapping, dest: &mut serde_yaml_ng::Mapping) {
for (key, value) in source.into_iter() {
match (value, dest.get_mut(key)) {
(
serde_yaml::Value::Mapping(src_child_mapping),
Some(serde_yaml::Value::Mapping(dst_child_mapping)),
serde_yaml_ng::Value::Mapping(src_child_mapping),
Some(serde_yaml_ng::Value::Mapping(dst_child_mapping)),
) => {
apply_overrides(src_child_mapping, dst_child_mapping);
}
Expand All @@ -62,7 +62,7 @@ pub fn apply_overrides(source: &serde_yaml::Mapping, dest: &mut serde_yaml::Mapp
}
}

fn get_candidate_substitutions(mapping: &serde_yaml::Mapping) -> HashMap<String, String> {
fn get_candidate_substitutions(mapping: &serde_yaml_ng::Mapping) -> HashMap<String, String> {
let mut variables: HashMap<String, String> = HashMap::new();

variables.insert(
Expand Down Expand Up @@ -93,7 +93,7 @@ fn get_candidate_substitutions(mapping: &serde_yaml::Mapping) -> HashMap<String,
variables
}

pub fn resolve(mapping: &mut serde_yaml::Mapping) {
pub fn resolve(mapping: &mut serde_yaml_ng::Mapping) {
let variables = get_candidate_substitutions(mapping);

let re = regex::Regex::new(r"\{.*?\}").unwrap();
Expand All @@ -118,6 +118,6 @@ pub fn resolve(mapping: &mut serde_yaml::Mapping) {
s = s.replace(&format!("{{{}}}", potential_variable), replacement.unwrap());
}

*value = serde_yaml::Value::String(s);
*value = serde_yaml_ng::Value::String(s);
}
}
17 changes: 8 additions & 9 deletions src/files.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,12 +9,12 @@ fn get_home_dir() -> Result<std::path::PathBuf, Error> {
}

pub fn delete_if_exists(path: &std::path::Path) -> Result<()> {
if path.exists() {
if path.is_dir() {
std::fs::remove_dir_all(path)?
} else {
std::fs::remove_file(path)?
}
if path.is_symlink() {
std::fs::remove_file(path)?
} else if path.is_dir() {
std::fs::remove_dir_all(path)?
} else if path.exists() {
std::fs::remove_file(path)?
}

Ok(())
Expand Down Expand Up @@ -51,11 +51,10 @@ pub fn check_path(ozy_bin_dir: &std::path::Path) -> Result<bool, Error> {
let bin_path_canonical = ozy_bin_dir.canonicalize()?;
if let Some(paths) = env::var_os("PATH") {
for path in env::split_paths(&paths) {
if let Ok(path) = path.canonicalize() {
if bin_path_canonical == path {
if let Ok(path) = path.canonicalize()
&& bin_path_canonical == path {
return Ok(true);
}
}
}
}
Ok(false)
Expand Down
14 changes: 7 additions & 7 deletions src/installers/conda.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
use super::installer::{run_subcommand_for_installer, Installer};
use super::installer::{Installer, run_subcommand_for_installer};
use crate::files::delete_if_exists;
use anyhow::{anyhow, Error, Result};
use anyhow::{Error, Result, anyhow};

use tempfile::tempdir;

Expand Down Expand Up @@ -53,28 +53,28 @@ pub fn conda_install(
}

impl Conda {
pub fn new(_: &str, version: &str, app_config: &serde_yaml::Mapping) -> Result<Self, Error> {
pub fn new(_: &str, version: &str, app_config: &serde_yaml_ng::Mapping) -> Result<Self, Error> {
let package = app_config["package"].as_str().unwrap().to_string();
let channels = match app_config.get("channels") {
// TODO: Probably a way to do this without unwrapping
Some(serde_yaml::Value::Sequence(seq)) => seq
Some(serde_yaml_ng::Value::Sequence(seq)) => seq
.iter()
.map(|x| x.as_str().unwrap().to_string())
.collect(),
_ => vec![],
};
let conda_bin = match app_config.get("conda_bin") {
Some(serde_yaml::Value::String(value)) => value.to_owned(),
Some(serde_yaml_ng::Value::String(value)) => value.to_owned(),
_ => String::from("conda"),
};

let pyinstaller = match app_config.get("pyinstaller") {
Some(serde_yaml::Value::Bool(value)) => value.to_owned(),
Some(serde_yaml_ng::Value::Bool(value)) => value.to_owned(),
_ => false,
};

let env = match app_config.get("env") {
Some(serde_yaml::Value::Mapping(env_map)) => env_map
Some(serde_yaml_ng::Value::Mapping(env_map)) => env_map
.iter()
.map(|(a, b)| {
(
Expand Down
6 changes: 3 additions & 3 deletions src/installers/file.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ use crate::utils::download_to;

use super::installer::Installer;

use anyhow::{anyhow, Error, Result};
use anyhow::{Error, Result, anyhow};
use std::os::unix::fs::PermissionsExt;

pub struct File {
Expand All @@ -15,10 +15,10 @@ impl File {
pub fn new(
name: &String,
version: &str,
app_config: &serde_yaml::Mapping,
app_config: &serde_yaml_ng::Mapping,
) -> Result<Self, Error> {
let url = match app_config.get("url") {
Some(serde_yaml::Value::String(url)) => url.clone(),
Some(serde_yaml_ng::Value::String(url)) => url.clone(),
_ => {
return Err(anyhow!("Expected a string url in config for {}", name));
}
Expand Down
16 changes: 10 additions & 6 deletions src/installers/pip.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
use super::installer::Installer;
use crate::installers::{conda::conda_install, installer::run_subcommand_for_installer};
use anyhow::{anyhow, Error, Result};
use anyhow::{Error, Result, anyhow};

pub struct Pip {
package: String,
Expand All @@ -11,19 +11,23 @@ pub struct Pip {
}

impl Pip {
pub fn new(_: &String, version: &str, app_config: &serde_yaml::Mapping) -> Result<Self, Error> {
pub fn new(
_: &String,
version: &str,
app_config: &serde_yaml_ng::Mapping,
) -> Result<Self, Error> {
let package = app_config["package"].as_str().unwrap().to_string();
let channels = match app_config.get("channels") {
// TODO: Probably a way to do this without unwrapping
Some(serde_yaml::Value::Sequence(seq)) => seq
Some(serde_yaml_ng::Value::Sequence(seq)) => seq
.iter()
.map(|x| x.as_str().unwrap().to_string())
.collect(),
_ => vec![],
};

let env = match app_config.get("env") {
Some(serde_yaml::Value::Mapping(env_map)) => env_map
Some(serde_yaml_ng::Value::Mapping(env_map)) => env_map
.iter()
.map(|(a, b)| {
(
Expand All @@ -36,8 +40,8 @@ impl Pip {
};

let python_version = match app_config.get("python_version") {
Some(serde_yaml::Value::String(v)) => Some(v.to_string()),
Some(serde_yaml::Value::Null) => None,
Some(serde_yaml_ng::Value::String(v)) => Some(v.to_string()),
Some(serde_yaml_ng::Value::Null) => None,
Some(_) => None,
None => None,
};
Expand Down
Loading