From 313b7098d6a5338eb529fa35a923a2466c0fda65 Mon Sep 17 00:00:00 2001 From: Sridhar Ratnakumar Date: Fri, 5 Sep 2025 20:40:59 -0400 Subject: [PATCH 1/4] `om ci`: Remove args (always work on current dir) --- Cargo.lock | 450 --------------------- crates/omnix-ci/Cargo.toml | 4 - crates/omnix-ci/src/command/core.rs | 16 +- crates/omnix-ci/src/command/gh_matrix.rs | 9 +- crates/omnix-ci/src/command/run.rs | 23 +- crates/omnix-ci/src/command/run_remote.rs | 12 +- crates/omnix-ci/src/flake_ref.rs | 93 ----- crates/omnix-ci/src/github/mod.rs | 1 - crates/omnix-ci/src/github/pull_request.rs | 124 ------ crates/omnix-ci/src/lib.rs | 1 - doc/history.md | 6 + doc/om/ci.md | 24 +- 12 files changed, 29 insertions(+), 734 deletions(-) delete mode 100644 crates/omnix-ci/src/flake_ref.rs delete mode 100644 crates/omnix-ci/src/github/pull_request.rs diff --git a/Cargo.lock b/Cargo.lock index d761eedb..acd47f2e 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -583,16 +583,6 @@ dependencies = [ "windows-sys 0.52.0", ] -[[package]] -name = "core-foundation" -version = "0.9.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "91e195e091a93c46f7102ec7818a2aa394e1e1771c3ab4825963fa03e45afb8f" -dependencies = [ - "core-foundation-sys", - "libc", -] - [[package]] name = "core-foundation-sys" version = "0.8.6" @@ -790,15 +780,6 @@ version = "0.3.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a357d28ed41a50f9c765dbfe56cbc04a64e53e5fc58ba79fbc34c10ef3df831f" -[[package]] -name = "encoding_rs" -version = "0.8.34" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b45de904aa0b010bce2ab45264d0631681847fa7b6f2eaa7dab7619943bc4f59" -dependencies = [ - "cfg-if", -] - [[package]] name = "equivalent" version = "1.0.1" @@ -934,21 +915,6 @@ dependencies = [ "ttf-parser", ] -[[package]] -name = "foreign-types" -version = "0.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f6f339eb8adc052cd2ca78910fda869aefa38d22d5cb648e6485e4d3fc06f3b1" -dependencies = [ - "foreign-types-shared", -] - -[[package]] -name = "foreign-types-shared" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "00b0228411908ca8685dba7fc2cdd70ec9990a6e753e89b6ac91a84c40fbaf4b" - [[package]] name = "form_urlencoded" version = "1.2.1" @@ -958,15 +924,6 @@ dependencies = [ "percent-encoding", ] -[[package]] -name = "futures-channel" -version = "0.3.29" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ff4dd66668b557604244583e3e1e1eada8c5c2e96a6d0d6653ede395b78bbacb" -dependencies = [ - "futures-core", -] - [[package]] name = "futures-core" version = "0.3.31" @@ -992,33 +949,6 @@ dependencies = [ "pin-project-lite", ] -[[package]] -name = "futures-sink" -version = "0.3.31" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e575fab7d1e0dcb8d0c7bcf9a63ee213816ab51902e6d244a95819acacf1d4f7" - -[[package]] -name = "futures-task" -version = "0.3.31" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f90f7dce0722e95104fcb095585910c0977252f286e354b5e3bd38902cd99988" - -[[package]] -name = "futures-util" -version = "0.3.31" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9fa08315bb612088cc391249efdc3bc77536f16c91f6cf495e6fbe85b20a4a81" -dependencies = [ - "futures-core", - "futures-io", - "futures-task", - "memchr", - "pin-project-lite", - "pin-utils", - "slab", -] - [[package]] name = "fuzzy-matcher" version = "0.3.7" @@ -1099,25 +1029,6 @@ dependencies = [ "walkdir", ] -[[package]] -name = "h2" -version = "0.3.26" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "81fe527a889e1532da5c525686d96d4c2e74cdd345badf8dfef9f6b39dd5f5e8" -dependencies = [ - "bytes", - "fnv", - "futures-core", - "futures-sink", - "futures-util", - "http", - "indexmap 2.1.0", - "slab", - "tokio", - "tokio-util", - "tracing", -] - [[package]] name = "half" version = "2.4.1" @@ -1173,40 +1084,6 @@ dependencies = [ "windows-sys 0.48.0", ] -[[package]] -name = "http" -version = "0.2.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8947b1a6fad4393052c7ba1f4cd97bed3e953a95c79c92ad9b051a04611d9fbb" -dependencies = [ - "bytes", - "fnv", - "itoa", -] - -[[package]] -name = "http-body" -version = "0.4.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7ceab25649e9960c0311ea418d17bee82c0dcec1bd053b5f9a66e265a693bed2" -dependencies = [ - "bytes", - "http", - "pin-project-lite", -] - -[[package]] -name = "httparse" -version = "1.9.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0fcc0b4a115bf80b728eb8ea024ad5bd707b615bfed49e0665b6e0f86fd082d9" - -[[package]] -name = "httpdate" -version = "1.0.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "df3b46402a9d5adb4c86a0cf463f42e19994e3ee891101b1841f30a545cb49a9" - [[package]] name = "human-panic" version = "1.2.2" @@ -1223,43 +1100,6 @@ dependencies = [ "uuid", ] -[[package]] -name = "hyper" -version = "0.14.30" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a152ddd61dfaec7273fe8419ab357f33aee0d914c5f4efbf0d96fa749eea5ec9" -dependencies = [ - "bytes", - "futures-channel", - "futures-core", - "futures-util", - "h2", - "http", - "http-body", - "httparse", - "httpdate", - "itoa", - "pin-project-lite", - "socket2", - "tokio", - "tower-service", - "tracing", - "want", -] - -[[package]] -name = "hyper-tls" -version = "0.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d6183ddfa99b85da61a140bea0efc93fdf56ceaa041b37d553518030827f9905" -dependencies = [ - "bytes", - "hyper", - "native-tls", - "tokio", - "tokio-native-tls", -] - [[package]] name = "iana-time-zone" version = "0.1.58" @@ -1539,12 +1379,6 @@ dependencies = [ "syn 2.0.53", ] -[[package]] -name = "ipnet" -version = "2.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8f518f335dce6725a761382244631d86cf0ccb2863413590b31338feb467f9c3" - [[package]] name = "is_proc_translated" version = "0.1.1" @@ -1763,23 +1597,6 @@ dependencies = [ "windows-sys 0.48.0", ] -[[package]] -name = "native-tls" -version = "0.2.12" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a8614eb2c83d59d1c8cc974dd3f920198647674a0a035e1af1fa58707e317466" -dependencies = [ - "libc", - "log", - "openssl", - "openssl-probe", - "openssl-sys", - "schannel", - "security-framework", - "security-framework-sys", - "tempfile", -] - [[package]] name = "new_debug_unreachable" version = "1.0.6" @@ -1961,7 +1778,6 @@ dependencies = [ "nonempty", "omnix-common", "omnix-health", - "reqwest", "serde", "serde_json", "shell-words", @@ -1970,9 +1786,6 @@ dependencies = [ "tokio", "tracing", "tracing-subscriber", - "try-guard", - "url", - "urlencoding", ] [[package]] @@ -2123,50 +1936,6 @@ dependencies = [ "pkg-config", ] -[[package]] -name = "openssl" -version = "0.10.72" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fedfea7d58a1f73118430a55da6a286e7b044961736ce96a16a17068ea25e5da" -dependencies = [ - "bitflags 2.4.1", - "cfg-if", - "foreign-types", - "libc", - "once_cell", - "openssl-macros", - "openssl-sys", -] - -[[package]] -name = "openssl-macros" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a948666b637a0f465e8564c73e89d4dde00d72d4d473cc972f390fc3dcee7d9c" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.53", -] - -[[package]] -name = "openssl-probe" -version = "0.1.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ff011a302c396a5197692431fc1948019154afc178baf7d8e37367442a4601cf" - -[[package]] -name = "openssl-sys" -version = "0.9.107" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8288979acd84749c744a9014b4382d42b8f7b2592847b5afb2ed29e5d16ede07" -dependencies = [ - "cc", - "libc", - "pkg-config", - "vcpkg", -] - [[package]] name = "os_info" version = "3.7.0" @@ -2248,12 +2017,6 @@ version = "0.2.13" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8afb450f006bf6385ca15ef45d71d2288452bc3683ce2e2cacc0d18e4be60b58" -[[package]] -name = "pin-utils" -version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" - [[package]] name = "piper" version = "0.2.1" @@ -2603,46 +2366,6 @@ version = "0.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c08c74e62047bb2de4ff487b251e4a92e24f48745648451635cec7d591162d9f" -[[package]] -name = "reqwest" -version = "0.11.27" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dd67538700a17451e7cba03ac727fb961abb7607553461627b97de0b89cf4a62" -dependencies = [ - "base64 0.21.5", - "bytes", - "encoding_rs", - "futures-core", - "futures-util", - "h2", - "http", - "http-body", - "hyper", - "hyper-tls", - "ipnet", - "js-sys", - "log", - "mime", - "native-tls", - "once_cell", - "percent-encoding", - "pin-project-lite", - "rustls-pemfile", - "serde", - "serde_json", - "serde_urlencoded", - "sync_wrapper", - "system-configuration", - "tokio", - "tokio-native-tls", - "tower-service", - "url", - "wasm-bindgen", - "wasm-bindgen-futures", - "web-sys", - "winreg", -] - [[package]] name = "resvg" version = "0.43.0" @@ -2722,15 +2445,6 @@ dependencies = [ "windows-sys 0.52.0", ] -[[package]] -name = "rustls-pemfile" -version = "1.0.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1c74cae0a4cf6ccbbf5f359f08efdf8ee7e1dc532573bf0db71968cb56b1448c" -dependencies = [ - "base64 0.21.5", -] - [[package]] name = "rustybuzz" version = "0.18.0" @@ -2764,44 +2478,12 @@ dependencies = [ "winapi-util", ] -[[package]] -name = "schannel" -version = "0.1.23" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fbc91545643bcf3a0bbb6569265615222618bdf33ce4ffbbd13c4bbd4c093534" -dependencies = [ - "windows-sys 0.52.0", -] - [[package]] name = "scopeguard" version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" -[[package]] -name = "security-framework" -version = "2.10.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "770452e37cad93e0a50d5abc3990d2bc351c36d0328f86cefec2f2fb206eaef6" -dependencies = [ - "bitflags 1.3.2", - "core-foundation", - "core-foundation-sys", - "libc", - "security-framework-sys", -] - -[[package]] -name = "security-framework-sys" -version = "2.11.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "75da29fe9b9b08fe9d6b22b5b4bcbc75d8db3aa31e639aa56bb62e9d46bfceaf" -dependencies = [ - "core-foundation-sys", - "libc", -] - [[package]] name = "semver" version = "1.0.22" @@ -2873,18 +2555,6 @@ dependencies = [ "serde", ] -[[package]] -name = "serde_urlencoded" -version = "0.7.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d3491c14715ca2294c4d6a88f15e84739788c1d030eed8c110436aafdaa2f3fd" -dependencies = [ - "form_urlencoded", - "itoa", - "ryu", - "serde", -] - [[package]] name = "serde_with" version = "3.4.0" @@ -3002,15 +2672,6 @@ version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "56199f7ddabf13fe5074ce809e7d3f42b42ae711800501b5b16ea82ad029c39d" -[[package]] -name = "slab" -version = "0.4.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8f92a496fb766b417c996b9c5e57daf2f7ad3b0bebe1ccfca4856390e3d3bb67" -dependencies = [ - "autocfg", -] - [[package]] name = "slotmap" version = "1.0.7" @@ -3104,12 +2765,6 @@ dependencies = [ "unicode-ident", ] -[[package]] -name = "sync_wrapper" -version = "0.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2047c6ded9c721764247e62cd3b03c09ffc529b2ba5b10ec482ae507a4a70160" - [[package]] name = "synstructure" version = "0.13.1" @@ -3159,27 +2814,6 @@ dependencies = [ "winapi", ] -[[package]] -name = "system-configuration" -version = "0.5.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ba3a3adc5c275d719af8cb4272ea1c4a6d668a777f37e115f6d11ddbc1c8e0e7" -dependencies = [ - "bitflags 1.3.2", - "core-foundation", - "system-configuration-sys", -] - -[[package]] -name = "system-configuration-sys" -version = "0.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a75fb188eb626b924683e3b95e3a48e63551fcfb51949de2f06a9d91dbee93c9" -dependencies = [ - "core-foundation-sys", - "libc", -] - [[package]] name = "system-deps" version = "6.2.2" @@ -3415,29 +3049,6 @@ dependencies = [ "syn 2.0.53", ] -[[package]] -name = "tokio-native-tls" -version = "0.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bbae76ab933c85776efabc971569dd6119c580d8f5d448769dec1764bf796ef2" -dependencies = [ - "native-tls", - "tokio", -] - -[[package]] -name = "tokio-util" -version = "0.7.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9cf6b47b3771c49ac75ad09a6162f53ad4b8088b76ac60e8ec1455b31a189fe1" -dependencies = [ - "bytes", - "futures-core", - "futures-sink", - "pin-project-lite", - "tokio", -] - [[package]] name = "toml" version = "0.5.11" @@ -3481,12 +3092,6 @@ dependencies = [ "winnow", ] -[[package]] -name = "tower-service" -version = "0.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b6bc1c9ce2b5135ac7f93c72918fc37feb872bdc6a5533a8b85eb4b86bfdae52" - [[package]] name = "tracing" version = "0.1.40" @@ -3548,18 +3153,6 @@ dependencies = [ "tracing-log", ] -[[package]] -name = "try-guard" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "940e09aaaaa89325d14f724cf8867458c86b8b992f7529c33ef1dae4e12a7afa" - -[[package]] -name = "try-lock" -version = "0.2.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e421abadd41a4225275504ea4d6566923418b7f05506fbc9c0fe86ba7396114b" - [[package]] name = "ttf-parser" version = "0.24.1" @@ -3656,12 +3249,6 @@ dependencies = [ "serde", ] -[[package]] -name = "urlencoding" -version = "2.1.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "daf8dba3b7eb870caf1ddeed7bc9d2a049f3cfdfae7cb521b087cc33ae4c49da" - [[package]] name = "usvg" version = "0.43.0" @@ -3733,12 +3320,6 @@ version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "830b7e5d4d90034032940e4ace0d9a9a057e7a45cd94e6c007832e39edb82f6d" -[[package]] -name = "vcpkg" -version = "0.2.15" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426" - [[package]] name = "version-compare" version = "0.2.0" @@ -3770,15 +3351,6 @@ dependencies = [ "winapi-util", ] -[[package]] -name = "want" -version = "0.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bfa7760aed19e106de2c7c0b581b509f2f25d3dacaf737cb82ac61bc6d760b0e" -dependencies = [ - "try-lock", -] - [[package]] name = "wasi" version = "0.11.0+wasi-snapshot-preview1" @@ -3816,18 +3388,6 @@ dependencies = [ "wasm-bindgen-shared", ] -[[package]] -name = "wasm-bindgen-futures" -version = "0.4.39" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ac36a15a220124ac510204aec1c3e5db8a22ab06fd6706d881dc6149f8ed9a12" -dependencies = [ - "cfg-if", - "js-sys", - "wasm-bindgen", - "web-sys", -] - [[package]] name = "wasm-bindgen-macro" version = "0.2.89" @@ -4084,16 +3644,6 @@ dependencies = [ "memchr", ] -[[package]] -name = "winreg" -version = "0.50.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "524e57b2c537c0f9b1e69f1965311ec12182b4122e45035b1508cd24d2adadb1" -dependencies = [ - "cfg-if", - "windows-sys 0.48.0", -] - [[package]] name = "write16" version = "1.0.0" diff --git a/crates/omnix-ci/Cargo.toml b/crates/omnix-ci/Cargo.toml index 86323c4c..75c59347 100644 --- a/crates/omnix-ci/Cargo.toml +++ b/crates/omnix-ci/Cargo.toml @@ -26,7 +26,6 @@ omnix-health = { workspace = true } nix_rs = { workspace = true, features = ["clap"] } nonempty = { workspace = true } omnix-common = { workspace = true } -reqwest = { workspace = true } serde = { workspace = true } serde_json = { workspace = true } shell-words = { workspace = true } @@ -35,6 +34,3 @@ thiserror = { workspace = true } tokio = { workspace = true } tracing = { workspace = true } tracing-subscriber = { workspace = true } -try-guard = { workspace = true } -url = { workspace = true } -urlencoding = { workspace = true } diff --git a/crates/omnix-ci/src/command/core.rs b/crates/omnix-ci/src/command/core.rs index fec327f8..23b861e3 100644 --- a/crates/omnix-ci/src/command/core.rs +++ b/crates/omnix-ci/src/command/core.rs @@ -5,14 +5,14 @@ use nix_rs::command::NixCmd; use omnix_common::config::OmConfig; use tracing::instrument; -use crate::flake_ref::FlakeRef; +use nix_rs::flake::url::FlakeUrl; use super::{gh_matrix::GHMatrixCommand, run::RunCommand}; /// Top-level commands for `om ci` #[derive(Debug, Subcommand, Clone)] pub enum Command { - /// Run all CI steps for all or given subflakes + /// Run all CI steps for current directory flake Run(RunCommand), /// Print the Github Actions matrix configuration as JSON @@ -31,7 +31,7 @@ impl Command { #[instrument(name = "run", skip(self))] pub async fn run(self) -> anyhow::Result<()> { tracing::info!("{}", "\nšŸ‘Ÿ Reading om.ci config from flake".bold()); - let url = self.get_flake_ref().to_flake_url().await?; + let url = self.get_flake_ref(); let cfg = OmConfig::get(self.nixcmd(), &url).await?; tracing::debug!("OmConfig: {cfg:?}"); @@ -48,12 +48,10 @@ impl Command { } } - /// Get the [FlakeRef] associated with this subcommand - fn get_flake_ref(&self) -> &FlakeRef { - match self { - Command::Run(cmd) => &cmd.flake_ref, - Command::DumpGithubActionsMatrix(cmd) => &cmd.flake_ref, - } + /// Get the [FlakeUrl] associated with this subcommand + fn get_flake_ref(&self) -> FlakeUrl { + // Always use current directory + FlakeUrl(".".to_string()) } /// Convert this type back to the user-facing command line arguments diff --git a/crates/omnix-ci/src/command/gh_matrix.rs b/crates/omnix-ci/src/command/gh_matrix.rs index 26f470e5..810e89a6 100644 --- a/crates/omnix-ci/src/command/gh_matrix.rs +++ b/crates/omnix-ci/src/command/gh_matrix.rs @@ -3,18 +3,11 @@ use clap::Parser; use nix_rs::{command::NixCmd, flake::system::System}; use omnix_common::config::OmConfig; -use crate::{config::subflakes::SubflakesConfig, flake_ref::FlakeRef, github}; +use crate::{config::subflakes::SubflakesConfig, github}; /// Command to generate a Github Actions matrix #[derive(Parser, Debug, Clone)] pub struct GHMatrixCommand { - /// Flake URL or github URL - /// - /// A specific omnix-ci configuration can be specified - /// using '#': e.g. `om ci run .#extra-tests` - #[arg(default_value = ".")] - pub flake_ref: FlakeRef, - /// Systems to include in the matrix #[arg(long, value_parser, value_delimiter = ',')] pub systems: Vec, diff --git a/crates/omnix-ci/src/command/run.rs b/crates/omnix-ci/src/command/run.rs index bd6c1315..ef90b116 100644 --- a/crates/omnix-ci/src/command/run.rs +++ b/crates/omnix-ci/src/command/run.rs @@ -22,13 +22,13 @@ use omnix_health::{traits::Checkable, NixHealth}; use serde::{Deserialize, Serialize}; use crate::{ - config::subflakes::SubflakesConfig, flake_ref::FlakeRef, github::actions::in_github_log_group, + config::subflakes::SubflakesConfig, github::actions::in_github_log_group, step::core::StepsResult, }; use super::run_remote; -/// Run all CI steps for all or given subflakes +/// Run all CI steps for current directory flake /// Command to run all CI steps #[derive(Parser, Debug, Clone)] pub struct RunCommand { @@ -61,13 +61,6 @@ pub struct RunCommand { #[arg(long)] no_link: bool, - /// Flake URL or github URL - /// - /// A specific configuration can be specified - /// using '#': e.g. `om ci run .#default.extra-tests` - #[arg(default_value = ".")] - pub flake_ref: FlakeRef, - /// Print Github Actions log groups (enabled by default when run in Github Actions) #[clap(long, default_value_t = env::var("GITHUB_ACTION").is_ok())] pub github_output: bool, @@ -97,11 +90,10 @@ impl RunCommand { } } - /// Override the `flake_ref` and `out_link`` for building locally. - pub fn local_with(&self, flake_ref: FlakeRef, out_link: Option) -> Self { + /// Override the `out_link` for building locally. + pub fn local_with(&self, out_link: Option) -> Self { let mut new = self.clone(); new.on = None; // Disable remote building - new.flake_ref = flake_ref; new.no_link = out_link.is_none(); new.out_link = out_link; new @@ -140,10 +132,7 @@ impl RunCommand { .await?; // Then, do the CI steps - tracing::info!( - "{}", - format!("\nšŸ¤– Running CI for {}", self.flake_ref).bold() - ); + tracing::info!("{}", "\nšŸ¤– Running CI for current directory".bold()); let res = ci_run(&self.nixcmd, self, &cfg, &nix_info.nix_config).await?; let msg = in_github_log_group::, _, _>( @@ -218,8 +207,6 @@ impl RunCommand { args.push("--no-link".to_string()); } - args.push(self.flake_ref.to_string()); - args.extend(self.steps_args.to_cli_args()); args diff --git a/crates/omnix-ci/src/command/run_remote.rs b/crates/omnix-ci/src/command/run_remote.rs index c68d2175..b1dff0fe 100644 --- a/crates/omnix-ci/src/command/run_remote.rs +++ b/crates/omnix-ci/src/command/run_remote.rs @@ -36,7 +36,7 @@ pub async fn run_on_remote_store( format!("\nšŸ›œ Running CI remotely on {} ({:?})", ssh_uri, opts).bold() ); - let (flake_closure, local_flake_url) = &cache_flake(nixcmd, cfg, opts.copy_inputs).await?; + let (flake_closure, _local_flake_url) = &cache_flake(nixcmd, cfg, opts.copy_inputs).await?; let omnix_source = PathBuf::from(OMNIX_SOURCE); let paths_to_push = vec![omnix_source, flake_closure.clone()]; @@ -59,9 +59,7 @@ pub async fn run_on_remote_store( // Then, SSH and run the same `om ci run` CLI but without the `--on` argument but with `--out-link` pointing to the temporary location. run_ssh( &ssh_uri.to_string(), - &om_cli_with( - run_cmd.local_with(local_flake_url.clone().into(), Some(om_json_path.clone())), - ), + &om_cli_with(run_cmd.local_with(Some(om_json_path.clone()))), ) .await?; @@ -93,11 +91,7 @@ pub async fn run_on_remote_store( ); } else { // Then, SSH and run the same `om ci run` CLI but without the `--on` argument. - run_ssh( - &ssh_uri.to_string(), - &om_cli_with(run_cmd.local_with(local_flake_url.clone().into(), None)), - ) - .await?; + run_ssh(&ssh_uri.to_string(), &om_cli_with(run_cmd.local_with(None))).await?; } Ok(()) } diff --git a/crates/omnix-ci/src/flake_ref.rs b/crates/omnix-ci/src/flake_ref.rs deleted file mode 100644 index 0463935a..00000000 --- a/crates/omnix-ci/src/flake_ref.rs +++ /dev/null @@ -1,93 +0,0 @@ -//! A reference to some flake living somewhere -use std::{ - fmt::{Display, Formatter}, - str::FromStr, -}; - -use anyhow::Result; -use nix_rs::flake::url::FlakeUrl; - -use crate::github::pull_request::{PullRequest, PullRequestRef}; - -/// A reference to some flake living somewhere -/// -/// This type captures the superset of what flake URLs allow. -#[derive(Debug, Clone, PartialEq, Eq)] -pub enum FlakeRef { - /// A github PR - GithubPR(PullRequestRef), - /// A flake URL supported by Nix commands - Flake(FlakeUrl), -} - -impl FromStr for FlakeRef { - type Err = String; - fn from_str(s: &str) -> std::result::Result { - let flake_ref = match PullRequestRef::from_web_url(s) { - Some(pr) => FlakeRef::GithubPR(pr), - None => FlakeRef::Flake(FlakeUrl(s.to_string())), - }; - Ok(flake_ref) - } -} - -impl From for FlakeRef { - fn from(url: FlakeUrl) -> Self { - FlakeRef::Flake(url) - } -} - -impl Display for FlakeRef { - fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { - match self { - FlakeRef::GithubPR(pr) => write!(f, "{}", pr), - FlakeRef::Flake(url) => write!(f, "{}", url), - } - } -} - -impl FlakeRef { - /// Convert the value to a flake URL that Nix command will recognize. - pub async fn to_flake_url(&self) -> Result { - match self { - FlakeRef::GithubPR(pr) => { - let pr = PullRequest::get(pr).await?; - Ok(pr.flake_url()) - } - FlakeRef::Flake(url) => Ok(url.clone()), - } - } -} - -#[cfg(test)] -mod tests { - use super::*; - - #[test] - fn test_github_pr() { - assert_eq!( - FlakeRef::from_str("https://github.com/srid/nixci/pull/19").unwrap(), - FlakeRef::GithubPR(PullRequestRef { - owner: "srid".to_string(), - repo: "nixci".to_string(), - pr: 19 - }) - ); - } - - #[test] - fn test_current_dir() { - assert_eq!( - FlakeRef::from_str(".").unwrap(), - FlakeRef::Flake(FlakeUrl(".".to_string())) - ); - } - - #[test] - fn test_flake_url() { - assert_eq!( - FlakeRef::from_str("github:srid/nixci").unwrap(), - FlakeRef::Flake(FlakeUrl("github:srid/nixci".to_string())) - ); - } -} diff --git a/crates/omnix-ci/src/github/mod.rs b/crates/omnix-ci/src/github/mod.rs index d4a31292..89b64a31 100644 --- a/crates/omnix-ci/src/github/mod.rs +++ b/crates/omnix-ci/src/github/mod.rs @@ -1,4 +1,3 @@ //! GitHub related types and functions. pub mod actions; pub mod matrix; -pub mod pull_request; diff --git a/crates/omnix-ci/src/github/pull_request.rs b/crates/omnix-ci/src/github/pull_request.rs deleted file mode 100644 index 372eec84..00000000 --- a/crates/omnix-ci/src/github/pull_request.rs +++ /dev/null @@ -1,124 +0,0 @@ -//! Github Pull Request API -use std::fmt::Display; - -/// Enough types to get branch info from Pull Request URL -use anyhow::{bail, Context}; -use nix_rs::flake::url::FlakeUrl; -use reqwest::header::USER_AGENT; -use serde::Deserialize; -use try_guard::guard; -use url::{Host, Url}; - -/// A reference to a Github Pull Request -#[derive(Debug, Clone, PartialEq, Eq)] -pub struct PullRequestRef { - pub(crate) owner: String, - pub(crate) repo: String, - pub(crate) pr: u64, -} - -impl Display for PullRequestRef { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - write!( - f, - "https://github.com/{}/{}/pull/{}", - self.owner, self.repo, self.pr - ) - } -} - -impl PullRequestRef { - fn api_url(&self) -> String { - format!( - "https://api.github.com/repos/{}/{}/pulls/{}", - self.owner, self.repo, self.pr - ) - } - /// Parse a Github PR URL into its owner, repo, and PR number - pub fn from_web_url(url: &str) -> Option { - let url = Url::parse(url).ok()?; - guard!(url.scheme() == "https" && url.host() == Some(Host::Domain("github.com"))); - let paths = url.path_segments().map(|c| c.collect::>())?; - match paths[..] { - [user, repo, "pull", pr_] => { - let pr = pr_.parse::().ok()?; - Some(PullRequestRef { - owner: user.to_string(), - repo: repo.to_string(), - pr, - }) - } - _ => None, - } - } -} - -/// Github Pull Request API Response -#[derive(Debug, Deserialize)] -pub struct PullRequest { - /// PR URL - pub url: String, - /// [Head] info - pub head: Head, -} - -/// Pull Request head info -#[derive(Debug, Deserialize)] -pub struct Head { - #[serde(rename = "ref")] - /// Head ref - pub ref_: String, - /// Head [Repo] - pub repo: Repo, -} - -/// Pull Request repo info -#[derive(Debug, Deserialize)] -pub struct Repo { - /// `/` - pub full_name: String, -} - -impl PullRequest { - /// Fetch the given PR using Github's API - pub async fn get(ref_: &PullRequestRef) -> anyhow::Result { - let v = api_get::(ref_.api_url()).await?; - Ok(v) - } - - /// The flake URL referencing the branch of this PR - pub fn flake_url(&self) -> FlakeUrl { - // We cannot use `github:user/repo` syntax, because it doesn't support - // special characters in branch name. For that, we need to use the full - // git+https URL with url encoded `ref` query parameter. - FlakeUrl(format!( - "git+https://github.com/{}?ref={}", - self.head.repo.full_name, - urlencoding::encode(&self.head.ref_) - )) - } -} - -/// Get an API response, parsing the response into the given type -async fn api_get(url: String) -> anyhow::Result -where - T: serde::de::DeserializeOwned, -{ - let client = reqwest::Client::new(); - let resp = client - .get(&url) - // Github API requires a user agent - .header(USER_AGENT, "github.com/juspay/omnix") - .send() - .await - .with_context(|| format!("cannot create request: {}", &url))?; - if resp.status().is_success() { - let v = resp - .json::() - .await - .with_context(|| format!("cannot parse response: {}", &url))?; - Ok(v) - } else { - bail!("cannot make request: {}", resp.status()) - } -} diff --git a/crates/omnix-ci/src/lib.rs b/crates/omnix-ci/src/lib.rs index 312c761e..38d3e8e1 100644 --- a/crates/omnix-ci/src/lib.rs +++ b/crates/omnix-ci/src/lib.rs @@ -2,7 +2,6 @@ #![warn(missing_docs)] pub mod command; pub mod config; -pub mod flake_ref; pub mod github; pub mod nix; pub mod step; diff --git a/doc/history.md b/doc/history.md index 0bd8b159..da533540 100644 --- a/doc/history.md +++ b/doc/history.md @@ -4,6 +4,12 @@ order: 100 # Release history +## 1.3.1 (unreleased) {#1.3.1} + +### Breaking changes + +- `om ci run`: Remove argument support for specifying directories, flake URLs, or GitHub PR URLs. The command now always operates on the current directory flake. + ## 1.3.0 (2025-07-15) {#1.3.0} - `om ci`: Allow impure builds through `impure = true;` setting in `om.yaml` (#445) diff --git a/doc/om/ci.md b/doc/om/ci.md index eaec2892..f7e6c0e7 100644 --- a/doc/om/ci.md +++ b/doc/om/ci.md @@ -13,30 +13,20 @@ A `result` symlink is also produced, containing a JSON of all built paths. See [ ## Basic Usage {#usage} -`om ci run` accepts any valid [flake URL](https://nixos.asia/en/flake-url) or a Github PR URL. +`om ci run` always operates on the current directory flake. ```sh # Run CI on current directory flake -$ om ci # Or `om ci run` or `om ci run .` - -# Run CI on a local flake (default is $PWD) -$ om ci run ~/code/myproject +$ om ci # Or `om ci run` # Pass custom arguments to `nix` after '--' -$ om ci run ~/code/myproject -- --accept-flake-config - -# Run CI on a github repo -$ om ci run github:hercules-ci/hercules-ci-agent - -# Run CI on a github PR -$ om ci run https://github.com/srid/emanote/pull/451 +$ om ci run -- --accept-flake-config -# Run CI only the selected sub-flake -$ git clone https://github.com/srid/haskell-flake && cd haskell-flake +# Run CI only the selected sub-flake configuration $ om ci run .#default.dev # Run CI remotely over SSH -$ om ci run --on ssh://myname@myserver ~/code/myproject +$ om ci run --on ssh://myname@myserver ``` ## Results JSON and closure {#out-link} @@ -173,7 +163,7 @@ For a real-world example of custom steps, checkout [Omnix's configuration](https Omnix can run CI over SSH. ```sh -om ci run --on ssh://myname@myserver ~/code/myproject +om ci run --on ssh://myname@myserver ``` What this does: @@ -183,7 +173,7 @@ What this does: ### Options -- Pass `copy-inputs=true` if you wish to copy all flake inputs recursively. This is useful if you have private Git inputs. For example, `om ci run --on "ssh://myname@myserver?copy-inputs=true" ~/code/myproject` +- Pass `copy-inputs=true` if you wish to copy all flake inputs recursively. This is useful if you have private Git inputs. For example, `om ci run --on "ssh://myname@myserver?copy-inputs=true"` - Omnix copies the results back to local store, unless `--no-link` was passed. ## Examples From 8cafcb057d89e3a34abccdd3cfb2931930bf3566 Mon Sep 17 00:00:00 2001 From: Sridhar Ratnakumar Date: Fri, 5 Sep 2025 20:52:45 -0400 Subject: [PATCH 2/4] Fix test? --- Cargo.lock | 1 + crates/omnix-cli/Cargo.toml | 2 + crates/omnix-cli/tests/command/ci.rs | 103 ++++++++++++++++++++------- 3 files changed, 82 insertions(+), 24 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index acd47f2e..196db7af 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1814,6 +1814,7 @@ dependencies = [ "serde", "serde_json", "tabled", + "tempfile", "tokio", "tracing", ] diff --git a/crates/omnix-cli/Cargo.toml b/crates/omnix-cli/Cargo.toml index a9a7e755..d19b2f1e 100644 --- a/crates/omnix-cli/Cargo.toml +++ b/crates/omnix-cli/Cargo.toml @@ -44,3 +44,5 @@ assert_matches = "1.5" ctor = "0.2" predicates = "3" regex = "1.9" +tempfile = { workspace = true } +tokio = { workspace = true } diff --git a/crates/omnix-cli/tests/command/ci.rs b/crates/omnix-cli/tests/command/ci.rs index cc1f9f5f..a2ff093f 100644 --- a/crates/omnix-cli/tests/command/ci.rs +++ b/crates/omnix-cli/tests/command/ci.rs @@ -5,18 +5,61 @@ use nix_rs::store::path::StorePath; use regex::Regex; use serde::de::DeserializeOwned; use serde_json::Value; +use tempfile; +use tokio::process::Command; use super::core::om; -/// Run `om ci run` passing given arguments, returning its stdout (parsed). -async fn om_ci_run(args: &[&str]) -> anyhow::Result { +/// Clone a GitHub repository to a temporary directory and run `om ci run` in it +async fn om_ci_run_in_cloned_repo( + github_url: &str, + commit_hash: Option<&str>, + args: &[&str], +) -> anyhow::Result { + // Create a temporary directory + let temp_dir = tempfile::tempdir()?; + let temp_path = temp_dir.path(); + + // Clone the repository + let mut clone_cmd = Command::new("git"); + clone_cmd.args(["clone", github_url, temp_path.to_str().unwrap()]); + + let clone_output = clone_cmd.output().await?; + if !clone_output.status.success() { + bail!( + "Failed to clone repository {}:\n{}", + github_url, + String::from_utf8_lossy(&clone_output.stderr) + ); + } + + // Checkout specific commit if provided + if let Some(hash) = commit_hash { + let mut checkout_cmd = Command::new("git"); + checkout_cmd.args(["checkout", hash]); + checkout_cmd.current_dir(temp_path); + + let checkout_output = checkout_cmd.output().await?; + if !checkout_output.status.success() { + bail!( + "Failed to checkout commit {} in {}:\n{}", + hash, + github_url, + String::from_utf8_lossy(&checkout_output.stderr) + ); + } + } + + // Run om ci run in the cloned directory let mut cmd = om()?; cmd.arg("ci").arg("run").args(args); + cmd.current_dir(temp_path); let output = cmd.output()?; if !output.status.success() { bail!( - "Failed to run `om ci run`:\n{}", + "Failed to run `om ci run` in {}:\n{}", + github_url, String::from_utf8_lossy(&output.stderr).to_string(), ); } @@ -28,9 +71,12 @@ async fn om_ci_run(args: &[&str]) -> anyhow::Result { #[tokio::test] /// Run `om ci build` and check if the stdout consists of only /nix/store/* paths async fn build_flake_output() -> anyhow::Result<()> { - let out = - om_ci_run(&["github:srid/haskell-multi-nix/c85563721c388629fa9e538a1d97274861bc8321"]) - .await?; + let out = om_ci_run_in_cloned_repo( + "https://github.com/srid/haskell-multi-nix.git", + Some("c85563721c388629fa9e538a1d97274861bc8321"), + &[], + ) + .await?; assert!( out.as_path().starts_with("/nix/store/"), @@ -44,9 +90,12 @@ async fn build_flake_output() -> anyhow::Result<()> { #[tokio::test] /// A simple test, without config async fn test_haskell_multi_nix() -> anyhow::Result<()> { - let out = - om_ci_run(&["github:srid/haskell-multi-nix/c85563721c388629fa9e538a1d97274861bc8321"]) - .await?; + let out = om_ci_run_in_cloned_repo( + "https://github.com/srid/haskell-multi-nix.git", + Some("c85563721c388629fa9e538a1d97274861bc8321"), + &[], + ) + .await?; let v: Value = serde_json::from_reader(std::fs::File::open(&out)?)?; let paths: Vec = lookup_path(&v, &["result", "ROOT", "build", "outPaths"]).unwrap(); let expected = vec![ @@ -64,10 +113,11 @@ async fn test_haskell_multi_nix() -> anyhow::Result<()> { #[tokio::test] async fn test_haskell_multi_nix_all_dependencies() -> anyhow::Result<()> { - let out = om_ci_run(&[ - "--include-all-dependencies", - "github:srid/haskell-multi-nix/c85563721c388629fa9e538a1d97274861bc8321", - ]) + let out = om_ci_run_in_cloned_repo( + "https://github.com/srid/haskell-multi-nix.git", + Some("c85563721c388629fa9e538a1d97274861bc8321"), + &["--include-all-dependencies"], + ) .await?; let v: Value = serde_json::from_reader(std::fs::File::open(&out)?)?; let paths: Vec = lookup_path(&v, &["result", "ROOT", "build", "allDeps"]).unwrap(); @@ -81,14 +131,17 @@ async fn test_haskell_multi_nix_all_dependencies() -> anyhow::Result<()> { #[tokio::test] /// Whether `--override-input` passes CI successfully async fn test_haskell_multi_nix_override_input() -> anyhow::Result<()> { - let _out = om_ci_run(&[ - "github:srid/haskell-multi-nix/c85563721c388629fa9e538a1d97274861bc8321", - "--", - "--override-input", - "haskell-flake", - // haskell-flake 0.4 release - "github:srid/haskell-flake/c8622c8a259e18e0a1919462ce885380108a723c", - ]) + let _out = om_ci_run_in_cloned_repo( + "https://github.com/srid/haskell-multi-nix.git", + Some("c85563721c388629fa9e538a1d97274861bc8321"), + &[ + "--", + "--override-input", + "haskell-flake", + // haskell-flake 0.4 release + "github:srid/haskell-flake/c8622c8a259e18e0a1919462ce885380108a723c", + ], + ) .await?; Ok(()) } @@ -96,9 +149,11 @@ async fn test_haskell_multi_nix_override_input() -> anyhow::Result<()> { #[tokio::test] /// A test, with config async fn test_services_flake() -> anyhow::Result<()> { - let out = om_ci_run(&[ - "github:juspay/services-flake/23cf162387af041035072ee4a9de20f8408907cb#default.simple-example", - ]) + let out = om_ci_run_in_cloned_repo( + "https://github.com/juspay/services-flake.git", + Some("23cf162387af041035072ee4a9de20f8408907cb"), + &[".#default.simple-example"], + ) .await?; let v: Value = serde_json::from_reader(std::fs::File::open(&out)?)?; let paths: Vec = From c93991d18a332b4df0f377a72805a8830995ccba Mon Sep 17 00:00:00 2001 From: Sridhar Ratnakumar Date: Fri, 5 Sep 2025 21:11:13 -0400 Subject: [PATCH 3/4] still take ref as arg --- crates/omnix-ci/src/command/core.rs | 13 +++++++++++-- crates/omnix-ci/src/command/run.rs | 8 ++++++++ crates/omnix-cli/tests/command/ci.rs | 3 +-- 3 files changed, 20 insertions(+), 4 deletions(-) diff --git a/crates/omnix-ci/src/command/core.rs b/crates/omnix-ci/src/command/core.rs index 23b861e3..247a3d5c 100644 --- a/crates/omnix-ci/src/command/core.rs +++ b/crates/omnix-ci/src/command/core.rs @@ -50,8 +50,17 @@ impl Command { /// Get the [FlakeUrl] associated with this subcommand fn get_flake_ref(&self) -> FlakeUrl { - // Always use current directory - FlakeUrl(".".to_string()) + let base_url = ".".to_string(); + match self { + Command::Run(cmd) => { + if let Some(config) = &cmd.config { + FlakeUrl(format!(".#{}", config)) + } else { + FlakeUrl(base_url) + } + } + Command::DumpGithubActionsMatrix(_) => FlakeUrl(base_url), + } } /// Convert this type back to the user-facing command line arguments diff --git a/crates/omnix-ci/src/command/run.rs b/crates/omnix-ci/src/command/run.rs index ef90b116..c659b3f0 100644 --- a/crates/omnix-ci/src/command/run.rs +++ b/crates/omnix-ci/src/command/run.rs @@ -65,6 +65,10 @@ pub struct RunCommand { #[clap(long, default_value_t = env::var("GITHUB_ACTION").is_ok())] pub github_output: bool, + /// CI configuration to use (e.g., "default.simple-example") + #[arg(help = "Specify which CI sub-configuration to run")] + pub config: Option, + /// Arguments for all steps #[command(flatten)] pub steps_args: crate::step::core::StepsArgs, @@ -207,6 +211,10 @@ impl RunCommand { args.push("--no-link".to_string()); } + if let Some(config) = self.config.as_ref() { + args.push(config.clone()); + } + args.extend(self.steps_args.to_cli_args()); args diff --git a/crates/omnix-cli/tests/command/ci.rs b/crates/omnix-cli/tests/command/ci.rs index a2ff093f..01712de5 100644 --- a/crates/omnix-cli/tests/command/ci.rs +++ b/crates/omnix-cli/tests/command/ci.rs @@ -5,7 +5,6 @@ use nix_rs::store::path::StorePath; use regex::Regex; use serde::de::DeserializeOwned; use serde_json::Value; -use tempfile; use tokio::process::Command; use super::core::om; @@ -152,7 +151,7 @@ async fn test_services_flake() -> anyhow::Result<()> { let out = om_ci_run_in_cloned_repo( "https://github.com/juspay/services-flake.git", Some("23cf162387af041035072ee4a9de20f8408907cb"), - &[".#default.simple-example"], + &["default.simple-example"], ) .await?; let v: Value = serde_json::from_reader(std::fs::File::open(&out)?)?; From 4888691509025f055e04599d4a55d674029c3d08 Mon Sep 17 00:00:00 2001 From: Sridhar Ratnakumar Date: Fri, 5 Sep 2025 21:12:31 -0400 Subject: [PATCH 4/4] docs --- doc/history.md | 2 +- doc/om/ci.md | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/doc/history.md b/doc/history.md index da533540..6a9b9425 100644 --- a/doc/history.md +++ b/doc/history.md @@ -8,7 +8,7 @@ order: 100 ### Breaking changes -- `om ci run`: Remove argument support for specifying directories, flake URLs, or GitHub PR URLs. The command now always operates on the current directory flake. +- `om ci run`: Remove argument support for specifying directories, flake URLs, or GitHub PR URLs. The command now always operates on the current directory flake. Sub-configurations can be specified with an optional config argument (e.g., `om ci run default.simple-example`). ## 1.3.0 (2025-07-15) {#1.3.0} diff --git a/doc/om/ci.md b/doc/om/ci.md index f7e6c0e7..088c6866 100644 --- a/doc/om/ci.md +++ b/doc/om/ci.md @@ -22,8 +22,8 @@ $ om ci # Or `om ci run` # Pass custom arguments to `nix` after '--' $ om ci run -- --accept-flake-config -# Run CI only the selected sub-flake configuration -$ om ci run .#default.dev +# Run CI with a specific sub-configuration +$ om ci run default.simple-example # Run CI remotely over SSH $ om ci run --on ssh://myname@myserver @@ -117,7 +117,7 @@ By default, `om ci` will build the top-level flake, but you can tell it to build } ``` -You can have more than one CI configuration. For eg., `om ci run .#foo` will run the configuration from `om.ci.foo` flake output. +You can have more than one CI configuration. For eg., `om ci run foo` will run the configuration from `om.ci.foo` flake output. ### Custom CI actions {#custom}