Add group reports for flake-group check (#436)

* `nix flake {info -> metadata}`

* Fix error variant for io errors

* Enable backtrace support for anyhow

* Improve error printing

* Write error report file

* Format workflow file

* Use report file

* Set non-zero exit status if a group fails

* Do not use `local`

* Apply suggestions from review

* Move exit outside the loop

* Fix multi line output

* Fix var substitution

* Different work around for multi lines
This commit is contained in:
Yannik Sander 2022-03-01 13:50:18 +01:00 committed by GitHub
parent d098ad2acb
commit 33da3a4a0c
Failed to generate hash of commit
6 changed files with 104 additions and 31 deletions

View file

@ -4,11 +4,10 @@ on:
workflow_dispatch: workflow_dispatch:
pull_request: pull_request:
paths: paths:
- 'flakes/**.toml' - "flakes/**.toml"
jobs: jobs:
automatic-custom-flakes-check: automatic-custom-flakes-check:
runs-on: ubuntu-latest runs-on: ubuntu-latest
strategy: strategy:
@ -34,7 +33,30 @@ jobs:
- name: Try importing all custom flakes - name: Try importing all custom flakes
run: | run: |
shopt -s globstar shopt -s globstar
had_error=0
for flake_group in flakes/**/*.toml for flake_group in flakes/**/*.toml
do do
./result/bin/flake-info group "$flake_group" "$(basename "$flake_group" .toml)" echo "::group::Group \"$(basename $flake_group .toml)\""
./result/bin/flake-info group "$flake_group" "$(basename "$flake_group" .toml)" --report
if [[ -f "./report.txt" ]]
then
had_error=1
# sic.:
# Workaround for multi line output
report="$(< ./report.txt)"
report="${report//'%'/'%25'}"
report="${report//$'\n'/'%0A'}"
report="${report//$'\r'/'%0D'}"
echo "::error file=$flake_group::$report"
fi
echo ::endgroup::
done done
exit $had_error

48
flake-info/Cargo.lock generated
View file

@ -2,6 +2,15 @@
# It is not intended for manual editing. # It is not intended for manual editing.
version = 3 version = 3
[[package]]
name = "addr2line"
version = "0.17.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b9ecd88a8c8378ca913a680cd98f0f13ac67383d35993f86c90a70e3f137816b"
dependencies = [
"gimli",
]
[[package]] [[package]]
name = "adler" name = "adler"
version = "1.0.2" version = "1.0.2"
@ -31,6 +40,9 @@ name = "anyhow"
version = "1.0.43" version = "1.0.43"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "28ae2b3dec75a406790005a200b1bd89785afc02517a00ca99ecfe093ee9e6cf" checksum = "28ae2b3dec75a406790005a200b1bd89785afc02517a00ca99ecfe093ee9e6cf"
dependencies = [
"backtrace",
]
[[package]] [[package]]
name = "async-compression" name = "async-compression"
@ -62,6 +74,21 @@ version = "1.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cdb031dd78e28731d87d56cc8ffef4a8f36ca26c38fe2de700543e627f8a464a" checksum = "cdb031dd78e28731d87d56cc8ffef4a8f36ca26c38fe2de700543e627f8a464a"
[[package]]
name = "backtrace"
version = "0.3.63"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "321629d8ba6513061f26707241fa9bc89524ff1cd7a915a97ef0c62c666ce1b6"
dependencies = [
"addr2line",
"cc",
"cfg-if",
"libc",
"miniz_oxide",
"object",
"rustc-demangle",
]
[[package]] [[package]]
name = "base64" name = "base64"
version = "0.11.0" version = "0.11.0"
@ -448,6 +475,12 @@ dependencies = [
"wasi", "wasi",
] ]
[[package]]
name = "gimli"
version = "0.26.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "78cc372d058dcf6d5ecd98510e7fbc9e5aec4d21de70f65fea8fecebcd881bd4"
[[package]] [[package]]
name = "h2" name = "h2"
version = "0.3.4" version = "0.3.4"
@ -766,6 +799,15 @@ dependencies = [
"libc", "libc",
] ]
[[package]]
name = "object"
version = "0.27.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "67ac1d3f9a1d3616fd9a60c8d74296f22406a238b6a72f5cc1e6f314df4ffbf9"
dependencies = [
"memchr",
]
[[package]] [[package]]
name = "once_cell" name = "once_cell"
version = "1.8.0" version = "1.8.0"
@ -1070,6 +1112,12 @@ dependencies = [
"winapi", "winapi",
] ]
[[package]]
name = "rustc-demangle"
version = "0.1.21"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7ef03e0a2b150c7a90d01faf6254c9c48a41e95fb2a8c2ac1c6f0d2b9aefc342"
[[package]] [[package]]
name = "rustc_version" name = "rustc_version"
version = "0.2.3" version = "0.2.3"

View file

@ -12,7 +12,7 @@ serde = {version="1.0", features = ["derive"]}
serde_json = "1.0" serde_json = "1.0"
serde_path_to_error = "0.1.5" serde_path_to_error = "0.1.5"
toml = "0.5" toml = "0.5"
anyhow = "1.0" anyhow = { version= "1.0", features = ["backtrace"] }
thiserror = "1.0" thiserror = "1.0"
structopt = "0.3" structopt = "0.3"
command-run = "0.13" command-run = "0.13"

View file

@ -6,11 +6,13 @@ use flake_info::elastic::{ElasticsearchError, ExistsStrategy};
use flake_info::{commands, elastic}; use flake_info::{commands, elastic};
use log::{debug, error, info, warn}; use log::{debug, error, info, warn};
use sha2::Digest; use sha2::Digest;
use std::fs;
use std::path::{Path, PathBuf}; use std::path::{Path, PathBuf};
use std::ptr::hash; use std::ptr::hash;
use std::{fs, io};
use structopt::{clap::ArgGroup, StructOpt}; use structopt::{clap::ArgGroup, StructOpt};
use thiserror::Error; use thiserror::Error;
use tokio::fs::File;
use tokio::io::AsyncWriteExt;
#[derive(StructOpt, Debug)] #[derive(StructOpt, Debug)]
#[structopt( #[structopt(
@ -74,6 +76,9 @@ enum Command {
#[structopt(long, help = "Whether to gc the store after info or not")] #[structopt(long, help = "Whether to gc the store after info or not")]
gc: bool, gc: bool,
#[structopt(long, help = "Whether write an error report about failed packages")]
report: bool,
}, },
} }
@ -155,17 +160,6 @@ async fn main() -> Result<()> {
let command_result = run_command(args.command, args.kind, &args.extra).await; let command_result = run_command(args.command, args.kind, &args.extra).await;
if let Err(error) = command_result { if let Err(error) = command_result {
match error {
FlakeInfoError::Flake(ref e)
| FlakeInfoError::Nixpkgs(ref e)
| FlakeInfoError::IO(ref e) => {
error!("{}", e);
}
FlakeInfoError::Group(ref el) => {
el.iter().for_each(|e| error!("{}", e));
}
}
return Err(error.into()); return Err(error.into());
} }
@ -187,11 +181,10 @@ enum FlakeInfoError {
Flake(anyhow::Error), Flake(anyhow::Error),
#[error("Getting nixpkgs info caused an error: {0:?}")] #[error("Getting nixpkgs info caused an error: {0:?}")]
Nixpkgs(anyhow::Error), Nixpkgs(anyhow::Error),
#[error("Getting group info caused one or more errors: {0:?}")] #[error("Some members of the group '{0}' could not be processed: \n {}", .1.iter().enumerate().map(|(n, e)| format!("{}: {:?}", n+1, e)).collect::<Vec<String>>().join("\n\n"))]
Group(Vec<anyhow::Error>), Group(String, Vec<anyhow::Error>),
#[error("Couldn't perform IO: {0}")] #[error("Couldn't perform IO: {0}")]
IO(anyhow::Error), IO(#[from] io::Error),
} }
async fn run_command( async fn run_command(
@ -239,8 +232,14 @@ async fn run_command(
temp_store, temp_store,
gc, gc,
name, name,
report,
} => { } => {
let sources = Source::read_sources_file(&targets).map_err(FlakeInfoError::IO)?; // if reporting is enabled delete old report
if report && tokio::fs::metadata("report.txt").await.is_ok() {
tokio::fs::remove_file("report.txt").await?;
}
let sources = Source::read_sources_file(&targets)?;
let (exports_and_hashes, errors) = sources let (exports_and_hashes, errors) = sources
.iter() .iter()
.map(|source| match source { .map(|source| match source {
@ -273,15 +272,19 @@ async fn run_command(
.collect::<Vec<_>>(); .collect::<Vec<_>>();
if !errors.is_empty() { if !errors.is_empty() {
let error = FlakeInfoError::Group(name.clone(), errors);
if exports.is_empty() { if exports.is_empty() {
return Err(FlakeInfoError::Group(errors)); return Err(error);
} }
warn!("=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-="); warn!("=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=");
warn!( warn!("{}", error);
"Some group members could not be evaluated: {}",
FlakeInfoError::Group(errors)
);
warn!("=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-="); warn!("=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=");
if report {
let mut file = File::create("report.txt").await?;
file.write_all(format!("{}", error).as_bytes()).await?;
}
} }
let hash = { let hash = {

View file

@ -6,13 +6,13 @@ use std::path::PathBuf;
use crate::data::Flake; use crate::data::Flake;
/// Uses `nix` to fetch the provided flake and read general information /// Uses `nix` to fetch the provided flake and read general information
/// about it using `nix flake info` /// about it using `nix flake metadata`
pub fn get_flake_info<T: AsRef<str> + Display>( pub fn get_flake_info<T: AsRef<str> + Display>(
flake_ref: T, flake_ref: T,
temp_store: bool, temp_store: bool,
extra: &[String], extra: &[String],
) -> Result<Flake> { ) -> Result<Flake> {
let args = ["flake", "info", "--json", "--no-write-lock-file"].iter(); let args = ["flake", "metadata", "--json", "--no-write-lock-file"];
let mut command = Command::with_args("nix", args); let mut command = Command::with_args("nix", args);
let command = command.add_arg(flake_ref.as_ref()); let command = command.add_arg(flake_ref.as_ref());
if temp_store { if temp_store {

View file

@ -3,7 +3,7 @@ use serde::{Deserialize, Serialize};
use std::{ use std::{
ffi::OsStr, ffi::OsStr,
fs::{self, File}, fs::{self, File},
io::Read, io::{Read, self},
path::Path, path::Path,
}; };
@ -74,8 +74,8 @@ impl Source {
} }
} }
pub fn read_sources_file(path: &Path) -> Result<Vec<Source>> { pub fn read_sources_file(path: &Path) -> io::Result<Vec<Source>> {
let mut file = File::open(path).with_context(|| "Failed to open input file")?; let mut file = File::open(path)?;
let mut buf = String::new(); let mut buf = String::new();
file.read_to_string(&mut buf)?; file.read_to_string(&mut buf)?;