Compare commits
2 Commits
791c20660c
...
f0a0593d4c
| Author | SHA1 | Date | |
|---|---|---|---|
| f0a0593d4c | |||
| ce64a82da3 |
11
.lazy.lua
11
.lazy.lua
@@ -3,5 +3,16 @@ vim.lsp.config("biome", {
|
||||
on_dir(vim.fn.getcwd())
|
||||
end
|
||||
})
|
||||
vim.lsp.config("rust_analyzer", {
|
||||
settings = {
|
||||
["rust-analyzer"] = {
|
||||
cargo = {
|
||||
features = {
|
||||
"inspector"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
return {}
|
||||
|
||||
160
Cargo.lock
generated
160
Cargo.lock
generated
@@ -47,12 +47,56 @@ version = "0.1.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4b46cbb362ab8752921c97e041f5e366ee6297bd428a31275b9fcf1e380f7299"
|
||||
|
||||
[[package]]
|
||||
name = "anstream"
|
||||
version = "0.6.21"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "43d5b281e737544384e969a5ccad3f1cdd24b48086a0fc1b2a5262a26b8f4f4a"
|
||||
dependencies = [
|
||||
"anstyle",
|
||||
"anstyle-parse",
|
||||
"anstyle-query",
|
||||
"anstyle-wincon",
|
||||
"colorchoice",
|
||||
"is_terminal_polyfill",
|
||||
"utf8parse",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "anstyle"
|
||||
version = "1.0.13"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5192cca8006f1fd4f7237516f40fa183bb07f8fbdfedaa0036de5ea9b0b45e78"
|
||||
|
||||
[[package]]
|
||||
name = "anstyle-parse"
|
||||
version = "0.2.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4e7644824f0aa2c7b9384579234ef10eb7efb6a0deb83f9630a49594dd9c15c2"
|
||||
dependencies = [
|
||||
"utf8parse",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "anstyle-query"
|
||||
version = "1.1.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "40c48f72fd53cd289104fc64099abca73db4166ad86ea0b4341abe65af83dadc"
|
||||
dependencies = [
|
||||
"windows-sys 0.61.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "anstyle-wincon"
|
||||
version = "3.0.11"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "291e6a250ff86cd4a820112fb8898808a366d8f9f58ce16d1f538353ad55747d"
|
||||
dependencies = [
|
||||
"anstyle",
|
||||
"once_cell_polyfill",
|
||||
"windows-sys 0.61.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "anyhow"
|
||||
version = "1.0.101"
|
||||
@@ -123,6 +167,12 @@ dependencies = [
|
||||
"backtrace",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "base64"
|
||||
version = "0.21.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9d297deb1925b89f2ccc13d7635fa0714f12c87adce1c75356b39ca9b7178567"
|
||||
|
||||
[[package]]
|
||||
name = "base64"
|
||||
version = "0.22.1"
|
||||
@@ -384,6 +434,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "63be97961acde393029492ce0be7a1af7e323e6bae9511ebfac33751be5e6806"
|
||||
dependencies = [
|
||||
"clap_builder",
|
||||
"clap_derive",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -392,8 +443,22 @@ version = "4.5.58"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7f13174bda5dfd69d7e947827e5af4b0f2f94a4a3ee92912fba07a66150f21e2"
|
||||
dependencies = [
|
||||
"anstream",
|
||||
"anstyle",
|
||||
"clap_lex",
|
||||
"strsim",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "clap_derive"
|
||||
version = "4.5.55"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a92793da1a46a5f2a02a6f4c46c6496b28c43638adea8306fcb0caa1634f24e5"
|
||||
dependencies = [
|
||||
"heck",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -420,6 +485,12 @@ dependencies = [
|
||||
"cc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "colorchoice"
|
||||
version = "1.0.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b05b61dc5112cbb17e4b6cd61790d9845d13888356391624cbe7e41efeac1e75"
|
||||
|
||||
[[package]]
|
||||
name = "combine"
|
||||
version = "4.6.7"
|
||||
@@ -962,6 +1033,26 @@ version = "2.3.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "37909eebbb50d72f9059c3b6d82c0463f2ff062c9e95845c43a6c9c0355411be"
|
||||
|
||||
[[package]]
|
||||
name = "fastwebsockets"
|
||||
version = "0.10.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "305d3ba574508e27190906d11707dad683e0494e6b85eae9b044cb2734a5e422"
|
||||
dependencies = [
|
||||
"base64 0.21.7",
|
||||
"bytes",
|
||||
"http-body-util",
|
||||
"hyper",
|
||||
"hyper-util",
|
||||
"pin-project",
|
||||
"rand 0.8.5",
|
||||
"sha1",
|
||||
"simdutf8",
|
||||
"thiserror 1.0.69",
|
||||
"tokio",
|
||||
"utf-8",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "fd-lock"
|
||||
version = "4.0.4"
|
||||
@@ -1315,6 +1406,12 @@ version = "1.10.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6dbf3de79e51f3d586ab4cb9d5c3e2c14aa28ed23d180cf89b4df0454a69cc87"
|
||||
|
||||
[[package]]
|
||||
name = "httpdate"
|
||||
version = "1.0.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "df3b46402a9d5adb4c86a0cf463f42e19994e3ee891101b1841f30a545cb49a9"
|
||||
|
||||
[[package]]
|
||||
name = "hyper"
|
||||
version = "1.8.1"
|
||||
@@ -1328,6 +1425,7 @@ dependencies = [
|
||||
"http",
|
||||
"http-body",
|
||||
"httparse",
|
||||
"httpdate",
|
||||
"itoa",
|
||||
"pin-project-lite",
|
||||
"pin-utils",
|
||||
@@ -1358,7 +1456,7 @@ version = "0.1.20"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "96547c2556ec9d12fb1578c4eaf448b04993e7fb79cbaad930a656880a6bdfa0"
|
||||
dependencies = [
|
||||
"base64",
|
||||
"base64 0.22.1",
|
||||
"bytes",
|
||||
"futures-channel",
|
||||
"futures-util",
|
||||
@@ -1578,6 +1676,12 @@ dependencies = [
|
||||
"windows-sys 0.60.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "is_terminal_polyfill"
|
||||
version = "1.70.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a6cb138bb79a146c1bd460005623e142ef0181e3d0219cb493e02f7d08a35695"
|
||||
|
||||
[[package]]
|
||||
name = "itertools"
|
||||
version = "0.13.0"
|
||||
@@ -1783,9 +1887,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "md5"
|
||||
version = "0.7.0"
|
||||
version = "0.8.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "490cc448043f947bae3cbee9c203358d62dbee0db12107a74be5c30ccfd09771"
|
||||
checksum = "ae960838283323069879657ca3de837e9f7bbb4c7bf6ea7f1b290d5e9476d2e0"
|
||||
|
||||
[[package]]
|
||||
name = "memchr"
|
||||
@@ -1918,17 +2022,23 @@ name = "nix-js"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"base64",
|
||||
"base64 0.22.1",
|
||||
"bzip2",
|
||||
"clap",
|
||||
"criterion",
|
||||
"deno_core",
|
||||
"deno_error",
|
||||
"derive_more",
|
||||
"dirs",
|
||||
"ere",
|
||||
"fastwebsockets",
|
||||
"flate2",
|
||||
"hashbrown 0.16.1",
|
||||
"hex",
|
||||
"http",
|
||||
"http-body-util",
|
||||
"hyper",
|
||||
"hyper-util",
|
||||
"itertools 0.14.0",
|
||||
"md5",
|
||||
"miette",
|
||||
@@ -1957,6 +2067,7 @@ dependencies = [
|
||||
"toml",
|
||||
"tracing",
|
||||
"tracing-subscriber",
|
||||
"uuid",
|
||||
"xz2",
|
||||
]
|
||||
|
||||
@@ -2075,6 +2186,12 @@ version = "1.21.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d"
|
||||
|
||||
[[package]]
|
||||
name = "once_cell_polyfill"
|
||||
version = "1.70.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "384b8ab6d37215f3c5301a95a4accb5d64aa607f1fcb26a11b5303878451b4fe"
|
||||
|
||||
[[package]]
|
||||
name = "oorandom"
|
||||
version = "11.1.5"
|
||||
@@ -2367,6 +2484,8 @@ version = "0.8.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404"
|
||||
dependencies = [
|
||||
"libc",
|
||||
"rand_chacha 0.3.1",
|
||||
"rand_core 0.6.4",
|
||||
]
|
||||
|
||||
@@ -2376,10 +2495,20 @@ version = "0.9.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6db2770f06117d490610c7488547d543617b21bfa07796d7a12f6f1bd53850d1"
|
||||
dependencies = [
|
||||
"rand_chacha",
|
||||
"rand_chacha 0.9.0",
|
||||
"rand_core 0.9.5",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rand_chacha"
|
||||
version = "0.3.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88"
|
||||
dependencies = [
|
||||
"ppv-lite86",
|
||||
"rand_core 0.6.4",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rand_chacha"
|
||||
version = "0.9.0"
|
||||
@@ -2492,7 +2621,7 @@ version = "0.13.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ab3f43e3283ab1488b624b44b0e988d0acea0b3214e694730a055cb6b2efa801"
|
||||
dependencies = [
|
||||
"base64",
|
||||
"base64 0.22.1",
|
||||
"bytes",
|
||||
"futures-channel",
|
||||
"futures-core",
|
||||
@@ -2965,6 +3094,12 @@ version = "0.3.8"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e320a6c5ad31d271ad523dcf3ad13e2767ad8b1cb8f047f75a8aeaf8da139da2"
|
||||
|
||||
[[package]]
|
||||
name = "simdutf8"
|
||||
version = "0.1.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e3a9fe34e3e7a50316060351f37187a3f546bce95496156754b601a5fa71b76e"
|
||||
|
||||
[[package]]
|
||||
name = "slab"
|
||||
version = "0.4.12"
|
||||
@@ -3064,6 +3199,12 @@ version = "0.4.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "72abeda133c49d7bddece6c154728f83eec8172380c80ab7096da9487e20d27c"
|
||||
|
||||
[[package]]
|
||||
name = "strsim"
|
||||
version = "0.11.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f"
|
||||
|
||||
[[package]]
|
||||
name = "strum"
|
||||
version = "0.27.2"
|
||||
@@ -3634,6 +3775,12 @@ dependencies = [
|
||||
"serde_derive",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "utf-8"
|
||||
version = "0.7.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "09cc8ee72d2a9becf2f2febe0205bbed8fc6615b7cb429ad062dc7b7ddd036a9"
|
||||
|
||||
[[package]]
|
||||
name = "utf8-ranges"
|
||||
version = "1.0.5"
|
||||
@@ -3658,6 +3805,7 @@ version = "1.21.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b672338555252d43fd2240c714dc444b8c6fb0a5c5335e65a07bba7742735ddb"
|
||||
dependencies = [
|
||||
"getrandom 0.4.1",
|
||||
"js-sys",
|
||||
"wasm-bindgen",
|
||||
]
|
||||
|
||||
16
Justfile
16
Justfile
@@ -1,15 +1,23 @@
|
||||
[no-exit-message]
|
||||
@repl:
|
||||
cargo run --bin repl
|
||||
cargo run -- repl
|
||||
|
||||
[no-exit-message]
|
||||
@eval expr:
|
||||
cargo run --bin eval -- '{{expr}}'
|
||||
cargo run -- eval '{{expr}}'
|
||||
|
||||
[no-exit-message]
|
||||
@replr:
|
||||
cargo run --bin repl --release
|
||||
cargo run --release -- repl
|
||||
|
||||
[no-exit-message]
|
||||
@evalr expr:
|
||||
cargo run --bin eval --release -- '{{expr}}'
|
||||
cargo run --release -- eval '{{expr}}'
|
||||
|
||||
[no-exit-message]
|
||||
@repli:
|
||||
cargo run --release --features inspector -- --inspect-brk 127.0.0.1:9229 repl
|
||||
|
||||
[no-exit-message]
|
||||
@evali expr:
|
||||
cargo run --release --features inspector -- --inspect-brk 127.0.0.1:9229 eval '{{expr}}'
|
||||
|
||||
@@ -14,6 +14,9 @@ nix-compat = { git = "https://git.snix.dev/snix/snix.git", version = "0.1.0", fe
|
||||
anyhow = "1.0"
|
||||
rustyline = "17.0"
|
||||
|
||||
# CLI
|
||||
clap = { version = "4", features = ["derive"] }
|
||||
|
||||
# Logging
|
||||
tracing = "0.1"
|
||||
tracing-subscriber = { version = "0.3", features = ["env-filter"] }
|
||||
@@ -63,6 +66,17 @@ ere = "0.2.4"
|
||||
num_enum = "0.7.5"
|
||||
tap = "1.0.1"
|
||||
|
||||
# Inspector (optional)
|
||||
fastwebsockets = { version = "0.10", features = ["upgrade"], optional = true }
|
||||
hyper = { version = "1", features = ["http1", "server"], optional = true }
|
||||
hyper-util = { version = "0.1", features = ["tokio"], optional = true }
|
||||
http-body-util = { version = "0.1", optional = true }
|
||||
http = { version = "1", optional = true }
|
||||
uuid = { version = "1", features = ["v4"], optional = true }
|
||||
|
||||
[features]
|
||||
inspector = ["dep:fastwebsockets", "dep:hyper", "dep:hyper-util", "dep:http-body-util", "dep:http", "dep:uuid"]
|
||||
|
||||
[dev-dependencies]
|
||||
criterion = { version = "0.8", features = ["html_reports"] }
|
||||
|
||||
|
||||
@@ -32,8 +32,12 @@ export const toJSON = (e: NixValue): NixString => {
|
||||
return mkStringWithContext(string, context);
|
||||
};
|
||||
|
||||
export const toXML = (_e: NixValue): never => {
|
||||
throw new Error("Not implemented: toXML");
|
||||
export const toXML = (e: NixValue): NixString => {
|
||||
const [xml, context] = Deno.core.ops.op_to_xml(force(e));
|
||||
if (context.length === 0) {
|
||||
return xml;
|
||||
}
|
||||
return mkStringWithContext(xml, new Set(context));
|
||||
};
|
||||
|
||||
/**
|
||||
|
||||
1
nix-js/runtime-ts/src/types/global.d.ts
vendored
1
nix-js/runtime-ts/src/types/global.d.ts
vendored
@@ -60,6 +60,7 @@ declare global {
|
||||
|
||||
function op_from_json(json: string): unknown;
|
||||
function op_from_toml(toml: string): unknown;
|
||||
function op_to_xml(e: NixValue): [string, string[]];
|
||||
|
||||
function op_finalize_derivation(input: {
|
||||
name: string;
|
||||
|
||||
@@ -1,26 +0,0 @@
|
||||
use anyhow::Result;
|
||||
use nix_js::{context::Context, error::Source};
|
||||
use std::process::exit;
|
||||
|
||||
fn main() -> Result<()> {
|
||||
nix_js::logging::init_logging();
|
||||
|
||||
let mut args = std::env::args();
|
||||
if args.len() != 2 {
|
||||
eprintln!("Usage: {} expr", args.next().unwrap());
|
||||
exit(1);
|
||||
}
|
||||
args.next();
|
||||
let expr = args.next().unwrap();
|
||||
let src = Source::new_eval(expr)?;
|
||||
match Context::new()?.eval(src) {
|
||||
Ok(value) => {
|
||||
println!("{value}");
|
||||
Ok(())
|
||||
}
|
||||
Err(err) => {
|
||||
eprintln!("{:?}", miette::Report::new(*err));
|
||||
exit(1);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -36,9 +36,7 @@ pub(crate) fn compile(expr: &Ir, ctx: &impl CodegenContext) -> String {
|
||||
}
|
||||
|
||||
code!(&mut buf, ctx;
|
||||
"Nix.builtins.storeDir="
|
||||
quoted(ctx.get_store_dir())
|
||||
";const __currentDir="
|
||||
"const __currentDir="
|
||||
quoted(&ctx.get_current_dir().display().to_string())
|
||||
";const __with=null;return "
|
||||
expr
|
||||
@@ -57,9 +55,7 @@ pub(crate) fn compile_scoped(expr: &Ir, ctx: &impl CodegenContext) -> String {
|
||||
}
|
||||
|
||||
code!(&mut buf, ctx;
|
||||
"Nix.builtins.storeDir="
|
||||
quoted(ctx.get_store_dir())
|
||||
";const __currentDir="
|
||||
"const __currentDir="
|
||||
quoted(&ctx.get_current_dir().display().to_string())
|
||||
";return "
|
||||
expr
|
||||
|
||||
@@ -47,6 +47,8 @@ fn handle_parse_error<'a>(
|
||||
pub struct Context {
|
||||
ctx: Ctx,
|
||||
runtime: Runtime<Ctx>,
|
||||
#[cfg(feature = "inspector")]
|
||||
_inspector_server: Option<crate::runtime::inspector::InspectorServer>,
|
||||
}
|
||||
|
||||
macro_rules! eval {
|
||||
@@ -66,23 +68,65 @@ macro_rules! eval {
|
||||
impl Context {
|
||||
pub fn new() -> Result<Self> {
|
||||
let ctx = Ctx::new()?;
|
||||
#[cfg(feature = "inspector")]
|
||||
let runtime = Runtime::new(Default::default())?;
|
||||
#[cfg(not(feature = "inspector"))]
|
||||
let runtime = Runtime::new()?;
|
||||
|
||||
let mut context = Self { ctx, runtime };
|
||||
context.init_derivation()?;
|
||||
let mut context = Self {
|
||||
ctx,
|
||||
runtime,
|
||||
#[cfg(feature = "inspector")]
|
||||
_inspector_server: None,
|
||||
};
|
||||
context.init()?;
|
||||
|
||||
Ok(context)
|
||||
}
|
||||
|
||||
fn init_derivation(&mut self) -> Result<()> {
|
||||
#[cfg(feature = "inspector")]
|
||||
pub fn new_with_inspector(addr: std::net::SocketAddr, wait_for_session: bool) -> Result<Self> {
|
||||
use crate::runtime::InspectorOptions;
|
||||
|
||||
let ctx = Ctx::new()?;
|
||||
let runtime = Runtime::new(InspectorOptions {
|
||||
enable: true,
|
||||
wait: wait_for_session,
|
||||
})?;
|
||||
|
||||
let server = crate::runtime::inspector::InspectorServer::new(addr, "nix-js")
|
||||
.map_err(|e| Error::internal(e.to_string()))?;
|
||||
server.register_inspector("nix-js".to_string(), runtime.inspector(), wait_for_session);
|
||||
|
||||
let mut context = Self {
|
||||
ctx,
|
||||
runtime,
|
||||
_inspector_server: Some(server),
|
||||
};
|
||||
context.init()?;
|
||||
Ok(context)
|
||||
}
|
||||
|
||||
#[cfg(feature = "inspector")]
|
||||
pub fn wait_for_inspector_disconnect(&mut self) {
|
||||
self.runtime.wait_for_inspector_disconnect();
|
||||
}
|
||||
|
||||
fn init(&mut self) -> Result<()> {
|
||||
const DERIVATION_NIX: &str = include_str!("runtime/corepkgs/derivation.nix");
|
||||
let source = Source::new_virtual(
|
||||
"<nix/derivation-internal.nix>".into(),
|
||||
DERIVATION_NIX.to_string(),
|
||||
);
|
||||
let code = self.ctx.compile(source, None)?;
|
||||
self.runtime
|
||||
.eval(format!("Nix.builtins.derivation = {}", code), &mut self.ctx)?;
|
||||
self.runtime.eval(
|
||||
format!(
|
||||
"Nix.builtins.derivation = {};Nix.builtins.storeDir=\"{}\"",
|
||||
code,
|
||||
self.get_store_dir()
|
||||
),
|
||||
&mut self.ctx,
|
||||
)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
|
||||
@@ -83,10 +83,7 @@ impl DerivationData {
|
||||
)
|
||||
}
|
||||
|
||||
pub fn generate_aterm_modulo(
|
||||
&self,
|
||||
input_drv_hashes: &BTreeMap<String, String>,
|
||||
) -> String {
|
||||
pub fn generate_aterm_modulo(&self, input_drv_hashes: &BTreeMap<String, String>) -> String {
|
||||
let mut output_entries = Vec::new();
|
||||
for (name, info) in &self.outputs {
|
||||
output_entries.push(format!(
|
||||
|
||||
@@ -1,15 +1,73 @@
|
||||
use std::process::exit;
|
||||
|
||||
use anyhow::Result;
|
||||
use clap::{Parser, Subcommand};
|
||||
use hashbrown::HashSet;
|
||||
use nix_js::context::Context;
|
||||
use nix_js::error::Source;
|
||||
use rustyline::DefaultEditor;
|
||||
use rustyline::error::ReadlineError;
|
||||
|
||||
fn main() -> Result<()> {
|
||||
nix_js::logging::init_logging();
|
||||
#[derive(Parser)]
|
||||
#[command(name = "nix-js", about = "Nix expression evaluator")]
|
||||
struct Cli {
|
||||
#[cfg(feature = "inspector")]
|
||||
#[arg(long, value_name = "HOST:PORT", num_args = 0..=1, default_missing_value = "127.0.0.1:9229")]
|
||||
inspect: Option<String>,
|
||||
|
||||
#[cfg(feature = "inspector")]
|
||||
#[arg(long, value_name = "HOST:PORT", num_args = 0..=1, default_missing_value = "127.0.0.1:9229")]
|
||||
inspect_brk: Option<String>,
|
||||
|
||||
#[command(subcommand)]
|
||||
command: Command,
|
||||
}
|
||||
|
||||
#[derive(Subcommand)]
|
||||
enum Command {
|
||||
Eval { expr: String },
|
||||
Repl,
|
||||
}
|
||||
|
||||
fn create_context(#[cfg(feature = "inspector")] cli: &Cli) -> Result<Context> {
|
||||
#[cfg(feature = "inspector")]
|
||||
{
|
||||
let (addr_str, wait) = if let Some(ref addr) = cli.inspect_brk {
|
||||
(Some(addr.as_str()), true)
|
||||
} else if let Some(ref addr) = cli.inspect {
|
||||
(Some(addr.as_str()), false)
|
||||
} else {
|
||||
(None, false)
|
||||
};
|
||||
|
||||
if let Some(addr_str) = addr_str {
|
||||
let addr: std::net::SocketAddr = addr_str
|
||||
.parse()
|
||||
.map_err(|e| anyhow::anyhow!("invalid inspector address '{}': {}", addr_str, e))?;
|
||||
return Ok(Context::new_with_inspector(addr, wait)?);
|
||||
}
|
||||
}
|
||||
Ok(Context::new()?)
|
||||
}
|
||||
|
||||
fn run_eval(context: &mut Context, expr: String) -> Result<()> {
|
||||
let src = Source::new_eval(expr)?;
|
||||
match context.eval(src) {
|
||||
Ok(value) => {
|
||||
println!("{value}");
|
||||
}
|
||||
Err(err) => {
|
||||
eprintln!("{:?}", miette::Report::new(*err));
|
||||
exit(1);
|
||||
}
|
||||
};
|
||||
#[cfg(feature = "inspector")]
|
||||
context.wait_for_inspector_disconnect();
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn run_repl(context: &mut Context) -> Result<()> {
|
||||
let mut rl = DefaultEditor::new()?;
|
||||
let mut context = Context::new()?;
|
||||
let mut scope = HashSet::new();
|
||||
const RE: ere::Regex<3> = ere::compile_regex!("^[ \t]*([a-zA-Z_][a-zA-Z0-9_'-]*)[ \t]*(.*)$");
|
||||
loop {
|
||||
@@ -61,3 +119,19 @@ fn main() -> Result<()> {
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn main() -> Result<()> {
|
||||
nix_js::logging::init_logging();
|
||||
|
||||
let cli = Cli::parse();
|
||||
|
||||
let mut context = create_context(
|
||||
#[cfg(feature = "inspector")]
|
||||
&cli,
|
||||
)?;
|
||||
|
||||
match cli.command {
|
||||
Command::Eval { expr } => run_eval(&mut context, expr),
|
||||
Command::Repl => run_repl(&mut context),
|
||||
}
|
||||
}
|
||||
@@ -2,12 +2,16 @@ use std::borrow::Cow;
|
||||
use std::marker::PhantomData;
|
||||
use std::path::Path;
|
||||
|
||||
#[cfg(feature = "inspector")]
|
||||
use deno_core::PollEventLoopOptions;
|
||||
use deno_core::{Extension, ExtensionFileSource, JsRuntime, OpState, RuntimeOptions, v8};
|
||||
|
||||
use crate::error::{Error, Result, Source};
|
||||
use crate::store::DaemonStore;
|
||||
use crate::value::{AttrSet, List, Symbol, Value};
|
||||
|
||||
#[cfg(feature = "inspector")]
|
||||
pub(crate) mod inspector;
|
||||
mod ops;
|
||||
use ops::*;
|
||||
|
||||
@@ -46,41 +50,30 @@ fn runtime_extension<Ctx: RuntimeContext>() -> Extension {
|
||||
let mut ops = vec![
|
||||
op_import::<Ctx>(),
|
||||
op_scoped_import::<Ctx>(),
|
||||
|
||||
op_resolve_path(),
|
||||
|
||||
op_read_file(),
|
||||
op_read_file_type(),
|
||||
op_read_dir(),
|
||||
op_path_exists(),
|
||||
op_walk_dir(),
|
||||
|
||||
op_make_placeholder(),
|
||||
op_store_path::<Ctx>(),
|
||||
|
||||
op_convert_hash(),
|
||||
op_hash_string(),
|
||||
op_hash_file(),
|
||||
op_parse_hash(),
|
||||
|
||||
op_add_path::<Ctx>(),
|
||||
op_add_filtered_path::<Ctx>(),
|
||||
|
||||
op_decode_span::<Ctx>(),
|
||||
|
||||
op_to_file::<Ctx>(),
|
||||
|
||||
op_copy_path_to_store::<Ctx>(),
|
||||
|
||||
op_get_env(),
|
||||
|
||||
op_match(),
|
||||
op_split(),
|
||||
|
||||
op_from_json(),
|
||||
op_from_toml(),
|
||||
|
||||
op_finalize_derivation::<Ctx>(),
|
||||
op_to_xml(),
|
||||
];
|
||||
ops.extend(crate::fetcher::register_ops::<Ctx>());
|
||||
|
||||
@@ -122,6 +115,9 @@ pub(crate) use private::NixRuntimeError;
|
||||
|
||||
pub(crate) struct Runtime<Ctx: RuntimeContext> {
|
||||
js_runtime: JsRuntime,
|
||||
rt: tokio::runtime::Runtime,
|
||||
#[cfg(feature = "inspector")]
|
||||
wait_for_inspector: bool,
|
||||
is_thunk_symbol: v8::Global<v8::Symbol>,
|
||||
primop_metadata_symbol: v8::Global<v8::Symbol>,
|
||||
has_context_symbol: v8::Global<v8::Symbol>,
|
||||
@@ -130,14 +126,21 @@ pub(crate) struct Runtime<Ctx: RuntimeContext> {
|
||||
_marker: PhantomData<Ctx>,
|
||||
}
|
||||
|
||||
#[cfg(feature = "inspector")]
|
||||
#[derive(Debug, Clone, Copy, Default)]
|
||||
pub(crate) struct InspectorOptions {
|
||||
pub(crate) enable: bool,
|
||||
pub(crate) wait: bool,
|
||||
}
|
||||
|
||||
impl<Ctx: RuntimeContext> Runtime<Ctx> {
|
||||
pub(crate) fn new() -> Result<Self> {
|
||||
pub(crate) fn new(
|
||||
#[cfg(feature = "inspector")] inspector_options: InspectorOptions,
|
||||
) -> Result<Self> {
|
||||
use std::sync::Once;
|
||||
|
||||
// Initialize V8 once
|
||||
static INIT: Once = Once::new();
|
||||
INIT.call_once(|| {
|
||||
// First flag is always not recognized
|
||||
assert_eq!(
|
||||
deno_core::v8_set_flags(vec!["".into(), format!("--stack-size={}", 8 * 1024)]),
|
||||
[""]
|
||||
@@ -147,6 +150,9 @@ impl<Ctx: RuntimeContext> Runtime<Ctx> {
|
||||
|
||||
let mut js_runtime = JsRuntime::new(RuntimeOptions {
|
||||
extensions: vec![runtime_extension::<Ctx>()],
|
||||
#[cfg(feature = "inspector")]
|
||||
inspector: inspector_options.enable,
|
||||
is_main: true,
|
||||
..Default::default()
|
||||
});
|
||||
|
||||
@@ -166,6 +172,12 @@ impl<Ctx: RuntimeContext> Runtime<Ctx> {
|
||||
|
||||
Ok(Self {
|
||||
js_runtime,
|
||||
rt: tokio::runtime::Builder::new_current_thread()
|
||||
.enable_all()
|
||||
.build()
|
||||
.expect("failed to build tokio runtime"),
|
||||
#[cfg(feature = "inspector")]
|
||||
wait_for_inspector: inspector_options.wait,
|
||||
is_thunk_symbol,
|
||||
primop_metadata_symbol,
|
||||
has_context_symbol,
|
||||
@@ -175,10 +187,33 @@ impl<Ctx: RuntimeContext> Runtime<Ctx> {
|
||||
})
|
||||
}
|
||||
|
||||
#[cfg(feature = "inspector")]
|
||||
pub(crate) fn inspector(&self) -> std::rc::Rc<deno_core::JsRuntimeInspector> {
|
||||
self.js_runtime.inspector()
|
||||
}
|
||||
|
||||
#[cfg(feature = "inspector")]
|
||||
pub(crate) fn wait_for_inspector_disconnect(&mut self) {
|
||||
let _ = self
|
||||
.rt
|
||||
.block_on(self.js_runtime.run_event_loop(PollEventLoopOptions {
|
||||
wait_for_inspector: true,
|
||||
..Default::default()
|
||||
}));
|
||||
}
|
||||
|
||||
pub(crate) fn eval(&mut self, script: String, ctx: &mut Ctx) -> Result<Value> {
|
||||
let ctx: &'static mut Ctx = unsafe { &mut *(ctx as *mut Ctx) };
|
||||
self.js_runtime.op_state().borrow_mut().put(ctx);
|
||||
|
||||
#[cfg(feature = "inspector")]
|
||||
if self.wait_for_inspector {
|
||||
self.js_runtime
|
||||
.inspector()
|
||||
.wait_for_session_and_break_on_next_statement();
|
||||
} else {
|
||||
self.js_runtime.inspector().wait_for_session();
|
||||
}
|
||||
let global_value = self
|
||||
.js_runtime
|
||||
.execute_script("<eval>", script)
|
||||
@@ -189,6 +224,22 @@ impl<Ctx: RuntimeContext> Runtime<Ctx> {
|
||||
|
||||
crate::error::parse_js_error(error, ctx)
|
||||
})?;
|
||||
let global_value = self
|
||||
.rt
|
||||
.block_on(self.js_runtime.resolve(global_value))
|
||||
.map_err(|error| {
|
||||
let op_state = self.js_runtime.op_state();
|
||||
let op_state_borrow = op_state.borrow();
|
||||
let ctx: &Ctx = op_state_borrow.get_ctx();
|
||||
|
||||
crate::error::parse_js_error(error, ctx)
|
||||
})?;
|
||||
#[cfg(feature = "inspector")]
|
||||
{
|
||||
let _ = self
|
||||
.rt
|
||||
.block_on(self.js_runtime.run_event_loop(Default::default()));
|
||||
}
|
||||
|
||||
// Retrieve scope from JsRuntime
|
||||
deno_core::scope!(scope, self.js_runtime);
|
||||
|
||||
491
nix-js/src/runtime/inspector.rs
Normal file
491
nix-js/src/runtime/inspector.rs
Normal file
@@ -0,0 +1,491 @@
|
||||
// Copyright 2018-2025 the Deno authors. MIT license.
|
||||
|
||||
// Alias for the future `!` type.
|
||||
use core::convert::Infallible as Never;
|
||||
use deno_core::InspectorMsg;
|
||||
use deno_core::InspectorSessionChannels;
|
||||
use deno_core::InspectorSessionKind;
|
||||
use deno_core::InspectorSessionProxy;
|
||||
use deno_core::JsRuntimeInspector;
|
||||
use deno_core::anyhow::Context;
|
||||
use deno_core::futures::channel::mpsc;
|
||||
use deno_core::futures::channel::mpsc::UnboundedReceiver;
|
||||
use deno_core::futures::channel::mpsc::UnboundedSender;
|
||||
use deno_core::futures::channel::oneshot;
|
||||
use deno_core::futures::prelude::*;
|
||||
use deno_core::futures::stream::StreamExt;
|
||||
use deno_core::serde_json::Value;
|
||||
use deno_core::serde_json::json;
|
||||
use deno_core::unsync::spawn;
|
||||
use deno_core::url::Url;
|
||||
use fastwebsockets::Frame;
|
||||
use fastwebsockets::OpCode;
|
||||
use fastwebsockets::WebSocket;
|
||||
use hyper::body::Bytes;
|
||||
use hyper_util::rt::TokioIo;
|
||||
use std::cell::RefCell;
|
||||
use std::collections::HashMap;
|
||||
use std::net::SocketAddr;
|
||||
use std::pin::pin;
|
||||
use std::process;
|
||||
use std::rc::Rc;
|
||||
use std::task::Poll;
|
||||
use std::thread;
|
||||
use tokio::net::TcpListener;
|
||||
use tokio::sync::broadcast;
|
||||
use uuid::Uuid;
|
||||
|
||||
/// Websocket server that is used to proxy connections from
|
||||
/// devtools to the inspector.
|
||||
pub struct InspectorServer {
|
||||
pub host: SocketAddr,
|
||||
register_inspector_tx: UnboundedSender<InspectorInfo>,
|
||||
shutdown_server_tx: Option<broadcast::Sender<()>>,
|
||||
thread_handle: Option<thread::JoinHandle<()>>,
|
||||
}
|
||||
|
||||
impl InspectorServer {
|
||||
pub fn new(host: SocketAddr, name: &'static str) -> Result<Self, anyhow::Error> {
|
||||
let (register_inspector_tx, register_inspector_rx) = mpsc::unbounded::<InspectorInfo>();
|
||||
|
||||
let (shutdown_server_tx, shutdown_server_rx) = broadcast::channel(1);
|
||||
|
||||
let tcp_listener = std::net::TcpListener::bind(host)
|
||||
.with_context(|| format!("Failed to bind inspector server socket at {}", host))?;
|
||||
tcp_listener.set_nonblocking(true)?;
|
||||
|
||||
let thread_handle = thread::spawn(move || {
|
||||
let rt = tokio::runtime::Builder::new_current_thread()
|
||||
.enable_all()
|
||||
.build()
|
||||
.expect("failed to build tokio runtime");
|
||||
let local = tokio::task::LocalSet::new();
|
||||
local.block_on(
|
||||
&rt,
|
||||
server(
|
||||
tcp_listener,
|
||||
register_inspector_rx,
|
||||
shutdown_server_rx,
|
||||
name,
|
||||
),
|
||||
)
|
||||
});
|
||||
|
||||
Ok(Self {
|
||||
host,
|
||||
register_inspector_tx,
|
||||
shutdown_server_tx: Some(shutdown_server_tx),
|
||||
thread_handle: Some(thread_handle),
|
||||
})
|
||||
}
|
||||
|
||||
pub fn register_inspector(
|
||||
&self,
|
||||
module_url: String,
|
||||
inspector: Rc<JsRuntimeInspector>,
|
||||
wait_for_session: bool,
|
||||
) {
|
||||
let session_sender = inspector.get_session_sender();
|
||||
let deregister_rx = inspector.add_deregister_handler();
|
||||
|
||||
let info = InspectorInfo::new(
|
||||
self.host,
|
||||
session_sender,
|
||||
deregister_rx,
|
||||
module_url,
|
||||
wait_for_session,
|
||||
);
|
||||
self.register_inspector_tx
|
||||
.unbounded_send(info)
|
||||
.expect("unreachable");
|
||||
}
|
||||
}
|
||||
|
||||
impl Drop for InspectorServer {
|
||||
fn drop(&mut self) {
|
||||
if let Some(shutdown_server_tx) = self.shutdown_server_tx.take() {
|
||||
shutdown_server_tx
|
||||
.send(())
|
||||
.expect("unable to send shutdown signal");
|
||||
}
|
||||
|
||||
if let Some(thread_handle) = self.thread_handle.take() {
|
||||
thread_handle.join().expect("unable to join thread");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn handle_ws_request(
|
||||
req: http::Request<hyper::body::Incoming>,
|
||||
inspector_map_rc: Rc<RefCell<HashMap<Uuid, InspectorInfo>>>,
|
||||
) -> http::Result<http::Response<Box<http_body_util::Full<Bytes>>>> {
|
||||
let (parts, body) = req.into_parts();
|
||||
let req = http::Request::from_parts(parts, ());
|
||||
|
||||
let maybe_uuid = req
|
||||
.uri()
|
||||
.path()
|
||||
.strip_prefix("/ws/")
|
||||
.and_then(|s| Uuid::parse_str(s).ok());
|
||||
|
||||
let Some(uuid) = maybe_uuid else {
|
||||
return http::Response::builder()
|
||||
.status(http::StatusCode::BAD_REQUEST)
|
||||
.body(Box::new(Bytes::from("Malformed inspector UUID").into()));
|
||||
};
|
||||
|
||||
// run in a block to not hold borrow to `inspector_map` for too long
|
||||
let new_session_tx = {
|
||||
let inspector_map = inspector_map_rc.borrow();
|
||||
let maybe_inspector_info = inspector_map.get(&uuid);
|
||||
|
||||
let Some(info) = maybe_inspector_info else {
|
||||
return http::Response::builder()
|
||||
.status(http::StatusCode::NOT_FOUND)
|
||||
.body(Box::new(Bytes::from("Invalid inspector UUID").into()));
|
||||
};
|
||||
info.new_session_tx.clone()
|
||||
};
|
||||
let (parts, _) = req.into_parts();
|
||||
let mut req = http::Request::from_parts(parts, body);
|
||||
|
||||
let Ok((resp, upgrade_fut)) = fastwebsockets::upgrade::upgrade(&mut req) else {
|
||||
return http::Response::builder()
|
||||
.status(http::StatusCode::BAD_REQUEST)
|
||||
.body(Box::new(
|
||||
Bytes::from("Not a valid Websocket Request").into(),
|
||||
));
|
||||
};
|
||||
|
||||
// spawn a task that will wait for websocket connection and then pump messages between
|
||||
// the socket and inspector proxy
|
||||
spawn(async move {
|
||||
let websocket = match upgrade_fut.await {
|
||||
Ok(w) => w,
|
||||
Err(err) => {
|
||||
eprintln!(
|
||||
"Inspector server failed to upgrade to WS connection: {:?}",
|
||||
err
|
||||
);
|
||||
return;
|
||||
}
|
||||
};
|
||||
|
||||
// The 'outbound' channel carries messages sent to the websocket.
|
||||
let (outbound_tx, outbound_rx) = mpsc::unbounded();
|
||||
// The 'inbound' channel carries messages received from the websocket.
|
||||
let (inbound_tx, inbound_rx) = mpsc::unbounded();
|
||||
|
||||
let inspector_session_proxy = InspectorSessionProxy {
|
||||
channels: InspectorSessionChannels::Regular {
|
||||
tx: outbound_tx,
|
||||
rx: inbound_rx,
|
||||
},
|
||||
kind: InspectorSessionKind::NonBlocking {
|
||||
wait_for_disconnect: true,
|
||||
},
|
||||
};
|
||||
|
||||
eprintln!("Debugger session started.");
|
||||
let _ = new_session_tx.unbounded_send(inspector_session_proxy);
|
||||
pump_websocket_messages(websocket, inbound_tx, outbound_rx).await;
|
||||
});
|
||||
|
||||
let (parts, _body) = resp.into_parts();
|
||||
let resp = http::Response::from_parts(parts, Box::new(http_body_util::Full::new(Bytes::new())));
|
||||
Ok(resp)
|
||||
}
|
||||
|
||||
fn handle_json_request(
|
||||
inspector_map: Rc<RefCell<HashMap<Uuid, InspectorInfo>>>,
|
||||
host: Option<String>,
|
||||
) -> http::Result<http::Response<Box<http_body_util::Full<Bytes>>>> {
|
||||
let data = inspector_map
|
||||
.borrow()
|
||||
.values()
|
||||
.map(move |info| info.get_json_metadata(&host))
|
||||
.collect::<Vec<_>>();
|
||||
let body: http_body_util::Full<Bytes> =
|
||||
Bytes::from(serde_json::to_string(&data).expect("unreachable")).into();
|
||||
http::Response::builder()
|
||||
.status(http::StatusCode::OK)
|
||||
.header(http::header::CONTENT_TYPE, "application/json")
|
||||
.body(Box::new(body))
|
||||
}
|
||||
|
||||
fn handle_json_version_request(
|
||||
version_response: Value,
|
||||
) -> http::Result<http::Response<Box<http_body_util::Full<Bytes>>>> {
|
||||
let body = Box::new(http_body_util::Full::from(
|
||||
serde_json::to_string(&version_response).expect("unreachable"),
|
||||
));
|
||||
|
||||
http::Response::builder()
|
||||
.status(http::StatusCode::OK)
|
||||
.header(http::header::CONTENT_TYPE, "application/json")
|
||||
.body(body)
|
||||
}
|
||||
|
||||
async fn server(
|
||||
listener: std::net::TcpListener,
|
||||
register_inspector_rx: UnboundedReceiver<InspectorInfo>,
|
||||
shutdown_server_rx: broadcast::Receiver<()>,
|
||||
name: &str,
|
||||
) {
|
||||
let inspector_map_ = Rc::new(RefCell::new(HashMap::<Uuid, InspectorInfo>::new()));
|
||||
|
||||
let inspector_map = Rc::clone(&inspector_map_);
|
||||
let register_inspector_handler =
|
||||
listen_for_new_inspectors(register_inspector_rx, inspector_map.clone()).boxed_local();
|
||||
|
||||
let inspector_map = Rc::clone(&inspector_map_);
|
||||
let deregister_inspector_handler = future::poll_fn(|cx| {
|
||||
inspector_map
|
||||
.borrow_mut()
|
||||
.retain(|_, info| info.deregister_rx.poll_unpin(cx) == Poll::Pending);
|
||||
Poll::<Never>::Pending
|
||||
})
|
||||
.boxed_local();
|
||||
|
||||
let json_version_response = json!({
|
||||
"Browser": name,
|
||||
"Protocol-Version": "1.3",
|
||||
"V8-Version": deno_core::v8::VERSION_STRING,
|
||||
});
|
||||
|
||||
// Create the server manually so it can use the Local Executor
|
||||
let listener = match TcpListener::from_std(listener) {
|
||||
Ok(l) => l,
|
||||
Err(err) => {
|
||||
eprintln!("Cannot create async listener from std listener: {:?}", err);
|
||||
return;
|
||||
}
|
||||
};
|
||||
|
||||
let server_handler = async move {
|
||||
loop {
|
||||
let mut rx = shutdown_server_rx.resubscribe();
|
||||
let mut shutdown_rx = pin!(rx.recv());
|
||||
let mut accept = pin!(listener.accept());
|
||||
|
||||
let stream = tokio::select! {
|
||||
accept_result = &mut accept => {
|
||||
match accept_result {
|
||||
Ok((s, _)) => s,
|
||||
Err(err) => {
|
||||
eprintln!("Failed to accept inspector connection: {:?}", err);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
_ = &mut shutdown_rx => {
|
||||
break;
|
||||
}
|
||||
};
|
||||
let io = TokioIo::new(stream);
|
||||
|
||||
let inspector_map = Rc::clone(&inspector_map_);
|
||||
let json_version_response = json_version_response.clone();
|
||||
let mut shutdown_server_rx = shutdown_server_rx.resubscribe();
|
||||
|
||||
let service =
|
||||
hyper::service::service_fn(move |req: http::Request<hyper::body::Incoming>| {
|
||||
future::ready({
|
||||
// If the host header can make a valid URL, use it
|
||||
let host = req
|
||||
.headers()
|
||||
.get("host")
|
||||
.and_then(|host| host.to_str().ok())
|
||||
.and_then(|host| Url::parse(&format!("http://{host}")).ok())
|
||||
.and_then(|url| match (url.host(), url.port()) {
|
||||
(Some(host), Some(port)) => Some(format!("{host}:{port}")),
|
||||
(Some(host), None) => Some(format!("{host}")),
|
||||
_ => None,
|
||||
});
|
||||
match (req.method(), req.uri().path()) {
|
||||
(&http::Method::GET, path) if path.starts_with("/ws/") => {
|
||||
handle_ws_request(req, Rc::clone(&inspector_map))
|
||||
}
|
||||
(&http::Method::GET, "/json/version") => {
|
||||
handle_json_version_request(json_version_response.clone())
|
||||
}
|
||||
(&http::Method::GET, "/json") => {
|
||||
handle_json_request(Rc::clone(&inspector_map), host)
|
||||
}
|
||||
(&http::Method::GET, "/json/list") => {
|
||||
handle_json_request(Rc::clone(&inspector_map), host)
|
||||
}
|
||||
_ => http::Response::builder()
|
||||
.status(http::StatusCode::NOT_FOUND)
|
||||
.body(Box::new(http_body_util::Full::new(Bytes::from(
|
||||
"Not Found",
|
||||
)))),
|
||||
}
|
||||
})
|
||||
});
|
||||
|
||||
deno_core::unsync::spawn(async move {
|
||||
let server = hyper::server::conn::http1::Builder::new();
|
||||
|
||||
let mut conn = pin!(server.serve_connection(io, service).with_upgrades());
|
||||
let mut shutdown_rx = pin!(shutdown_server_rx.recv());
|
||||
|
||||
tokio::select! {
|
||||
result = conn.as_mut() => {
|
||||
if let Err(err) = result {
|
||||
eprintln!("Failed to serve connection: {:?}", err);
|
||||
}
|
||||
},
|
||||
_ = &mut shutdown_rx => {
|
||||
conn.as_mut().graceful_shutdown();
|
||||
let _ = conn.await;
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
.boxed_local();
|
||||
|
||||
tokio::select! {
|
||||
_ = register_inspector_handler => {},
|
||||
_ = deregister_inspector_handler => unreachable!(),
|
||||
_ = server_handler => {},
|
||||
}
|
||||
}
|
||||
|
||||
async fn listen_for_new_inspectors(
|
||||
mut register_inspector_rx: UnboundedReceiver<InspectorInfo>,
|
||||
inspector_map: Rc<RefCell<HashMap<Uuid, InspectorInfo>>>,
|
||||
) {
|
||||
while let Some(info) = register_inspector_rx.next().await {
|
||||
eprintln!(
|
||||
"Debugger listening on {}",
|
||||
info.get_websocket_debugger_url(&info.host.to_string())
|
||||
);
|
||||
eprintln!("Visit chrome://inspect to connect to the debugger.");
|
||||
if info.wait_for_session {
|
||||
eprintln!("nix-js is waiting for debugger to connect.");
|
||||
}
|
||||
if inspector_map.borrow_mut().insert(info.uuid, info).is_some() {
|
||||
panic!("Inspector UUID already in map");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// The pump future takes care of forwarding messages between the websocket
|
||||
/// and channels. It resolves when either side disconnects, ignoring any
|
||||
/// errors.
|
||||
///
|
||||
/// The future proxies messages sent and received on a WebSocket
|
||||
/// to a UnboundedSender/UnboundedReceiver pair. We need these "unbounded" channel ends to sidestep
|
||||
/// Tokio's task budget, which causes issues when JsRuntimeInspector::poll_sessions()
|
||||
/// needs to block the thread because JavaScript execution is paused.
|
||||
///
|
||||
/// This works because UnboundedSender/UnboundedReceiver are implemented in the
|
||||
/// 'futures' crate, therefore they can't participate in Tokio's cooperative
|
||||
/// task yielding.
|
||||
async fn pump_websocket_messages(
|
||||
mut websocket: WebSocket<TokioIo<hyper::upgrade::Upgraded>>,
|
||||
inbound_tx: UnboundedSender<String>,
|
||||
mut outbound_rx: UnboundedReceiver<InspectorMsg>,
|
||||
) {
|
||||
'pump: loop {
|
||||
tokio::select! {
|
||||
Some(msg) = outbound_rx.next() => {
|
||||
let msg = Frame::text(msg.content.into_bytes().into());
|
||||
let _ = websocket.write_frame(msg).await;
|
||||
}
|
||||
Ok(msg) = websocket.read_frame() => {
|
||||
match msg.opcode {
|
||||
OpCode::Text => {
|
||||
if let Ok(s) = String::from_utf8(msg.payload.to_vec()) {
|
||||
let _ = inbound_tx.unbounded_send(s);
|
||||
}
|
||||
}
|
||||
OpCode::Close => {
|
||||
// Users don't care if there was an error coming from debugger,
|
||||
// just about the fact that debugger did disconnect.
|
||||
eprintln!("Debugger session ended");
|
||||
break 'pump;
|
||||
}
|
||||
_ => {
|
||||
// Ignore other messages.
|
||||
}
|
||||
}
|
||||
}
|
||||
else => {
|
||||
break 'pump;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Inspector information that is sent from the isolate thread to the server
|
||||
/// thread when a new inspector is created.
|
||||
pub struct InspectorInfo {
|
||||
pub host: SocketAddr,
|
||||
pub uuid: Uuid,
|
||||
pub thread_name: Option<String>,
|
||||
pub new_session_tx: UnboundedSender<InspectorSessionProxy>,
|
||||
pub deregister_rx: oneshot::Receiver<()>,
|
||||
pub url: String,
|
||||
pub wait_for_session: bool,
|
||||
}
|
||||
|
||||
impl InspectorInfo {
|
||||
pub fn new(
|
||||
host: SocketAddr,
|
||||
new_session_tx: mpsc::UnboundedSender<InspectorSessionProxy>,
|
||||
deregister_rx: oneshot::Receiver<()>,
|
||||
url: String,
|
||||
wait_for_session: bool,
|
||||
) -> Self {
|
||||
Self {
|
||||
host,
|
||||
uuid: Uuid::new_v4(),
|
||||
thread_name: thread::current().name().map(|n| n.to_owned()),
|
||||
new_session_tx,
|
||||
deregister_rx,
|
||||
url,
|
||||
wait_for_session,
|
||||
}
|
||||
}
|
||||
|
||||
fn get_json_metadata(&self, host: &Option<String>) -> Value {
|
||||
let host_listen = format!("{}", self.host);
|
||||
let host = host.as_ref().unwrap_or(&host_listen);
|
||||
json!({
|
||||
"description": "nix-js",
|
||||
"devtoolsFrontendUrl": self.get_frontend_url(host),
|
||||
"faviconUrl": "https://deno.land/favicon.ico",
|
||||
"id": self.uuid.to_string(),
|
||||
"title": self.get_title(),
|
||||
"type": "node",
|
||||
"url": self.url.to_string(),
|
||||
"webSocketDebuggerUrl": self.get_websocket_debugger_url(host),
|
||||
})
|
||||
}
|
||||
|
||||
pub fn get_websocket_debugger_url(&self, host: &str) -> String {
|
||||
format!("ws://{}/ws/{}", host, &self.uuid)
|
||||
}
|
||||
|
||||
fn get_frontend_url(&self, host: &str) -> String {
|
||||
format!(
|
||||
"devtools://devtools/bundled/js_app.html?ws={}/ws/{}&experiments=true&v8only=true",
|
||||
host, &self.uuid
|
||||
)
|
||||
}
|
||||
|
||||
fn get_title(&self) -> String {
|
||||
format!(
|
||||
"nix-js{} [pid: {}]",
|
||||
self.thread_name
|
||||
.as_ref()
|
||||
.map(|n| format!(" - {n}"))
|
||||
.unwrap_or_default(),
|
||||
process::id(),
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -3,8 +3,7 @@ use std::str::FromStr;
|
||||
|
||||
use hashbrown::hash_map::{Entry, HashMap};
|
||||
|
||||
use deno_core::OpState;
|
||||
use deno_core::v8;
|
||||
use deno_core::{FromV8, OpState, v8};
|
||||
use regex::Regex;
|
||||
use rust_embed::Embed;
|
||||
|
||||
@@ -12,6 +11,8 @@ use super::{NixRuntimeError, OpStateExt, RuntimeContext};
|
||||
use crate::error::Source;
|
||||
use crate::store::Store as _;
|
||||
|
||||
type Result<T> = std::result::Result<T, NixRuntimeError>;
|
||||
|
||||
#[derive(Debug, Default)]
|
||||
pub(super) struct RegexCache {
|
||||
cache: HashMap<String, Regex>,
|
||||
@@ -24,7 +25,7 @@ impl RegexCache {
|
||||
}
|
||||
}
|
||||
|
||||
fn get_regex(&mut self, pattern: &str) -> Result<Regex, regex::Error> {
|
||||
fn get_regex(&mut self, pattern: &str) -> std::result::Result<Regex, regex::Error> {
|
||||
Ok(match self.cache.entry(pattern.to_string()) {
|
||||
Entry::Occupied(occupied) => occupied.get().clone(),
|
||||
Entry::Vacant(vacant) => {
|
||||
@@ -44,7 +45,7 @@ pub(crate) struct CorePkgs;
|
||||
pub(super) fn op_import<Ctx: RuntimeContext>(
|
||||
state: &mut OpState,
|
||||
#[string] path: String,
|
||||
) -> std::result::Result<String, NixRuntimeError> {
|
||||
) -> Result<String> {
|
||||
let _span = tracing::info_span!("op_import", path = %path).entered();
|
||||
let ctx: &mut Ctx = state.get_ctx_mut();
|
||||
|
||||
@@ -93,7 +94,7 @@ pub(super) fn op_scoped_import<Ctx: RuntimeContext>(
|
||||
state: &mut OpState,
|
||||
#[string] path: String,
|
||||
#[serde] scope: Vec<String>,
|
||||
) -> std::result::Result<String, NixRuntimeError> {
|
||||
) -> Result<String> {
|
||||
let _span = tracing::info_span!("op_scoped_import", path = %path).entered();
|
||||
let ctx: &mut Ctx = state.get_ctx_mut();
|
||||
|
||||
@@ -119,7 +120,7 @@ pub(super) fn op_scoped_import<Ctx: RuntimeContext>(
|
||||
|
||||
#[deno_core::op2]
|
||||
#[string]
|
||||
pub(super) fn op_read_file(#[string] path: String) -> std::result::Result<String, NixRuntimeError> {
|
||||
pub(super) fn op_read_file(#[string] path: String) -> Result<String> {
|
||||
Ok(std::fs::read_to_string(&path).map_err(|e| format!("Failed to read {}: {}", path, e))?)
|
||||
}
|
||||
|
||||
@@ -140,9 +141,7 @@ pub(super) fn op_path_exists(#[string] path: String) -> bool {
|
||||
|
||||
#[deno_core::op2]
|
||||
#[string]
|
||||
pub(super) fn op_read_file_type(
|
||||
#[string] path: String,
|
||||
) -> std::result::Result<String, NixRuntimeError> {
|
||||
pub(super) fn op_read_file_type(#[string] path: String) -> Result<String> {
|
||||
let path = Path::new(&path);
|
||||
let metadata = std::fs::symlink_metadata(path)
|
||||
.map_err(|e| format!("Failed to read file type for {}: {}", path.display(), e))?;
|
||||
@@ -165,7 +164,7 @@ pub(super) fn op_read_file_type(
|
||||
#[serde]
|
||||
pub(super) fn op_read_dir(
|
||||
#[string] path: String,
|
||||
) -> std::result::Result<std::collections::HashMap<String, String>, NixRuntimeError> {
|
||||
) -> Result<std::collections::HashMap<String, String>> {
|
||||
let path = Path::new(&path);
|
||||
|
||||
if !path.is_dir() {
|
||||
@@ -210,7 +209,7 @@ pub(super) fn op_read_dir(
|
||||
pub(super) fn op_resolve_path(
|
||||
#[string] current_dir: String,
|
||||
#[string] path: String,
|
||||
) -> std::result::Result<String, NixRuntimeError> {
|
||||
) -> Result<String> {
|
||||
let _span = tracing::debug_span!("op_resolve_path").entered();
|
||||
tracing::debug!(current_dir, path);
|
||||
|
||||
@@ -261,7 +260,7 @@ pub(super) fn op_make_placeholder(#[string] output: String) -> String {
|
||||
pub(super) fn op_decode_span<Ctx: RuntimeContext>(
|
||||
state: &mut OpState,
|
||||
#[string] span_str: String,
|
||||
) -> std::result::Result<serde_json::Value, NixRuntimeError> {
|
||||
) -> Result<serde_json::Value> {
|
||||
let parts: Vec<&str> = span_str.split(':').collect();
|
||||
if parts.len() != 3 {
|
||||
return Ok(serde_json::json!({
|
||||
@@ -317,7 +316,7 @@ pub(super) struct ParsedHash {
|
||||
pub(super) fn op_parse_hash(
|
||||
#[string] hash_str: String,
|
||||
#[string] algo: Option<String>,
|
||||
) -> std::result::Result<ParsedHash, NixRuntimeError> {
|
||||
) -> Result<ParsedHash> {
|
||||
use nix_compat::nixhash::{HashAlgo, NixHash};
|
||||
|
||||
let hash_algo = algo
|
||||
@@ -347,7 +346,7 @@ pub(super) fn op_add_path<Ctx: RuntimeContext>(
|
||||
#[string] name: Option<String>,
|
||||
recursive: bool,
|
||||
#[string] sha256: Option<String>,
|
||||
) -> std::result::Result<String, NixRuntimeError> {
|
||||
) -> Result<String> {
|
||||
use nix_compat::nixhash::{HashAlgo, NixHash};
|
||||
use sha2::{Digest, Sha256};
|
||||
use std::fs;
|
||||
@@ -423,7 +422,7 @@ pub(super) fn op_add_path<Ctx: RuntimeContext>(
|
||||
pub(super) fn op_store_path<Ctx: RuntimeContext>(
|
||||
state: &mut OpState,
|
||||
#[string] path: String,
|
||||
) -> std::result::Result<String, NixRuntimeError> {
|
||||
) -> Result<String> {
|
||||
use crate::store::validate_store_path;
|
||||
|
||||
let ctx: &Ctx = state.get_ctx();
|
||||
@@ -446,7 +445,7 @@ pub(super) fn op_to_file<Ctx: RuntimeContext>(
|
||||
#[string] name: String,
|
||||
#[string] contents: String,
|
||||
#[serde] references: Vec<String>,
|
||||
) -> std::result::Result<String, NixRuntimeError> {
|
||||
) -> Result<String> {
|
||||
let ctx: &Ctx = state.get_ctx();
|
||||
let store = ctx.get_store();
|
||||
let store_path = store
|
||||
@@ -461,7 +460,7 @@ pub(super) fn op_to_file<Ctx: RuntimeContext>(
|
||||
pub(super) fn op_copy_path_to_store<Ctx: RuntimeContext>(
|
||||
state: &mut OpState,
|
||||
#[string] path: String,
|
||||
) -> std::result::Result<String, NixRuntimeError> {
|
||||
) -> Result<String> {
|
||||
use std::path::Path;
|
||||
|
||||
let path_obj = Path::new(&path);
|
||||
@@ -490,7 +489,7 @@ pub(super) fn op_copy_path_to_store<Ctx: RuntimeContext>(
|
||||
|
||||
#[deno_core::op2]
|
||||
#[string]
|
||||
pub(super) fn op_get_env(#[string] key: String) -> std::result::Result<String, NixRuntimeError> {
|
||||
pub(super) fn op_get_env(#[string] key: String) -> Result<String> {
|
||||
match std::env::var(key) {
|
||||
Ok(val) => Ok(val),
|
||||
Err(std::env::VarError::NotPresent) => Ok("".into()),
|
||||
@@ -500,14 +499,12 @@ pub(super) fn op_get_env(#[string] key: String) -> std::result::Result<String, N
|
||||
|
||||
#[deno_core::op2]
|
||||
#[serde]
|
||||
pub(super) fn op_walk_dir(
|
||||
#[string] path: String,
|
||||
) -> std::result::Result<Vec<(String, String)>, NixRuntimeError> {
|
||||
pub(super) fn op_walk_dir(#[string] path: String) -> Result<Vec<(String, String)>> {
|
||||
fn walk_recursive(
|
||||
base: &Path,
|
||||
current: &Path,
|
||||
results: &mut Vec<(String, String)>,
|
||||
) -> std::result::Result<(), NixRuntimeError> {
|
||||
) -> Result<()> {
|
||||
let entries = std::fs::read_dir(current)
|
||||
.map_err(|e| NixRuntimeError::from(format!("failed to read directory: {}", e)))?;
|
||||
|
||||
@@ -564,7 +561,7 @@ pub(super) fn op_add_filtered_path<Ctx: RuntimeContext>(
|
||||
recursive: bool,
|
||||
#[string] sha256: Option<String>,
|
||||
#[serde] include_paths: Vec<String>,
|
||||
) -> std::result::Result<String, NixRuntimeError> {
|
||||
) -> Result<String> {
|
||||
use nix_compat::nixhash::{HashAlgo, NixHash};
|
||||
use sha2::{Digest, Sha256};
|
||||
use std::fs;
|
||||
@@ -667,7 +664,7 @@ pub(super) fn op_match(
|
||||
state: &mut OpState,
|
||||
#[string] regex: String,
|
||||
#[string] text: String,
|
||||
) -> std::result::Result<Option<Vec<Option<String>>>, NixRuntimeError> {
|
||||
) -> Result<Option<Vec<Option<String>>>> {
|
||||
let cache = state.borrow_mut::<RegexCache>();
|
||||
let re = cache
|
||||
.get_regex(&format!("^{}$", regex))
|
||||
@@ -692,7 +689,7 @@ pub(super) fn op_split(
|
||||
state: &mut OpState,
|
||||
#[string] regex: String,
|
||||
#[string] text: String,
|
||||
) -> std::result::Result<Vec<SplitResult>, NixRuntimeError> {
|
||||
) -> Result<Vec<SplitResult>> {
|
||||
let cache = state.borrow_mut::<RegexCache>();
|
||||
let re = cache
|
||||
.get_regex(®ex)
|
||||
@@ -741,41 +738,39 @@ pub(super) enum NixJsonValue {
|
||||
}
|
||||
|
||||
impl<'a> deno_core::convert::ToV8<'a> for NixJsonValue {
|
||||
type Error = deno_error::JsErrorBox;
|
||||
type Error = NixRuntimeError;
|
||||
|
||||
fn to_v8<'i>(
|
||||
self,
|
||||
scope: &mut v8::PinScope<'a, 'i>,
|
||||
) -> std::result::Result<v8::Local<'a, v8::Value>, Self::Error> {
|
||||
match self {
|
||||
Self::Null => Ok(v8::null(scope).into()),
|
||||
Self::Bool(b) => Ok(v8::Boolean::new(scope, b).into()),
|
||||
Self::Int(i) => Ok(v8::BigInt::new_from_i64(scope, i).into()),
|
||||
Self::Float(f) => Ok(v8::Number::new(scope, f).into()),
|
||||
Ok(match self {
|
||||
Self::Null => v8::null(scope).into() ,
|
||||
Self::Bool(b) => v8::Boolean::new(scope, b).into() ,
|
||||
Self::Int(i) => v8::BigInt::new_from_i64(scope, i).into() ,
|
||||
Self::Float(f) => v8::Number::new(scope, f).into() ,
|
||||
Self::Str(s) => v8::String::new(scope, &s)
|
||||
.map(|s| s.into())
|
||||
.ok_or_else(|| deno_error::JsErrorBox::type_error("failed to create v8 string")),
|
||||
.ok_or("failed to create v8 string")?,
|
||||
Self::Arr(arr) => {
|
||||
let elements = arr
|
||||
.into_iter()
|
||||
.map(|v| v.to_v8(scope))
|
||||
.collect::<std::result::Result<Vec<_>, _>>()?;
|
||||
Ok(v8::Array::new_with_elements(scope, &elements).into())
|
||||
v8::Array::new_with_elements(scope, &elements).into()
|
||||
}
|
||||
Self::Obj(entries) => {
|
||||
let obj = v8::Object::new(scope);
|
||||
for (k, v) in entries {
|
||||
let key: v8::Local<v8::Value> = v8::String::new(scope, &k)
|
||||
.ok_or_else(|| {
|
||||
deno_error::JsErrorBox::type_error("failed to create v8 string")
|
||||
})?
|
||||
.ok_or("failed to create v8 string")?
|
||||
.into();
|
||||
let val = v.to_v8(scope)?;
|
||||
obj.set(scope, key, val);
|
||||
}
|
||||
Ok(obj.into())
|
||||
obj.into()
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -815,7 +810,7 @@ impl DrvHashCache {
|
||||
}
|
||||
}
|
||||
|
||||
fn toml_to_nix(value: toml::Value) -> std::result::Result<NixJsonValue, NixRuntimeError> {
|
||||
fn toml_to_nix(value: toml::Value) -> Result<NixJsonValue> {
|
||||
match value {
|
||||
toml::Value::String(s) => Ok(NixJsonValue::Str(s)),
|
||||
toml::Value::Integer(i) => Ok(NixJsonValue::Int(i)),
|
||||
@@ -839,18 +834,14 @@ fn toml_to_nix(value: toml::Value) -> std::result::Result<NixJsonValue, NixRunti
|
||||
}
|
||||
|
||||
#[deno_core::op2]
|
||||
pub(super) fn op_from_json(
|
||||
#[string] json_str: String,
|
||||
) -> std::result::Result<NixJsonValue, NixRuntimeError> {
|
||||
pub(super) fn op_from_json(#[string] json_str: String) -> Result<NixJsonValue> {
|
||||
let parsed: serde_json::Value = serde_json::from_str(&json_str)
|
||||
.map_err(|e| NixRuntimeError::from(format!("builtins.fromJSON: {e}")))?;
|
||||
Ok(json_to_nix(parsed))
|
||||
}
|
||||
|
||||
#[deno_core::op2]
|
||||
pub(super) fn op_from_toml(
|
||||
#[string] toml_str: String,
|
||||
) -> std::result::Result<NixJsonValue, NixRuntimeError> {
|
||||
pub(super) fn op_from_toml(#[string] toml_str: String) -> Result<NixJsonValue> {
|
||||
let parsed: toml::Value = toml::from_str(&toml_str)
|
||||
.map_err(|e| NixRuntimeError::from(format!("while parsing TOML: {e}")))?;
|
||||
toml_to_nix(parsed)
|
||||
@@ -898,7 +889,7 @@ fn output_path_name(drv_name: &str, output: &str) -> String {
|
||||
pub(super) fn op_finalize_derivation<Ctx: RuntimeContext>(
|
||||
state: &mut OpState,
|
||||
#[serde] input: FinalizeDerivationInput,
|
||||
) -> std::result::Result<FinalizeDerivationOutput, NixRuntimeError> {
|
||||
) -> Result<FinalizeDerivationOutput> {
|
||||
use crate::derivation::{DerivationData, OutputInfo};
|
||||
use crate::string_context::extract_input_drvs_and_srcs;
|
||||
|
||||
@@ -909,8 +900,7 @@ pub(super) fn op_finalize_derivation<Ctx: RuntimeContext>(
|
||||
let (input_drvs, input_srcs) =
|
||||
extract_input_drvs_and_srcs(&input.context).map_err(NixRuntimeError::from)?;
|
||||
|
||||
let env: std::collections::BTreeMap<String, String> =
|
||||
input.env.into_iter().collect();
|
||||
let env: std::collections::BTreeMap<String, String> = input.env.into_iter().collect();
|
||||
|
||||
let drv_path;
|
||||
let output_paths: Vec<(String, String)>;
|
||||
@@ -1008,19 +998,16 @@ pub(super) fn op_finalize_derivation<Ctx: RuntimeContext>(
|
||||
{
|
||||
let cache = state.borrow::<DrvHashCache>();
|
||||
for (dep_drv_path, output_names) in &input_drvs {
|
||||
let cached_hash =
|
||||
cache.cache.get(dep_drv_path).ok_or_else(|| {
|
||||
NixRuntimeError::from(format!(
|
||||
"Missing modulo hash for input derivation: {}",
|
||||
dep_drv_path
|
||||
))
|
||||
})?;
|
||||
let cached_hash = cache.cache.get(dep_drv_path).ok_or_else(|| {
|
||||
NixRuntimeError::from(format!(
|
||||
"Missing modulo hash for input derivation: {}",
|
||||
dep_drv_path
|
||||
))
|
||||
})?;
|
||||
let mut sorted_outs: Vec<&String> = output_names.iter().collect();
|
||||
sorted_outs.sort();
|
||||
let outputs_csv: Vec<&str> =
|
||||
sorted_outs.iter().map(|s| s.as_str()).collect();
|
||||
input_drv_hashes
|
||||
.insert(cached_hash.clone(), outputs_csv.join(","));
|
||||
let outputs_csv: Vec<&str> = sorted_outs.iter().map(|s| s.as_str()).collect();
|
||||
input_drv_hashes.insert(cached_hash.clone(), outputs_csv.join(","));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1070,8 +1057,7 @@ pub(super) fn op_finalize_derivation<Ctx: RuntimeContext>(
|
||||
.map_err(|e| NixRuntimeError::from(format!("failed to write derivation: {}", e)))?;
|
||||
|
||||
let final_aterm_modulo = final_drv.generate_aterm_modulo(&input_drv_hashes);
|
||||
let cached_modulo_hash =
|
||||
crate::nix_utils::sha256_hex(final_aterm_modulo.as_bytes());
|
||||
let cached_modulo_hash = crate::nix_utils::sha256_hex(final_aterm_modulo.as_bytes());
|
||||
|
||||
let cache = state.borrow_mut::<DrvHashCache>();
|
||||
cache.cache.insert(drv_path.clone(), cached_modulo_hash);
|
||||
@@ -1109,10 +1095,7 @@ fn op_make_fixed_output_path_impl(
|
||||
|
||||
#[deno_core::op2]
|
||||
#[string]
|
||||
pub(super) fn op_hash_string(
|
||||
#[string] algo: String,
|
||||
#[string] data: String,
|
||||
) -> std::result::Result<String, NixRuntimeError> {
|
||||
pub(super) fn op_hash_string(#[string] algo: String, #[string] data: String) -> Result<String> {
|
||||
use sha2::{Digest, Sha256, Sha512};
|
||||
|
||||
let hash_bytes: Vec<u8> = match algo.as_str() {
|
||||
@@ -1149,10 +1132,7 @@ pub(super) fn op_hash_string(
|
||||
|
||||
#[deno_core::op2]
|
||||
#[string]
|
||||
pub(super) fn op_hash_file(
|
||||
#[string] algo: String,
|
||||
#[string] path: String,
|
||||
) -> std::result::Result<String, NixRuntimeError> {
|
||||
pub(super) fn op_hash_file(#[string] algo: String, #[string] path: String) -> Result<String> {
|
||||
let data = std::fs::read(&path)
|
||||
.map_err(|e| NixRuntimeError::from(format!("cannot read '{}': {}", path, e)))?;
|
||||
|
||||
@@ -1192,9 +1172,7 @@ pub(super) fn op_hash_file(
|
||||
|
||||
#[deno_core::op2]
|
||||
#[string]
|
||||
pub(super) fn op_convert_hash(
|
||||
#[serde] input: ConvertHashInput,
|
||||
) -> std::result::Result<String, NixRuntimeError> {
|
||||
pub(super) fn op_convert_hash(#[serde] input: ConvertHashInput) -> Result<String> {
|
||||
use nix_compat::nixhash::{HashAlgo, NixHash};
|
||||
|
||||
let hash_algo = input
|
||||
@@ -1215,14 +1193,10 @@ pub(super) fn op_convert_hash(
|
||||
use base64::Engine as _;
|
||||
Ok(base64::engine::general_purpose::STANDARD.encode(bytes))
|
||||
}
|
||||
"sri" => Ok(format!(
|
||||
"{}-{}",
|
||||
hash.algo(),
|
||||
{
|
||||
use base64::Engine as _;
|
||||
base64::engine::general_purpose::STANDARD.encode(bytes)
|
||||
}
|
||||
)),
|
||||
"sri" => Ok(format!("{}-{}", hash.algo(), {
|
||||
use base64::Engine as _;
|
||||
base64::engine::general_purpose::STANDARD.encode(bytes)
|
||||
})),
|
||||
_ => Err(NixRuntimeError::from(format!(
|
||||
"unknown hash format '{}'",
|
||||
input.to_format
|
||||
@@ -1238,3 +1212,540 @@ pub(super) struct ConvertHashInput {
|
||||
#[serde(rename = "toHashFormat")]
|
||||
to_format: String,
|
||||
}
|
||||
|
||||
struct XmlCtx<'s> {
|
||||
force_fn: v8::Local<'s, v8::Function>,
|
||||
is_thunk: v8::Local<'s, v8::Symbol>,
|
||||
has_context: v8::Local<'s, v8::Symbol>,
|
||||
is_path: v8::Local<'s, v8::Symbol>,
|
||||
primop_meta: v8::Local<'s, v8::Symbol>,
|
||||
}
|
||||
|
||||
impl<'s> XmlCtx<'s> {
|
||||
fn new<'i>(
|
||||
scope: &mut v8::PinScope<'s, 'i>,
|
||||
nix_obj: v8::Local<'s, v8::Object>,
|
||||
) -> Result<Self> {
|
||||
let get_fn = |scope: &v8::PinScope<'s, 'i>, name: &str| {
|
||||
let key = v8::String::new(scope, name).ok_or("v8 string" )?;
|
||||
let val = nix_obj
|
||||
.get(scope, key.into())
|
||||
.ok_or_else(|| format!("no {name}") )?;
|
||||
v8::Local::<v8::Function>::try_from(val)
|
||||
.map_err(|e| format!("{name} not function: {e}") )
|
||||
};
|
||||
let get_sym = |scope: &v8::PinScope<'s, 'i>, name: &str| {
|
||||
let key = v8::String::new(scope, name).ok_or("v8 string" )?;
|
||||
let val = nix_obj
|
||||
.get(scope, key.into())
|
||||
.ok_or_else(|| format!("no {name}") )?;
|
||||
v8::Local::<v8::Symbol>::try_from(val).map_err(|e| format!("{name} not symbol: {e}") )
|
||||
};
|
||||
Ok(Self {
|
||||
force_fn: get_fn(scope, "force")?,
|
||||
is_thunk: get_sym(scope, "IS_THUNK")?,
|
||||
has_context: get_sym(scope, "HAS_CONTEXT")?,
|
||||
is_path: get_sym(scope, "IS_PATH")?,
|
||||
primop_meta: get_sym(scope, "PRIMOP_METADATA")?,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
struct XmlWriter {
|
||||
buf: String,
|
||||
context: Vec<String>,
|
||||
drvs_seen: hashbrown::HashSet<String>,
|
||||
}
|
||||
|
||||
impl XmlWriter {
|
||||
fn new() -> Self {
|
||||
Self {
|
||||
buf: String::with_capacity(4096),
|
||||
context: Vec::new(),
|
||||
drvs_seen: hashbrown::HashSet::new(),
|
||||
}
|
||||
}
|
||||
|
||||
fn indent(&mut self, depth: usize) {
|
||||
for _ in 0..depth {
|
||||
self.buf.push_str(" ");
|
||||
}
|
||||
}
|
||||
|
||||
fn escape_attr(&mut self, s: &str) {
|
||||
for ch in s.chars() {
|
||||
match ch {
|
||||
'"' => self.buf.push_str("""),
|
||||
'<' => self.buf.push_str("<"),
|
||||
'>' => self.buf.push_str(">"),
|
||||
'&' => self.buf.push_str("&"),
|
||||
'\n' => self.buf.push_str("
"),
|
||||
'\r' => self.buf.push_str("
"),
|
||||
'\t' => self.buf.push_str("	"),
|
||||
c => self.buf.push(c),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn force<'s>(
|
||||
&self,
|
||||
val: v8::Local<'s, v8::Value>,
|
||||
scope: &mut v8::PinScope<'s, '_>,
|
||||
ctx: &XmlCtx<'s>,
|
||||
) -> Result<v8::Local<'s, v8::Value>> {
|
||||
let undef = v8::undefined(scope);
|
||||
ctx.force_fn
|
||||
.call(scope, undef.into(), &[val])
|
||||
.ok_or_else(|| ("force() threw an exception").into())
|
||||
}
|
||||
|
||||
fn has_sym<'s>(
|
||||
obj: v8::Local<'s, v8::Object>,
|
||||
scope: &mut v8::PinScope<'s, '_>,
|
||||
sym: v8::Local<'s, v8::Symbol>,
|
||||
) -> bool {
|
||||
matches!(obj.get(scope, sym.into()), Some(v) if v.is_true())
|
||||
}
|
||||
|
||||
fn extract_str<'s>(
|
||||
&self,
|
||||
val: v8::Local<'s, v8::Value>,
|
||||
scope: &mut v8::PinScope<'s, '_>,
|
||||
ctx: &XmlCtx<'s>,
|
||||
) -> Option<String> {
|
||||
if val.is_string() {
|
||||
return Some(val.to_rust_string_lossy(scope));
|
||||
}
|
||||
if val.is_object() {
|
||||
let obj = val.to_object(scope)?;
|
||||
if Self::has_sym(obj, scope, ctx.has_context) {
|
||||
let key = v8::String::new(scope, "value")?;
|
||||
let s = obj.get(scope, key.into())?;
|
||||
return Some(s.to_rust_string_lossy(scope));
|
||||
}
|
||||
}
|
||||
None
|
||||
}
|
||||
|
||||
fn collect_context<'s>(
|
||||
&mut self,
|
||||
obj: v8::Local<'s, v8::Object>,
|
||||
scope: &mut v8::PinScope<'s, '_>,
|
||||
) {
|
||||
let context_key = match v8::String::new(scope, "context") {
|
||||
Some(k) => k,
|
||||
None => return,
|
||||
};
|
||||
let ctx_val = match obj.get(scope, context_key.into()) {
|
||||
Some(v) => v,
|
||||
None => return,
|
||||
};
|
||||
let global = scope.get_current_context().global(scope);
|
||||
let array_key = match v8::String::new(scope, "Array") {
|
||||
Some(k) => k,
|
||||
None => return,
|
||||
};
|
||||
let array_obj = match global
|
||||
.get(scope, array_key.into())
|
||||
.and_then(|v| v.to_object(scope))
|
||||
{
|
||||
Some(o) => o,
|
||||
None => return,
|
||||
};
|
||||
let from_key = match v8::String::new(scope, "from") {
|
||||
Some(k) => k,
|
||||
None => return,
|
||||
};
|
||||
let from_fn = match array_obj
|
||||
.get(scope, from_key.into())
|
||||
.and_then(|v| v8::Local::<v8::Function>::try_from(v).ok())
|
||||
{
|
||||
Some(f) => f,
|
||||
None => return,
|
||||
};
|
||||
let arr_val = match from_fn.call(scope, array_obj.into(), &[ctx_val]) {
|
||||
Some(v) => v,
|
||||
None => return,
|
||||
};
|
||||
let arr = match v8::Local::<v8::Array>::try_from(arr_val) {
|
||||
Ok(a) => a,
|
||||
Err(_) => return,
|
||||
};
|
||||
for i in 0..arr.length() {
|
||||
if let Some(elem) = arr.get_index(scope, i) {
|
||||
self.context.push(elem.to_rust_string_lossy(scope));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn is_derivation<'s>(obj: v8::Local<'s, v8::Object>, scope: &mut v8::PinScope<'s, '_>) -> bool {
|
||||
let key = match v8::String::new(scope, "type") {
|
||||
Some(k) => k,
|
||||
None => return false,
|
||||
};
|
||||
match obj.get(scope, key.into()) {
|
||||
Some(v) if v.is_string() => v.to_rust_string_lossy(scope) == "derivation",
|
||||
_ => false,
|
||||
}
|
||||
}
|
||||
|
||||
fn write_value<'s>(
|
||||
&mut self,
|
||||
val: v8::Local<'s, v8::Value>,
|
||||
scope: &mut v8::PinScope<'s, '_>,
|
||||
ctx: &XmlCtx<'s>,
|
||||
depth: usize,
|
||||
) -> Result<()> {
|
||||
let val = self.force(val, scope, ctx)?;
|
||||
|
||||
if val.is_null() {
|
||||
self.indent(depth);
|
||||
self.buf.push_str("<null />\n");
|
||||
return Ok(());
|
||||
}
|
||||
if val.is_true() {
|
||||
self.indent(depth);
|
||||
self.buf.push_str("<bool value=\"true\" />\n");
|
||||
return Ok(());
|
||||
}
|
||||
if val.is_false() {
|
||||
self.indent(depth);
|
||||
self.buf.push_str("<bool value=\"false\" />\n");
|
||||
return Ok(());
|
||||
}
|
||||
if val.is_big_int() {
|
||||
let bi = val.to_big_int(scope).ok_or("bigint" )?;
|
||||
let (i, _) = bi.i64_value();
|
||||
self.indent(depth);
|
||||
self.buf.push_str("<int value=\"");
|
||||
self.buf.push_str(&i.to_string());
|
||||
self.buf.push_str("\" />\n");
|
||||
return Ok(());
|
||||
}
|
||||
if val.is_number() {
|
||||
let n = val.number_value(scope).ok_or("number" )?;
|
||||
self.indent(depth);
|
||||
self.buf.push_str("<float value=\"");
|
||||
self.buf.push_str(&format!("{}", n));
|
||||
self.buf.push_str("\" />\n");
|
||||
return Ok(());
|
||||
}
|
||||
if val.is_string() {
|
||||
let s = val.to_rust_string_lossy(scope);
|
||||
self.indent(depth);
|
||||
self.buf.push_str("<string value=\"");
|
||||
self.escape_attr(&s);
|
||||
self.buf.push_str("\" />\n");
|
||||
return Ok(());
|
||||
}
|
||||
if val.is_array() {
|
||||
let arr = v8::Local::<v8::Array>::try_from(val).map_err(|e| e.to_string() )?;
|
||||
self.indent(depth);
|
||||
self.buf.push_str("<list>\n");
|
||||
for i in 0..arr.length() {
|
||||
let elem = arr.get_index(scope, i).ok_or("array elem" )?;
|
||||
self.write_value(elem, scope, ctx, depth + 1)?;
|
||||
}
|
||||
self.indent(depth);
|
||||
self.buf.push_str("</list>\n");
|
||||
return Ok(());
|
||||
}
|
||||
if val.is_function() {
|
||||
return self.write_function(val, scope, ctx, depth);
|
||||
}
|
||||
if val.is_object() {
|
||||
let obj = val.to_object(scope).ok_or("to_object" )?;
|
||||
|
||||
if Self::has_sym(obj, scope, ctx.has_context) {
|
||||
let key = v8::String::new(scope, "value").ok_or("v8 str" )?;
|
||||
let s = obj
|
||||
.get(scope, key.into())
|
||||
.ok_or("value" )?
|
||||
.to_rust_string_lossy(scope);
|
||||
self.collect_context(obj, scope);
|
||||
self.indent(depth);
|
||||
self.buf.push_str("<string value=\"");
|
||||
self.escape_attr(&s);
|
||||
self.buf.push_str("\" />\n");
|
||||
return Ok(());
|
||||
}
|
||||
if Self::has_sym(obj, scope, ctx.is_path) {
|
||||
let key = v8::String::new(scope, "value").ok_or("v8 str" )?;
|
||||
let s = obj
|
||||
.get(scope, key.into())
|
||||
.ok_or("value" )?
|
||||
.to_rust_string_lossy(scope);
|
||||
self.indent(depth);
|
||||
self.buf.push_str("<path value=\"");
|
||||
self.escape_attr(&s);
|
||||
self.buf.push_str("\" />\n");
|
||||
return Ok(());
|
||||
}
|
||||
if Self::has_sym(obj, scope, ctx.is_thunk) {
|
||||
self.indent(depth);
|
||||
self.buf.push_str("<unevaluated />\n");
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
return self.write_attrs(obj, scope, ctx, depth);
|
||||
}
|
||||
|
||||
self.indent(depth);
|
||||
self.buf.push_str("<unevaluated />\n");
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn write_attrs<'s>(
|
||||
&mut self,
|
||||
obj: v8::Local<'s, v8::Object>,
|
||||
scope: &mut v8::PinScope<'s, '_>,
|
||||
ctx: &XmlCtx<'s>,
|
||||
depth: usize,
|
||||
) -> Result<()> {
|
||||
if Self::is_derivation(obj, scope) {
|
||||
self.indent(depth);
|
||||
self.buf.push_str("<derivation");
|
||||
|
||||
let drv_path_key = v8::String::new(scope, "drvPath").ok_or("v8 str" )?;
|
||||
let drv_str = if let Some(drv_val) = obj.get(scope, drv_path_key.into()) {
|
||||
let forced = self.force(drv_val, scope, ctx)?;
|
||||
let s = self.extract_str(forced, scope, ctx);
|
||||
if let Some(ref s) = s {
|
||||
self.buf.push_str(" drvPath=\"");
|
||||
self.escape_attr(s);
|
||||
self.buf.push('"');
|
||||
}
|
||||
s
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
let out_path_key = v8::String::new(scope, "outPath").ok_or("v8 str" )?;
|
||||
if let Some(out_val) = obj.get(scope, out_path_key.into()) {
|
||||
let forced = self.force(out_val, scope, ctx)?;
|
||||
if let Some(ref s) = self.extract_str(forced, scope, ctx) {
|
||||
self.buf.push_str(" outPath=\"");
|
||||
self.escape_attr(s);
|
||||
self.buf.push('"');
|
||||
}
|
||||
}
|
||||
|
||||
self.buf.push_str(">\n");
|
||||
|
||||
let is_repeated = if let Some(ref dp) = drv_str {
|
||||
!self.drvs_seen.insert(dp.clone())
|
||||
} else {
|
||||
false
|
||||
};
|
||||
|
||||
if is_repeated {
|
||||
self.indent(depth + 1);
|
||||
self.buf.push_str("<repeated />\n");
|
||||
} else {
|
||||
self.write_attrs_sorted(obj, scope, ctx, depth + 1)?;
|
||||
}
|
||||
|
||||
self.indent(depth);
|
||||
self.buf.push_str("</derivation>\n");
|
||||
} else {
|
||||
self.indent(depth);
|
||||
self.buf.push_str("<attrs>\n");
|
||||
self.write_attrs_sorted(obj, scope, ctx, depth + 1)?;
|
||||
self.indent(depth);
|
||||
self.buf.push_str("</attrs>\n");
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn write_attrs_sorted<'s>(
|
||||
&mut self,
|
||||
obj: v8::Local<'s, v8::Object>,
|
||||
scope: &mut v8::PinScope<'s, '_>,
|
||||
ctx: &XmlCtx<'s>,
|
||||
depth: usize,
|
||||
) -> Result<()> {
|
||||
let keys = obj
|
||||
.get_own_property_names(scope, v8::GetPropertyNamesArgsBuilder::new().build())
|
||||
.ok_or("property names" )?;
|
||||
|
||||
let mut key_strings: Vec<String> = Vec::with_capacity(keys.length() as usize);
|
||||
for i in 0..keys.length() {
|
||||
let key = keys.get_index(scope, i).ok_or("key index" )?;
|
||||
key_strings.push(key.to_rust_string_lossy(scope));
|
||||
}
|
||||
key_strings.sort();
|
||||
|
||||
for key_str in &key_strings {
|
||||
let v8_key = v8::String::new(scope, key_str).ok_or("v8 str" )?;
|
||||
let val = obj
|
||||
.get(scope, v8_key.into())
|
||||
.ok_or("attr value" )?;
|
||||
|
||||
self.indent(depth);
|
||||
self.buf.push_str("<attr name=\"");
|
||||
self.escape_attr(key_str);
|
||||
self.buf.push_str("\">\n");
|
||||
|
||||
self.write_value(val, scope, ctx, depth + 1)?;
|
||||
|
||||
self.indent(depth);
|
||||
self.buf.push_str("</attr>\n");
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn write_function<'s>(
|
||||
&mut self,
|
||||
val: v8::Local<'s, v8::Value>,
|
||||
scope: &mut v8::PinScope<'s, '_>,
|
||||
ctx: &XmlCtx<'s>,
|
||||
depth: usize,
|
||||
) -> Result<()> {
|
||||
let obj = val.to_object(scope).ok_or("fn to_object" )?;
|
||||
|
||||
if let Some(meta) = obj.get(scope, ctx.primop_meta.into())
|
||||
&& meta.is_object()
|
||||
&& !meta.is_null_or_undefined()
|
||||
{
|
||||
self.indent(depth);
|
||||
self.buf.push_str("<unevaluated />\n");
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
let args_key = v8::String::new(scope, "args").ok_or("v8 str" )?;
|
||||
let args_val = obj.get(scope, args_key.into());
|
||||
|
||||
match args_val {
|
||||
Some(args) if args.is_object() && !args.is_null_or_undefined() => {
|
||||
let args_obj = args.to_object(scope).ok_or("args to_object" )?;
|
||||
|
||||
let req_key = v8::String::new(scope, "required").ok_or("v8 str" )?;
|
||||
let opt_key = v8::String::new(scope, "optional").ok_or("v8 str" )?;
|
||||
let ellipsis_key = v8::String::new(scope, "ellipsis").ok_or("v8 str" )?;
|
||||
|
||||
let mut all_formals: Vec<String> = Vec::new();
|
||||
if let Some(req) = args_obj.get(scope, req_key.into())
|
||||
&& let Ok(arr) = v8::Local::<v8::Array>::try_from(req)
|
||||
{
|
||||
for i in 0..arr.length() {
|
||||
if let Some(elem) = arr.get_index(scope, i) {
|
||||
all_formals.push(elem.to_rust_string_lossy(scope));
|
||||
}
|
||||
}
|
||||
}
|
||||
if let Some(opt) = args_obj.get(scope, opt_key.into())
|
||||
&& let Ok(arr) = v8::Local::<v8::Array>::try_from(opt)
|
||||
{
|
||||
for i in 0..arr.length() {
|
||||
if let Some(elem) = arr.get_index(scope, i) {
|
||||
all_formals.push(elem.to_rust_string_lossy(scope));
|
||||
}
|
||||
}
|
||||
}
|
||||
all_formals.sort();
|
||||
|
||||
let has_ellipsis = matches!(
|
||||
args_obj.get(scope, ellipsis_key.into()),
|
||||
Some(v) if v.is_true()
|
||||
);
|
||||
|
||||
self.indent(depth);
|
||||
self.buf.push_str("<function>\n");
|
||||
self.indent(depth + 1);
|
||||
if has_ellipsis {
|
||||
self.buf.push_str("<attrspat ellipsis=\"1\">\n");
|
||||
} else {
|
||||
self.buf.push_str("<attrspat>\n");
|
||||
}
|
||||
for formal in &all_formals {
|
||||
self.indent(depth + 2);
|
||||
self.buf.push_str("<attr name=\"");
|
||||
self.escape_attr(formal);
|
||||
self.buf.push_str("\" />\n");
|
||||
}
|
||||
self.indent(depth + 1);
|
||||
self.buf.push_str("</attrspat>\n");
|
||||
self.indent(depth);
|
||||
self.buf.push_str("</function>\n");
|
||||
}
|
||||
_ => {
|
||||
let source = val
|
||||
.to_detail_string(scope)
|
||||
.map(|s| s.to_rust_string_lossy(scope));
|
||||
let param = source.as_deref().and_then(extract_lambda_param);
|
||||
self.indent(depth);
|
||||
self.buf.push_str("<function>\n");
|
||||
self.indent(depth + 1);
|
||||
self.buf.push_str("<varpat name=\"");
|
||||
self.buf.push_str(param.unwrap_or("x"));
|
||||
self.buf.push_str("\" />\n");
|
||||
self.indent(depth);
|
||||
self.buf.push_str("</function>\n");
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
fn extract_lambda_param(source: &str) -> Option<&str> {
|
||||
let s = source.trim();
|
||||
let arrow_pos = s.find("=>")?;
|
||||
let before = s[..arrow_pos].trim();
|
||||
if before.starts_with('(') && before.ends_with(')') {
|
||||
let inner = before[1..before.len() - 1].trim();
|
||||
if !inner.is_empty() && !inner.contains(',') {
|
||||
return Some(inner);
|
||||
}
|
||||
} else if !before.is_empty()
|
||||
&& !before.contains(' ')
|
||||
&& !before.contains('(')
|
||||
&& before
|
||||
.chars()
|
||||
.all(|c| c.is_alphanumeric() || c == '_' || c == '$')
|
||||
{
|
||||
return Some(before);
|
||||
}
|
||||
None
|
||||
}
|
||||
|
||||
pub(super) struct ToXmlResult {
|
||||
pub xml: String,
|
||||
pub context: Vec<String>,
|
||||
}
|
||||
|
||||
impl<'a> FromV8<'a> for ToXmlResult {
|
||||
type Error = NixRuntimeError;
|
||||
|
||||
fn from_v8(
|
||||
scope: &mut v8::PinScope<'a, '_>,
|
||||
value: v8::Local<'a, v8::Value>,
|
||||
) -> std::result::Result<Self, Self::Error> {
|
||||
let global = scope.get_current_context().global(scope);
|
||||
let nix_key = v8::String::new(scope, "Nix").ok_or("v8 string" )?;
|
||||
let nix_obj = global
|
||||
.get(scope, nix_key.into())
|
||||
.ok_or("no Nix global" )?
|
||||
.to_object(scope)
|
||||
.ok_or("Nix not object" )?;
|
||||
|
||||
let ctx = XmlCtx::new(scope, nix_obj)?;
|
||||
|
||||
let mut writer = XmlWriter::new();
|
||||
writer
|
||||
.buf
|
||||
.push_str("<?xml version='1.0' encoding='utf-8'?>\n<expr>\n");
|
||||
writer.write_value(value, scope, &ctx, 1)?;
|
||||
writer.buf.push_str("</expr>\n");
|
||||
|
||||
Ok(ToXmlResult {
|
||||
xml: writer.buf,
|
||||
context: writer.context,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
#[deno_core::op2]
|
||||
#[serde]
|
||||
pub(super) fn op_to_xml(#[scoped] value: ToXmlResult) -> (String, Vec<String>) {
|
||||
(value.xml, value.context)
|
||||
}
|
||||
|
||||
@@ -32,9 +32,7 @@ impl StringContextElem {
|
||||
|
||||
pub type InputDrvs = BTreeMap<String, BTreeSet<String>>;
|
||||
pub type Srcs = BTreeSet<String>;
|
||||
pub fn extract_input_drvs_and_srcs(
|
||||
context: &[String],
|
||||
) -> Result<(InputDrvs, Srcs), String> {
|
||||
pub fn extract_input_drvs_and_srcs(context: &[String]) -> Result<(InputDrvs, Srcs), String> {
|
||||
let mut input_drvs: BTreeMap<String, BTreeSet<String>> = BTreeMap::new();
|
||||
let mut input_srcs: BTreeSet<String> = BTreeSet::new();
|
||||
|
||||
|
||||
@@ -241,14 +241,8 @@ eval_okay_test!(
|
||||
tail_call_1
|
||||
);
|
||||
eval_okay_test!(tojson);
|
||||
eval_okay_test!(
|
||||
#[ignore = "not implemented: toXML"]
|
||||
toxml
|
||||
);
|
||||
eval_okay_test!(
|
||||
#[ignore = "not implemented: toXML"]
|
||||
toxml2
|
||||
);
|
||||
eval_okay_test!(toxml);
|
||||
eval_okay_test!(toxml2);
|
||||
eval_okay_test!(tryeval);
|
||||
eval_okay_test!(types);
|
||||
eval_okay_test!(versions);
|
||||
|
||||
Reference in New Issue
Block a user