mirror of
https://github.com/nix-community/nurl.git
synced 2024-09-11 18:39:19 +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::{
|
use crate::{
|
||||||
fetcher::{UrlFetcher, UrlFlakeFetcher},
|
common::{CvsFetcher, CvsFlakeFetcher},
|
||||||
impl_fetcher,
|
impl_fetcher,
|
||||||
};
|
};
|
||||||
|
|
||||||
pub struct Fetchgit;
|
pub struct Fetchgit;
|
||||||
impl_fetcher!(Fetchgit);
|
impl_fetcher!(Fetchgit);
|
||||||
|
|
||||||
impl UrlFetcher for Fetchgit {
|
impl CvsFetcher for Fetchgit {
|
||||||
const NAME: &'static str = "fetchgit";
|
const NAME: &'static str = "fetchgit";
|
||||||
}
|
}
|
||||||
|
|
||||||
impl UrlFlakeFetcher for Fetchgit {
|
impl CvsFlakeFetcher for Fetchgit {
|
||||||
const FLAKE_TYPE: &'static str = "git";
|
const FLAKE_TYPE: &'static str = "git";
|
||||||
}
|
}
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
use crate::{
|
use crate::{
|
||||||
fetcher::{SimpleFetcher, SimpleFlakeFetcher},
|
common::{SimpleFetcher, SimpleFlakeFetcher},
|
||||||
impl_fetcher,
|
impl_fetcher,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
use crate::{
|
use crate::{
|
||||||
fetcher::{SimpleFetcher, SimpleFlakeFetcher},
|
common::{SimpleFetcher, SimpleFlakeFetcher},
|
||||||
impl_fetcher,
|
impl_fetcher,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -1,15 +1,15 @@
|
|||||||
use crate::{
|
use crate::{
|
||||||
fetcher::{UrlFetcher, UrlFlakeFetcher},
|
common::{CvsFetcher, CvsFlakeFetcher},
|
||||||
impl_fetcher,
|
impl_fetcher,
|
||||||
};
|
};
|
||||||
|
|
||||||
pub struct Fetchhg;
|
pub struct Fetchhg;
|
||||||
impl_fetcher!(Fetchhg);
|
impl_fetcher!(Fetchhg);
|
||||||
|
|
||||||
impl UrlFetcher for Fetchhg {
|
impl CvsFetcher for Fetchhg {
|
||||||
const NAME: &'static str = "fetchhg";
|
const NAME: &'static str = "fetchhg";
|
||||||
}
|
}
|
||||||
|
|
||||||
impl UrlFlakeFetcher for Fetchhg {
|
impl CvsFlakeFetcher for Fetchhg {
|
||||||
const FLAKE_TYPE: &'static str = "hg";
|
const FLAKE_TYPE: &'static str = "hg";
|
||||||
}
|
}
|
||||||
|
@ -10,19 +10,11 @@ pub use gitlab::FetchFromGitLab;
|
|||||||
pub use hg::Fetchhg;
|
pub use hg::Fetchhg;
|
||||||
pub use sourcehut::FetchFromSourcehut;
|
pub use sourcehut::FetchFromSourcehut;
|
||||||
|
|
||||||
|
use anyhow::Result;
|
||||||
use enum_dispatch::enum_dispatch;
|
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 url::Url;
|
||||||
|
|
||||||
use std::{
|
use std::io::Write;
|
||||||
fmt::Write as _,
|
|
||||||
io::{BufRead, Write},
|
|
||||||
process::{Command, Output, Stdio},
|
|
||||||
};
|
|
||||||
|
|
||||||
#[enum_dispatch]
|
#[enum_dispatch]
|
||||||
pub trait Fetcher {
|
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::{
|
use crate::{
|
||||||
fetcher::{SimpleFetcher, SimpleFlakeFetcher},
|
common::{SimpleFetcher, SimpleFlakeFetcher},
|
||||||
impl_fetcher,
|
impl_fetcher,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
#![allow(clippy::too_many_arguments)]
|
#![allow(clippy::too_many_arguments)]
|
||||||
|
|
||||||
mod cli;
|
mod cli;
|
||||||
|
mod common;
|
||||||
mod fetcher;
|
mod fetcher;
|
||||||
|
|
||||||
use anyhow::{bail, Result};
|
use anyhow::{bail, Result};
|
||||||
|
Loading…
Reference in New Issue
Block a user