use std::ffi::OsString;
use std::io::{self, Write};
use std::os::unix::ffi::OsStringExt;
use std::path::PathBuf;
use std::process;
use clap::Parser;
use futures::{future, StreamExt};
use nix_index::files::{FileNode, FileType};
use nix_index::hydra::Fetcher;
use nix_index::listings;
use nix_index::{errors::*, CACHE_URL};
use rusqlite::Connection;
async fn update_index(args: &Args) -> Result<()> {
let fetcher = Fetcher::new(CACHE_URL.to_string()).map_err(Error::ParseProxy)?;
let connection = Connection::open_in_memory().map_err(|e| Error::CreateDatabase {
path: args.output.clone(),
source: Box::new(e),
})?;
connection
.execute(
r#"
create table Programs (
name text not null,
system text not null,
package text not null,
primary key (name, system, package)
);
"#,
(),
)
.map_err(|e| Error::CreateDatabase {
path: args.output.clone(),
source: Box::new(e),
})?;
let debug_connection = Connection::open_in_memory().map_err(|e| Error::CreateDatabase {
path: args.debug_output.clone(),
source: Box::new(e),
})?;
debug_connection
.execute(
r#"
create table DebugInfo (
build_id text unique not null,
url text not null,
filename text not null,
primary key (build_id)
);
"#,
(),
)
.map_err(|e| Error::CreateDatabase {
path: args.debug_output.clone(),
source: Box::new(e),
})?;
let systems = match &args.systems {
Some(systems) => systems.iter().map(|x| Some(x.as_str())).collect(),
None => vec![None],
};
eprint!("+ querying available packages");
let (files, watch) = listings::fetch(
&fetcher,
args.jobs,
&args.nixpkgs,
systems,
&args.extra_scopes,
args.show_trace,
)?;
let files = files.map(|r| {
r.unwrap_or_else(|e| {
eprint!("\n{:?}", e);
None
})
});
let (mut indexed, mut missing) = (0, 0);
let files = files.inspect(|entry| {
if entry.is_some() {
indexed += 1;
} else {
missing += 1;
};
eprint!("+ generating index: {:05} paths found :: {:05} paths not in binary cache :: {:05} paths in queue \r",
indexed, missing, watch.queue_len());
io::stderr().flush().expect("flushing stderr failed");
});
let mut files = files.filter_map(future::ready);
eprint!("+ generating index");
eprint!("\r");
while let Some((path, nar, files)) = files.next().await {
let origin = path.origin();
if !origin.toplevel {
continue;
}
for item in files.to_list(&[]) {
if let FileNode::Symlink { target: _ } | FileNode::Regular {
size: _,
executable: true,
} = item.node
{
let path = PathBuf::from(OsString::from_vec(item.path));
if let Ok(binary) = path.strip_prefix("/bin") {
let attr = origin.attr.clone();
let system = origin.system.clone();
let binary: String = binary.to_string_lossy().into();
if binary.starts_with('.') || binary.contains('/') || binary.is_empty() {
continue;
}
connection
.execute(
"insert or replace into Programs(name, system, package) values (?, ?, ?)",
(binary, system, attr),
)
.map_err(|e| Error::CreateDatabase { path: args.output.clone(), source: Box::new(e) })?;
}
if let Ok(debuginfo) = path.strip_prefix("/lib/debug/.build-id") {
if item.node.get_type() == FileType::Symlink {
continue;
}
let build_id: String = debuginfo
.to_string_lossy()
.replace('/', "")
.strip_suffix(".debug")
.expect("Debug info files must end with .debug")
.into();
debug_connection
.execute(
"insert or replace into DebugInfo(build_id, url, filename) values (?, ?, ?)",
(build_id, format!("../{}", nar), path.to_string_lossy().strip_prefix('/')),
)
.map_err(|e| Error::CreateDatabase { path: args.debug_output.clone(), source: Box::new(e) })?;
}
}
}
}
eprintln!();
eprint!("+ dumping index");
connection
.backup("main", &args.output, None)
.map_err(|e| Error::CreateDatabase {
path: args.output.clone(),
source: Box::new(e),
})?;
debug_connection
.backup("main", &args.debug_output, None)
.map_err(|e| Error::CreateDatabase {
path: args.debug_output.clone(),
source: Box::new(e),
})?;
Ok(())
}
#[derive(Debug, Parser)]
#[clap(author, about, version)]
struct Args {
#[clap(short = 'r', long = "requests", default_value = "500")]
jobs: usize,
#[clap(short = 'f', long, default_value = "<nixpkgs>")]
nixpkgs: String,
#[clap(short, long, default_value = "programs.sqlite")]
output: PathBuf,
#[clap(short, long, default_value = "debug.sqlite")]
debug_output: PathBuf,
#[clap(short = 's', long = "platform")]
systems: Option<Vec<String>>,
#[clap(long)]
show_trace: bool,
#[clap(long, default_values = ["haskellPackages", "rPackages", "coqPackages", "texlive.pkgs"])]
extra_scopes: Vec<String>,
}
#[tokio::main]
async fn main() {
let args = Args::parse();
if let Err(e) = update_index(&args).await {
eprintln!("error: {:?}", e);
process::exit(2);
}
}