Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add subcommand to generate Github Actions Matrix #50

Merged
merged 29 commits into from
Mar 25, 2024
Merged
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
4 changes: 3 additions & 1 deletion .github/workflows/ci.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -10,9 +10,11 @@ jobs:
strategy:
matrix:
system: [aarch64-darwin, aarch64-linux]
fail-fast: false
steps:
- uses: actions/checkout@v4
- name: nixci
run: nixci --build-systems "github:nix-systems/${{ matrix.system }}"
run: nixci build --build-systems "github:nix-systems/${{ matrix.system }}"
# FIXME: This should run only darwin
- name: Integration Test
run: nix develop -c cargo test -F integration_test
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@
- Add new config `nixci.*.*.systems` acting as a whitelist of systems to build that subflake.
- Add `--build-systems` option to build on an arbitrary systems (\#39)
- Allow selecting sub-flake to build, e.g.: `nixci .#default.myflake` (\#45)
- Add subcommand to generate Github Actions matrix (\#50)
- Consequently, you must run `nixci build` instead of `nixci` now.
- Fixes
- Fix regression in Nix 2.19+ (`devour-flake produced an outpath with no outputs`) (\#35)
- Evaluate OS configurations for current system only (\#38)
Expand Down
38 changes: 33 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -27,22 +27,27 @@ To install, run `nix profile install github:srid/nixci`. You can also use use `n
`nixci` accepts any valid [flake URL](https://nixos.asia/en/flake-url) or a Github PR URL.

```sh
# Run nixci on current directory flake
$ nixci # Or `nixci build` or `nixci build .`

# Run nixci on a local flake (default is $PWD)
$ nixci ~/code/myproject
$ nixci build ~/code/myproject

# Run nixci on a github repo
$ nixci github:hercules-ci/hercules-ci-agent
$ nixci build github:hercules-ci/hercules-ci-agent

# Run nixci on a github PR
$ nixci https://github.com/srid/emanote/pull/451
$ nixci build https://github.com/srid/emanote/pull/451

# Run only the selected sub-flake
$ git clone https://github.com/srid/haskell-flake && cd haskell-flake
$ nixci .#default.dev
$ nixci build .#default.dev
```

### Using in Github Actions

#### Standard Runners

Add the following to your workflow file,

```yaml
Expand All @@ -55,7 +60,30 @@ Add the following to your workflow file,
- uses: yaxitech/nix-install-pkgs-action@v3
with:
packages: "github:srid/nixci"
- run: nixci
- run: nixci build
```

#### Self-hosted Runners with Job Matrix

```yaml
jobs:
configure:
runs-on: self-hosted
outputs:
matrix: ${{ steps.set-matrix.outputs.matrix }}
steps:
- uses: actions/checkout@v4
- id: set-matrix
run: echo "matrix=$(nixci dump-github-actions-matrix --systems=aarch64-linux,aarch64-darwin | jq -c .)" >> $GITHUB_OUTPUT
nix:
runs-on: self-hosted
needs: configure
strategy:
matrix: ${{ fromJson(needs.configure.outputs.matrix) }}
fail-fast: false
steps:
- uses: actions/checkout@v4
- run: nixci build --build-systems "github:nix-systems/${{ matrix.system }}" .#default.${{ matrix.subflake}}
```

## Configuring
Expand Down
74 changes: 59 additions & 15 deletions src/cli.rs
Original file line number Diff line number Diff line change
@@ -1,16 +1,18 @@
use std::str::FromStr;

use anyhow::Result;
use clap::Parser;
use clap::{Parser, Subcommand};
use colored::Colorize;
use nix_rs::{
command::{NixCmd, NixCmdError},
config::NixConfig,
flake::{system::System, url::FlakeUrl},
};

use crate::{
github::{self, PullRequest, PullRequestRef},
nix::system_list::SystemsList,
config,
github::pull_request::{PullRequest, PullRequestRef},
nix::system_list::{SystemsList, SystemsListFlakeRef},
};

/// A reference to some flake living somewhere
Expand All @@ -25,7 +27,7 @@ pub enum FlakeRef {
impl FromStr for FlakeRef {
type Err = String;
fn from_str(s: &str) -> std::result::Result<FlakeRef, String> {
let flake_ref = match github::PullRequestRef::from_web_url(s) {
let flake_ref = match PullRequestRef::from_web_url(s) {
Some(pr) => FlakeRef::GithubPR(pr),
None => FlakeRef::Flake(FlakeUrl(s.to_string())),
};
Expand Down Expand Up @@ -56,31 +58,73 @@ pub struct CliArgs {
#[arg(short = 'v')]
pub verbose: bool,

/// Flake URL or github URL
///
/// A specific nixci` configuration can be specified
/// using '#': e.g. `nixci .#extra-tests`
#[arg(default_value = ".")]
pub flake_ref: FlakeRef,
#[clap(subcommand)]
pub command: Command,
}

#[derive(Debug, Subcommand)]
pub enum Command {
/// Build all outputs of a flake
Build(BuildConfig),

/// Print the Github Actions matrix configuration as JSON
#[clap(name = "gh-matrix")]
DumpGithubActionsMatrix {
/// Flake URL or github URL
///
/// A specific nixci configuration can be specified
/// using '#': e.g. `nixci .#extra-tests`
#[arg(default_value = ".")]
flake_ref: FlakeRef,

/// Systems to include in the matrix
#[arg(long, value_parser, value_delimiter = ',')]
systems: Vec<System>,
},
}

impl Command {
/// Get the [FlakeUrl] & nixci [config::Config] associated with this subcommand
pub async fn get_config(&self) -> anyhow::Result<(FlakeUrl, config::Config)> {
let flake_ref = match self {
Command::Build(build_cfg) => &build_cfg.flake_ref,
Command::DumpGithubActionsMatrix { flake_ref, .. } => flake_ref,
};
let url = flake_ref.to_flake_url().await?;
tracing::info!("{}", format!("🍏 {}", url.0).bold());
let cfg = config::Config::from_flake_url(&url).await?;
tracing::debug!("Config: {cfg:?}");
Ok((url, cfg))
}
}

#[derive(Parser, Debug)]
pub struct BuildConfig {
/// The systems list to build for. If empty, build for current system.
///
/// Must be a flake reference which, when imported, must return a Nix list
/// of systems. You may use one of the lists from
/// https://github.com/nix-systems.
#[arg(long, default_value = "github:nix-systems/empty")]
pub build_systems: FlakeUrl,
pub build_systems: SystemsListFlakeRef,

/// Flake URL or github URL
///
/// A specific nixci` configuration can be specified
/// using '#': e.g. `nixci .#extra-tests`
#[arg(default_value = ".")]
pub flake_ref: FlakeRef,

/// Additional arguments to pass through to `nix build`
#[arg(last = true, default_values_t = vec![
"--refresh".to_string(),
"-j".to_string(),
"auto".to_string(),
"--refresh".to_string(),
"-j".to_string(),
"auto".to_string(),
])]
pub extra_nix_build_args: Vec<String>,
}

impl CliArgs {
impl BuildConfig {
pub async fn get_build_systems(&self) -> Result<Vec<System>> {
let systems = SystemsList::from_flake(&self.build_systems).await?.0;
if systems.is_empty() {
Expand Down
10 changes: 5 additions & 5 deletions src/config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ use anyhow::Result;
use nix_rs::flake::{eval::nix_eval_attr_json, system::System, url::FlakeUrl};
use serde::Deserialize;

use crate::cli::CliArgs;
use crate::cli::BuildConfig;

/// The `nixci` configuration encoded in flake.nix
///
Expand Down Expand Up @@ -117,7 +117,7 @@ impl Default for SubFlakish {
}

impl SubFlakish {
pub fn can_build_on(&self, systems: &Vec<System>) -> bool {
pub fn can_build_on(&self, systems: &[System]) -> bool {
match self.systems.as_ref() {
Some(systems_whitelist) => systems_whitelist.iter().any(|s| systems.contains(s)),
None => true,
Expand All @@ -128,7 +128,7 @@ impl SubFlakish {
/// subflake configuration.
pub fn nix_build_args_for_flake(
&self,
cli_args: &CliArgs,
build_cfg: &BuildConfig,
flake_url: &FlakeUrl,
) -> Vec<String> {
std::iter::once(flake_url.sub_flake_url(self.dir.clone()).0)
Expand All @@ -145,9 +145,9 @@ impl SubFlakish {
.chain([
"--override-input".to_string(),
"systems".to_string(),
cli_args.build_systems.0.clone(),
build_cfg.build_systems.0 .0.clone(),
])
.chain(cli_args.extra_nix_build_args.iter().cloned())
.chain(build_cfg.extra_nix_build_args.iter().cloned())
.collect()
}
}
Expand Down
33 changes: 33 additions & 0 deletions src/github/matrix.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
/// Enough types to get branch info from Pull Request URL
use nix_rs::flake::system::System;
use serde::{Deserialize, Serialize};

use crate::config::Subflakes;

#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct GitHubMatrixRow {
pub system: System,
pub subflake: String,
}

#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct GitHubMatrix {
pub include: Vec<GitHubMatrixRow>,
}

impl GitHubMatrix {
pub fn from(systems: Vec<System>, subflakes: &Subflakes) -> Self {
let include: Vec<GitHubMatrixRow> = systems
.iter()
.flat_map(|system| {
subflakes.0.iter().filter_map(|(k, v)| {
v.can_build_on(&[system.clone()]).then(|| GitHubMatrixRow {
system: system.clone(),
subflake: k.clone(),
})
})
})
.collect();
GitHubMatrix { include }
}
}
2 changes: 2 additions & 0 deletions src/github/mod.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
pub mod matrix;
pub mod pull_request;
File renamed without changes.
36 changes: 24 additions & 12 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ pub mod nix;

use std::collections::HashSet;

use cli::CliArgs;
use cli::{BuildConfig, CliArgs};
use colored::Colorize;
use nix::devour_flake::{DevourFlakeOutput, DrvOut};
use nix_rs::flake::url::FlakeUrl;
Expand All @@ -16,15 +16,26 @@ use tracing::instrument;
#[instrument(name = "nixci", skip(args))]
pub async fn nixci(args: CliArgs) -> anyhow::Result<Vec<DrvOut>> {
tracing::debug!("Args: {args:?}");
let url = args.flake_ref.to_flake_url().await?;
tracing::info!("{}", format!("🍏 {}", url.0).bold());

let cfg = config::Config::from_flake_url(&url).await?;
tracing::debug!("Config: {cfg:?}");
let (url, cfg) = args.command.get_config().await?;
match args.command {
cli::Command::Build(build_cfg) => nixci_build(args.verbose, &build_cfg, &url, &cfg).await,
cli::Command::DumpGithubActionsMatrix { systems, .. } => {
let matrix = github::matrix::GitHubMatrix::from(systems, &cfg.subflakes);
println!("{}", serde_json::to_string(&matrix)?);
Ok(vec![])
}
}
}

async fn nixci_build(
verbose: bool,
build_cfg: &BuildConfig,
url: &FlakeUrl,
cfg: &config::Config,
) -> anyhow::Result<Vec<DrvOut>> {
let mut all_outs = HashSet::new();

let systems = args.get_build_systems().await?;
let systems = build_cfg.get_build_systems().await?;

for (subflake_name, subflake) in &cfg.subflakes.0 {
let name = format!("{}.{}", cfg.name, subflake_name).italic();
Expand All @@ -38,7 +49,7 @@ pub async fn nixci(args: CliArgs) -> anyhow::Result<Vec<DrvOut>> {
}
tracing::info!("🍎 {}", name);
if subflake.can_build_on(&systems) {
let outs = nixci_subflake(&args, &url, &subflake_name, &subflake).await?;
let outs = nixci_subflake(verbose, build_cfg, url, subflake_name, subflake).await?;
all_outs.extend(outs.0);
} else {
tracing::info!(
Expand All @@ -51,9 +62,10 @@ pub async fn nixci(args: CliArgs) -> anyhow::Result<Vec<DrvOut>> {
Ok(all_outs.into_iter().collect())
}

#[instrument(skip(cli_args, url))]
#[instrument(skip(build_cfg, url))]
async fn nixci_subflake(
cli_args: &CliArgs,
verbose: bool,
build_cfg: &BuildConfig,
url: &FlakeUrl,
subflake_name: &str,
subflake: &config::SubFlakish,
Expand All @@ -62,8 +74,8 @@ async fn nixci_subflake(
nix::lock::nix_flake_lock_check(&url.sub_flake_url(subflake.dir.clone())).await?;
}

let nix_args = subflake.nix_build_args_for_flake(cli_args, url);
let outs = nix::devour_flake::devour_flake(cli_args.verbose, nix_args).await?;
let nix_args = subflake.nix_build_args_for_flake(build_cfg, url);
let outs = nix::devour_flake::devour_flake(verbose, nix_args).await?;
for out in &outs.0 {
println!("{}", out.0.bold());
}
Expand Down
2 changes: 1 addition & 1 deletion src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,6 @@ use nixci::cli;
async fn main() -> Result<()> {
let args = cli::CliArgs::parse();
nixci::logging::setup_logging(args.verbose);
let _outs = nixci::nixci(args).await?;
nixci::nixci(args).await?;
Ok(())
}
Loading
Loading