mirror of
https://github.com/nix-community/nurl.git
synced 2024-08-16 21:41:20 +03:00
restructure
This commit is contained in:
parent
d14dd7061d
commit
1e2c7c24c4
154
src/common/cvs.rs
Normal file
154
src/common/cvs.rs
Normal file
@ -0,0 +1,154 @@
|
||||
use anyhow::Result;
|
||||
use indoc::writedoc;
|
||||
use serde_json::json;
|
||||
use url::Url;
|
||||
|
||||
use std::{fmt::Write as _, io::Write};
|
||||
|
||||
use crate::common::{flake_prefetch, fod_prefetch};
|
||||
|
||||
pub trait CvsFetcher {
|
||||
const NAME: &'static str;
|
||||
|
||||
fn fetch_fod(&self, url: &Url, rev: &str, args: &[(String, String)]) -> Result<String> {
|
||||
let mut expr = format!(
|
||||
r#"(import <nixpkgs> {{}}).{}{{url="{url}";rev="{rev}";hash="sha256-AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=";"#,
|
||||
Self::NAME
|
||||
);
|
||||
for (key, value) in args {
|
||||
write!(expr, "{key}={value};")?;
|
||||
}
|
||||
expr.push('}');
|
||||
fod_prefetch(expr)
|
||||
}
|
||||
|
||||
fn write_nix(
|
||||
&self,
|
||||
out: &mut impl Write,
|
||||
url: Url,
|
||||
rev: String,
|
||||
hash: String,
|
||||
args: Vec<(String, String)>,
|
||||
indent: String,
|
||||
) -> Result<()> {
|
||||
writedoc!(
|
||||
out,
|
||||
r#"
|
||||
{} {{
|
||||
{indent} url = "{url}";
|
||||
{indent} rev = "{rev}";
|
||||
{indent} hash = "{hash}";
|
||||
"#,
|
||||
Self::NAME
|
||||
)?;
|
||||
|
||||
for (key, value) in args {
|
||||
writeln!(out, "{indent} {key} = {value};")?;
|
||||
}
|
||||
|
||||
write!(out, "{indent}}}")?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn write_json(
|
||||
&self,
|
||||
out: &mut impl Write,
|
||||
url: Url,
|
||||
rev: String,
|
||||
hash: String,
|
||||
args: Vec<(String, String)>,
|
||||
) -> Result<()> {
|
||||
let mut fetcher_args = json!({
|
||||
"url": url.to_string(),
|
||||
"rev": rev,
|
||||
"hash": hash,
|
||||
});
|
||||
|
||||
for (key, value) in args {
|
||||
fetcher_args[key] = json!(value);
|
||||
}
|
||||
|
||||
serde_json::to_writer(
|
||||
out,
|
||||
&json!({
|
||||
"fetcher": Self::NAME,
|
||||
"args": {
|
||||
"url": url.to_string(),
|
||||
"rev": rev,
|
||||
"hash": hash,
|
||||
},
|
||||
}),
|
||||
)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
pub trait CvsFodFetcher: CvsFetcher {
|
||||
fn fetch_nix_impl(
|
||||
&self,
|
||||
out: &mut impl Write,
|
||||
url: Url,
|
||||
rev: String,
|
||||
args: Vec<(String, String)>,
|
||||
indent: String,
|
||||
) -> Result<()> {
|
||||
let hash = self.fetch_fod(&url, &rev, &args)?;
|
||||
self.write_nix(out, url, rev, hash, args, indent)
|
||||
}
|
||||
|
||||
fn fetch_json_impl(
|
||||
&self,
|
||||
out: &mut impl Write,
|
||||
url: Url,
|
||||
rev: String,
|
||||
args: Vec<(String, String)>,
|
||||
) -> Result<()> {
|
||||
let hash = self.fetch_fod(&url, &rev, &args)?;
|
||||
self.write_json(out, url, rev, hash, args)
|
||||
}
|
||||
}
|
||||
|
||||
pub trait CvsFlakeFetcher: CvsFetcher {
|
||||
const FLAKE_TYPE: &'static str;
|
||||
|
||||
fn fetch(&self, url: &Url, rev: &str) -> Result<String> {
|
||||
flake_prefetch(format!(
|
||||
"{}+{url}?{}={rev}",
|
||||
Self::FLAKE_TYPE,
|
||||
if rev.len() == 40 { "rev" } else { "ref" },
|
||||
))
|
||||
}
|
||||
|
||||
fn fetch_nix_impl(
|
||||
&self,
|
||||
out: &mut impl Write,
|
||||
url: Url,
|
||||
rev: String,
|
||||
args: Vec<(String, String)>,
|
||||
indent: String,
|
||||
) -> Result<()> {
|
||||
let hash = if args.is_empty() {
|
||||
self.fetch(&url, &rev)?
|
||||
} else {
|
||||
self.fetch_fod(&url, &rev, &args)?
|
||||
};
|
||||
self.write_nix(out, url, rev, hash, args, indent)
|
||||
}
|
||||
|
||||
fn fetch_json_impl(
|
||||
&self,
|
||||
out: &mut impl Write,
|
||||
url: Url,
|
||||
rev: String,
|
||||
args: Vec<(String, String)>,
|
||||
) -> Result<()> {
|
||||
let hash = if args.is_empty() {
|
||||
self.fetch(&url, &rev)?
|
||||
} else {
|
||||
self.fetch_fod(&url, &rev, &args)?
|
||||
};
|
||||
self.write_json(out, url, rev, hash, args)
|
||||
}
|
||||
}
|
86
src/common/mod.rs
Normal file
86
src/common/mod.rs
Normal file
@ -0,0 +1,86 @@
|
||||
mod cvs;
|
||||
mod simple;
|
||||
|
||||
pub use cvs::{CvsFetcher, CvsFlakeFetcher, CvsFodFetcher};
|
||||
pub use simple::{SimpleFetcher, SimpleFlakeFetcher, SimpleFodFetcher};
|
||||
|
||||
use anyhow::{anyhow, bail, Result};
|
||||
use serde::Deserialize;
|
||||
|
||||
use std::{
|
||||
io::BufRead,
|
||||
process::{Command, Output, Stdio},
|
||||
};
|
||||
|
||||
trait GetStdout {
|
||||
fn get_stdout(&mut self) -> Result<Vec<u8>>;
|
||||
}
|
||||
|
||||
impl GetStdout for Command {
|
||||
fn get_stdout(&mut self) -> Result<Vec<u8>> {
|
||||
let Output { stdout, status, .. } = self.stderr(Stdio::inherit()).output()?;
|
||||
if !status.success() {
|
||||
bail!("command exited with exit code {}", status);
|
||||
}
|
||||
Ok(stdout)
|
||||
}
|
||||
}
|
||||
|
||||
pub fn flake_prefetch(flake_ref: String) -> Result<String> {
|
||||
#[derive(Deserialize)]
|
||||
struct PrefetchOutput {
|
||||
hash: String,
|
||||
}
|
||||
|
||||
eprintln!("$ nix flake prefetch --json {flake_ref}");
|
||||
Ok(serde_json::from_slice::<PrefetchOutput>(
|
||||
&Command::new("nix")
|
||||
.arg("flake")
|
||||
.arg("prefetch")
|
||||
.arg("--json")
|
||||
.arg(flake_ref)
|
||||
.get_stdout()?,
|
||||
)?
|
||||
.hash)
|
||||
}
|
||||
|
||||
pub fn fod_prefetch(expr: String) -> Result<String> {
|
||||
eprintln!("$ nix build --impure --no-link --expr '{expr}'");
|
||||
|
||||
let Output {
|
||||
stdout,
|
||||
stderr,
|
||||
status,
|
||||
} = Command::new("nix")
|
||||
.arg("build")
|
||||
.arg("--impure")
|
||||
.arg("--no-link")
|
||||
.arg("--expr")
|
||||
.arg(expr)
|
||||
.output()?;
|
||||
|
||||
if status.success() {
|
||||
bail!(
|
||||
"command succeeded unexpectedly\nstdout:\n{}",
|
||||
String::from_utf8_lossy(&stdout),
|
||||
);
|
||||
}
|
||||
|
||||
let mut lines = stderr.lines();
|
||||
while let Some(line) = lines.next() {
|
||||
if !matches!(line, Ok(line) if line.trim_start().starts_with("specified:")) {
|
||||
continue;
|
||||
}
|
||||
let Some(line) = lines.next() else { break; };
|
||||
if let Ok(line) = line {
|
||||
let Some(hash) = line.trim_start().strip_prefix("got:") else { continue; };
|
||||
return Ok(hash.trim().into());
|
||||
}
|
||||
}
|
||||
|
||||
Err(anyhow!(
|
||||
"failed to find the hash from error messages\nstdout: {}\nstderr:\n{}",
|
||||
String::from_utf8_lossy(&stdout),
|
||||
String::from_utf8_lossy(&stderr),
|
||||
))
|
||||
}
|
196
src/common/simple.rs
Normal file
196
src/common/simple.rs
Normal file
@ -0,0 +1,196 @@
|
||||
use anyhow::{Context, Result};
|
||||
use indoc::writedoc;
|
||||
use serde_json::json;
|
||||
use url::Url;
|
||||
|
||||
use std::{fmt::Write as _, io::Write};
|
||||
|
||||
use crate::common::{flake_prefetch, fod_prefetch};
|
||||
|
||||
pub trait SimpleFetcher<'a> {
|
||||
const NAME: &'static str;
|
||||
|
||||
fn host(&'a self) -> &'a Option<String>;
|
||||
|
||||
fn get_repo(&self, url: &Url) -> Option<(String, String)> {
|
||||
let mut xs = url.path_segments()?;
|
||||
let owner = xs.next()?;
|
||||
let repo = xs.next()?;
|
||||
Some((
|
||||
owner.into(),
|
||||
repo.strip_suffix(".git").unwrap_or(repo).into(),
|
||||
))
|
||||
}
|
||||
|
||||
fn fetch_fod(
|
||||
&'a self,
|
||||
url: &Url,
|
||||
rev: &str,
|
||||
args: &[(String, String)],
|
||||
) -> Result<(String, String, String)> {
|
||||
let (owner, repo) = self
|
||||
.get_repo(url)
|
||||
.with_context(|| format!("failed to parse {url}"))?;
|
||||
|
||||
let mut expr = format!(
|
||||
r#"(import <nixpkgs> {{}}).{}{{owner="{owner}";repo="{repo}";rev="{rev}";hash="sha256-AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=";"#,
|
||||
Self::NAME
|
||||
);
|
||||
if let Some(host) = self.host() {
|
||||
write!(expr, r#"domain="{host}""#)?;
|
||||
}
|
||||
for (key, value) in args {
|
||||
write!(expr, "{key}={value};")?;
|
||||
}
|
||||
expr.push('}');
|
||||
|
||||
let hash = fod_prefetch(expr)?;
|
||||
|
||||
Ok((owner, repo, hash))
|
||||
}
|
||||
|
||||
fn write_nix(
|
||||
&'a self,
|
||||
out: &mut impl Write,
|
||||
owner: String,
|
||||
repo: String,
|
||||
rev: String,
|
||||
hash: String,
|
||||
args: Vec<(String, String)>,
|
||||
indent: String,
|
||||
) -> Result<()> {
|
||||
writeln!(out, "{} {{", Self::NAME)?;
|
||||
|
||||
if let Some(domain) = self.host() {
|
||||
writeln!(out, r#"{indent} domain = "{domain}";"#)?;
|
||||
}
|
||||
|
||||
writedoc!(
|
||||
out,
|
||||
r#"
|
||||
{indent} owner = "{owner}";
|
||||
{indent} repo = "{repo}";
|
||||
{indent} rev = "{rev}";
|
||||
{indent} hash = "{hash}";
|
||||
"#
|
||||
)?;
|
||||
|
||||
for (key, value) in args {
|
||||
writeln!(out, "{indent} {key} = {value};")?;
|
||||
}
|
||||
|
||||
write!(out, "{indent}}}")?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn write_json(
|
||||
&'a self,
|
||||
out: &mut impl Write,
|
||||
owner: String,
|
||||
repo: String,
|
||||
rev: String,
|
||||
hash: String,
|
||||
args: Vec<(String, String)>,
|
||||
) -> Result<()> {
|
||||
let mut fetcher_args = json! ({
|
||||
"owner": owner,
|
||||
"repo": repo,
|
||||
"rev": rev,
|
||||
"hash": hash,
|
||||
});
|
||||
|
||||
if let Some(host) = self.host() {
|
||||
fetcher_args["host"] = json!(host);
|
||||
}
|
||||
|
||||
for (key, value) in args {
|
||||
fetcher_args[key] = json!(value);
|
||||
}
|
||||
|
||||
serde_json::to_writer(
|
||||
out,
|
||||
&json!({
|
||||
"fetcher": Self::NAME,
|
||||
"args": fetcher_args,
|
||||
}),
|
||||
)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
pub trait SimpleFodFetcher<'a>: SimpleFetcher<'a> {
|
||||
fn fetch_nix_impl(
|
||||
&'a self,
|
||||
out: &mut impl Write,
|
||||
url: Url,
|
||||
rev: String,
|
||||
args: Vec<(String, String)>,
|
||||
indent: String,
|
||||
) -> Result<()> {
|
||||
let (owner, repo, hash) = self.fetch_fod(&url, &rev, &args)?;
|
||||
self.write_nix(out, owner, repo, rev, hash, args, indent)
|
||||
}
|
||||
|
||||
fn fetch_json_impl(
|
||||
&'a self,
|
||||
out: &mut impl Write,
|
||||
url: Url,
|
||||
rev: String,
|
||||
args: Vec<(String, String)>,
|
||||
) -> Result<()> {
|
||||
let (owner, repo, hash) = self.fetch_fod(&url, &rev, &args)?;
|
||||
self.write_json(out, owner, repo, rev, hash, args)
|
||||
}
|
||||
}
|
||||
|
||||
pub trait SimpleFlakeFetcher<'a>: SimpleFetcher<'a> {
|
||||
const FLAKE_TYPE: &'static str;
|
||||
|
||||
fn fetch(&'a self, url: &Url, rev: &str) -> Result<(String, String, String)> {
|
||||
let (owner, repo) = self
|
||||
.get_repo(url)
|
||||
.with_context(|| format!("failed to parse {url} as a {} url", Self::FLAKE_TYPE))?;
|
||||
|
||||
let hash = flake_prefetch(if let Some(host) = self.host() {
|
||||
format!("{}:{owner}/{repo}/{rev}?host={}", Self::FLAKE_TYPE, host)
|
||||
} else {
|
||||
format!("{}:{owner}/{repo}/{rev}", Self::FLAKE_TYPE)
|
||||
})?;
|
||||
|
||||
Ok((owner, repo, hash))
|
||||
}
|
||||
|
||||
fn fetch_nix_impl(
|
||||
&'a self,
|
||||
out: &mut impl Write,
|
||||
url: Url,
|
||||
rev: String,
|
||||
args: Vec<(String, String)>,
|
||||
indent: String,
|
||||
) -> Result<()> {
|
||||
let (owner, repo, hash) = if args.is_empty() {
|
||||
self.fetch(&url, &rev)?
|
||||
} else {
|
||||
self.fetch_fod(&url, &rev, &args)?
|
||||
};
|
||||
|
||||
self.write_nix(out, owner, repo, rev, hash, args, indent)
|
||||
}
|
||||
|
||||
fn fetch_json_impl(
|
||||
&'a self,
|
||||
out: &mut impl Write,
|
||||
url: Url,
|
||||
rev: String,
|
||||
args: Vec<(String, String)>,
|
||||
) -> Result<()> {
|
||||
let (owner, repo, hash) = if args.is_empty() {
|
||||
self.fetch(&url, &rev)?
|
||||
} else {
|
||||
self.fetch_fod(&url, &rev, &args)?
|
||||
};
|
||||
self.write_json(out, owner, repo, rev, hash, args)
|
||||
}
|
||||
}
|
@ -1,15 +1,15 @@
|
||||
use crate::{
|
||||
fetcher::{UrlFetcher, UrlFlakeFetcher},
|
||||
common::{CvsFetcher, CvsFlakeFetcher},
|
||||
impl_fetcher,
|
||||
};
|
||||
|
||||
pub struct Fetchgit;
|
||||
impl_fetcher!(Fetchgit);
|
||||
|
||||
impl UrlFetcher for Fetchgit {
|
||||
impl CvsFetcher for Fetchgit {
|
||||
const NAME: &'static str = "fetchgit";
|
||||
}
|
||||
|
||||
impl UrlFlakeFetcher for Fetchgit {
|
||||
impl CvsFlakeFetcher for Fetchgit {
|
||||
const FLAKE_TYPE: &'static str = "git";
|
||||
}
|
||||
|
@ -1,5 +1,5 @@
|
||||
use crate::{
|
||||
fetcher::{SimpleFetcher, SimpleFlakeFetcher},
|
||||
common::{SimpleFetcher, SimpleFlakeFetcher},
|
||||
impl_fetcher,
|
||||
};
|
||||
|
||||
|
@ -1,5 +1,5 @@
|
||||
use crate::{
|
||||
fetcher::{SimpleFetcher, SimpleFlakeFetcher},
|
||||
common::{SimpleFetcher, SimpleFlakeFetcher},
|
||||
impl_fetcher,
|
||||
};
|
||||
|
||||
|
@ -1,15 +1,15 @@
|
||||
use crate::{
|
||||
fetcher::{UrlFetcher, UrlFlakeFetcher},
|
||||
common::{CvsFetcher, CvsFlakeFetcher},
|
||||
impl_fetcher,
|
||||
};
|
||||
|
||||
pub struct Fetchhg;
|
||||
impl_fetcher!(Fetchhg);
|
||||
|
||||
impl UrlFetcher for Fetchhg {
|
||||
impl CvsFetcher for Fetchhg {
|
||||
const NAME: &'static str = "fetchhg";
|
||||
}
|
||||
|
||||
impl UrlFlakeFetcher for Fetchhg {
|
||||
impl CvsFlakeFetcher for Fetchhg {
|
||||
const FLAKE_TYPE: &'static str = "hg";
|
||||
}
|
||||
|
@ -10,19 +10,11 @@ pub use gitlab::FetchFromGitLab;
|
||||
pub use hg::Fetchhg;
|
||||
pub use sourcehut::FetchFromSourcehut;
|
||||
|
||||
use anyhow::Result;
|
||||
use enum_dispatch::enum_dispatch;
|
||||
use indoc::writedoc;
|
||||
use serde_json::json;
|
||||
|
||||
use anyhow::{anyhow, bail, Context, Result};
|
||||
use serde::Deserialize;
|
||||
use url::Url;
|
||||
|
||||
use std::{
|
||||
fmt::Write as _,
|
||||
io::{BufRead, Write},
|
||||
process::{Command, Output, Stdio},
|
||||
};
|
||||
use std::io::Write;
|
||||
|
||||
#[enum_dispatch]
|
||||
pub trait Fetcher {
|
||||
@ -79,410 +71,3 @@ macro_rules! impl_fetcher {
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
pub trait SimpleFetcher<'a> {
|
||||
const NAME: &'static str;
|
||||
|
||||
fn host(&'a self) -> &'a Option<String>;
|
||||
|
||||
fn get_repo(&self, url: &Url) -> Option<(String, String)> {
|
||||
let mut xs = url.path_segments()?;
|
||||
let owner = xs.next()?;
|
||||
let repo = xs.next()?;
|
||||
Some((
|
||||
owner.into(),
|
||||
repo.strip_suffix(".git").unwrap_or(repo).into(),
|
||||
))
|
||||
}
|
||||
|
||||
fn fetch_fod(
|
||||
&'a self,
|
||||
url: &Url,
|
||||
rev: &str,
|
||||
args: &[(String, String)],
|
||||
) -> Result<(String, String, String)> {
|
||||
let (owner, repo) = self
|
||||
.get_repo(url)
|
||||
.with_context(|| format!("failed to parse {url}"))?;
|
||||
|
||||
let mut expr = format!(
|
||||
r#"(import <nixpkgs> {{}}).{}{{owner="{owner}";repo="{repo}";rev="{rev}";hash="sha256-AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=";"#,
|
||||
Self::NAME
|
||||
);
|
||||
if let Some(host) = self.host() {
|
||||
write!(expr, r#"domain="{host}""#)?;
|
||||
}
|
||||
for (key, value) in args {
|
||||
write!(expr, "{key}={value};")?;
|
||||
}
|
||||
expr.push('}');
|
||||
|
||||
let hash = fod_prefetch(expr)?;
|
||||
|
||||
Ok((owner, repo, hash))
|
||||
}
|
||||
|
||||
fn write_nix(
|
||||
&'a self,
|
||||
out: &mut impl Write,
|
||||
owner: String,
|
||||
repo: String,
|
||||
rev: String,
|
||||
hash: String,
|
||||
args: Vec<(String, String)>,
|
||||
indent: String,
|
||||
) -> Result<()> {
|
||||
writeln!(out, "{} {{", Self::NAME)?;
|
||||
|
||||
if let Some(domain) = self.host() {
|
||||
writeln!(out, r#"{indent} domain = "{domain}";"#)?;
|
||||
}
|
||||
|
||||
writedoc!(
|
||||
out,
|
||||
r#"
|
||||
{indent} owner = "{owner}";
|
||||
{indent} repo = "{repo}";
|
||||
{indent} rev = "{rev}";
|
||||
{indent} hash = "{hash}";
|
||||
"#
|
||||
)?;
|
||||
|
||||
for (key, value) in args {
|
||||
writeln!(out, "{indent} {key} = {value};")?;
|
||||
}
|
||||
|
||||
write!(out, "{indent}}}")?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn write_json(
|
||||
&'a self,
|
||||
out: &mut impl Write,
|
||||
owner: String,
|
||||
repo: String,
|
||||
rev: String,
|
||||
hash: String,
|
||||
args: Vec<(String, String)>,
|
||||
) -> Result<()> {
|
||||
let mut fetcher_args = json! ({
|
||||
"owner": owner,
|
||||
"repo": repo,
|
||||
"rev": rev,
|
||||
"hash": hash,
|
||||
});
|
||||
|
||||
if let Some(host) = self.host() {
|
||||
fetcher_args["host"] = json!(host);
|
||||
}
|
||||
|
||||
for (key, value) in args {
|
||||
fetcher_args[key] = json!(value);
|
||||
}
|
||||
|
||||
serde_json::to_writer(
|
||||
out,
|
||||
&json!({
|
||||
"fetcher": Self::NAME,
|
||||
"args": fetcher_args,
|
||||
}),
|
||||
)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
pub trait SimpleFodFetcher<'a>: SimpleFetcher<'a> {
|
||||
fn fetch_nix_impl(
|
||||
&'a self,
|
||||
out: &mut impl Write,
|
||||
url: Url,
|
||||
rev: String,
|
||||
args: Vec<(String, String)>,
|
||||
indent: String,
|
||||
) -> Result<()> {
|
||||
let (owner, repo, hash) = self.fetch_fod(&url, &rev, &args)?;
|
||||
self.write_nix(out, owner, repo, rev, hash, args, indent)
|
||||
}
|
||||
|
||||
fn fetch_json_impl(
|
||||
&'a self,
|
||||
out: &mut impl Write,
|
||||
url: Url,
|
||||
rev: String,
|
||||
args: Vec<(String, String)>,
|
||||
) -> Result<()> {
|
||||
let (owner, repo, hash) = self.fetch_fod(&url, &rev, &args)?;
|
||||
self.write_json(out, owner, repo, rev, hash, args)
|
||||
}
|
||||
}
|
||||
|
||||
pub trait SimpleFlakeFetcher<'a>: SimpleFetcher<'a> {
|
||||
const FLAKE_TYPE: &'static str;
|
||||
|
||||
fn fetch(&'a self, url: &Url, rev: &str) -> Result<(String, String, String)> {
|
||||
let (owner, repo) = self
|
||||
.get_repo(url)
|
||||
.with_context(|| format!("failed to parse {url} as a {} url", Self::FLAKE_TYPE))?;
|
||||
|
||||
let hash = flake_prefetch(if let Some(host) = self.host() {
|
||||
format!("{}:{owner}/{repo}/{rev}?host={}", Self::FLAKE_TYPE, host)
|
||||
} else {
|
||||
format!("{}:{owner}/{repo}/{rev}", Self::FLAKE_TYPE)
|
||||
})?;
|
||||
|
||||
Ok((owner, repo, hash))
|
||||
}
|
||||
|
||||
fn fetch_nix_impl(
|
||||
&'a self,
|
||||
out: &mut impl Write,
|
||||
url: Url,
|
||||
rev: String,
|
||||
args: Vec<(String, String)>,
|
||||
indent: String,
|
||||
) -> Result<()> {
|
||||
let (owner, repo, hash) = if args.is_empty() {
|
||||
self.fetch(&url, &rev)?
|
||||
} else {
|
||||
self.fetch_fod(&url, &rev, &args)?
|
||||
};
|
||||
|
||||
self.write_nix(out, owner, repo, rev, hash, args, indent)
|
||||
}
|
||||
|
||||
fn fetch_json_impl(
|
||||
&'a self,
|
||||
out: &mut impl Write,
|
||||
url: Url,
|
||||
rev: String,
|
||||
args: Vec<(String, String)>,
|
||||
) -> Result<()> {
|
||||
let (owner, repo, hash) = if args.is_empty() {
|
||||
self.fetch(&url, &rev)?
|
||||
} else {
|
||||
self.fetch_fod(&url, &rev, &args)?
|
||||
};
|
||||
self.write_json(out, owner, repo, rev, hash, args)
|
||||
}
|
||||
}
|
||||
|
||||
pub trait UrlFetcher {
|
||||
const NAME: &'static str;
|
||||
|
||||
fn fetch_fod(&self, url: &Url, rev: &str, args: &[(String, String)]) -> Result<String> {
|
||||
let mut expr = format!(
|
||||
r#"(import <nixpkgs> {{}}).{}{{url="{url}";rev="{rev}";hash="sha256-AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=";"#,
|
||||
Self::NAME
|
||||
);
|
||||
for (key, value) in args {
|
||||
write!(expr, "{key}={value};")?;
|
||||
}
|
||||
expr.push('}');
|
||||
fod_prefetch(expr)
|
||||
}
|
||||
|
||||
fn write_nix(
|
||||
&self,
|
||||
out: &mut impl Write,
|
||||
url: Url,
|
||||
rev: String,
|
||||
hash: String,
|
||||
args: Vec<(String, String)>,
|
||||
indent: String,
|
||||
) -> Result<()> {
|
||||
writedoc!(
|
||||
out,
|
||||
r#"
|
||||
{} {{
|
||||
{indent} url = "{url}";
|
||||
{indent} rev = "{rev}";
|
||||
{indent} hash = "{hash}";
|
||||
"#,
|
||||
Self::NAME
|
||||
)?;
|
||||
|
||||
for (key, value) in args {
|
||||
writeln!(out, "{indent} {key} = {value};")?;
|
||||
}
|
||||
|
||||
write!(out, "{indent}}}")?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn write_json(
|
||||
&self,
|
||||
out: &mut impl Write,
|
||||
url: Url,
|
||||
rev: String,
|
||||
hash: String,
|
||||
args: Vec<(String, String)>,
|
||||
) -> Result<()> {
|
||||
let mut fetcher_args = json!({
|
||||
"url": url.to_string(),
|
||||
"rev": rev,
|
||||
"hash": hash,
|
||||
});
|
||||
|
||||
for (key, value) in args {
|
||||
fetcher_args[key] = json!(value);
|
||||
}
|
||||
|
||||
serde_json::to_writer(
|
||||
out,
|
||||
&json!({
|
||||
"fetcher": Self::NAME,
|
||||
"args": {
|
||||
"url": url.to_string(),
|
||||
"rev": rev,
|
||||
"hash": hash,
|
||||
},
|
||||
}),
|
||||
)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
pub trait UrlFodFetcher: UrlFetcher {
|
||||
fn fetch_nix_impl(
|
||||
&self,
|
||||
out: &mut impl Write,
|
||||
url: Url,
|
||||
rev: String,
|
||||
args: Vec<(String, String)>,
|
||||
indent: String,
|
||||
) -> Result<()> {
|
||||
let hash = self.fetch_fod(&url, &rev, &args)?;
|
||||
self.write_nix(out, url, rev, hash, args, indent)
|
||||
}
|
||||
|
||||
fn fetch_json_impl(
|
||||
&self,
|
||||
out: &mut impl Write,
|
||||
url: Url,
|
||||
rev: String,
|
||||
args: Vec<(String, String)>,
|
||||
) -> Result<()> {
|
||||
let hash = self.fetch_fod(&url, &rev, &args)?;
|
||||
self.write_json(out, url, rev, hash, args)
|
||||
}
|
||||
}
|
||||
|
||||
pub trait UrlFlakeFetcher: UrlFetcher {
|
||||
const FLAKE_TYPE: &'static str;
|
||||
|
||||
fn fetch(&self, url: &Url, rev: &str) -> Result<String> {
|
||||
flake_prefetch(format!(
|
||||
"{}+{url}?{}={rev}",
|
||||
Self::FLAKE_TYPE,
|
||||
if rev.len() == 40 { "rev" } else { "ref" },
|
||||
))
|
||||
}
|
||||
|
||||
fn fetch_nix_impl(
|
||||
&self,
|
||||
out: &mut impl Write,
|
||||
url: Url,
|
||||
rev: String,
|
||||
args: Vec<(String, String)>,
|
||||
indent: String,
|
||||
) -> Result<()> {
|
||||
let hash = if args.is_empty() {
|
||||
self.fetch(&url, &rev)?
|
||||
} else {
|
||||
self.fetch_fod(&url, &rev, &args)?
|
||||
};
|
||||
self.write_nix(out, url, rev, hash, args, indent)
|
||||
}
|
||||
|
||||
fn fetch_json_impl(
|
||||
&self,
|
||||
out: &mut impl Write,
|
||||
url: Url,
|
||||
rev: String,
|
||||
args: Vec<(String, String)>,
|
||||
) -> Result<()> {
|
||||
let hash = if args.is_empty() {
|
||||
self.fetch(&url, &rev)?
|
||||
} else {
|
||||
self.fetch_fod(&url, &rev, &args)?
|
||||
};
|
||||
self.write_json(out, url, rev, hash, args)
|
||||
}
|
||||
}
|
||||
|
||||
trait GetStdout {
|
||||
fn get_stdout(&mut self) -> Result<Vec<u8>>;
|
||||
}
|
||||
|
||||
impl GetStdout for Command {
|
||||
fn get_stdout(&mut self) -> Result<Vec<u8>> {
|
||||
let Output { stdout, status, .. } = self.stderr(Stdio::inherit()).output()?;
|
||||
if !status.success() {
|
||||
bail!("command exited with exit code {}", status);
|
||||
}
|
||||
Ok(stdout)
|
||||
}
|
||||
}
|
||||
|
||||
pub fn flake_prefetch(flake_ref: String) -> Result<String> {
|
||||
#[derive(Deserialize)]
|
||||
struct PrefetchOutput {
|
||||
hash: String,
|
||||
}
|
||||
|
||||
eprintln!("$ nix flake prefetch --json {flake_ref}");
|
||||
Ok(serde_json::from_slice::<PrefetchOutput>(
|
||||
&Command::new("nix")
|
||||
.arg("flake")
|
||||
.arg("prefetch")
|
||||
.arg("--json")
|
||||
.arg(flake_ref)
|
||||
.get_stdout()?,
|
||||
)?
|
||||
.hash)
|
||||
}
|
||||
|
||||
pub fn fod_prefetch(expr: String) -> Result<String> {
|
||||
eprintln!("$ nix build --impure --no-link --expr '{expr}'");
|
||||
|
||||
let Output {
|
||||
stdout,
|
||||
stderr,
|
||||
status,
|
||||
} = Command::new("nix")
|
||||
.arg("build")
|
||||
.arg("--impure")
|
||||
.arg("--no-link")
|
||||
.arg("--expr")
|
||||
.arg(expr)
|
||||
.output()?;
|
||||
|
||||
if status.success() {
|
||||
bail!(
|
||||
"command succeeded unexpectedly\nstdout:\n{}",
|
||||
String::from_utf8_lossy(&stdout),
|
||||
);
|
||||
}
|
||||
|
||||
let mut lines = stderr.lines();
|
||||
while let Some(line) = lines.next() {
|
||||
if !matches!(line, Ok(line) if line.trim_start().starts_with("specified:")) {
|
||||
continue;
|
||||
}
|
||||
let Some(line) = lines.next() else { break; };
|
||||
if let Ok(line) = line {
|
||||
let Some(hash) = line.trim_start().strip_prefix("got:") else { continue; };
|
||||
return Ok(hash.trim().into());
|
||||
}
|
||||
}
|
||||
|
||||
Err(anyhow!(
|
||||
"failed to find the hash from error messages\nstdout: {}\nstderr:\n{}",
|
||||
String::from_utf8_lossy(&stdout),
|
||||
String::from_utf8_lossy(&stderr),
|
||||
))
|
||||
}
|
||||
|
@ -1,5 +1,5 @@
|
||||
use crate::{
|
||||
fetcher::{SimpleFetcher, SimpleFlakeFetcher},
|
||||
common::{SimpleFetcher, SimpleFlakeFetcher},
|
||||
impl_fetcher,
|
||||
};
|
||||
|
||||
|
@ -1,6 +1,7 @@
|
||||
#![allow(clippy::too_many_arguments)]
|
||||
|
||||
mod cli;
|
||||
mod common;
|
||||
mod fetcher;
|
||||
|
||||
use anyhow::{bail, Result};
|
||||
|
Loading…
Reference in New Issue
Block a user