mirror of
https://github.com/oxalica/nil.git
synced 2025-12-23 09:19:49 +00:00
parent
fb8a8cb077
commit
ee2ff825c5
2 changed files with 147 additions and 41 deletions
|
|
@ -8,7 +8,7 @@ fn main() {
|
|||
// Disable rebuild when source files changed.
|
||||
println!("cargo:rerun-if-changed=build.rs");
|
||||
|
||||
let builtin_names: Vec<String> = Command::new("nix")
|
||||
let builtins_attr_names: Vec<String> = Command::new("nix")
|
||||
.args([
|
||||
"eval",
|
||||
"--experimental-features",
|
||||
|
|
@ -26,7 +26,7 @@ fn main() {
|
|||
// Here we run them in parallel. There are hundreds of names to test.
|
||||
#[allow(clippy::needless_collect)]
|
||||
let global_names: Vec<bool> = {
|
||||
builtin_names
|
||||
builtins_attr_names
|
||||
.iter()
|
||||
.map(|name| {
|
||||
Command::new("nix")
|
||||
|
|
@ -52,50 +52,127 @@ fn main() {
|
|||
.collect()
|
||||
};
|
||||
|
||||
// Use a secret subcommand `__dump-language` to dump Nix builtins with documentations.
|
||||
// It is introduced since Nix 2.17 in
|
||||
// https://github.com/NixOS/nix/commit/22b278e011ab9c1328749a126514c57b89a39173#diff-20a8b5b2a231db80eab27840bd32ac0214aa0c4e9e923e649d3d741c3da77b48L355
|
||||
let builtins_dump: DumpBuiltins = Command::new("nix")
|
||||
.arg("__dump-language")
|
||||
.json::<DumpLanguage>()
|
||||
.map_or_else(
|
||||
|_| {
|
||||
// Fallback to the older command `__dump-builtins` so that the package
|
||||
// doesn't fail to build for people using older versions of nix
|
||||
// (introduced in 2.4)
|
||||
// https://github.com/NixOS/nix/commit/0f314f3c2594e80322c675b70a61dcfda11bf423#diff-20a8b5b2a231db80eab27840bd32ac0214aa0c4e9e923e649d3d741c3da77b48R187
|
||||
Command::new("nix")
|
||||
.arg("__dump-builtins")
|
||||
.json::<DumpBuiltins>()
|
||||
},
|
||||
|v| Ok(v.builtins),
|
||||
)
|
||||
.expect("Failed to dump builtins");
|
||||
let mut builtin_infos = dump_builtin_infos();
|
||||
builtin_infos.sort_by(|lhs, rhs| lhs.name.cmp(&rhs.name));
|
||||
assert_eq!(
|
||||
builtin_infos.windows(2).find(|w| w[0].name == w[1].name),
|
||||
None,
|
||||
"no duplicated builtins",
|
||||
);
|
||||
|
||||
let mut phf_gen = phf_codegen::Map::<&'static str>::new();
|
||||
for (name, is_global) in builtin_names.iter().zip(&global_names) {
|
||||
for (name, is_global) in builtins_attr_names.iter().zip(&global_names) {
|
||||
let name = &**name;
|
||||
let kind = match name {
|
||||
"builtins" => "Attrset",
|
||||
"true" | "false" | "null" => "Const",
|
||||
_ => "Function",
|
||||
let BuiltinInfo {
|
||||
name: _,
|
||||
kind,
|
||||
doc,
|
||||
args,
|
||||
impure_only,
|
||||
experimental_feature,
|
||||
} = match builtin_infos.binary_search_by(|probe| (*probe.name).cmp(name)) {
|
||||
Ok(i) => builtin_infos[i].clone(),
|
||||
Err(_) => BuiltinInfo {
|
||||
name: String::new(), // Not used.
|
||||
kind: guess_name_kind(name).into(),
|
||||
doc: "(No documentation from Nix)".into(),
|
||||
args: Vec::new(),
|
||||
impure_only: false,
|
||||
experimental_feature: None,
|
||||
},
|
||||
};
|
||||
let args = builtins_dump
|
||||
.get(name)
|
||||
.map(|b @ DumpBuiltin { args, arity, .. }| {
|
||||
assert_eq!(args.len(), *arity, "Arity mismatch: {b:?}");
|
||||
args.iter().flat_map(|arg| [" ", arg]).collect::<String>()
|
||||
})
|
||||
.unwrap_or_default();
|
||||
let summary = format!("`builtins.{name}{args}`");
|
||||
let doc = builtins_dump.get(name).map(|b| &b.doc);
|
||||
phf_gen.entry(name, &format!("crate::Builtin {{ kind: crate::BuiltinKind::{kind}, is_global: {is_global}, summary: {summary:?}, doc: {doc:?} }}"));
|
||||
|
||||
let summary = ["`builtins.", name]
|
||||
.into_iter()
|
||||
.chain(args.iter().flat_map(|arg| [" ", arg]))
|
||||
.chain(Some("`"))
|
||||
.collect::<String>();
|
||||
let rhs = format!(
|
||||
"crate::Builtin {{
|
||||
kind: crate::BuiltinKind::{kind},
|
||||
is_global: {is_global},
|
||||
summary: {summary:?},
|
||||
doc: Some({doc:?}),
|
||||
impure_only: {impure_only},
|
||||
experimental_feature: {experimental_feature:?},
|
||||
}}"
|
||||
);
|
||||
phf_gen.entry(name, &rhs);
|
||||
}
|
||||
|
||||
let path = Path::new(&env::var("OUT_DIR").unwrap()).join("generated.expr");
|
||||
fs::write(path, phf_gen.build().to_string()).unwrap();
|
||||
}
|
||||
|
||||
fn dump_builtin_infos() -> Vec<BuiltinInfo> {
|
||||
// Use a secret subcommand `__dump-language` to dump Nix builtins with documentations.
|
||||
// It is introduced since Nix 2.17 in
|
||||
// https://github.com/NixOS/nix/commit/22b278e011ab9c1328749a126514c57b89a39173#diff-20a8b5b2a231db80eab27840bd32ac0214aa0c4e9e923e649d3d741c3da77b48L355
|
||||
if let Ok(lang) = Command::new("nix")
|
||||
.arg("__dump-language")
|
||||
.json::<DumpLanguage>()
|
||||
{
|
||||
return std::iter::empty()
|
||||
.chain(
|
||||
lang.builtins
|
||||
.into_iter()
|
||||
.map(|(name, builtin)| BuiltinInfo {
|
||||
name,
|
||||
kind: "Function".into(),
|
||||
doc: builtin.doc,
|
||||
args: builtin.args,
|
||||
impure_only: false,
|
||||
experimental_feature: builtin.experimental_feature,
|
||||
}),
|
||||
)
|
||||
.chain(lang.constants.into_iter().map(|(name, constant)| {
|
||||
let kind = if constant.type_.eq_ignore_ascii_case("set") {
|
||||
"Attrset"
|
||||
} else {
|
||||
"Const"
|
||||
};
|
||||
BuiltinInfo {
|
||||
name,
|
||||
kind: kind.into(),
|
||||
doc: constant.doc,
|
||||
args: Vec::new(),
|
||||
impure_only: constant.impure_only,
|
||||
experimental_feature: None,
|
||||
}
|
||||
}))
|
||||
.collect();
|
||||
}
|
||||
|
||||
// Fallback to the older command `__dump-builtins` so that the package
|
||||
// doesn't fail to build for people using older versions of nix
|
||||
// (introduced in 2.4)
|
||||
// https://github.com/NixOS/nix/commit/0f314f3c2594e80322c675b70a61dcfda11bf423#diff-20a8b5b2a231db80eab27840bd32ac0214aa0c4e9e923e649d3d741c3da77b48R187
|
||||
let builtins = Command::new("nix")
|
||||
.arg("__dump-builtins")
|
||||
.json::<DumpBuiltins>()
|
||||
.expect("failed to dump builtin docs");
|
||||
|
||||
builtins
|
||||
.into_iter()
|
||||
.map(|(name, builtin)| BuiltinInfo {
|
||||
kind: guess_name_kind(&name).into(),
|
||||
name,
|
||||
doc: builtin.doc,
|
||||
args: builtin.args,
|
||||
impure_only: false,
|
||||
experimental_feature: None,
|
||||
})
|
||||
.collect()
|
||||
}
|
||||
|
||||
fn guess_name_kind(name: &str) -> &'static str {
|
||||
match name {
|
||||
"builtins" => "Attrset",
|
||||
"true" | "false" | "null" => "Const",
|
||||
_ => "Function",
|
||||
}
|
||||
}
|
||||
|
||||
trait CommandExt {
|
||||
fn json<T: for<'de> Deserialize<'de>>(&mut self) -> Result<T, Box<dyn std::error::Error>>;
|
||||
}
|
||||
|
|
@ -110,17 +187,40 @@ impl CommandExt for Command {
|
|||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
struct BuiltinInfo {
|
||||
name: String,
|
||||
kind: String,
|
||||
doc: String,
|
||||
args: Vec<String>,
|
||||
impure_only: bool,
|
||||
experimental_feature: Option<String>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
struct DumpLanguage {
|
||||
builtins: DumpBuiltins,
|
||||
constants: BTreeMap<String, DumpConstant>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
#[serde(rename_all = "kebab-case")]
|
||||
struct DumpConstant {
|
||||
doc: String,
|
||||
impure_only: bool,
|
||||
// TODO
|
||||
#[serde(rename = "type")]
|
||||
type_: String,
|
||||
}
|
||||
|
||||
// Keep names sorted.
|
||||
type DumpBuiltins = BTreeMap<String, DumpBuiltin>;
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
#[serde(rename_all = "kebab-case")]
|
||||
struct DumpBuiltin {
|
||||
args: Vec<String>,
|
||||
arity: usize,
|
||||
doc: String,
|
||||
#[serde(default)]
|
||||
experimental_feature: Option<String>,
|
||||
}
|
||||
|
|
|
|||
|
|
@ -4,6 +4,8 @@ pub struct Builtin {
|
|||
pub is_global: bool,
|
||||
pub summary: &'static str,
|
||||
pub doc: Option<&'static str>,
|
||||
pub impure_only: bool,
|
||||
pub experimental_feature: Option<&'static str>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
|
||||
|
|
@ -23,15 +25,17 @@ mod tests {
|
|||
|
||||
#[test]
|
||||
fn sanity() {
|
||||
assert_eq!(
|
||||
assert!(matches!(
|
||||
ALL_BUILTINS["true"],
|
||||
Builtin {
|
||||
kind: BuiltinKind::Const,
|
||||
is_global: true,
|
||||
summary: "`builtins.true`",
|
||||
doc: None,
|
||||
},
|
||||
);
|
||||
doc: _,
|
||||
impure_only: false,
|
||||
experimental_feature: None,
|
||||
}
|
||||
));
|
||||
|
||||
assert_eq!(
|
||||
ALL_BUILTINS["attrNames"],
|
||||
|
|
@ -46,6 +50,8 @@ alphabetically sorted list. For instance, `builtins.attrNames { y
|
|||
= 1; x = \"foo\"; }` evaluates to `[ \"x\" \"y\" ]`.\
|
||||
"
|
||||
),
|
||||
impure_only: false,
|
||||
experimental_feature: None,
|
||||
}
|
||||
);
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue