1use crate::CACHEDIR;
2use anyhow::{Context, Result};
3use log::info;
4use sqlx::SqlitePool;
5use std::{
6 collections::{HashMap, HashSet},
7 fs::{self, File},
8 io::{Read, Write},
9 path::Path,
10 process::Command,
11};
12
13use super::{
14 nixos::{self, getnixospkgs, nixospkgs},
15 NixPkg,
16};
17
18pub async fn flakespkgs() -> Result<String> {
22 let versionout = Command::new("nixos-version").arg("--json").output()?;
23 let version: HashMap<String, String> = serde_json::from_slice(&versionout.stdout)?;
24
25 let nixosversion = version
26 .get("nixosVersion")
27 .context("No NixOS version found")?;
28
29 if !std::path::Path::new(&*CACHEDIR).exists() {
31 std::fs::create_dir_all(&*CACHEDIR)?;
32 }
33
34 if let Ok(prevver) = fs::read_to_string(&format!("{}/flakespkgs.ver", &*CACHEDIR)) {
36 if prevver.eq(nixosversion) && Path::new(&format!("{}/flakespkgs.db", &*CACHEDIR)).exists()
37 {
38 info!("No new version of NixOS flakes found");
39 return Ok(format!("{}/flakespkgs.db", &*CACHEDIR));
40 }
41 }
42
43 let pkgsout = if let Some(rev) = version.get("nixpkgsRevision") {
45 let url = format!("https://raw.githubusercontent.com/snowflakelinux/nixpkgs-version-data/main/nixos-{}/{}.json.br", nixosversion.get(0..5).context("Invalid NixOS version")?, rev);
46 let resp = reqwest::get(&url).await?;
47 if resp.status().is_success() {
48 let r = resp.bytes().await?;
49 let mut br = brotli::Decompressor::new(r.as_ref(), 4096);
50 let mut pkgsout = Vec::new();
51 br.read_to_end(&mut pkgsout)?;
52 let pkgsjson: HashMap<String, String> = serde_json::from_slice(&pkgsout)?;
53 pkgsjson
54 } else {
55 let url = format!("https://raw.githubusercontent.com/snowflakelinux/nixpkgs-version-data/main/nixos-unstable/{}.json.br", rev);
56 let resp = reqwest::get(&url).await?;
57 if resp.status().is_success() {
58 let r = resp.bytes().await?;
59 let mut br = brotli::Decompressor::new(r.as_ref(), 4096);
60 let mut pkgsout = Vec::new();
61 br.read_to_end(&mut pkgsout)?;
62 let pkgsjson: HashMap<String, String> = serde_json::from_slice(&pkgsout)?;
63 pkgsjson
64 } else {
65 let pkgsout = Command::new("nix")
66 .arg("search")
67 .arg("--json")
68 .arg(&format!("nixpkgs/{}", rev))
69 .output()?;
70 let pkgsjson: HashMap<String, NixPkg> =
71 serde_json::from_str(&String::from_utf8(pkgsout.stdout)?)?;
72 let pkgsjson = pkgsjson
73 .iter()
74 .map(|(k, v)| {
75 (
76 k.split('.').collect::<Vec<_>>()[2..].join("."),
77 v.version.to_string(),
78 )
79 })
80 .collect::<HashMap<String, String>>();
81 pkgsjson
82 }
83 }
84 } else {
85 let pkgsout = Command::new("nix")
86 .arg("search")
87 .arg("--json")
88 .arg("nixpkgs")
91 .output()?;
92 let pkgsjson: HashMap<String, NixPkg> =
93 serde_json::from_str(&String::from_utf8(pkgsout.stdout)?)?;
94 let pkgsjson = pkgsjson
95 .iter()
96 .map(|(k, v)| {
97 (
98 k.split('.').collect::<Vec<_>>()[2..].join("."),
99 v.version.to_string(),
100 )
101 })
102 .collect::<HashMap<String, String>>();
103 pkgsjson
104 };
105
106 let dbfile = format!("{}/flakespkgs.db", &*CACHEDIR);
107 nixos::createdb(&dbfile, &pkgsout).await?;
108
109 File::create(format!("{}/flakespkgs.ver", &*CACHEDIR))?.write_all(nixosversion.as_bytes())?;
111
112 Ok(format!("{}/flakespkgs.db", &*CACHEDIR))
113}
114
115pub async fn getflakepkgs(paths: &[&str]) -> Result<HashMap<String, String>> {
118 getnixospkgs(paths, nixos::NixosType::Flake).await
119}
120
121pub fn uptodate() -> Result<Option<(String, String)>> {
122 let flakesver = fs::read_to_string(&format!("{}/flakespkgs.ver", &*CACHEDIR))?;
123 let nixosver = fs::read_to_string(&format!("{}/nixospkgs.ver", &*CACHEDIR))?;
124 let flakeslast = flakesver
125 .split('.')
126 .collect::<Vec<_>>()
127 .last()
128 .context("Invalid version")?
129 .to_string();
130 let nixoslast = nixosver
131 .split('.')
132 .collect::<Vec<_>>()
133 .last()
134 .context("Invalid version")?
135 .to_string();
136 if !nixoslast.starts_with(&flakeslast) {
137 Ok(Some((flakesver, nixosver)))
138 } else {
139 Ok(None)
140 }
141}
142
143pub async fn unavailablepkgs(paths: &[&str]) -> Result<HashMap<String, String>> {
144 let versionout = Command::new("nixos-version").arg("--json").output()?;
145 let version: HashMap<String, String> = serde_json::from_slice(&versionout.stdout)?;
146 let nixpath = if let Some(rev) = version.get("nixpkgsRevision") {
147 Command::new("nix")
148 .arg("eval")
149 .arg(&format!("nixpkgs/{}#path", rev))
150 .output()?
151 .stdout
152 } else {
153 Command::new("nix")
154 .arg("eval")
155 .arg("nixpkgs#path")
156 .output()?
157 .stdout
158 };
159 let nixpath = String::from_utf8(nixpath)?;
160 let nixpath = nixpath.trim();
161
162 let aliases = Command::new("nix-instantiate")
163 .arg("--eval")
164 .arg("-E")
165 .arg(&format!("with import {} {{}}; builtins.attrNames ((self: super: lib.optionalAttrs config.allowAliases (import {}/pkgs/top-level/aliases.nix lib self super)) {{}} {{}})", nixpath, nixpath))
166 .arg("--json")
167 .output()?;
168 let aliasstr = String::from_utf8(aliases.stdout)?;
169 let aliasesout: HashSet<String> = serde_json::from_str(&aliasstr)?;
170
171 let pkgs = {
172 let mut allpkgs: HashSet<String> = HashSet::new();
173 for path in paths {
174 if let Ok(filepkgs) = nix_editor::read::getarrvals(
175 &fs::read_to_string(path)?,
176 "environment.systemPackages",
177 ) {
178 let filepkgset = filepkgs
179 .into_iter()
180 .map(|x| x.strip_prefix("pkgs.").unwrap_or(&x).to_string())
181 .collect::<HashSet<_>>();
182 allpkgs = allpkgs.union(&filepkgset).map(|x| x.to_string()).collect();
183 }
184 }
185 allpkgs
186 };
187
188 let mut unavailable = HashMap::new();
189 for pkg in pkgs {
190 if aliasesout.contains(&pkg) && Command::new("nix-instantiate")
191 .arg("--eval")
192 .arg("-E")
193 .arg(&format!("with import {} {{}}; builtins.tryEval ((self: super: lib.optionalAttrs config.allowAliases (import {}/pkgs/top-level/aliases.nix lib self super)) {{}} {{}}).{}", nixpath, nixpath, pkg))
194 .output()?.status.success() {
195 let out = Command::new("nix-instantiate")
196 .arg("--eval")
197 .arg("-E")
198 .arg(&format!("with import {} {{}}; ((self: super: lib.optionalAttrs config.allowAliases (import {}/pkgs/top-level/aliases.nix lib self super)) {{}} {{}}).{}", nixpath, nixpath, pkg))
199 .output()?;
200 let err = String::from_utf8(out.stderr)?;
201 let err = err.strip_prefix("error: ").unwrap_or(&err).trim();
202 unavailable.insert(pkg, err.to_string());
203 }
204 }
205
206 let profilepkgs = getflakepkgs(paths).await?;
207 let nixospkgs = nixospkgs().await?;
208 let pool = SqlitePool::connect(&format!("sqlite://{}", nixospkgs)).await?;
209
210 for (pkg, _) in profilepkgs {
211 let (x, broken, insecure): (String, u8, u8) =
212 sqlx::query_as("SELECT attribute,broken,insecure FROM meta WHERE attribute = $1")
213 .bind(&pkg)
214 .fetch_one(&pool)
215 .await?;
216 if x != pkg {
217 unavailable.insert(
218 pkg,
219 String::from("Package not found in newer version of nixpkgs"),
220 );
221 } else if broken == 1 {
222 unavailable.insert(pkg, String::from("Package is marked as broken"));
223 } else if insecure == 1 {
224 unavailable.insert(pkg, String::from("Package is marked as insecure"));
225 }
226 }
227 Ok(unavailable)
228}