Skip to content

Commit

Permalink
Merge branch 'main' into shell-configurations
Browse files Browse the repository at this point in the history
  • Loading branch information
sinrohit committed Oct 24, 2024
2 parents 960fd92 + d69c489 commit aa86f83
Show file tree
Hide file tree
Showing 12 changed files with 158 additions and 132 deletions.
5 changes: 2 additions & 3 deletions .envrc
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ source ./omnixrc
watch_file \
./omnixrc \
nix/modules/flake-parts/nixpkgs.nix \
nix/modules/flake-parts/om.nix \
nix/modules/flake-parts/rust.nix \
nix/modules/flake-parts/devshell.nix \
./crates/*/crate.nix \
Expand All @@ -11,6 +12,4 @@ watch_file \
crates/omnix-init/registry/flake.*

# Dogfood our own ./omnixrc!
# This may lead to compiling omnix if the commit hasn't yet been built in CI
# But let's live with that for now.
use omnix .
use omnix
18 changes: 14 additions & 4 deletions bacon.toml
Original file line number Diff line number Diff line change
Expand Up @@ -83,10 +83,6 @@ need_stdout = true
allow_warnings = true
background = true

# This parameterized job runs the example of your choice, as soon
# as the code compiles.
# Call it as
# bacon ex -- my-example
[jobs.health-failing]
command = [
"cargo",
Expand All @@ -100,6 +96,20 @@ command = [
need_stdout = true
allow_warnings = true

[jobs.hack]
command = [
"cargo",
"run",
"--color",
"always",
"--",
"hack",
".",
]
need_stdout = true
allow_warnings = true


# You may define here keybindings that would be specific to
# a project, for example a shortcut to launch a specific job.
# Shortcuts to internal functions (scrolling, toggling, etc.)
Expand Down
21 changes: 20 additions & 1 deletion crates/omnix-cli/src/command/hack.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,11 +8,30 @@ pub struct HackCommand {
/// Directory of the project
#[arg(name = "DIR", default_value = ".")]
dir: PathBuf,

/// The stage to run in. If not provided, runs all stages.
#[arg(long, value_enum)]
stage: Option<Stage>,
}

/// The stage to run in
#[derive(clap::ValueEnum, Debug, Clone)]
enum Stage {
/// Stage before Nix shell is invoked.
PreShell,

/// Stage after Nix shell is successfully invoked.
PostShell,
}

impl HackCommand {
pub async fn run(&self) -> anyhow::Result<()> {
omnix_hack::core::hack_on(&self.dir).await?;
let prj = omnix_hack::core::Project::new(&self.dir).await?;
match self.stage {
Some(Stage::PreShell) => omnix_hack::core::hack_on_pre_shell(&prj).await?,
Some(Stage::PostShell) => omnix_hack::core::hack_on_post_shell(&prj).await?,
None => omnix_hack::core::hack_on(&prj).await?,
}
Ok(())
}
}
4 changes: 2 additions & 2 deletions crates/omnix-cli/src/command/health.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
use clap::Parser;
use nix_rs::flake::url::FlakeUrl;
use omnix_health::{run_checks_with, NixHealth};
use omnix_health::{run_all_checks_with, NixHealth};

/// Display the health of your Nix dev environment
#[derive(Parser, Debug)]
Expand All @@ -21,7 +21,7 @@ impl HealthCommand {
println!("{}", NixHealth::schema()?);
return Ok(());
}
let checks = run_checks_with(self.flake_url.clone()).await?;
let checks = run_all_checks_with(self.flake_url.clone()).await?;
let exit_code = NixHealth::print_report_returning_exit_code(&checks).await?;
if exit_code != 0 {
std::process::exit(exit_code);
Expand Down
14 changes: 2 additions & 12 deletions crates/omnix-hack/src/config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,23 +7,13 @@ use crate::readme::Readme;

#[derive(Debug, Deserialize, Clone, Default)]
pub struct HackConfig {
pub cache: Option<CacheConfig>,
pub readme: Readme,
}

#[derive(Debug, Deserialize, Clone)]
pub struct CacheConfig {
pub cachix: CachixConfig,
}

#[derive(Debug, Deserialize, Clone)]
pub struct CachixConfig {
/// Name of the cachix cache (`https://<name>.cachix.org`)
pub name: String,
/// The read-only auth token to use if this is a private cache
///
/// If provided, will run `cachix authtoken <auth_token>`.
pub auth_token: Option<String>,
/// Cache substituter URL
pub url: String,
}

impl HackConfig {
Expand Down
71 changes: 56 additions & 15 deletions crates/omnix-hack/src/core.rs
Original file line number Diff line number Diff line change
@@ -1,27 +1,68 @@
use std::path::Path;
use anyhow::Context;
use std::path::{Path, PathBuf};

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

use crate::config::HackConfig;

pub async fn hack_on(dir: &Path) -> anyhow::Result<()> {
let dir = dir.canonicalize()?;
let here_flake: FlakeUrl = Into::<FlakeUrl>::into(dir.as_ref());
let cfg = HackConfig::from_flake(&here_flake).await?;
pub struct Project {
pub dir: PathBuf,
pub flake: FlakeUrl,
pub cfg: HackConfig,
}

// TODO: cachix check
impl Project {
pub async fn new(dir: &Path) -> anyhow::Result<Self> {
let dir = dir.canonicalize()?;
let flake: FlakeUrl = Into::<FlakeUrl>::into(dir.as_ref());
let cfg = HackConfig::from_flake(&flake).await?;
Ok(Self { dir, flake, cfg })
}
}

// Run `om health` foremost
// TODO: Run with --quiet, possibly using `tracing::subscriber::with_default` (it doesn't work for some reason)
let checks = omnix_health::run_checks_with(Some(here_flake)).await?;
let exit_code = omnix_health::NixHealth::print_report_returning_exit_code(&checks).await?;
if exit_code != 0 {
anyhow::bail!("Health checks failed");
pub async fn hack_on(prj: &Project) -> anyhow::Result<()> {
hack_on_pre_shell(prj).await?;
hack_on_post_shell(prj).await?;
Ok(())
}

pub async fn hack_on_pre_shell(prj: &Project) -> anyhow::Result<()> {
// Run relevant `om health` checks
tracing::info!("om hack: Running pre-shell checks");
let health = NixHealth::from_flake(&prj.flake).await?;
let nix_info = NixInfo::get()
.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,
];
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");
};
};
}
}
if !health.caches.required.is_empty() {
// TODO: Auto-resolve some problems; like running 'cachix use' automatically
};
Ok(())
}

pub async fn hack_on_post_shell(prj: &Project) -> anyhow::Result<()> {
eprintln!();
print_markdown(&dir, &cfg.readme.get_markdown()).await?;

print_markdown(&prj.dir, prj.cfg.readme.get_markdown()).await?;
Ok(())
}
36 changes: 3 additions & 33 deletions crates/omnix-hack/src/readme.rs
Original file line number Diff line number Diff line change
@@ -1,39 +1,11 @@
use serde::Deserialize;

// TODO(idea): What if we provide `om health` like checkmark for each item. Automatically check if the user is in Nix shell or direnv, and ✅ the title accordingly. If not, nudge them to do it.
const OM_SHELL: &str = r#"## Enter the Nix shell
We recommend that you setup nix-direnv (preferably using the convenient template is provided at <https://github.com/juspay/nixos-unified-template>). Then run the following in terminal to activate the Nix shell:
```sh-session
direnv allow
```
From this point, anytime you `cd` to this project directory, the Nix shell will be automatically activated.
"#;

const OM_IDE: &str = r#"## IDE or editor setup
>[!IMPORTANT] ❗Make sure you have setup `direnv` as stated above.
You can now launch your favourite editor or IDE from inside the Nix devshell. For VSCode in particular, consult <https://nixos.asia/en/vscode>.
"#;

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

/// The README to display at the end.
///
/// Placeholder parameters:
/// - `OM_SHELL`: Instructions to enter the Nix shell.
/// - `OM_IDE`: Instructions to setup the IDE.
#[derive(Debug, Deserialize, Clone)]
pub struct Readme(pub String);

Expand All @@ -44,10 +16,8 @@ impl Default for Readme {
}

impl Readme {
/// Get the Markdown string, after doing parameter replacements.
pub fn get_markdown(&self) -> String {
self.0
.replace("OM_SHELL", OM_SHELL)
.replace("OM_IDE", OM_IDE)
/// Get the Markdown string
pub fn get_markdown(&self) -> &str {
&self.0
}
}
50 changes: 15 additions & 35 deletions crates/omnix-health/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -23,33 +23,34 @@ use self::check::{
rosetta::Rosetta, shell::Shell, trusted_users::TrustedUsers,
};

/// Nix Health check information for user's install
/// Nix Health check of user's install
///
/// Each field represents an individual check which satisfies the [traits::Checkable] trait.
/// Each check field is expected to implement [traits::Checkable].
#[derive(Debug, Default, Serialize, Deserialize, Clone)]
#[serde(default, rename_all = "kebab-case")]
pub struct NixHealth {
pub nix_version: MinNixVersion,
pub flake_enabled: FlakeEnabled,
pub max_jobs: MaxJobs,
pub nix_version: MinNixVersion,
pub rosetta: Rosetta,
pub max_jobs: MaxJobs,
pub trusted_users: TrustedUsers,
pub caches: Caches,
pub direnv: Direnv,
pub shell: Shell,
}

/// Convert [NixHealth] into a generic [Vec] of checks
impl<'a> IntoIterator for &'a NixHealth {
type Item = &'a dyn traits::Checkable;
type IntoIter = std::vec::IntoIter<Self::Item>;

/// Return an iterator to iterate on the fields of [NixHealth]
fn into_iter(self) -> Self::IntoIter {
let items: Vec<Self::Item> = vec![
&self.nix_version,
&self.flake_enabled,
&self.max_jobs,
&self.nix_version,
&self.rosetta,
&self.max_jobs,
&self.trusted_users,
&self.caches,
&self.direnv,
Expand All @@ -74,7 +75,7 @@ impl NixHealth {

/// Run all checks and collect the results
#[instrument(skip_all)]
pub fn run_checks(
pub fn run_all_checks(
&self,
nix_info: &nix_rs::info::NixInfo,
flake_url: Option<FlakeUrl>,
Expand All @@ -86,32 +87,11 @@ impl NixHealth {

pub async fn print_report_returning_exit_code(checks: &[traits::Check]) -> anyhow::Result<i32> {
let mut res = AllChecksResult::new();
let pwd = std::env::current_dir()?;
let md = async |s: &str| render_markdown(&pwd, s).await;
for check in checks {
match &check.result {
traits::CheckResult::Green => {
tracing::info!("✅ {}", check.title.green().bold());
tracing::info!("{}", md(&check.info).await?.dimmed());
}
traits::CheckResult::Red { msg, suggestion } => {
res.register_failure(check.required);
let solution = md(&format!(
"**Problem**: {}\\\n**Fix**: {}\n",
msg, suggestion
))
.await?;
if check.required {
tracing::error!("❌ {}", md(&check.title).await?.red().bold());
tracing::error!("{}", md(&check.info).await?.dimmed());
tracing::error!("{}", solution);
} else {
tracing::warn!("🟧 {}", md(&check.title).await?.yellow().bold());
tracing::warn!("{}", md(&check.info).await?.dimmed());
tracing::warn!("{}", solution);
}
}
}
check.tracing_log().await?;
if !check.result.green() {
res.register_failure(check.required);
};
}
let code = res.report();
Ok(code)
Expand All @@ -122,8 +102,8 @@ impl NixHealth {
}
}

/// Run health checks, optionally using the given flake's configuration
pub async fn run_checks_with(flake_url: Option<FlakeUrl>) -> anyhow::Result<Vec<Check>> {
/// Run all health checks, optionally using the given flake's configuration
pub async fn run_all_checks_with(flake_url: Option<FlakeUrl>) -> anyhow::Result<Vec<Check>> {
let nix_info = NixInfo::get()
.await
.as_ref()
Expand All @@ -138,7 +118,7 @@ pub async fn run_checks_with(flake_url: Option<FlakeUrl>) -> anyhow::Result<Vec<

print_info_banner(flake_url.as_ref(), nix_info).await?;

let checks = health.run_checks(nix_info, flake_url);
let checks = health.run_all_checks(nix_info, flake_url);
Ok(checks)
}

Expand Down
Loading

0 comments on commit aa86f83

Please sign in to comment.