Add support for parsing the flake.lock.
This commit is contained in:
150
src/nix_util/lock_parser.rs
Normal file
150
src/nix_util/lock_parser.rs
Normal file
@@ -0,0 +1,150 @@
|
||||
use std::collections::BTreeMap;
|
||||
use std::path::Path;
|
||||
|
||||
use serde::Deserialize;
|
||||
use serde::Serialize;
|
||||
|
||||
use crate::Result;
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
#[serde(deny_unknown_fields)]
|
||||
pub(crate) struct FlakeLock {
|
||||
pub(crate) root: String,
|
||||
pub(crate) version: u8,
|
||||
pub(crate) nodes: BTreeMap<String, FlakeLockNode>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
#[serde(deny_unknown_fields)]
|
||||
pub(crate) struct FlakeLockNode {
|
||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||
pub(crate) locked: Option<FlakeLockNodeLocked>,
|
||||
|
||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||
pub(crate) original: Option<FlakeLockNodeOriginal>,
|
||||
|
||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||
pub(crate) inputs: Option<BTreeMap<String, FlakeLockInputValue>>,
|
||||
|
||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||
pub(crate) flake: Option<bool>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
#[serde(deny_unknown_fields)]
|
||||
pub(crate) struct FlakeLockNodeLocked {
|
||||
#[serde(rename = "lastModified")]
|
||||
pub(crate) last_modified: i64,
|
||||
|
||||
#[serde(rename = "narHash")]
|
||||
pub(crate) nar_hash: String,
|
||||
|
||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||
pub(crate) url: Option<String>,
|
||||
|
||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||
pub(crate) owner: Option<String>,
|
||||
|
||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||
pub(crate) repo: Option<String>,
|
||||
|
||||
#[serde(rename = "ref", default, skip_serializing_if = "Option::is_none")]
|
||||
pub(crate) git_ref: Option<String>,
|
||||
|
||||
pub(crate) rev: String,
|
||||
|
||||
#[serde(rename = "revCount", default, skip_serializing_if = "Option::is_none")]
|
||||
pub(crate) rev_count: Option<i64>,
|
||||
|
||||
#[serde(rename = "type")]
|
||||
pub(crate) repo_type: String,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
#[serde(tag = "type", rename_all = "lowercase", deny_unknown_fields)]
|
||||
pub(crate) enum FlakeLockNodeOriginal {
|
||||
Github {
|
||||
owner: String,
|
||||
|
||||
repo: String,
|
||||
|
||||
#[serde(rename = "ref", default, skip_serializing_if = "Option::is_none")]
|
||||
git_ref: Option<String>,
|
||||
|
||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||
rev: Option<String>,
|
||||
},
|
||||
Git {
|
||||
url: String,
|
||||
},
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
#[serde(untagged, deny_unknown_fields)]
|
||||
pub(crate) enum FlakeLockInputValue {
|
||||
String(String),
|
||||
List(Vec<String>),
|
||||
}
|
||||
|
||||
pub(crate) async fn parse_flake_lock<F>(flake_path: F) -> Result<FlakeLock>
|
||||
where
|
||||
F: AsRef<Path>,
|
||||
{
|
||||
let lock_file_path = flake_path.as_ref().join("flake.lock");
|
||||
let contents = tokio::fs::read_to_string(&lock_file_path).await?;
|
||||
let parsed_flake_lock = serde_json::from_str(&contents)?;
|
||||
Ok(parsed_flake_lock)
|
||||
}
|
||||
|
||||
impl FlakeLock {
|
||||
pub(crate) fn get_root_node(&self) -> Result<&FlakeLockNode> {
|
||||
let root_node = self.nodes.get(&self.root).ok_or("Root node not found.")?;
|
||||
Ok(root_node)
|
||||
}
|
||||
|
||||
pub(crate) fn get_inputs<'lock>(
|
||||
&'lock self,
|
||||
source_node: &'lock FlakeLockNode,
|
||||
) -> Result<BTreeMap<&'lock String, &'lock FlakeLockNode>> {
|
||||
let direct_inputs = match &source_node.inputs {
|
||||
Some(inputs) => inputs,
|
||||
None => {
|
||||
return Ok(BTreeMap::new());
|
||||
}
|
||||
};
|
||||
let mut inputs = BTreeMap::new();
|
||||
|
||||
for (input_name, input_id_or_path) in direct_inputs.iter() {
|
||||
inputs.insert(input_name, self.resolve_path(input_id_or_path)?);
|
||||
}
|
||||
|
||||
Ok(inputs)
|
||||
}
|
||||
|
||||
fn resolve_path<'lock>(
|
||||
&'lock self,
|
||||
input_id_or_path: &'lock FlakeLockInputValue,
|
||||
) -> Result<&'lock FlakeLockNode> {
|
||||
match input_id_or_path {
|
||||
FlakeLockInputValue::String(input_id) => {
|
||||
// If the input value is a plain string, then it refers to a node.
|
||||
let node = self.nodes.get(input_id).ok_or("Input not found.")?;
|
||||
Ok(node)
|
||||
}
|
||||
FlakeLockInputValue::List(items) => {
|
||||
// If the input value is a list, then it refers to a path to an input.
|
||||
let mut current_node = self.get_root_node()?;
|
||||
for step in items {
|
||||
let child_input_id_or_path = current_node
|
||||
.inputs
|
||||
.as_ref()
|
||||
.ok_or("Node without inputs while walking input tree.")?
|
||||
.get(step)
|
||||
.ok_or("Step not found in inputs while walking input tree")?;
|
||||
current_node = self.resolve_path(child_input_id_or_path)?;
|
||||
}
|
||||
Ok(current_node)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user