Fetch flake outputs and display them

This commit is contained in:
2025-08-24 15:36:47 +02:00
parent 5171770f1a
commit b0079a2756
7 changed files with 165 additions and 12 deletions

View File

@@ -0,0 +1,36 @@
outputAttrs: let
attrNamePathToString = attrNamePath: builtins.concatStringsSep "." attrNamePath;
outputInfo = drv: builtins.listToAttrs (builtins.map (outputName: {
name = outputName;
value = {
store_path = drv."${outputName}".outPath;
};
}) drv.outputs);
recurseAttrs = attrs: attrNamePath: builtins.concatLists (builtins.attrValues (
builtins.mapAttrs (attrName: attrValue:
if builtins.typeOf attrValue == "set" then
if builtins.hasAttr "type" attrValue then
if attrValue.type == "derivation" then
[ {
name = attrNamePathToString (attrNamePath ++ [ attrName ]);
value = {
name = attrValue.name;
derivation_path = attrValue.drvPath;
system = attrValue.system;
outputs = outputInfo attrValue;
};
} ]
else
[ "unknown type" ]
else
recurseAttrs attrValue (attrNamePath ++ [ attrName ])
else
[]
) attrs
));
in
builtins.listToAttrs (recurseAttrs outputAttrs [])

View File

@@ -1,20 +1,20 @@
CREATE TABLE revisions (
revision_uri TEXT NOT NULL PRIMARY KEY,
flake_uri TEXT NOT NULL,
store_path TEXT,
last_modified INT
store_path TEXT NOT NULL,
last_modified INT NOT NULL
);
CREATE TABLE output_attributes (
revision_uri TEXT NOT NULL,
output_attribute_name TEXT NOT NULL,
derivation_path TEXT,
derivation_path TEXT NOT NULL,
PRIMARY KEY (revision_uri, output_attribute_name)
);
CREATE TABLE build_outputs (
derivation_path TEXT NOT NULL,
build_output_name TEXT NOT NULL,
store_path TEXT,
store_path TEXT NOT NULL,
PRIMARY KEY (derivation_path, build_output_name)
);

View File

@@ -13,12 +13,29 @@ use crate::{
},
storage::{
FlakeRow,
OutputAttributeRow,
RevisionRow,
Storage,
},
};
use serde::{
Deserialize,
};
use std::collections::HashMap;
use std::process::Command;
#[derive(Deserialize, Debug)]
pub struct OutputAttribute {
pub derivation_path: String,
pub name: String,
pub outputs: HashMap<String, BuildOutput>,
}
#[derive(Deserialize, Debug)]
pub struct BuildOutput {
pub store_path: String,
}
pub async fn scan_flake(storage: Storage, flake_uri: &str) -> Result<()> {
let scan_time = Utc::now().timestamp();
@@ -30,6 +47,20 @@ pub async fn scan_flake(storage: Storage, flake_uri: &str) -> Result<()> {
storage.set_revision(revision_row)
.await?;
let flake_outputs = fetch_outputs(flake_uri)
.await?;
for (output_attribute_name, derivation_info) in &flake_outputs {
let output_attribute_row = OutputAttributeRow {
revision_uri: flake_metadata.locked.flake_uri()?.clone(),
output_attribute_name: output_attribute_name.clone(),
derivation_path: derivation_info.derivation_path.clone(),
};
storage.set_output_attribute(output_attribute_row)
.await?;
}
Ok(())
}
@@ -52,9 +83,28 @@ pub fn get_revision_from_metadata(flake_metadata: &FlakeMetadata) -> Result<Revi
let revision_row = RevisionRow {
revision_uri: flake_metadata.locked.flake_uri()?.clone(),
flake_uri: flake_metadata.resolved.flake_uri()?.clone(),
store_path: Some(flake_metadata.path.clone()),
last_modified: Some(flake_metadata.locked.last_modified.clone()),
store_path: flake_metadata.path.clone(),
last_modified: flake_metadata.locked.last_modified.clone(),
};
Ok(revision_row)
}
pub async fn fetch_outputs(flake_uri: &str) -> Result<HashMap<String, OutputAttribute>> {
let figure_out_flake_outputs = std::include_str!("../figure-out-flake-outputs.nix");
let flake_outputs_raw = Command::new("nix")
.arg("eval")
.arg("--json")
.arg(format!("{}#hydraJobs", flake_uri))
.arg("--apply")
.arg(figure_out_flake_outputs)
.output()
.context("Failed to fetch flake outputs")?;
println!("{}", str::from_utf8(&flake_outputs_raw.stdout)?);
let flake_outputs: HashMap<String, OutputAttribute> = serde_json::from_slice(&flake_outputs_raw.stdout)
.context("Failed to parse flake outputs")?;
Ok(flake_outputs)
}

View File

@@ -97,14 +97,44 @@ impl Storage {
.await
.context("Failed to fetch data from database")
}
pub async fn set_output_attribute(&self, output_attribute_row: OutputAttributeRow) -> Result<SqliteQueryResult> {
sqlx::query("INSERT INTO output_attributes (revision_uri, output_attribute_name, derivation_path)
VALUES (?, ?, ?)
ON CONFLICT(revision_uri, output_attribute_name) DO UPDATE SET
derivation_path=excluded.derivation_path
")
.bind(&output_attribute_row.revision_uri)
.bind(&output_attribute_row.output_attribute_name)
.bind(&output_attribute_row.derivation_path)
.execute(&self.db)
.await
.context("Failed to execute database query")
}
pub async fn output_attributes_for_revision(&self, revision_uri: &str) -> Result<Vec<OutputAttributeRow>> {
sqlx::query_as("
SELECT
revision_uri,
output_attribute_name,
derivation_path
FROM output_attributes
WHERE revision_uri = ?
ORDER BY output_attribute_name DESC
")
.bind(&revision_uri)
.fetch_all(&self.db)
.await
.context("Failed to fetch data from database")
}
}
#[derive(FromRow)]
pub struct RevisionRow {
pub revision_uri: String,
pub flake_uri: String,
pub store_path: Option<String>,
pub last_modified: Option<i64>,
pub store_path: String,
pub last_modified: i64,
}
impl RevisionRow {
@@ -117,10 +147,7 @@ impl RevisionRow {
}
pub fn last_modified_time(&self) -> Option<DateTime<Utc>> {
match &self.last_modified {
Some(last_modified) => DateTime::from_timestamp(last_modified.clone(), 0),
None => None,
}
DateTime::from_timestamp(self.last_modified.clone(), 0)
}
}
@@ -135,3 +162,33 @@ impl FlakeRow {
}
}
#[derive(FromRow)]
pub struct OutputAttributeRow {
pub revision_uri: String,
pub output_attribute_name: String,
pub derivation_path: String,
}
impl OutputAttributeRow {
pub fn revision_link(&self) -> String {
format!("/revisions/{}", urlencode(&self.revision_uri))
}
pub fn derivation_link(&self) -> String {
format!("/derivations/{}", urlencode(&self.derivation_path))
}
}
#[derive(FromRow)]
pub struct BuildOutputRow {
pub derivation_path: String,
pub build_output_name: String,
pub store_path: String,
}
impl BuildOutputRow {
pub fn derivation_link(&self ) -> String {
format!("/derivations/{}", urlencode(&self.derivation_path))
}
}

View File

@@ -4,6 +4,7 @@ use askama::{
use crate::{
storage::{
FlakeRow,
OutputAttributeRow,
RevisionRow,
},
};
@@ -26,4 +27,5 @@ pub struct FlakeTemplate {
pub struct RevisionTemplate {
pub revision_uri: String,
pub revision: RevisionRow,
pub output_attributes: Vec<OutputAttributeRow>,
}

View File

@@ -118,5 +118,6 @@ async fn route_revision(
Ok(render_template(&RevisionTemplate {
revision_uri: revision_uri.clone(),
revision: state.storage.revision(&revision_uri).await?,
output_attributes: state.storage.output_attributes_for_revision(&revision_uri).await?,
})?)
}

View File

@@ -8,5 +8,12 @@
<li>Revision of: <a href="{{ revision.flake_link() }}">{{ revision.flake_uri }}</a></a>
</ul>
<h2>Outputs</h2>
<ul>
{% for output in output_attributes %}
<li>{{ output.output_attribute_name }}: <a href="{{ output.derivation_link() }}">{{ output.derivation_path }}</a></li>
{% endfor %}
</ul>
{% endblock %}