Skip to content

Commit

Permalink
om hack: Auto cachix use missing cachix caches (#330)
Browse files Browse the repository at this point in the history
  • Loading branch information
srid authored Oct 24, 2024
1 parent d69c489 commit 95b3666
Show file tree
Hide file tree
Showing 7 changed files with 98 additions and 25 deletions.
1 change: 1 addition & 0 deletions crates/omnix-cli/crate.nix
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ in
# > error: don't yet have a `targetPackages.darwin.LibsystemCross for x86_64-apple-darwin`
(if (stdenv.isDarwin && stdenv.isAarch64) then pkgsStatic.libiconv else pkgs.libiconv)
pkgs.pkg-config
pkgs.cachix
];
buildInputs = lib.optionals pkgs.stdenv.isDarwin
(
Expand Down
4 changes: 2 additions & 2 deletions crates/omnix-hack/crate.nix
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
{ flake
, pkgs
{ pkgs
, lib
, rust-project
, ...
Expand All @@ -15,6 +14,7 @@
IOKit
]
);
nativeBuildInputs = [ pkgs.cachix ];
inherit (rust-project.crates."nix_rs".crane.args)
DEFAULT_FLAKE_SCHEMAS
INSPECT_FLAKE
Expand Down
58 changes: 44 additions & 14 deletions crates/omnix-hack/src/core.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ use std::path::{Path, PathBuf};

use nix_rs::{flake::url::FlakeUrl, info::NixInfo};
use omnix_common::markdown::print_markdown;
use omnix_health::{traits::Checkable, NixHealth};
use omnix_health::{check::caches::CachixCache, traits::Checkable, NixHealth};

use crate::config::HackConfig;

Expand Down Expand Up @@ -36,28 +36,40 @@ pub async fn hack_on_pre_shell(prj: &Project) -> anyhow::Result<()> {
.await
.as_ref()
.with_context(|| "Unable to gather nix info")?;
let relevant_checks: Vec<&'_ dyn Checkable> = vec![
&health.nix_version,
&health.rosetta,
&health.max_jobs,
// TODO: Run this only when a cache is configured
&health.trusted_users,
&health.caches,
];

let mut relevant_checks: Vec<&'_ dyn Checkable> =
vec![&health.nix_version, &health.rosetta, &health.max_jobs];
if !health.caches.required.is_empty() {
relevant_checks.push(&health.trusted_users);
};

// Run cache related checks, and try to resolve it automatically using `cachix use` as appropriate
if !health.caches.required.is_empty() {
let missing = health.caches.get_missing_caches(nix_info);
let (missing_cachix, missing_other) = parse_many(&missing, CachixCache::from_url);
for cachix_cache in &missing_cachix {
tracing::info!("🐦 Running `cachix use` for {}", cachix_cache.0);
cachix_cache.cachix_use().await?;
}
if !missing_other.is_empty() {
// We cannot add these caches automatically, so defer to `om health`
relevant_checks.push(&health.caches);
};
// TODO: Re-calculate NixInfo since our nix.conf has changed (due to `cachix use`)
// To better implement this, we need a mutable database of NixInfo, NixConfig, etc. OnceCell is not sufficient
};

for check_kind in relevant_checks.into_iter() {
for check in check_kind.check(nix_info, Some(&prj.flake)) {
if !check.result.green() {
check.tracing_log().await?;
if !check.result.green() && check.required {
tracing::error!("ERROR: Your Nix invironment is not properly setup. Run `om health` for details.");
anyhow::bail!("Cannot proceed");
anyhow::bail!("ERROR: Your Nix invironment is not properly setup. See suggestions above, or run `om health` for details.");
};
};
}
}
if !health.caches.required.is_empty() {
// TODO: Auto-resolve some problems; like running 'cachix use' automatically
};

Ok(())
}

Expand All @@ -66,3 +78,21 @@ pub async fn hack_on_post_shell(prj: &Project) -> anyhow::Result<()> {
print_markdown(&prj.dir, prj.cfg.readme.get_markdown()).await?;
Ok(())
}

/// Parse all items using the given parse function
fn parse_many<'a, T, Q, F>(vec: &'a [T], f: F) -> (Vec<Q>, Vec<&'a T>)
where
F: Fn(&T) -> Option<Q>,
{
let mut successes: Vec<Q> = Vec::new();
let mut failures: Vec<&'a T> = Vec::new();

for item in vec {
match f(item) {
Some(transformed) => successes.push(transformed),
None => failures.push(item),
}
}

(successes, failures)
}
1 change: 1 addition & 0 deletions crates/omnix-hack/src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
#![feature(let_chains)]
pub mod config;
pub mod core;
pub mod readme;
2 changes: 1 addition & 1 deletion crates/omnix-hack/src/readme.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ use serde::Deserialize;

const DEFAULT: &str = r#"🍾 Welcome to the project
*(Want to add more instructions here? Add them to the `om.hack.default.readme` field in your `flake.nix` file)*
*(Want to show custom instructions here? Add them to the `om.hack.default.readme` field in your `flake.nix` file)*
"#;

/// The README to display at the end.
Expand Down
52 changes: 45 additions & 7 deletions crates/omnix-health/src/check/caches.rs
Original file line number Diff line number Diff line change
Expand Up @@ -26,12 +26,7 @@ impl Checkable for Caches {
nix_info: &info::NixInfo,
_: Option<&nix_rs::flake::url::FlakeUrl>,
) -> Vec<Check> {
let val = &nix_info.nix_config.substituters.value;
let missing_caches = self
.required
.iter()
.filter(|required_cache| !val.contains(required_cache))
.collect::<Vec<_>>();
let missing_caches = self.get_missing_caches(nix_info);
let result = if missing_caches.is_empty() {
CheckResult::Green
} else {
Expand All @@ -54,7 +49,11 @@ impl Checkable for Caches {
title: "Nix Caches in use".to_string(),
info: format!(
"substituters = {}",
val.iter()
nix_info
.nix_config
.substituters
.value
.iter()
.map(|url| url.to_string())
.collect::<Vec<_>>()
.join(" ")
Expand All @@ -65,3 +64,42 @@ impl Checkable for Caches {
vec![check]
}
}

impl Caches {
/// Get subset of required caches not already in use
pub fn get_missing_caches(&self, nix_info: &info::NixInfo) -> Vec<Url> {
let val = &nix_info.nix_config.substituters.value;
self.required
.iter()
.filter(|required_cache| !val.contains(required_cache))
.cloned()
.collect()
}
}

pub struct CachixCache(pub String);

impl CachixCache {
/// Parse the https URL into a CachixCache
pub fn from_url(url: &Url) -> Option<Self> {
// Parse https://foo.cachix.org into CachixCache("foo")
// If domain is not cachix.org, return None.
let host = url.host_str()?;
if host.ends_with(".cachix.org") {
Some(CachixCache(host.split('.').next()?.to_string()))
} else {
None
}
}

/// Run `cachix use` for this cache
pub async fn cachix_use(&self) -> anyhow::Result<()> {
let mut cmd = tokio::process::Command::new("cachix");
cmd.arg("use").arg(&self.0);
let status = cmd.spawn()?.wait().await?;
if !status.success() {
anyhow::bail!("Failed to run `cachix use {}`", self.0);
}
Ok(())
}
}
5 changes: 4 additions & 1 deletion doc/src/om/hack.md
Original file line number Diff line number Diff line change
@@ -1,4 +1,7 @@
# Hack

> [!TODO]
> `om hack` is a work in progress
> `om hack` is a work in progress, but checkout [Emanote's `.envrc`](https://github.com/srid/emanote/blob/master/.envrc) to see what is available so far.
- Tight `direnv` integration
- Health check; `cachix use`

0 comments on commit 95b3666

Please sign in to comment.